Compare commits

..

146 Commits

Author SHA1 Message Date
github-actions[bot]
3234871b53 🔖 Release 2.0.0-rc.1 2022-10-02 08:05:19 +00:00
Ju4tCode
03543f01f2 🔖 bump version 2.0.0rc1 (#1300) 2022-10-02 15:49:31 +08:00
github-actions[bot]
ba5c0303c7 📝 Update changelog 2022-09-29 09:21:16 +00:00
Ju4tCode
e56fdd04ad 🍻 publish adapter GitHub (#1297) 2022-09-29 17:20:05 +08:00
github-actions[bot]
9f10bb70db 📝 Update changelog 2022-09-29 08:57:20 +00:00
Ju4tCode
71aad502d1 🐛 Fix: 内置规则和权限没有捕获错误 (#1291) 2022-09-29 16:56:06 +08:00
github-actions[bot]
ab85b8651e 📝 Update changelog 2022-09-29 08:43:25 +00:00
NewYearPrism
4fe8929441 🍻 publish plugin 文字识别 (#1294) 2022-09-29 16:42:06 +08:00
github-actions[bot]
5c303710f6 📝 Update changelog 2022-09-29 08:17:33 +00:00
RandomEnch
68d2ada94b 🍻 publish plugin 在线编曲 (#1292) 2022-09-29 16:16:07 +08:00
github-actions[bot]
75470fe157 📝 Update changelog 2022-09-25 15:24:53 +00:00
koking0
47b3fc516a 🍻 publish plugin 图灵机器人 (#1288) 2022-09-25 23:23:49 +08:00
github-actions[bot]
84c24b014f 📝 Update changelog 2022-09-23 12:26:33 +00:00
lgc2333
756cde6525 🍻 publish plugin PicStatus (#1286) 2022-09-23 20:25:22 +08:00
github-actions[bot]
57ef19af94 📝 Update changelog 2022-09-21 08:58:18 +00:00
Kaguya233qwq
5927b517e2 🍻 publish plugin 阿里云盘福利码自动兑换 (#1282) 2022-09-21 16:57:01 +08:00
github-actions[bot]
132205bfcc 📝 Update changelog 2022-09-21 08:49:44 +00:00
dpm12345
b31dfa9ab0 🍻 publish plugin gal角色语音生成 (#1280) 2022-09-21 16:48:17 +08:00
github-actions[bot]
b249802c38 📝 Update changelog 2022-09-19 09:56:45 +00:00
Todysheep
9df705aaa7 🍻 publish plugin 漂流瓶 (#1278) 2022-09-19 17:55:38 +08:00
github-actions[bot]
a0df535f0c 📝 Update changelog 2022-09-18 14:36:17 +00:00
AkiraXie
31022a653d Feature: SUPERUSER 权限匹配任意超管事件 (#1275)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-09-18 22:33:36 +08:00
github-actions[bot]
ba77443dde 📝 Update changelog 2022-09-18 14:30:09 +00:00
XZhouQD
984f743097 🍻 publish plugin BWIKI助手移植版 (#1273) 2022-09-18 22:29:03 +08:00
github-actions[bot]
638a9c94af 📝 Update changelog 2022-09-16 11:52:43 +00:00
su226
92f1d5a4d7 🍻 publish bot IdhagnBot (#1266) 2022-09-16 19:51:35 +08:00
github-actions[bot]
248af2ae1a 📝 Update changelog 2022-09-15 13:58:15 +00:00
littlebutt
4c37be7312 🍻 publish plugin nonebot物联网插件 (#1264) 2022-09-15 21:56:57 +08:00
github-actions[bot]
2cb8eafa81 📝 Update changelog 2022-09-13 02:47:25 +00:00
CMHopeSunshine
05bff5ec17 🍻 publish bot LittlePaimon (#1255) 2022-09-13 10:46:18 +08:00
github-actions[bot]
13245cb58f 📝 Update changelog 2022-09-12 13:27:14 +00:00
AbCooly
37bc7326b5 🍻 publish plugin 狼人杀插件 (#1251) 2022-09-12 21:25:50 +08:00
github-actions[bot]
f6d189d8c5 📝 Update changelog 2022-09-12 04:30:37 +00:00
bridgeL
600ef7031f 🍻 publish plugin ayaka - 文字游戏开发辅助插件 (#1253) 2022-09-12 12:29:30 +08:00
github-actions[bot]
7bedf7c8d0 📝 Update changelog 2022-09-11 12:59:20 +00:00
ppxxxg22
f62ee5893c 🍻 publish plugin 图像超分辨率重建 (#1249) 2022-09-11 20:57:55 +08:00
github-actions[bot]
71234e9a68 📝 Update changelog 2022-09-10 12:56:12 +00:00
Akirami
3bbca0fa70 Feature: 改进 CommandGroupMatcherGroup 的结构 (#1240) 2022-09-10 20:54:49 +08:00
github-actions[bot]
20f144ba93 📝 Update changelog 2022-09-09 10:53:37 +00:00
Akirami
4c8bc9f0cb 🍻 Fix: 修正 GenshinUID 的发布类型 (#1243) 2022-09-09 18:52:12 +08:00
github-actions[bot]
064509f26b 📝 Update changelog 2022-09-09 03:54:19 +00:00
Ju4tCode
8c42490a7e 🔇 Feature: 调整日志输出格式与等级 (#1233) 2022-09-09 11:52:57 +08:00
github-actions[bot]
179d7105c9 📝 Update changelog 2022-09-09 02:16:34 +00:00
KarisAya
1c14e638c8 🍻 publish plugin Minecraft Server 聊天同步 (#1244) 2022-09-09 10:15:26 +08:00
github-actions[bot]
c6eef06b55 📝 Update changelog 2022-09-08 02:38:39 +00:00
Akirami
beef564a22 🔥 remove unused imports (#1236) 2022-09-08 10:37:16 +08:00
github-actions[bot]
672f2ceecc 📝 Update changelog 2022-09-07 02:25:49 +00:00
Sclock
28142402d7 🍻 publish plugin 查询ETH合并日期 (#1231) 2022-09-07 10:24:39 +08:00
github-actions[bot]
b886329fb8 📝 Update changelog 2022-09-07 02:00:34 +00:00
Ju4tCode
a0b186aff3 ♻️ improve dependent structure (#1227) 2022-09-07 09:59:05 +08:00
pre-commit-ci[bot]
595c64e760 ⬆️ auto update by pre-commit hooks (#1229)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-09-06 09:54:57 +08:00
github-actions[bot]
5114749073 📝 Update changelog 2022-09-05 06:23:20 +00:00
84227871
af2d7b5797 🍻 publish bot GenshinUID (#1225) 2022-09-05 14:21:53 +08:00
github-actions[bot]
56943c0908 📝 Update changelog 2022-09-05 05:52:33 +00:00
SDIJF1521
45478deb95 🍻 publish bot 小白机器人 (#1223) 2022-09-05 13:51:16 +08:00
github-actions[bot]
d281ec5bf9 📝 Update changelog 2022-09-05 02:02:23 +00:00
17TheWord
291a7cbb8b 🍻 publish plugin 星际战甲事件查询 (#1219) 2022-09-05 10:01:20 +08:00
github-actions[bot]
41259546bd 📝 Update changelog 2022-09-04 11:31:42 +00:00
Ljzd-PRO
f96038241f ✏️ 更新插件米游社辅助工具 tag (#1221) 2022-09-04 19:30:32 +08:00
github-actions[bot]
b051320d78 📝 Update changelog 2022-09-04 03:18:37 +00:00
Ljzd-PRO
d12efac9f4 🍻 publish plugin 米游社辅助工具 (#1217) 2022-09-04 11:17:20 +08:00
github-actions[bot]
f87a38a30a 📝 Update changelog 2022-09-03 02:27:15 +00:00
monsterxcn
bf016b3f69 🍻 publish plugin 原神每日材料查询 (#1215) 2022-09-03 10:26:07 +08:00
github-actions[bot]
373f5255f1 📝 Update changelog 2022-09-02 05:41:40 +00:00
Ju4tCode
5a35015195 📝 update documentation 2022-09-02 13:40:24 +08:00
Melodyknit
d3a2f1dc08 🍻 publish adapter Console (#1212) 2022-09-02 13:40:24 +08:00
github-actions[bot]
f1aec4eb10 📝 Update changelog 2022-09-01 02:42:53 +00:00
Ju4tCode
cd30be21ba 🐛 fix nested user permission update (#1208) 2022-09-01 10:41:43 +08:00
17TheWord
f150a9ee89 🍻 publish plugin MC_QQ_MCRcon (#1210) 2022-09-01 10:41:08 +08:00
github-actions[bot]
e68281f60f 📝 Update changelog 2022-09-01 02:05:08 +00:00
monsterxcn
32be64485a 🍻 publish plugin 原神角色展柜查询 (#1207) 2022-09-01 10:03:55 +08:00
github-actions[bot]
c76f492305 📝 Update changelog 2022-08-31 07:46:34 +00:00
s52047qwas
29b0351644 🍻 publish plugin 修仙模拟器 (#1195) 2022-08-31 15:45:27 +08:00
github-actions[bot]
ef3350fd9c 📝 Update changelog 2022-08-31 06:57:46 +00:00
Raidenneox
459699de5c 🍻 publish plugin 赛博浅草寺 (#1205) 2022-08-31 14:56:25 +08:00
github-actions[bot]
31c3eb8fd6 📝 Update changelog 2022-08-31 02:08:36 +00:00
Lan
1cfdee2645 Featue: load_plugin 支持 pathlib.Path (#1194)
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
2022-08-31 10:07:14 +08:00
github-actions[bot]
4e76518a58 📝 Update changelog 2022-08-31 02:05:28 +00:00
GC-ZF
b53e029df1 🍻 publish plugin 不背单词 (#1203) 2022-08-31 10:04:01 +08:00
github-actions[bot]
c1faf68806 📝 Update changelog 2022-08-30 11:39:33 +00:00
CofinCup
fe64b904ff 🍻 publish plugin 自识别todo (#1192) 2022-08-30 19:38:24 +08:00
github-actions[bot]
a621ade449 📝 Update changelog 2022-08-30 01:55:21 +00:00
Ju4tCode
3fda978064 Feature: 新增事件类型过滤 rule (#1183) 2022-08-30 09:54:09 +08:00
github-actions[bot]
60ab93164c 📝 Update changelog 2022-08-28 12:12:28 +00:00
sena-nana
2b22d5abda 🍻 publish plugin 雨课堂自动签到 (#1188) 2022-08-28 20:11:22 +08:00
github-actions[bot]
e3a4834383 📝 Update changelog 2022-08-27 14:30:10 +00:00
sena-nana
21087036af 🍻 publish plugin 反馈及通知 (#1186) 2022-08-27 22:29:05 +08:00
github-actions[bot]
94d336ef4d 📝 Update changelog 2022-08-27 14:16:48 +00:00
sena-nana
07707213a5 🍻 publish plugin MagiaDice骰娘及TRPGLOG (#1184) 2022-08-27 22:15:41 +08:00
github-actions[bot]
7579878fb4 📝 Update changelog 2022-08-27 13:51:18 +00:00
Nranphy
6599b6420e 🍻 publish plugin 面麻小助手 (#1190) 2022-08-27 21:50:04 +08:00
github-actions[bot]
135c6e8168 📝 Update changelog 2022-08-26 02:16:15 +00:00
X-Skirt-X
743e7363ea 🍻 publish plugin 话痨排行榜 (#1181) 2022-08-26 10:15:07 +08:00
github-actions[bot]
a101428c81 📝 Update changelog 2022-08-25 02:27:25 +00:00
YunJin
0d2b1f693e ✏️ Plugin: 修改插件多功能简易群管信息 (#1180) 2022-08-25 10:26:06 +08:00
github-actions[bot]
e5a53dfd5c 📝 Update changelog 2022-08-25 02:00:45 +00:00
KarisAya
915c2b3e43 🍻 publish plugin 保存群聊闪照 (#1178) 2022-08-25 09:59:40 +08:00
github-actions[bot]
767b6a9913 📝 Update changelog 2022-08-24 01:55:17 +00:00
Ju4tCode
3f8af04803 add rich text support for shell command (#1171) 2022-08-24 09:54:08 +08:00
github-actions[bot]
00af815b8a 📝 Update changelog 2022-08-23 07:01:42 +00:00
InariInDream
24df594b97 🍻 publish plugin 课表查询 (#1167) 2022-08-23 15:00:34 +08:00
github-actions[bot]
d6567f9288 📝 Update changelog 2022-08-23 06:35:43 +00:00
yzyyz1387
4eb158245e 🍻 publish plugin 业余无线电助手 (#1172) 2022-08-23 14:34:27 +08:00
github-actions[bot]
ef35266d3e 📝 Update changelog 2022-08-23 04:04:55 +00:00
he0119
1d1beb100a 🍻 publish plugin NoneBot 树形帮助插件 (#1176) 2022-08-23 12:03:41 +08:00
github-actions[bot]
06ab6093b7 📝 Update changelog 2022-08-23 03:54:04 +00:00
yzyyz1387
c1ce7fb940 🍻 publish plugin 工作性价比 (#1174) 2022-08-23 11:53:01 +08:00
github-actions[bot]
6e03ddbf12 📝 Update changelog 2022-08-23 03:48:13 +00:00
KarisAya
40c8787828 🍻 publish plugin 娶群友 (#1169) 2022-08-23 11:47:03 +08:00
github-actions[bot]
be459e0bbb 📝 Update changelog 2022-08-22 10:12:31 +00:00
ssttkkl
1056828f90 🍻 publish plugin PixivBot (#1164) 2022-08-22 18:11:28 +08:00
github-actions[bot]
ef18e8943d 📝 Update changelog 2022-08-22 06:40:04 +00:00
Mix
92ff1df419 🐛 修复当消息与不支持的类型相加时抛出的异常类型错误 (#1166) 2022-08-22 14:39:00 +08:00
github-actions[bot]
be5ac88a18 📝 Update changelog 2022-08-21 03:25:26 +00:00
Yiyuiii
fac647370a 🍻 publish plugin 日韩中 VITS 模型原神拟声 (#1161) 2022-08-21 11:24:11 +08:00
github-actions[bot]
05a3891903 📝 Update changelog 2022-08-20 02:19:01 +00:00
Ju4tCode
4deae8f00c 🔥 remove deprecated State param (#1160) 2022-08-20 10:17:52 +08:00
github-actions[bot]
0f70e975b0 📝 Update changelog 2022-08-19 01:10:13 +00:00
SkyDynamic
982680be91 🍻 publish plugin 每日人品 (#1155) 2022-08-19 09:09:10 +08:00
github-actions[bot]
96b0a863e6 📝 Update changelog 2022-08-18 01:43:06 +00:00
CrazyBoyM
d64bb37c6d 🍻 publish plugin nonebot-plugin-drawer (#1145) 2022-08-18 09:41:48 +08:00
github-actions[bot]
51d7f1783d 📝 Update changelog 2022-08-16 02:08:08 +00:00
YunJin
64c18379c9 ✏️ change plugin admin hello info (#1159) 2022-08-16 10:06:52 +08:00
GC_XiaoZhang
2735f0cba9 ✏️ change plugin fireN info (#1158) 2022-08-16 10:06:02 +08:00
github-actions[bot]
660dbaf3b8 📝 Update changelog 2022-08-16 02:04:40 +00:00
Ju4tCode
898c29d7ee 💥 remove deprecated nonebot.plugins toml table (#1151)
Feature: 移除过时的 `nonebot.plugins` toml 配置
2022-08-16 10:03:37 +08:00
github-actions[bot]
cdc507bab9 📝 Update changelog 2022-08-15 13:34:28 +00:00
YunJin
f32bcdc1fc ✏️ update plugin 多功能简易群管 (#1154) 2022-08-15 21:33:13 +08:00
github-actions[bot]
013602da21 📝 Update changelog 2022-08-14 11:42:11 +00:00
Ju4tCode
4974c596ec 💥 remove Python 3.7 support (#1148) 2022-08-14 19:41:00 +08:00
github-actions[bot]
0620bec51f 📝 Update changelog 2022-08-14 09:11:29 +00:00
KarisAya
8870e6a26e 🍻 publish plugin 小游戏合集 (#1149) 2022-08-14 17:10:21 +08:00
github-actions[bot]
0e3ed0e7ab 📝 Update changelog 2022-08-12 07:02:00 +00:00
HuYihe2008
6cc3b68447 🍻 publish plugin 简易群管(带入群欢迎) (#1141) 2022-08-12 15:00:52 +08:00
github-actions[bot]
549a37b172 📝 Update changelog 2022-08-11 05:52:38 +00:00
ZombieFly
16394ad68b 🍻 publish plugin wiki条目搜索、获取简介 (#1132) 2022-08-11 13:51:20 +08:00
github-actions[bot]
57e580c255 📝 Update changelog 2022-08-11 05:51:05 +00:00
Ankhyty
6c23d89494 🍻 publish plugin bangumi搜索 (#1136) 2022-08-11 13:50:01 +08:00
github-actions[bot]
675e70f579 📝 Update changelog 2022-08-09 11:01:23 +00:00
bingqiu456
7a098b96f8 🍻 publish plugin 疫情小助手-频道版 (#1130) 2022-08-09 19:00:15 +08:00
github-actions[bot]
c9794bf91d 📝 Update changelog 2022-08-08 13:08:47 +00:00
Ju4tCode
1766d4da69 💥 remove deprecated export (#1125) 2022-08-08 21:07:36 +08:00
github-actions[bot]
6583bc8c61 📝 Update changelog 2022-08-08 04:19:01 +00:00
17TheWord
179f16346a 🍻 publish plugin MC_QQ通信 (#1126) 2022-08-08 12:17:47 +08:00
github-actions[bot]
badb0c9ff4 📝 Update changelog 2022-08-08 02:26:00 +00:00
lgc2333
ee0ea85e40 🍻 publish plugin BAWiki (#1128) 2022-08-08 10:24:48 +08:00
150 changed files with 3225 additions and 1662 deletions

View File

@@ -5,7 +5,7 @@ inputs:
python-version:
description: Python version
required: false
default: "3.9"
default: "3.10"
runs:
using: "composite"

View File

@@ -15,7 +15,7 @@ jobs:
cancel-in-progress: true
strategy:
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10"]
python-version: ["3.8", "3.9", "3.10"]
os: [ubuntu-latest, windows-latest, macos-latest]
fail-fast: false
env:

View File

@@ -13,20 +13,20 @@ repos:
stages: [commit]
- repo: https://github.com/psf/black
rev: 22.6.0
rev: 22.8.0
hooks:
- id: black
stages: [commit]
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.7.1
rev: v3.0.0-alpha.0
hooks:
- id: prettier
types_or: [javascript, jsx, ts, tsx, markdown, yaml]
stages: [commit]
- repo: https://github.com/nonebot/nonemoji
rev: v0.1.2
rev: v0.1.3
hooks:
- id: nonemoji
stages: [prepare-commit-msg]

View File

@@ -21,7 +21,7 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
<a href="https://pypi.python.org/pypi/nonebot2">
<img src="https://img.shields.io/pypi/v/nonebot2" alt="pypi">
</a>
<img src="https://img.shields.io/badge/python-3.7.3+-blue" alt="python">
<img src="https://img.shields.io/badge/python-3.8+-blue" alt="python">
<a href="https://codecov.io/gh/nonebot/nonebot2">
<img src="https://codecov.io/gh/nonebot/nonebot2/branch/master/graph/badge.svg?token=2P0G0VS7N4" alt="codecov"/>
</a>
@@ -38,17 +38,20 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
<a href="https://onebot.dev/">
<img src="https://img.shields.io/badge/OneBot-v12-black?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAAAIVBMVEUAAAAAAAADAwMHBwceHh4UFBQNDQ0ZGRkoKCgvLy8iIiLWSdWYAAAAAXRSTlMAQObYZgAAAQVJREFUSMftlM0RgjAQhV+0ATYK6i1Xb+iMd0qgBEqgBEuwBOxU2QDKsjvojQPvkJ/ZL5sXkgWrFirK4MibYUdE3OR2nEpuKz1/q8CdNxNQgthZCXYVLjyoDQftaKuniHHWRnPh2GCUetR2/9HsMAXyUT4/3UHwtQT2AggSCGKeSAsFnxBIOuAggdh3AKTL7pDuCyABcMb0aQP7aM4AnAbc/wHwA5D2wDHTTe56gIIOUA/4YYV2e1sg713PXdZJAuncdZMAGkAukU9OAn40O849+0ornPwT93rphWF0mgAbauUrEOthlX8Zu7P5A6kZyKCJy75hhw1Mgr9RAUvX7A3csGqZegEdniCx30c3agAAAABJRU5ErkJggg==" alt="onebot">
</a>
<a href="https://ding-doc.dingtalk.com/document#/org-dev-guide/elzz1p">
<img src="https://img.shields.io/badge/%E9%92%89%E9%92%89-Bot-lightgrey?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAnFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4jUzeAAAAM3RSTlMAQKSRaA+/f0YyFevh29R3cyklIfrlyrGsn41tVUs48c/HqJm9uZdhX1otGwkF9IN8V1CX0Q+IAAABY0lEQVRYw+3V2W7CMBAF0JuNQAhhX9OEfYdu9///rUVWpagE27Ef2gfO+0zGozsKnv6bMGzAhkNytIe5gDdzrwtTCwrbI8x4/NF668NAxgI3Q3UtFi3TyPwNQtPLUUmDd8YfqGLNe4v22XwEYb5zoOuF5baHq2UHtsKe5ivWfGAwrWu2mC34QM0PoCAuqZdOmiwV+5BLyMRtZ7dTSEcs48rzWfzwptMLyzpApka1SJ5FtR4kfCqNIBPEVDmqoqgwUYY5plQOlf6UEjNoOPnuKB6wzDyCrks///TDza8+PnR109WQdxLo8RKWq0PPnuXG0OXKQ6wWLFnCg75uYYbhmMIVVdQ709q33aHbGIj6Duz+2k1HQFX9VwqmY8xYsEJll2ahvhWgsjYLHFRXvIi2Qb0jzMQCzC3FAoydxCma88UCzE3JCWwkjCNYyMUCzHX4DiuTMawEwwhW6hnshPhjZzzJfAH0YacpbmRd7QAAAABJRU5ErkJggg==" alt="dingtalk">
</a>
<a href="https://core.telegram.org/bots/api">
<img src="https://img.shields.io/badge/telegram-Bot-lightgrey?style=social&logo=telegram" alt="telegram">
</a>
<a href="https://open.feishu.cn/document/home/index">
<img src="https://img.shields.io/badge/%E9%A3%9E%E4%B9%A6-Bot-lightgrey?style=social&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDQ4IDQ4IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNyAyOUMyMSAyOSAyNSAyNi45MzM5IDI4IDIzLjQwNjVDMzYgMTQgNDEuNDI0MiAxNi44MTY2IDQ0IDE3Ljk5OThDMzguNSAyMC45OTk4IDQwLjUgMjkuNjIzMyAzMyAzNS45OTk4QzI4LjM4MiAzOS45MjU5IDIzLjQ5NDUgNDEuMDE0IDE5IDQxQzEyLjUyMzEgNDAuOTc5OSA2Ljg2MjI2IDM3Ljc2MzcgNCAzNS40MDYzVjE2Ljk5OTgiIHN0cm9rZT0iIzMzMyIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48cGF0aCBkPSJNNS42NDgwOCAxNS44NjY5QzUuMDIyMzEgMTQuOTU2NyAzLjc3NzE1IDE0LjcyNjEgMi44NjY5NCAxNS4zNTE5QzEuOTU2NzMgMTUuOTc3NyAxLjcyNjE1IDE3LjIyMjggMi4zNTE5MiAxOC4xMzMxTDUuNjQ4MDggMTUuODY2OVpNMzYuMDAyMSAzNS43MzA5QzM2Ljk1OCAzNS4xNzc0IDM3LjI4NDMgMzMuOTUzOSAzNi43MzA5IDMyLjk5NzlDMzYuMTc3NCAzMi4wNDIgMzQuOTUzOSAzMS43MTU3IDMzLjk5NzkgMzIuMjY5MUwzNi4wMDIxIDM1LjczMDlaTTIuMzUxOTIgMTguMTMzMUM1LjI0MzUgMjIuMzM5IDEwLjc5OTIgMjguMTQ0IDE2Ljg4NjUgMzIuMjIzOUMxOS45MzQ1IDM0LjI2NjcgMjMuMjE3IDM1Ljk0NiAyNi40NDkgMzYuNzMyNEMyOS42OTQ2IDM3LjUyMiAzMy4wNDUxIDM3LjQ0MjggMzYuMDAyMSAzNS43MzA5TDMzLjk5NzkgMzIuMjY5MUMzMi4yMDQ5IDMzLjMwNzIgMjkuOTkyOSAzMy40NzggMjcuMzk0NyAzMi44NDU4QzI0Ljc4MyAzMi4yMTAzIDIxLjk0MDUgMzAuNzk1OCAxOS4xMTM1IDI4LjkwMTFDMTMuNDUwOCAyNS4xMDYgOC4yNTY1IDE5LjY2MSA1LjY0ODA4IDE1Ljg2NjlMMi4zNTE5MiAxOC4xMzMxWiIgZmlsbD0iIzMzMyIvPjxwYXRoIGQ9Ik0zMy41OTQ1IDE3QzMyLjgzOTggMTQuNzAyNyAzMC44NTQ5IDkuOTQwNTQgMjcuNTk0NSA3SDExLjU5NDVDMTUuMjE3MSAxMC42NzU3IDIzIDE2IDI3IDI0IiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PC9zdmc+" alt="feishu">
</a>
<a href="https://docs.github.com/en/developers/apps">
<img src="https://img.shields.io/badge/GitHub-Bot-181717?style=social&logo=github" alt="github"/>
</a>
<a href="https://bot.q.qq.com/wiki/">
<img src="https://img.shields.io/badge/QQ%E9%A2%91%E9%81%93-Bot-lightgrey?style=social&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMTIuODIgMTMwLjg5Ij48ZyBkYXRhLW5hbWU9IuWbvuWxgiAyIj48ZyBkYXRhLW5hbWU9IuWbvuWxgiAxIj48cGF0aCBkPSJNNTUuNjMgMTMwLjhjLTcgMC0xMy45LjA4LTIwLjg2IDAtMTkuMTUtLjI1LTMxLjcxLTExLjQtMzQuMjItMzAuMy00LjA3LTMwLjY2IDE0LjkzLTU5LjIgNDQuODMtNjYuNjQgMi0uNTEgNS4yMS0uMzEgNS4yMS0xLjYzIDAtMi4xMy4xNC0yLjEzLjE0LTUuNTcgMC0uODktMS4zLTEuNDYtMi4yMi0yLjMxLTYuNzMtNi4yMy03LjY3LTEzLjQxLTEtMjAuMTggNS40LTUuNTIgMTEuODctNS40IDE3LjgtLjU5IDYuNDkgNS4yNiA2LjMxIDEzLjA4LS44NiAyMS0uNjguNzQtMS43OCAxLjYtMS43OCAyLjY3djQuMjFjMCAxLjM1IDIuMiAxLjYyIDQuNzkgMi4zNSAzMS4wOSA4LjY1IDQ4LjE3IDM0LjEzIDQ1IDY2LjM3LTEuNzYgMTguMTUtMTQuNTYgMzAuMjMtMzIuNyAzMC42My04LjAyLjE5LTE2LjA3LS4wMS0yNC4xMy0uMDF6IiBmaWxsPSIjMDI5OWZlIi8+PHBhdGggZD0iTTMxLjQ2IDExOC4zOGMtMTAuNS0uNjktMTYuOC02Ljg2LTE4LjM4LTE3LjI3LTMtMTkuNDIgMi43OC0zNS44NiAxOC40Ni00Ny44MyAxNC4xNi0xMC44IDI5Ljg3LTEyIDQ1LjM4LTMuMTkgMTcuMjUgOS44NCAyNC41OSAyNS44MSAyNCA0NS4yOS0uNDkgMTUuOS04LjQyIDIzLjE0LTI0LjM4IDIzLjUtNi41OS4xNC0xMy4xOSAwLTE5Ljc5IDAiIGZpbGw9IiNmZWZlZmUiLz48cGF0aCBkPSJNNDYuMDUgNzkuNThjLjA5IDUgLjIzIDkuODItNyA5Ljc3LTcuODItLjA2LTYuMS01LjY5LTYuMjQtMTAuMTktLjE1LTQuODItLjczLTEwIDYuNzMtOS44NHM2LjM3IDUuNTUgNi41MSAxMC4yNnoiIGZpbGw9IiMxMDlmZmUiLz48cGF0aCBkPSJNODAuMjcgNzkuMjdjLS41MyAzLjkxIDEuNzUgOS42NC01Ljg4IDEwLTcuNDcuMzctNi44MS00LjgyLTYuNjEtOS41LjItNC4zMi0xLjgzLTEwIDUuNzgtMTAuNDJzNi41OSA0Ljg5IDYuNzEgOS45MnoiIGZpbGw9IiMwODljZmUiLz48L2c+PC9nPjwvc3ZnPg==" alt="QQ频道">
<a href="https://ding-doc.dingtalk.com/document#/org-dev-guide/elzz1p">
<img src="https://img.shields.io/badge/%E9%92%89%E9%92%89-Bot-lightgrey?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAnFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4jUzeAAAAM3RSTlMAQKSRaA+/f0YyFevh29R3cyklIfrlyrGsn41tVUs48c/HqJm9uZdhX1otGwkF9IN8V1CX0Q+IAAABY0lEQVRYw+3V2W7CMBAF0JuNQAhhX9OEfYdu9///rUVWpagE27Ef2gfO+0zGozsKnv6bMGzAhkNytIe5gDdzrwtTCwrbI8x4/NF668NAxgI3Q3UtFi3TyPwNQtPLUUmDd8YfqGLNe4v22XwEYb5zoOuF5baHq2UHtsKe5ivWfGAwrWu2mC34QM0PoCAuqZdOmiwV+5BLyMRtZ7dTSEcs48rzWfzwptMLyzpApka1SJ5FtR4kfCqNIBPEVDmqoqgwUYY5plQOlf6UEjNoOPnuKB6wzDyCrks///TDza8+PnR109WQdxLo8RKWq0PPnuXG0OXKQ6wWLFnCg75uYYbhmMIVVdQ709q33aHbGIj6Duz+2k1HQFX9VwqmY8xYsEJll2ahvhWgsjYLHFRXvIi2Qb0jzMQCzC3FAoydxCma88UCzE3JCWwkjCNYyMUCzHX4DiuTMawEwwhW6hnshPhjZzzJfAH0YacpbmRd7QAAAABJRU5ErkJggg==" alt="dingtalk">
</a>
</a>
<br />
<a href="https://jq.qq.com/?_wv=1027&k=5OFifDh">
@@ -92,17 +95,28 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
- 生而可靠100% 类型注解覆盖,配合编辑器的类型推导功能,能将绝大多数的 Bug 杜绝在编辑器中 ([编辑器支持](https://v2.nonebot.dev/docs/start/editor-support))
- 社区丰富:社区用户众多,直接和间接用户超过十万人,每天都有大量的活跃用户 ([社区资源](#社区资源))
- 海纳百川:一个框架,支持多个聊天软件平台,可自定义通信协议
- [OneBot 协议](https://onebot.dev/) (QQ 等)
- [钉钉](https://ding-doc.dingtalk.com/document#/org-dev-guide/elzz1p)
- [Telegram](https://core.telegram.org/bots/api)
- [飞书](https://open.feishu.cn/document/home/index)
- [QQ 频道](https://bot.q.qq.com/wiki/)
- 坚实后盾:支持多种 web 框架,可自定义替换
- [FastAPI](https://fastapi.tiangolo.com/)
- [Quart](https://pgjones.gitlab.io/quart/) (异步 Flask)
- [aiohttp](https://docs.aiohttp.org/en/stable/)
- [httpx](https://www.python-httpx.org/)
- [websockets](https://websockets.readthedocs.io/en/stable/)
| 协议名称 | 状态 | 注释 |
| :---------------------------------------------------: | :--: | :----------------------------------------------------------------: |
| [OneBot 协议](https://onebot.dev/) | ✅ | 支持 QQ、TG、微信公众号等[平台](https://onebot.dev/ecosystem.html) |
| [Telegram](https://core.telegram.org/bots/api) | ✅ | |
| [飞书](https://open.feishu.cn/document/home/index) | ✅ | |
| [GitHub](https://docs.github.com/en/developers/apps) | ✅ | GitHub APP & OAuth APP |
| [QQ 频道](https://bot.q.qq.com/wiki/) | ✅ | 官方接口调整较多 |
| [钉钉](https://open.dingtalk.com/document/) | 🤗 | 寻找 Maintainer |
| Console | ✅ | 控制台交互 |
| [开黑啦](https://developer.kookapp.cn/) | ↗️ | 由社区贡献 |
| [Mirai](https://docs.mirai.mamoe.net/mirai-api-http/) | ↗️ | 由社区贡献 |
- 坚实后盾:支持多种 web 框架,可自定义替换、组合
| 驱动框架 | 类型 |
| :--------------------------------------------------------: | :----: |
| [FastAPI](https://fastapi.tiangolo.com/) | 服务端 |
| [Quart](https://pgjones.gitlab.io/quart/) (异步 Flask) | 服务端 |
| [aiohttp](https://docs.aiohttp.org/en/stable/) | 客户端 |
| [httpx](https://www.python-httpx.org/) | 客户端 |
| [websockets](https://websockets.readthedocs.io/en/stable/) | 客户端 |
更多:[概览](https://v2.nonebot.dev/docs/)

View File

@@ -16,6 +16,7 @@
- `on_command` => {ref}``on_command` <nonebot.plugin.on.on_command>`
- `on_shell_command` => {ref}``on_shell_command` <nonebot.plugin.on.on_shell_command>`
- `on_regex` => {ref}``on_regex` <nonebot.plugin.on.on_regex>`
- `on_type` => {ref}``on_type` <nonebot.plugin.on.on_type>`
- `CommandGroup` => {ref}``CommandGroup` <nonebot.plugin.on.CommandGroup>`
- `Matchergroup` => {ref}``MatcherGroup` <nonebot.plugin.on.MatcherGroup>`
- `load_plugin` => {ref}``load_plugin` <nonebot.plugin.load.load_plugin>`
@@ -29,7 +30,6 @@
- `get_plugin_by_module_name` => {ref}``get_plugin_by_module_name` <nonebot.plugin.get_plugin_by_module_name>`
- `get_loaded_plugins` => {ref}``get_loaded_plugins` <nonebot.plugin.get_loaded_plugins>`
- `get_available_plugin_names` => {ref}``get_available_plugin_names` <nonebot.plugin.get_available_plugin_names>`
- `export` => {ref}``export` <nonebot.plugin.export.export>`
- `require` => {ref}``require` <nonebot.plugin.load.require>`
FrontMatter:
@@ -40,10 +40,12 @@ FrontMatter:
import importlib
from typing import Any, Dict, Type, Optional
import loguru
from nonebot.log import logger
from nonebot.adapters import Bot
from nonebot.utils import escape_tag
from nonebot.config import Env, Config
from nonebot.log import logger, default_filter
from nonebot.drivers import Driver, ReverseDriver, combine_driver
try:
@@ -172,8 +174,7 @@ def get_bots() -> Dict[str, Bot]:
bots = nonebot.get_bots()
```
"""
driver = get_driver()
return driver.bots
return get_driver().bots
def _resolve_dot_notation(
@@ -207,6 +208,15 @@ def _resolve_combine_expr(obj_str: str) -> Type[Driver]:
return combine_driver(DriverClass, *mixins)
def _log_patcher(record: "loguru.Record"):
record["name"] = (
plugin.name
if (module_name := record["name"])
and (plugin := get_plugin_by_module_name(module_name))
else (module_name and module_name.split(".")[0])
)
def init(*, _env_file: Optional[str] = None, **kwargs: Any) -> None:
"""初始化 NoneBot 以及 全局 {ref}`nonebot.drivers.Driver` 对象。
@@ -233,7 +243,9 @@ def init(*, _env_file: Optional[str] = None, **kwargs: Any) -> None:
_env_file=_env_file or f".env.{env.environment}",
)
default_filter.level = config.log_level
logger.configure(
extra={"nonebot_log_level": config.log_level}, patcher=_log_patcher
)
logger.opt(colors=True).info(
f"Current <y><b>Env: {escape_tag(env.environment)}</b></y>"
)
@@ -241,7 +253,7 @@ def init(*, _env_file: Optional[str] = None, **kwargs: Any) -> None:
f"Loaded <y><b>Config</b></y>: {escape_tag(str(config.dict()))}"
)
DriverClass: Type[Driver] = _resolve_combine_expr(config.driver)
DriverClass = _resolve_combine_expr(config.driver)
_driver = DriverClass(env, config)
@@ -262,7 +274,7 @@ def run(*args: Any, **kwargs: Any) -> None:
from nonebot.plugin import on as on
from nonebot.plugin import export as export
from nonebot.plugin import on_type as on_type
from nonebot.plugin import require as require
from nonebot.plugin import on_regex as on_regex
from nonebot.plugin import on_notice as on_notice

View File

@@ -25,7 +25,6 @@ from pydantic.env_settings import (
)
from nonebot.log import logger
from nonebot.utils import escape_tag
class CustomEnvSettings(EnvSettingsSource):
@@ -85,13 +84,15 @@ class CustomEnvSettings(EnvSettingsSource):
if env_file_vars:
for env_name in env_file_vars.keys():
env_val = env_vars[env_name]
try:
if env_val:
env_val = settings.__config__.json_loads(env_val.strip())
except ValueError as e:
logger.opt(colors=True, exception=e).trace(
f"Error while parsing JSON for {escape_tag(env_name)}. Assumed as string."
)
if env_val and (val_striped := env_val.strip()):
try:
env_val = settings.__config__.json_loads(val_striped)
except ValueError as e:
logger.trace(
"Error while parsing JSON for "
f"{env_name!r}={val_striped!r}. "
"Assumed as string."
)
d[env_name] = env_val

View File

@@ -4,7 +4,7 @@ FrontMatter:
sidebar_position: 9
description: nonebot.consts 模块
"""
from typing_extensions import Literal
from typing import Literal
# used by Matcher
RECEIVE_KEY: Literal["_receive_{id}"] = "_receive_{id}"

View File

@@ -6,15 +6,31 @@ FrontMatter:
"""
import abc
import asyncio
import inspect
from typing import Any, Dict, List, Type, Generic, TypeVar, Callable, Optional
from dataclasses import field, dataclass
from typing import (
Any,
Dict,
List,
Type,
Tuple,
Generic,
TypeVar,
Callable,
Iterable,
Optional,
Awaitable,
cast,
)
from pydantic import BaseConfig
from pydantic.schema import get_annotation_from_field_info
from pydantic.fields import Required, FieldInfo, Undefined, ModelField
from nonebot.log import logger
from nonebot.exception import TypeMisMatch
from nonebot.typing import _DependentCallable
from nonebot.exception import SkippedException
from nonebot.utils import run_sync, is_coroutine_callable
from .utils import check_field_type, get_typed_signature
@@ -31,25 +47,29 @@ class Param(abc.ABC, FieldInfo):
@classmethod
def _check_param(
cls, dependent: "Dependent", name: str, param: inspect.Parameter
cls, param: inspect.Parameter, allow_types: Tuple[Type["Param"], ...]
) -> Optional["Param"]:
return None
return
@classmethod
def _check_parameterless(
cls, dependent: "Dependent", value: Any
cls, value: Any, allow_types: Tuple[Type["Param"], ...]
) -> Optional["Param"]:
return None
return
@abc.abstractmethod
async def _solve(self, **kwargs: Any) -> Any:
raise NotImplementedError
async def _check(self, **kwargs: Any) -> None:
return
class CustomConfig(BaseConfig):
arbitrary_types_allowed = True
@dataclass(frozen=True)
class Dependent(Generic[R]):
"""依赖注入容器
@@ -61,101 +81,70 @@ class Dependent(Generic[R]):
allow_types: 允许的参数类型
"""
def __init__(
self,
*,
call: Callable[..., Any],
pre_checkers: Optional[List[Param]] = None,
params: Optional[List[ModelField]] = None,
parameterless: Optional[List[Param]] = None,
allow_types: Optional[List[Type[Param]]] = None,
) -> None:
self.call = call
self.pre_checkers = pre_checkers or []
self.params = params or []
self.parameterless = parameterless or []
self.allow_types = allow_types or []
call: _DependentCallable[R]
params: Tuple[ModelField] = field(default_factory=tuple)
parameterless: Tuple[Param] = field(default_factory=tuple)
def __repr__(self) -> str:
if inspect.isfunction(self.call) or inspect.isclass(self.call):
call_str = self.call.__name__
else:
call_str = repr(self.call)
return (
f"<Dependent call={self.call}, params={self.params},"
f" parameterless={self.parameterless}>"
f"Dependent(call={call_str}"
+ (f", parameterless={self.parameterless}" if self.parameterless else "")
+ ")"
)
def __str__(self) -> str:
return self.__repr__()
async def __call__(self, **kwargs: Any) -> R:
# do pre-check
await self.check(**kwargs)
# solve param values
values = await self.solve(**kwargs)
# call function
if is_coroutine_callable(self.call):
return await self.call(**values)
return await cast(Callable[..., Awaitable[R]], self.call)(**values)
else:
return await run_sync(self.call)(**values)
return await run_sync(cast(Callable[..., R], self.call))(**values)
def parse_param(self, name: str, param: inspect.Parameter) -> Param:
for allow_type in self.allow_types:
field_info = allow_type._check_param(self, name, param)
if field_info:
return field_info
else:
raise ValueError(
f"Unknown parameter {name} for function {self.call} with type {param.annotation}"
)
@staticmethod
def parse_params(
call: _DependentCallable[R], allow_types: Tuple[Type[Param], ...]
) -> Tuple[ModelField]:
fields: List[ModelField] = []
params = get_typed_signature(call).parameters.values()
def parse_parameterless(self, value: Any) -> Param:
for allow_type in self.allow_types:
field_info = allow_type._check_parameterless(self, value)
if field_info:
return field_info
else:
raise ValueError(
f"Unknown parameterless {value} for function {self.call} with type {type(value)}"
)
def prepend_parameterless(self, value: Any) -> None:
self.parameterless.insert(0, self.parse_parameterless(value))
def append_parameterless(self, value: Any) -> None:
self.parameterless.append(self.parse_parameterless(value))
@classmethod
def parse(
cls: Type[T],
*,
call: Callable[..., Any],
parameterless: Optional[List[Any]] = None,
allow_types: Optional[List[Type[Param]]] = None,
) -> T:
signature = get_typed_signature(call)
params = signature.parameters
dependent = cls(
call=call,
allow_types=allow_types,
)
for param_name, param in params.items():
for param in params:
default_value = Required
if param.default != param.empty:
default_value = param.default
if isinstance(default_value, Param):
field_info = default_value
default_value = field_info.default
else:
field_info = dependent.parse_param(param_name, param)
default_value = field_info.default
for allow_type in allow_types:
if field_info := allow_type._check_param(param, allow_types):
break
else:
raise ValueError(
f"Unknown parameter {param.name} for function {call} with type {param.annotation}"
)
default_value = field_info.default
annotation: Any = Any
required = default_value == Required
if param.annotation != param.empty:
annotation = param.annotation
annotation = get_annotation_from_field_info(
annotation, field_info, param_name
annotation, field_info, param.name
)
dependent.params.append(
fields.append(
ModelField(
name=param_name,
name=param.name,
type_=annotation,
class_validators=None,
model_config=CustomConfig,
@@ -165,49 +154,72 @@ class Dependent(Generic[R]):
)
)
parameterless_params = [
dependent.parse_parameterless(param) for param in (parameterless or [])
]
dependent.parameterless.extend(parameterless_params)
return tuple(fields)
logger.trace(
f"Parsed dependent with call={call}, "
f"params={[param.field_info for param in dependent.params]}, "
f"parameterless={dependent.parameterless}"
@staticmethod
def parse_parameterless(
parameterless: Tuple[Any, ...], allow_types: Tuple[Type[Param], ...]
) -> Tuple[Param, ...]:
parameterless_params: List[Param] = []
for value in parameterless:
for allow_type in allow_types:
if param := allow_type._check_parameterless(value, allow_types):
break
else:
raise ValueError(f"Unknown parameterless {value}")
parameterless_params.append(param)
return tuple(parameterless_params)
@classmethod
def parse(
cls,
*,
call: _DependentCallable[R],
parameterless: Optional[Iterable[Any]] = None,
allow_types: Iterable[Type[Param]],
) -> "Dependent[R]":
allow_types = tuple(allow_types)
params = cls.parse_params(call, allow_types)
parameterless_params = (
tuple()
if parameterless is None
else cls.parse_parameterless(tuple(parameterless), allow_types)
)
return dependent
return cls(call, params, parameterless_params)
async def solve(
self,
**params: Any,
) -> Dict[str, Any]:
values: Dict[str, Any] = {}
async def check(self, **params: Any) -> None:
try:
await asyncio.gather(
*(param._check(**params) for param in self.parameterless)
)
await asyncio.gather(
*(
cast(Param, param.field_info)._check(**params)
for param in self.params
)
)
except SkippedException as e:
logger.trace(f"{self} skipped due to {e}")
raise
for checker in self.pre_checkers:
await checker._solve(**params)
async def _solve_field(self, field: ModelField, params: Dict[str, Any]) -> Any:
value = await cast(Param, field.field_info)._solve(**params)
if value is Undefined:
value = field.get_default()
return check_field_type(field, value)
async def solve(self, **params: Any) -> Dict[str, Any]:
# solve parameterless
for param in self.parameterless:
await param._solve(**params)
for field in self.params:
field_info = field.field_info
assert isinstance(field_info, Param), "Params must be subclasses of Param"
value = await field_info._solve(**params)
if value is Undefined:
value = field.get_default()
try:
values[field.name] = check_field_type(field, value)
except TypeMisMatch:
logger.debug(
f"{field_info} "
f"type {type(value)} not match depends {self.call} "
f"annotation {field._type_display()}, ignored"
)
raise
return values
# solve param values
values = await asyncio.gather(
*(self._solve_field(field, params) for field in self.params)
)
return {field.name: value for field, value in zip(self.params, values)}
__autodoc__ = {"CustomConfig": False}

View File

@@ -28,8 +28,7 @@ def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
)
for param in signature.parameters.values()
]
typed_signature = inspect.Signature(typed_params)
return typed_signature
return inspect.Signature(typed_params)
def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, Any]) -> Any:

View File

@@ -1,7 +1,7 @@
import signal
import asyncio
import threading
from typing import Set, Union, Callable, Awaitable
from typing import Set, Union, Callable, Awaitable, cast
from nonebot.log import logger
from nonebot.drivers import Driver
@@ -9,8 +9,7 @@ from nonebot.typing import overrides
from nonebot.config import Env, Config
from nonebot.utils import run_sync, is_coroutine_callable
STARTUP_FUNC = Callable[[], Union[None, Awaitable[None]]]
SHUTDOWN_FUNC = Callable[[], Union[None, Awaitable[None]]]
HOOK_FUNC = Union[Callable[[], None], Callable[[], Awaitable[None]]]
HANDLED_SIGNALS = (
signal.SIGINT, # Unix signal 2. Sent by Ctrl+C.
signal.SIGTERM, # Unix signal 15. Sent by `kill <pid>`.
@@ -20,8 +19,8 @@ HANDLED_SIGNALS = (
class BlockDriver(Driver):
def __init__(self, env: Env, config: Config):
super().__init__(env, config)
self.startup_funcs: Set[STARTUP_FUNC] = set()
self.shutdown_funcs: Set[SHUTDOWN_FUNC] = set()
self.startup_funcs: Set[HOOK_FUNC] = set()
self.shutdown_funcs: Set[HOOK_FUNC] = set()
self.should_exit: asyncio.Event = asyncio.Event()
self.force_exit: bool = False
@@ -38,7 +37,7 @@ class BlockDriver(Driver):
return logger
@overrides(Driver)
def on_startup(self, func: STARTUP_FUNC) -> STARTUP_FUNC:
def on_startup(self, func: HOOK_FUNC) -> HOOK_FUNC:
"""
注册一个启动时执行的函数
"""
@@ -46,7 +45,7 @@ class BlockDriver(Driver):
return func
@overrides(Driver)
def on_shutdown(self, func: SHUTDOWN_FUNC) -> SHUTDOWN_FUNC:
def on_shutdown(self, func: HOOK_FUNC) -> HOOK_FUNC:
"""
注册一个停止时执行的函数
"""
@@ -71,7 +70,9 @@ class BlockDriver(Driver):
async def startup(self):
# run startup
cors = [
startup() if is_coroutine_callable(startup) else run_sync(startup)()
cast(Callable[..., Awaitable[None]], startup)()
if is_coroutine_callable(startup)
else run_sync(startup)()
for startup in self.startup_funcs
]
if cors:
@@ -94,7 +95,9 @@ class BlockDriver(Driver):
logger.info("Waiting for application shutdown.")
# run shutdown
cors = [
shutdown() if is_coroutine_callable(shutdown) else run_sync(shutdown)()
cast(Callable[..., Awaitable[None]], shutdown)()
if is_coroutine_callable(shutdown)
else run_sync(shutdown)()
for shutdown in self.shutdown_funcs
]
if cors:

View File

@@ -27,7 +27,7 @@ from nonebot.drivers import HTTPVersion, ForwardMixin, ForwardDriver, combine_dr
try:
import aiohttp
except ImportError:
except ImportError: # pragma: no cover
raise ImportError(
"Please install aiohttp first to use this driver. `pip install nonebot2[aiohttp]`"
) from None

View File

@@ -9,7 +9,9 @@ FrontMatter:
description: nonebot.drivers.fastapi 模块
"""
import logging
import contextlib
from functools import wraps
from typing import Any, List, Tuple, Union, Callable, Optional
@@ -186,14 +188,12 @@ class Driver(ReverseDriver):
setup: HTTPServerSetup,
) -> Response:
json: Any = None
try:
with contextlib.suppress(Exception):
json = await request.json()
except Exception:
pass
data: Optional[dict] = None
files: Optional[List[Tuple[str, FileTypes]]] = None
try:
with contextlib.suppress(Exception):
form = await request.form()
data = {}
files = []
@@ -204,8 +204,7 @@ class Driver(ReverseDriver):
)
else:
data[key] = value
except Exception:
pass
http_request = BaseRequest(
request.method,
str(request.url),
@@ -219,7 +218,9 @@ class Driver(ReverseDriver):
)
response = await setup.handle_func(http_request)
return Response(response.content, response.status_code, dict(response.headers))
return Response(
response.content, response.status_code, dict(response.headers.items())
)
async def _handle_ws(self, websocket: WebSocket, setup: WebSocketServerSetup):
request = BaseRequest(

View File

@@ -31,7 +31,7 @@ from nonebot.drivers import (
try:
import httpx
except ImportError:
except ImportError: # pragma: no cover
raise ImportError(
"Please install httpx by using `pip install nonebot2[httpx]`"
) from None

View File

@@ -37,7 +37,7 @@ try:
from quart import Quart, Request, Response
from quart.datastructures import FileStorage
from quart import Websocket as QuartWebSocket
except ImportError:
except ImportError: # pragma: no cover
raise ImportError(
"Please install Quart by using `pip install nonebot2[quart]`"
) from None

View File

@@ -30,10 +30,10 @@ from nonebot.drivers import ForwardMixin, ForwardDriver, combine_driver
try:
from websockets.exceptions import ConnectionClosed
from websockets.legacy.client import Connect, WebSocketClientProtocol
except ImportError:
except ImportError: # pragma: no cover
raise ImportError(
"Please install websockets by using `pip install nonebot2[websockets]`"
)
) from None
logger = logging.Logger("websockets.client", "INFO")
logger.addHandler(LoguruHandler())

View File

@@ -46,10 +46,14 @@ class ParserExit(NoneBotException):
self.status = status
self.message = message
def __repr__(self):
return f"<ParserExit status={self.status} message={self.message}>"
def __repr__(self) -> str:
return (
f"ParserExit(status={self.status}"
+ (f", message={self.message!r}" if self.message else "")
+ ")"
)
def __str__(self):
def __str__(self) -> str:
return self.__repr__()
@@ -68,10 +72,10 @@ class IgnoredException(ProcessException):
def __init__(self, reason: Any):
self.reason: Any = reason
def __repr__(self):
return f"<IgnoredException, reason={self.reason}>"
def __repr__(self) -> str:
return f"IgnoredException(reason={self.reason!r})"
def __str__(self):
def __str__(self) -> str:
return self.__repr__()
@@ -99,11 +103,14 @@ class TypeMisMatch(SkippedException):
self.param: ModelField = param
self.value: Any = value
def __repr__(self):
return f"<TypeMisMatch, param={self.param}, value={self.value}>"
def __repr__(self) -> str:
return (
f"TypeMisMatch(param={self.param.name}, "
f"type={self.param._type_display()}, value={self.value!r}>"
)
def __str__(self):
self.__repr__()
def __str__(self) -> str:
return self.__repr__()
class MockApiException(ProcessException):
@@ -116,10 +123,10 @@ class MockApiException(ProcessException):
def __init__(self, result: Any):
self.result = result
def __repr__(self):
return f"<ApiCancelledException, result={self.result}>"
def __repr__(self) -> str:
return f"MockApiException(result={self.result!r})"
def __str__(self):
def __str__(self) -> str:
return self.__repr__()
@@ -195,7 +202,8 @@ class AdapterException(NoneBotException):
adapter_name: 标识 adapter
"""
def __init__(self, adapter_name: str) -> None:
def __init__(self, adapter_name: str, *args: object) -> None:
super().__init__(*args)
self.adapter_name: str = adapter_name
@@ -231,4 +239,11 @@ class WebSocketClosed(DriverException):
self.reason = reason
def __repr__(self) -> str:
return f"<WebSocketClosed code={self.code} reason={self.reason}>"
return (
f"WebSocketClosed(code={self.code}"
+ (f", reason={self.reason!r}" if self.reason else "")
+ ")"
)
def __str__(self) -> str:
return self.__repr__()

View File

@@ -33,6 +33,9 @@ class Adapter(abc.ABC):
self.bots: Dict[str, Bot] = {}
"""本协议适配器已建立连接的 {ref}`nonebot.adapters.Bot` 实例"""
def __repr__(self) -> str:
return f"Adapter(name={self.get_name()!r})"
@classmethod
@abc.abstractmethod
def get_name(cls) -> str:

View File

@@ -1,8 +1,7 @@
import abc
import asyncio
from functools import partial
from typing_extensions import Protocol
from typing import TYPE_CHECKING, Any, Set, Union, Optional
from typing import TYPE_CHECKING, Any, Set, Union, Optional, Protocol
from nonebot.log import logger
from nonebot.config import Config
@@ -14,10 +13,9 @@ if TYPE_CHECKING:
from .adapter import Adapter
from .message import Message, MessageSegment
class _ApiCall(Protocol):
async def __call__(self, **kwargs: Any) -> Any:
...
class _ApiCall(Protocol):
async def __call__(self, **kwargs: Any) -> Any:
...
class Bot(abc.ABC):
@@ -41,7 +39,10 @@ class Bot(abc.ABC):
self.self_id: str = self_id
"""机器人 ID"""
def __getattr__(self, name: str) -> _ApiCall:
def __repr__(self) -> str:
return f"Bot(type={self.type!r}, self_id={self.self_id!r})"
def __getattr__(self, name: str) -> "_ApiCall":
return partial(self.call_api, name)
@property
@@ -72,8 +73,7 @@ class Bot(abc.ABC):
skip_calling_api: bool = False
exception: Optional[Exception] = None
coros = list(map(lambda x: x(self, api, data), self._calling_api_hook))
if coros:
if coros := [hook(self, api, data) for hook in self._calling_api_hook]:
try:
logger.debug("Running CallingAPI hooks...")
await asyncio.gather(*coros)
@@ -95,10 +95,9 @@ class Bot(abc.ABC):
except Exception as e:
exception = e
coros = list(
map(lambda x: x(self, exception, api, data, result), self._called_api_hook)
)
if coros:
if coros := [
hook(self, exception, api, data, result) for hook in self._called_api_hook
]:
try:
logger.debug("Running CalledAPI hooks...")
await asyncio.gather(*coros)

View File

@@ -186,7 +186,7 @@ class Message(List[TMS], abc.ABC):
elif isinstance(other, Iterable):
self.extend(other)
else:
raise ValueError(f"Unsupported type: {type(other)}") # pragma: no cover
raise TypeError(f"Unsupported type {type(other)!r}")
return self
@overload

View File

@@ -56,6 +56,9 @@ class MessageTemplate(Formatter, Generic[TF]):
self.factory: Type[TF] = factory
self.format_specs: Dict[str, FormatSpecFunc] = {}
def __repr__(self) -> str:
return f"MessageTemplate({self.template!r}, factory={self.factory!r})"
def add_format_spec(
self, spec: FormatSpecFunc_T, name: Optional[str] = None
) -> FormatSpecFunc_T:

View File

@@ -40,12 +40,18 @@ class Driver(abc.ABC):
"""环境名称"""
self.config: Config = config
"""全局配置对象"""
self._clients: Dict[str, "Bot"] = {}
self._bots: Dict[str, "Bot"] = {}
def __repr__(self) -> str:
return (
f"Driver(type={self.type!r}, "
f"adapters={len(self._adapters)}, bots={len(self._bots)})"
)
@property
def bots(self) -> Dict[str, "Bot"]:
"""获取当前所有已连接的 Bot"""
return self._clients
return self._bots
def register_adapter(self, adapter: Type["Adapter"], **kwargs) -> None:
"""注册一个协议适配器
@@ -124,9 +130,9 @@ class Driver(abc.ABC):
def _bot_connect(self, bot: "Bot") -> None:
"""在连接成功后,调用该函数来注册 bot 对象"""
if bot.self_id in self._clients:
if bot.self_id in self._bots:
raise RuntimeError(f"Duplicate bot connection with id {bot.self_id}")
self._clients[bot.self_id] = bot
self._bots[bot.self_id] = bot
async def _run_hook(bot: "Bot") -> None:
coros = list(
@@ -148,8 +154,8 @@ class Driver(abc.ABC):
def _bot_disconnect(self, bot: "Bot") -> None:
"""在连接断开后,调用该函数来注销 bot 对象"""
if bot.self_id in self._clients:
del self._clients[bot.self_id]
if bot.self_id in self._bots:
del self._bots[bot.self_id]
async def _run_hook(bot: "Bot") -> None:
coros = list(
@@ -233,13 +239,11 @@ def combine_driver(driver: Type[Driver], *mixins: Type[ForwardMixin]) -> Type[Dr
if not mixins:
return driver
class CombinedDriver(*mixins, driver, ForwardDriver): # type: ignore
@property
def type(self) -> str:
return (
driver.type.__get__(self)
+ "+"
+ "+".join(map(lambda x: x.type.__get__(self), mixins))
)
def type_(self: ForwardDriver) -> str:
return (
driver.type.__get__(self)
+ "+"
+ "+".join(map(lambda x: x.type.__get__(self), mixins))
)
return CombinedDriver
return type("CombinedDriver", (*mixins, driver, ForwardDriver), {"type": property(type_)}) # type: ignore

View File

@@ -131,9 +131,7 @@ class Request:
self.files.append((name, file_info)) # type: ignore
def __repr__(self) -> str:
class_name = self.__class__.__name__
url = str(self.url)
return f"<{class_name}({self.method!r}, {url!r})>"
return f"{self.__class__.__name__}(method={self.method!r}, url='{self.url!s}')"
class Response:
@@ -161,12 +159,18 @@ class Response:
# request
self.request: Optional[Request] = request
def __repr__(self) -> str:
return f"{self.__class__.__name__}(status_code={self.status_code!r})"
class WebSocket(abc.ABC):
def __init__(self, *, request: Request):
# request
self.request: Request = request
def __repr__(self) -> str:
return f"{self.__class__.__name__}('{self.request.url!s}')"
@property
@abc.abstractmethod
def closed(self) -> bool:
@@ -320,17 +324,14 @@ class Cookies(MutableMapping):
return len(self.jar)
def __iter__(self) -> Iterator[Cookie]:
return (cookie for cookie in self.jar)
return iter(self.jar)
def __repr__(self) -> str:
cookies_repr = ", ".join(
[
f"<Cookie {cookie.name}={cookie.value} for {cookie.domain} />"
for cookie in self.jar
]
f"Cookie({cookie.name}={cookie.value} for {cookie.domain})"
for cookie in self.jar
)
return f"<Cookies [{cookies_repr}]>"
return f"{self.__class__.__name__}({cookies_repr})"
@dataclass

View File

@@ -12,8 +12,10 @@ from typing import (
Union,
TypeVar,
Callable,
Iterable,
NoReturn,
Optional,
overload,
)
from nonebot.log import logger
@@ -34,7 +36,6 @@ from nonebot.typing import (
T_PermissionUpdater,
)
from nonebot.exception import (
TypeMisMatch,
PausedException,
StopPropagation,
SkippedException,
@@ -43,7 +44,7 @@ from nonebot.exception import (
)
from .rule import Rule
from .permission import USER, Permission
from .permission import USER, User, Permission
from .adapter import Bot, Event, Message, MessageSegment, MessageTemplate
from .params import (
Depends,
@@ -71,29 +72,16 @@ current_handler: ContextVar[Dependent] = ContextVar("current_handler")
class MatcherMeta(type):
if TYPE_CHECKING:
module: Optional[str]
plugin_name: Optional[str]
module_name: Optional[str]
module_prefix: Optional[str]
type: str
rule: Rule
permission: Permission
handlers: List[T_Handler]
priority: int
block: bool
temp: bool
expire_time: Optional[datetime]
def __repr__(self) -> str:
return (
f"<Matcher from {self.module_name or 'unknown'}, "
f"type={self.type}, priority={self.priority}, "
f"temp={self.temp}>"
f"Matcher(type={self.type!r}"
+ (f", module={self.module_name}" if self.module_name else "")
+ ")"
)
def __str__(self) -> str:
return repr(self)
class Matcher(metaclass=MatcherMeta):
"""事件响应器类"""
@@ -132,7 +120,7 @@ class Matcher(metaclass=MatcherMeta):
_default_permission_updater: Optional[Dependent[Permission]] = None
"""事件响应器权限更新函数"""
HANDLER_PARAM_TYPES = [
HANDLER_PARAM_TYPES = (
DependParam,
BotParam,
EventParam,
@@ -140,7 +128,7 @@ class Matcher(metaclass=MatcherMeta):
ArgParam,
MatcherParam,
DefaultParam,
]
)
def __init__(self):
self.handlers = self.handlers.copy()
@@ -148,13 +136,11 @@ class Matcher(metaclass=MatcherMeta):
def __repr__(self) -> str:
return (
f"<Matcher from {self.module_name or 'unknown'}, type={self.type}, "
f"priority={self.priority}, temp={self.temp}>"
f"Matcher(type={self.type!r}"
+ (f", module={self.module_name}" if self.module_name else "")
+ ")"
)
def __str__(self) -> str:
return repr(self)
@classmethod
def new(
cls,
@@ -218,27 +204,35 @@ class Matcher(metaclass=MatcherMeta):
"temp": temp,
"expire_time": (
expire_time
if isinstance(expire_time, datetime)
else expire_time and datetime.now() + expire_time
and (
expire_time
if isinstance(expire_time, datetime)
else datetime.now() + expire_time
)
),
"priority": priority,
"block": block,
"_default_state": default_state or {},
"_default_type_updater": (
default_type_updater
if isinstance(default_type_updater, Dependent)
else default_type_updater
and Dependent[str].parse(
call=default_type_updater, allow_types=cls.HANDLER_PARAM_TYPES
and (
default_type_updater
if isinstance(default_type_updater, Dependent)
else Dependent[str].parse(
call=default_type_updater,
allow_types=cls.HANDLER_PARAM_TYPES,
)
)
),
"_default_permission_updater": (
default_permission_updater
if isinstance(default_permission_updater, Dependent)
else default_permission_updater
and Dependent[Permission].parse(
call=default_permission_updater,
allow_types=cls.HANDLER_PARAM_TYPES,
and (
default_permission_updater
if isinstance(default_permission_updater, Dependent)
else Dependent[Permission].parse(
call=default_permission_updater,
allow_types=cls.HANDLER_PARAM_TYPES,
)
)
),
},
@@ -326,7 +320,7 @@ class Matcher(metaclass=MatcherMeta):
@classmethod
def append_handler(
cls, handler: T_Handler, parameterless: Optional[List[Any]] = None
cls, handler: T_Handler, parameterless: Optional[Iterable[Any]] = None
) -> Dependent[Any]:
handler_ = Dependent[Any].parse(
call=handler,
@@ -338,7 +332,7 @@ class Matcher(metaclass=MatcherMeta):
@classmethod
def handle(
cls, parameterless: Optional[List[Any]] = None
cls, parameterless: Optional[Iterable[Any]] = None
) -> Callable[[T_Handler], T_Handler]:
"""装饰一个函数来向事件响应器直接添加一个处理函数
@@ -354,7 +348,7 @@ class Matcher(metaclass=MatcherMeta):
@classmethod
def receive(
cls, id: str = "", parameterless: Optional[List[Any]] = None
cls, id: str = "", parameterless: Optional[Iterable[Any]] = None
) -> Callable[[T_Handler], T_Handler]:
"""装饰一个函数来指示 NoneBot 在接收用户新的一条消息后继续运行该函数
@@ -372,14 +366,21 @@ class Matcher(metaclass=MatcherMeta):
return
await matcher.reject()
_parameterless = [Depends(_receive), *(parameterless or [])]
_parameterless = (Depends(_receive), *(parameterless or tuple()))
def _decorator(func: T_Handler) -> T_Handler:
if cls.handlers and cls.handlers[-1].call is func:
func_handler = cls.handlers[-1]
for depend in reversed(_parameterless):
func_handler.prepend_parameterless(depend)
new_handler = Dependent(
call=func_handler.call,
params=func_handler.params,
parameterless=Dependent.parse_parameterless(
tuple(_parameterless), cls.HANDLER_PARAM_TYPES
)
+ func_handler.parameterless,
)
cls.handlers[-1] = new_handler
else:
cls.append_handler(func, parameterless=_parameterless)
@@ -392,7 +393,7 @@ class Matcher(metaclass=MatcherMeta):
cls,
key: str,
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
parameterless: Optional[List[Any]] = None,
parameterless: Optional[Iterable[Any]] = None,
) -> Callable[[T_Handler], T_Handler]:
"""装饰一个函数来指示 NoneBot 获取一个参数 `key`
@@ -413,17 +414,21 @@ class Matcher(metaclass=MatcherMeta):
return
await matcher.reject(prompt)
_parameterless = [
Depends(_key_getter),
*(parameterless or []),
]
_parameterless = (Depends(_key_getter), *(parameterless or tuple()))
def _decorator(func: T_Handler) -> T_Handler:
if cls.handlers and cls.handlers[-1].call is func:
func_handler = cls.handlers[-1]
for depend in reversed(_parameterless):
func_handler.prepend_parameterless(depend)
new_handler = Dependent(
call=func_handler.call,
params=func_handler.params,
parameterless=Dependent.parse_parameterless(
tuple(_parameterless), cls.HANDLER_PARAM_TYPES
)
+ func_handler.parameterless,
)
cls.handlers[-1] = new_handler
else:
cls.append_handler(func, parameterless=_parameterless)
@@ -551,7 +556,17 @@ class Matcher(metaclass=MatcherMeta):
"""
raise SkippedException
def get_receive(self, id: str, default: T = None) -> Union[Event, T]:
@overload
def get_receive(self, id: str) -> Union[Event, None]:
...
@overload
def get_receive(self, id: str, default: T) -> Union[Event, T]:
...
def get_receive(
self, id: str, default: Optional[T] = None
) -> Optional[Union[Event, T]]:
"""获取一个 `receive` 事件
如果没有找到对应的事件,返回 `default` 值
@@ -563,14 +578,34 @@ class Matcher(metaclass=MatcherMeta):
self.state[RECEIVE_KEY.format(id=id)] = event
self.state[LAST_RECEIVE_KEY] = event
def get_last_receive(self, default: T = None) -> Union[Event, T]:
@overload
def get_last_receive(self) -> Union[Event, None]:
...
@overload
def get_last_receive(self, default: T) -> Union[Event, T]:
...
def get_last_receive(
self, default: Optional[T] = None
) -> Optional[Union[Event, T]]:
"""获取最近一次 `receive` 事件
如果没有事件,返回 `default` 值
"""
return self.state.get(LAST_RECEIVE_KEY, default)
def get_arg(self, key: str, default: T = None) -> Union[Message, T]:
@overload
def get_arg(self, key: str) -> Union[Message, None]:
...
@overload
def get_arg(self, key: str, default: T) -> Union[Message, T]:
...
def get_arg(
self, key: str, default: Optional[T] = None
) -> Optional[Union[Message, T]]:
"""获取一个 `got` 消息
如果没有找到对应的消息,返回 `default` 值
@@ -587,7 +622,15 @@ class Matcher(metaclass=MatcherMeta):
else:
self.state[REJECT_TARGET] = target
def get_target(self, default: T = None) -> Union[str, T]:
@overload
def get_target(self) -> Union[str, None]:
...
@overload
def get_target(self, default: T) -> Union[str, T]:
...
def get_target(self, default: Optional[T] = None) -> Optional[Union[str, T]]:
return self.state.get(REJECT_TARGET, default)
def stop_propagation(self):
@@ -596,15 +639,21 @@ class Matcher(metaclass=MatcherMeta):
async def update_type(self, bot: Bot, event: Event) -> str:
updater = self.__class__._default_type_updater
if not updater:
return "message"
return await updater(bot=bot, event=event, state=self.state, matcher=self)
return (
await updater(bot=bot, event=event, state=self.state, matcher=self)
if updater
else "message"
)
async def update_permission(self, bot: Bot, event: Event) -> Permission:
updater = self.__class__._default_permission_updater
if not updater:
return USER(event.get_session_id(), perm=self.permission)
return await updater(bot=bot, event=event, state=self.state, matcher=self)
if updater := self.__class__._default_permission_updater:
return await updater(bot=bot, event=event, state=self.state, matcher=self)
permission = self.permission
if len(permission.checkers) == 1 and isinstance(
user_perm := tuple(permission.checkers)[0].call, User
):
permission = user_perm.perm
return USER(event.get_session_id(), perm=permission)
async def resolve_reject(self):
handler = current_handler.get()
@@ -621,8 +670,8 @@ class Matcher(metaclass=MatcherMeta):
dependency_cache: Optional[T_DependencyCache] = None,
):
logger.trace(
f"Matcher {self} run with incoming args: "
f"bot={bot}, event={event}, state={state}"
f"{self} run with incoming args: "
f"bot={bot}, event={event!r}, state={state!r}"
)
b_t = current_bot.set(bot)
e_t = current_event.set(event)
@@ -644,17 +693,12 @@ class Matcher(metaclass=MatcherMeta):
stack=stack,
dependency_cache=dependency_cache,
)
except TypeMisMatch as e:
logger.debug(
f"Handler {handler} param {e.param.name} value {e.value} "
f"mismatch type {e.param._type_display()}, skipped"
)
except SkippedException as e:
except SkippedException:
logger.debug(f"Handler {handler} skipped")
except StopPropagation:
self.block = True
finally:
logger.info(f"Matcher {self} running complete")
logger.info(f"{self} running complete")
current_bot.reset(b_t)
current_event.reset(e_t)
current_matcher.reset(m_t)

View File

@@ -1,14 +1,10 @@
import asyncio
import inspect
import warnings
from typing_extensions import Literal
from typing import TYPE_CHECKING, Any, Callable, Optional, cast
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
from typing import TYPE_CHECKING, Any, Type, Tuple, Literal, Callable, Optional, cast
from pydantic.fields import Required, Undefined, ModelField
from nonebot.log import logger
from nonebot.exception import TypeMisMatch
from nonebot.dependencies.utils import check_field_type
from nonebot.dependencies import Param, Dependent, CustomConfig
from nonebot.typing import T_State, T_Handler, T_DependencyCache
@@ -40,7 +36,7 @@ class DependsInner:
def __repr__(self) -> str:
dep = get_name(self.dependency)
cache = "" if self.use_cache else ", use_cache=False"
return f"{self.__class__.__name__}({dep}{cache})"
return f"DependsInner({dep}{cache})"
def Depends(
@@ -75,12 +71,12 @@ def Depends(
class DependParam(Param):
"""子依赖参数"""
def __repr__(self) -> str:
return f"Depends({self.extra['dependent']})"
@classmethod
def _check_param(
cls,
dependent: Dependent,
name: str,
param: inspect.Parameter,
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["DependParam"]:
if isinstance(param.default, DependsInner):
dependency: T_Handler
@@ -91,22 +87,20 @@ class DependParam(Param):
dependency = param.default.dependency
sub_dependent = Dependent[Any].parse(
call=dependency,
allow_types=dependent.allow_types,
allow_types=allow_types,
)
dependent.pre_checkers.extend(sub_dependent.pre_checkers)
sub_dependent.pre_checkers.clear()
return cls(
Required, use_cache=param.default.use_cache, dependent=sub_dependent
)
@classmethod
def _check_parameterless(
cls, dependent: "Dependent", value: Any
cls, value: Any, allow_types: Tuple[Type[Param], ...]
) -> Optional["Param"]:
if isinstance(value, DependsInner):
assert value.dependency, "Dependency cannot be empty"
dependent = Dependent[Any].parse(
call=value.dependency, allow_types=dependent.allow_types
call=value.dependency, allow_types=allow_types
)
return cls(Required, use_cache=value.use_cache, dependent=dependent)
@@ -120,8 +114,7 @@ class DependParam(Param):
dependency_cache = {} if dependency_cache is None else dependency_cache
sub_dependent: Dependent = self.extra["dependent"]
sub_dependent.call = cast(Callable[..., Any], sub_dependent.call)
call = sub_dependent.call
call = cast(Callable[..., Any], sub_dependent.call)
# solve sub dependency with current cache
sub_values = await sub_dependent.solve(
@@ -133,7 +126,7 @@ class DependParam(Param):
# run dependency function
task: asyncio.Task[Any]
if use_cache and call in dependency_cache:
solved = await dependency_cache[call]
return await dependency_cache[call]
elif is_gen_callable(call) or is_async_gen_callable(call):
assert isinstance(
stack, AsyncExitStack
@@ -144,134 +137,124 @@ class DependParam(Param):
cm = asynccontextmanager(call)(**sub_values)
task = asyncio.create_task(stack.enter_async_context(cm))
dependency_cache[call] = task
solved = await task
return await task
elif is_coroutine_callable(call):
task = asyncio.create_task(call(**sub_values))
dependency_cache[call] = task
solved = await task
return await task
else:
task = asyncio.create_task(run_sync(call)(**sub_values))
dependency_cache[call] = task
solved = await task
return await task
return solved
class _BotChecker(Param):
async def _solve(self, bot: "Bot", **kwargs: Any) -> Any:
field: ModelField = self.extra["field"]
try:
return check_field_type(field, bot)
except TypeMisMatch:
logger.debug(
f"Bot type {type(bot)} not match "
f"annotation {field._type_display()}, ignored"
)
raise
async def _check(self, **kwargs: Any) -> None:
# run sub dependent pre-checkers
sub_dependent: Dependent = self.extra["dependent"]
await sub_dependent.check(**kwargs)
class BotParam(Param):
"""{ref}`nonebot.adapters.Bot` 参数"""
def __repr__(self) -> str:
return (
"BotParam("
+ (
repr(cast(ModelField, checker).type_)
if (checker := self.extra.get("checker"))
else ""
)
+ ")"
)
@classmethod
def _check_param(
cls, dependent: Dependent, name: str, param: inspect.Parameter
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["BotParam"]:
from nonebot.adapters import Bot
if param.default == param.empty:
if generic_check_issubclass(param.annotation, Bot):
checker: Optional[ModelField] = None
if param.annotation is not Bot:
dependent.pre_checkers.append(
_BotChecker(
Required,
field=ModelField(
name=name,
type_=param.annotation,
class_validators=None,
model_config=CustomConfig,
default=None,
required=True,
),
)
checker = ModelField(
name=param.name,
type_=param.annotation,
class_validators=None,
model_config=CustomConfig,
default=None,
required=True,
)
return cls(Required)
elif param.annotation == param.empty and name == "bot":
return cls(Required, checker=checker)
elif param.annotation == param.empty and param.name == "bot":
return cls(Required)
async def _solve(self, bot: "Bot", **kwargs: Any) -> Any:
return bot
class _EventChecker(Param):
async def _solve(self, event: "Event", **kwargs: Any) -> Any:
field: ModelField = self.extra["field"]
try:
return check_field_type(field, event)
except TypeMisMatch:
logger.debug(
f"Event type {type(event)} not match "
f"annotation {field._type_display()}, ignored"
)
raise
async def _check(self, bot: "Bot", **kwargs: Any) -> None:
if checker := self.extra.get("checker"):
check_field_type(checker, bot)
class EventParam(Param):
"""{ref}`nonebot.adapters.Event` 参数"""
def __repr__(self) -> str:
return (
"EventParam("
+ (
repr(cast(ModelField, checker).type_)
if (checker := self.extra.get("checker"))
else ""
)
+ ")"
)
@classmethod
def _check_param(
cls, dependent: Dependent, name: str, param: inspect.Parameter
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["EventParam"]:
from nonebot.adapters import Event
if param.default == param.empty:
if generic_check_issubclass(param.annotation, Event):
checker: Optional[ModelField] = None
if param.annotation is not Event:
dependent.pre_checkers.append(
_EventChecker(
Required,
field=ModelField(
name=name,
type_=param.annotation,
class_validators=None,
model_config=CustomConfig,
default=None,
required=True,
),
)
checker = ModelField(
name=param.name,
type_=param.annotation,
class_validators=None,
model_config=CustomConfig,
default=None,
required=True,
)
return cls(Required)
elif param.annotation == param.empty and name == "event":
return cls(Required, checker=checker)
elif param.annotation == param.empty and param.name == "event":
return cls(Required)
async def _solve(self, event: "Event", **kwargs: Any) -> Any:
return event
class StateInner(T_State):
...
def State() -> T_State:
"""**Deprecated**: 事件处理状态参数,请直接使用 {ref}`nonebot.typing.T_State`"""
warnings.warn("State() is deprecated, use `T_State` instead", DeprecationWarning)
return StateInner()
async def _check(self, event: "Event", **kwargs: Any) -> Any:
if checker := self.extra.get("checker", None):
check_field_type(checker, event)
class StateParam(Param):
"""事件处理状态参数"""
def __repr__(self) -> str:
return "StateParam()"
@classmethod
def _check_param(
cls, dependent: Dependent, name: str, param: inspect.Parameter
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["StateParam"]:
if isinstance(param.default, StateInner):
return cls(Required)
elif param.default == param.empty:
if param.default == param.empty:
if param.annotation is T_State:
return cls(Required)
elif param.annotation == param.empty and name == "state":
elif param.annotation == param.empty and param.name == "state":
return cls(Required)
async def _solve(self, state: T_State, **kwargs: Any) -> Any:
@@ -281,14 +264,17 @@ class StateParam(Param):
class MatcherParam(Param):
"""事件响应器实例参数"""
def __repr__(self) -> str:
return "MatcherParam()"
@classmethod
def _check_param(
cls, dependent: Dependent, name: str, param: inspect.Parameter
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["MatcherParam"]:
from nonebot.matcher import Matcher
if generic_check_issubclass(param.annotation, Matcher) or (
param.annotation == param.empty and name == "matcher"
param.annotation == param.empty and param.name == "matcher"
):
return cls(Required)
@@ -303,6 +289,9 @@ class ArgInner:
self.key = key
self.type = type
def __repr__(self) -> str:
return f"ArgInner(key={self.key!r}, type={self.type!r})"
def Arg(key: Optional[str] = None) -> Any:
"""`got` 的 Arg 参数消息"""
@@ -322,12 +311,17 @@ def ArgPlainText(key: Optional[str] = None) -> str:
class ArgParam(Param):
"""`got` 的 Arg 参数"""
def __repr__(self) -> str:
return f"ArgParam(key={self.extra['key']!r}, type={self.extra['type']!r})"
@classmethod
def _check_param(
cls, dependent: Dependent, name: str, param: inspect.Parameter
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["ArgParam"]:
if isinstance(param.default, ArgInner):
return cls(Required, key=param.default.key or name, type=param.default.type)
return cls(
Required, key=param.default.key or param.name, type=param.default.type
)
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
message = matcher.get_arg(self.extra["key"])
@@ -344,12 +338,15 @@ class ArgParam(Param):
class ExceptionParam(Param):
"""`run_postprocessor` 的异常参数"""
def __repr__(self) -> str:
return "ExceptionParam()"
@classmethod
def _check_param(
cls, dependent: Dependent, name: str, param: inspect.Parameter
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["ExceptionParam"]:
if generic_check_issubclass(param.annotation, Exception) or (
param.annotation == param.empty and name == "exception"
param.annotation == param.empty and param.name == "exception"
):
return cls(Required)
@@ -360,9 +357,12 @@ class ExceptionParam(Param):
class DefaultParam(Param):
"""默认值参数"""
def __repr__(self) -> str:
return f"DefaultParam(default={self.default!r})"
@classmethod
def _check_param(
cls, dependent: Dependent, name: str, param: inspect.Parameter
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["DefaultParam"]:
if param.default != param.empty:
return cls(param.default)

View File

@@ -1,6 +1,6 @@
import asyncio
from contextlib import AsyncExitStack
from typing import Any, Set, Tuple, Union, NoReturn, Optional, Coroutine
from typing import Set, Tuple, Union, NoReturn, Optional
from nonebot.dependencies import Dependent
from nonebot.utils import run_coro_with_catch
@@ -37,16 +37,19 @@ class Permission:
]
def __init__(self, *checkers: Union[T_PermissionChecker, Dependent[bool]]) -> None:
self.checkers: Set[Dependent[bool]] = set(
self.checkers: Set[Dependent[bool]] = {
checker
if isinstance(checker, Dependent)
else Dependent[bool].parse(
call=checker, allow_types=self.HANDLER_PARAM_TYPES
)
for checker in checkers
)
}
"""存储 `PermissionChecker`"""
def __repr__(self) -> str:
return f"Permission({', '.join(repr(checker) for checker in self.checkers)})"
async def __call__(
self,
bot: Bot,
@@ -121,10 +124,20 @@ class User:
self.users = users
self.perm = perm
def __repr__(self) -> str:
return (
f"User(users={self.users}"
+ (f", permission={self.perm})" if self.perm else "")
+ ")"
)
async def __call__(self, bot: Bot, event: Event) -> bool:
try:
session = event.get_session_id()
except Exception:
return False
return bool(
event.get_session_id() in self.users
and (self.perm is None or await self.perm(bot, event))
session in self.users and (self.perm is None or await self.perm(bot, event))
)

View File

@@ -37,16 +37,19 @@ class Rule:
]
def __init__(self, *checkers: Union[T_RuleChecker, Dependent[bool]]) -> None:
self.checkers: Set[Dependent[bool]] = set(
self.checkers: Set[Dependent[bool]] = {
checker
if isinstance(checker, Dependent)
else Dependent[bool].parse(
call=checker, allow_types=self.HANDLER_PARAM_TYPES
)
for checker in checkers
)
}
"""存储 `RuleChecker`"""
def __repr__(self) -> str:
return f"Rule({', '.join(repr(checker) for checker in self.checkers)})"
async def __call__(
self,
bot: Bot,

View File

@@ -14,16 +14,14 @@ FrontMatter:
import sys
import logging
from typing import TYPE_CHECKING, Union
from typing import TYPE_CHECKING
import loguru
if TYPE_CHECKING:
# avoid sphinx autodoc resolve annotation failed
# because loguru module do not have `Logger` class actually
from loguru import Logger
from nonebot.plugin import Plugin
from loguru import Logger, Record
# logger = logging.getLogger("nonebot")
logger: "Logger" = loguru.logger
@@ -47,26 +45,10 @@ logger: "Logger" = loguru.logger
# logger.addHandler(default_handler)
class Filter:
def __init__(self) -> None:
self.level: Union[int, str] = "INFO"
def __call__(self, record):
module_name: str = record["name"]
# TODO: get plugin name instead of module name
# module = sys.modules.get(module_name)
# if module and hasattr(module, "__plugin__"):
# plugin: "Plugin" = getattr(module, "__plugin__")
# module_name = plugin.module_name
record["name"] = module_name.split(".")[0]
levelno = (
logger.level(self.level).no if isinstance(self.level, str) else self.level
)
return record["level"].no >= levelno
class LoguruHandler(logging.Handler): # pragma: no cover
def emit(self, record):
"""logging 与 loguru 之间的桥梁,将 logging 的日志转发到 loguru。"""
def emit(self, record: logging.LogRecord):
try:
level = logger.level(record.levelname).name
except ValueError:
@@ -82,9 +64,13 @@ class LoguruHandler(logging.Handler): # pragma: no cover
)
logger.remove()
default_filter: Filter = Filter()
"""默认日志等级过滤器"""
def default_filter(record: "Record"):
"""默认的日志过滤器,根据 `config.log_level` 配置改变日志等级。"""
log_level = record["extra"].get("nonebot_log_level", "INFO")
levelno = logger.level(log_level).no if isinstance(log_level, str) else log_level
return record["level"].no >= levelno
default_format: str = (
"<g>{time:MM-DD HH:mm:ss}</g> "
"[<lvl>{level}</lvl>] "
@@ -93,6 +79,8 @@ default_format: str = (
"{message}"
)
"""默认日志格式"""
logger.remove()
logger_id = logger.add(
sys.stdout,
level=0,
@@ -101,4 +89,4 @@ logger_id = logger.add(
format=default_format,
)
__autodoc__ = {"Filter": False, "LoguruHandler": False}
__autodoc__ = {"logger_id": False}

View File

@@ -8,9 +8,10 @@ FrontMatter:
"""
import asyncio
import contextlib
from datetime import datetime
from contextlib import AsyncExitStack
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional, Coroutine
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional
from nonebot.log import logger
from nonebot.rule import TrieRule
@@ -50,14 +51,14 @@ _event_postprocessors: Set[Dependent[Any]] = set()
_run_preprocessors: Set[Dependent[Any]] = set()
_run_postprocessors: Set[Dependent[Any]] = set()
EVENT_PCS_PARAMS = [
EVENT_PCS_PARAMS = (
DependParam,
BotParam,
EventParam,
StateParam,
DefaultParam,
]
RUN_PREPCS_PARAMS = [
)
RUN_PREPCS_PARAMS = (
DependParam,
BotParam,
EventParam,
@@ -65,8 +66,8 @@ RUN_PREPCS_PARAMS = [
ArgParam,
MatcherParam,
DefaultParam,
]
RUN_POSTPCS_PARAMS = [
)
RUN_POSTPCS_PARAMS = (
DependParam,
ExceptionParam,
BotParam,
@@ -75,7 +76,7 @@ RUN_POSTPCS_PARAMS = [
ArgParam,
MatcherParam,
DefaultParam,
]
)
def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor:
@@ -120,10 +121,8 @@ async def _check_matcher(
dependency_cache: Optional[T_DependencyCache] = None,
) -> None:
if Matcher.expire_time and datetime.now() > Matcher.expire_time:
try:
with contextlib.suppress(Exception):
matchers[priority].remove(Matcher)
except Exception:
pass
return
try:
@@ -138,11 +137,8 @@ async def _check_matcher(
return
if Matcher.temp:
try:
with contextlib.suppress(Exception):
matchers[priority].remove(Matcher)
except Exception:
pass
await _run_matcher(Matcher, bot, event, state, stack, dependency_cache)
@@ -157,67 +153,58 @@ async def _run_matcher(
logger.info(f"Event will be handled by {Matcher}")
matcher = Matcher()
coros = list(
map(
lambda x: run_coro_with_catch(
x(
matcher=matcher,
bot=bot,
event=event,
state=state,
stack=stack,
dependency_cache=dependency_cache,
),
(SkippedException,),
if coros := [
run_coro_with_catch(
proc(
matcher=matcher,
bot=bot,
event=event,
state=state,
stack=stack,
dependency_cache=dependency_cache,
),
_run_preprocessors,
(SkippedException,),
)
)
if coros:
for proc in _run_preprocessors
]:
try:
await asyncio.gather(*coros)
except IgnoredException:
logger.opt(colors=True).info(
f"Matcher {matcher} running is <b>cancelled</b>"
)
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>"
"<r><bg #f8bbd0>Error when running RunPreProcessors. Running cancelled!</bg #f8bbd0></r>"
)
return
exception = None
try:
logger.debug(f"Running matcher {matcher}")
logger.debug(f"Running {matcher}")
await matcher.run(bot, event, state, stack, dependency_cache)
except Exception as e:
logger.opt(colors=True, exception=e).error(
f"<r><bg #f8bbd0>Running matcher {matcher} failed.</bg #f8bbd0></r>"
f"<r><bg #f8bbd0>Running {matcher} failed.</bg #f8bbd0></r>"
)
exception = e
coros = list(
map(
lambda x: run_coro_with_catch(
x(
matcher=matcher,
exception=exception,
bot=bot,
event=event,
state=matcher.state,
stack=stack,
dependency_cache=dependency_cache,
),
(SkippedException,),
if coros := [
run_coro_with_catch(
proc(
matcher=matcher,
exception=exception,
bot=bot,
event=event,
state=matcher.state,
stack=stack,
dependency_cache=dependency_cache,
),
_run_postprocessors,
(SkippedException,),
)
)
if coros:
for proc in _run_postprocessors
]:
try:
await asyncio.gather(*coros)
except Exception as e:
@@ -244,7 +231,7 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
```
"""
show_log = True
log_msg = f"<m>{escape_tag(bot.type.upper())} {escape_tag(bot.self_id)}</m> | "
log_msg = f"<m>{escape_tag(bot.type)} {escape_tag(bot.self_id)}</m> | "
try:
log_msg += event.get_log_string()
except NoLogException:
@@ -256,22 +243,19 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
dependency_cache: T_DependencyCache = {}
async with AsyncExitStack() as stack:
coros = list(
map(
lambda x: run_coro_with_catch(
x(
bot=bot,
event=event,
state=state,
stack=stack,
dependency_cache=dependency_cache,
),
(SkippedException,),
if coros := [
run_coro_with_catch(
proc(
bot=bot,
event=event,
state=state,
stack=stack,
dependency_cache=dependency_cache,
),
_event_preprocessors,
(SkippedException,),
)
)
if coros:
for proc in _event_preprocessors
]:
try:
if show_log:
logger.debug("Running PreProcessors...")
@@ -324,22 +308,19 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
"<r><bg #f8bbd0>Error when checking Matcher.</bg #f8bbd0></r>"
)
coros = list(
map(
lambda x: run_coro_with_catch(
x(
bot=bot,
event=event,
state=state,
stack=stack,
dependency_cache=dependency_cache,
),
(SkippedException,),
if coros := [
run_coro_with_catch(
proc(
bot=bot,
event=event,
state=state,
stack=stack,
dependency_cache=dependency_cache,
),
_event_postprocessors,
(SkippedException,),
)
)
if coros:
for proc in _event_postprocessors
]:
try:
if show_log:
logger.debug("Running PostProcessors...")

View File

@@ -5,17 +5,16 @@ FrontMatter:
description: nonebot.params 模块
"""
from typing import Any, Dict, List, Tuple, Optional
from typing import Any, Dict, List, Tuple, Union, Optional
from nonebot.typing import T_State
from nonebot.matcher import Matcher
from nonebot.adapters import Event, Message
from nonebot.internal.params import Arg as Arg
from nonebot.internal.params import State as State
from nonebot.internal.params import ArgStr as ArgStr
from nonebot.internal.params import Depends as Depends
from nonebot.internal.params import ArgParam as ArgParam
from nonebot.internal.params import BotParam as BotParam
from nonebot.adapters import Event, Message, MessageSegment
from nonebot.internal.params import EventParam as EventParam
from nonebot.internal.params import StateParam as StateParam
from nonebot.internal.params import DependParam as DependParam
@@ -110,15 +109,15 @@ def CommandStart() -> str:
def _shell_command_args(state: T_State) -> Any:
return state[SHELL_ARGS]
return state[SHELL_ARGS] # Namespace or ParserExit
def ShellCommandArgs():
def ShellCommandArgs() -> Any:
"""shell 命令解析后的参数字典"""
return Depends(_shell_command_args, use_cache=False)
def _shell_command_argv(state: T_State) -> List[str]:
def _shell_command_argv(state: T_State) -> List[Union[str, MessageSegment]]:
return state[SHELL_ARGV]
@@ -174,7 +173,6 @@ def LastReceived(default: Any = None) -> Any:
__autodoc__ = {
"Arg": True,
"State": True,
"ArgStr": True,
"Depends": True,
"ArgParam": True,

View File

@@ -20,6 +20,9 @@ class Message:
__slots__ = ()
def __repr__(self) -> str:
return "Message()"
async def __call__(self, type: str = EventType()) -> bool:
return type == "message"
@@ -29,6 +32,9 @@ class Notice:
__slots__ = ()
def __repr__(self) -> str:
return "Notice()"
async def __call__(self, type: str = EventType()) -> bool:
return type == "notice"
@@ -38,6 +44,9 @@ class Request:
__slots__ = ()
def __repr__(self) -> str:
return "Request()"
async def __call__(self, type: str = EventType()) -> bool:
return type == "request"
@@ -47,6 +56,9 @@ class MetaEvent:
__slots__ = ()
def __repr__(self) -> str:
return "MetaEvent()"
async def __call__(self, type: str = EventType()) -> bool:
return type == "meta_event"
@@ -78,16 +90,23 @@ class SuperUser:
__slots__ = ()
def __repr__(self) -> str:
return "Superuser()"
async def __call__(self, bot: Bot, event: Event) -> bool:
return event.get_type() == "message" and (
f"{bot.adapter.get_name().split(maxsplit=1)[0].lower()}:{event.get_user_id()}"
try:
user_id = event.get_user_id()
except Exception:
return False
return (
f"{bot.adapter.get_name().split(maxsplit=1)[0].lower()}:{user_id}"
in bot.config.superusers
or event.get_user_id() in bot.config.superusers # 兼容旧配置
or user_id in bot.config.superusers # 兼容旧配置
)
SUPERUSER: Permission = Permission(SuperUser())
"""匹配任意超级用户消息类型事件"""
"""匹配任意超级用户事件"""
__autodoc__ = {
"Permission": True,

View File

@@ -16,6 +16,7 @@
- `on_command` => {ref}``on_command` <nonebot.plugin.on.on_command>`
- `on_shell_command` => {ref}``on_shell_command` <nonebot.plugin.on.on_shell_command>`
- `on_regex` => {ref}``on_regex` <nonebot.plugin.on.on_regex>`
- `on_type` => {ref}``on_type` <nonebot.plugin.on.on_type>`
- `CommandGroup` => {ref}``CommandGroup` <nonebot.plugin.on.CommandGroup>`
- `Matchergroup` => {ref}``MatcherGroup` <nonebot.plugin.on.MatcherGroup>`
- `load_plugin` => {ref}``load_plugin` <nonebot.plugin.load.load_plugin>`
@@ -25,8 +26,8 @@
- `load_from_toml` => {ref}``load_from_toml` <nonebot.plugin.load.load_from_toml>`
- `load_builtin_plugin` => {ref}``load_builtin_plugin` <nonebot.plugin.load.load_builtin_plugin>`
- `load_builtin_plugins` => {ref}``load_builtin_plugins` <nonebot.plugin.load.load_builtin_plugins>`
- `export` => {ref}``export` <nonebot.plugin.export.export>`
- `require` => {ref}``require` <nonebot.plugin.load.require>`
- `PluginMetadata` => {ref}``PluginMetadata` <nonebot.plugin.plugin.PluginMetadata>`
FrontMatter:
sidebar_position: 0
@@ -85,13 +86,12 @@ def get_plugin_by_module_name(module_name: str) -> Optional["Plugin"]:
参数:
module_name: 模块名,即 {ref}`nonebot.plugin.plugin.Plugin.module_name`。
"""
splits = module_name.split(".")
loaded = {plugin.module_name: plugin for plugin in _plugins.values()}
while splits:
name = ".".join(splits)
if name in loaded:
return loaded[name]
splits.pop()
has_parent = True
while has_parent:
if module_name in loaded:
return loaded[module_name]
module_name, *has_parent = module_name.rsplit(".", 1)
def get_loaded_plugins() -> Set["Plugin"]:
@@ -106,8 +106,7 @@ def get_available_plugin_names() -> Set[str]:
from .on import on as on
from .manager import PluginManager
from .export import Export as Export
from .export import export as export
from .on import on_type as on_type
from .load import require as require
from .on import on_regex as on_regex
from .plugin import Plugin as Plugin

View File

@@ -1,64 +0,0 @@
"""本模块定义了插件导出的内容对象。
在新版插件系统中,推荐优先使用直接 import 所需要的插件内容。
FrontMatter:
sidebar_position: 4
description: nonebot.plugin.export 模块
"""
import warnings
from . import _current_plugin_chain
class Export(dict):
"""插件导出内容以使得其他插件可以获得。
用法:
```python
nonebot.export().default = "bar"
@nonebot.export()
def some_function():
pass
# this doesn't work before python 3.9
# use
# export = nonebot.export(); @export.sub
# instead
# See also PEP-614: https://www.python.org/dev/peps/pep-0614/
@nonebot.export().sub
def something_else():
pass
```
"""
def __call__(self, func, **kwargs):
self[func.__name__] = func
self.update(kwargs)
return func
def __setitem__(self, key, value):
super().__setitem__(key, Export(value) if isinstance(value, dict) else value)
def __setattr__(self, name, value):
self[name] = Export(value) if isinstance(value, dict) else value
def __getattr__(self, name):
if name not in self:
self[name] = Export()
return self[name]
def export() -> Export:
"""获取当前插件的导出内容对象"""
warnings.warn(
"nonebot.export() is deprecated. "
"See https://github.com/nonebot/nonebot2/issues/935.",
DeprecationWarning,
)
plugins = _current_plugin_chain.get()
if not plugins:
raise RuntimeError("Export outside of the plugin!")
return plugins[-1].export

View File

@@ -5,24 +5,30 @@ FrontMatter:
description: nonebot.plugin.load 模块
"""
import json
import warnings
from typing import Set, Iterable, Optional
from pathlib import Path
from types import ModuleType
from typing import Set, Union, Iterable, Optional
import tomlkit
from .export import Export
from nonebot.utils import path_to_module_name
from .plugin import Plugin
from .manager import PluginManager
from . import _managers, get_plugin, _module_name_to_plugin_name
def load_plugin(module_path: str) -> Optional[Plugin]:
def load_plugin(module_path: Union[str, Path]) -> Optional[Plugin]:
"""加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。
参数:
module_path: 插件名称 `path.to.your.plugin`
module_path: 插件名称 `path.to.your.plugin` 或插件路径 `pathlib.Path(path/to/your/plugin)`
"""
module_path = (
path_to_module_name(module_path)
if isinstance(module_path, Path)
else module_path
)
manager = PluginManager([module_path])
_managers.append(manager)
return manager.load_plugin(module_path)
@@ -74,6 +80,8 @@ def load_from_json(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
"""
with open(file_path, "r", encoding=encoding) as f:
data = json.load(f)
if not isinstance(data, dict):
raise TypeError("json file must contains a dict!")
plugins = data.get("plugins")
plugin_dirs = data.get("plugin_dirs")
assert isinstance(plugins, list), "plugins must be a list of plugin name"
@@ -103,15 +111,10 @@ def load_from_toml(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
data = tomlkit.parse(f.read()) # type: ignore
nonebot_data = data.get("tool", {}).get("nonebot")
if not nonebot_data:
nonebot_data = data.get("nonebot", {}).get("plugins")
if nonebot_data:
warnings.warn(
"[nonebot.plugins] table is deprecated. Use [tool.nonebot] instead.",
DeprecationWarning,
)
else:
raise ValueError("Cannot find '[tool.nonebot]' in given toml file!")
if nonebot_data is None:
raise ValueError("Cannot find '[tool.nonebot]' in given toml file!")
if not isinstance(nonebot_data, dict):
raise TypeError("'[tool.nonebot]' must be a Table!")
plugins = nonebot_data.get("plugins", [])
plugin_dirs = nonebot_data.get("plugin_dirs", [])
assert isinstance(plugins, list), "plugins must be a list of plugin name"
@@ -143,7 +146,7 @@ def _find_manager_by_name(name: str) -> Optional[PluginManager]:
return manager
def require(name: str) -> Export:
def require(name: str) -> ModuleType:
"""获取一个插件的导出内容。
如果为 `load_plugins` 文件夹导入的插件,则为文件(夹)名。
@@ -156,11 +159,10 @@ def require(name: str) -> Export:
"""
plugin = get_plugin(_module_name_to_plugin_name(name))
if not plugin:
manager = _find_manager_by_name(name)
if manager:
if manager := _find_manager_by_name(name):
plugin = manager.load_plugin(name)
else:
plugin = load_plugin(name)
if not plugin:
raise RuntimeError(f'Cannot load plugin "{name}"!')
return plugin.export
if not plugin:
raise RuntimeError(f'Cannot load plugin "{name}"!')
return plugin.module

View File

@@ -17,7 +17,7 @@ from importlib.machinery import PathFinder, SourceFileLoader
from typing import Set, Dict, List, Union, Iterable, Optional, Sequence
from nonebot.log import logger
from nonebot.utils import escape_tag
from nonebot.utils import escape_tag, path_to_module_name
from .plugin import Plugin, PluginMetadata
from . import (
@@ -51,6 +51,9 @@ class PluginManager:
self._searched_plugin_names: Dict[str, Path] = {}
self.prepare_plugins()
def __repr__(self) -> str:
return f"PluginManager(plugins={self.plugins}, search_path={self.search_path})"
@property
def third_party_plugins(self) -> Set[str]:
"""返回所有独立插件名称。"""
@@ -66,13 +69,6 @@ class PluginManager:
"""返回当前插件管理器中可用的插件名称。"""
return self.third_party_plugins | self.searched_plugins
def _path_to_module_name(self, path: Path) -> str:
rel_path = path.resolve().relative_to(Path(".").resolve())
if rel_path.stem == "__init__":
return ".".join(rel_path.parts[:-1])
else:
return ".".join(rel_path.parts[:-1] + (rel_path.stem,))
def _previous_plugins(self) -> Set[str]:
_pre_managers: List[PluginManager]
if self in _managers:
@@ -86,7 +82,6 @@ class PluginManager:
def prepare_plugins(self) -> Set[str]:
"""搜索插件并缓存插件名称。"""
# get all previous ready to load plugins
previous_plugins = self._previous_plugins()
searched_plugins: Dict[str, Path] = {}
@@ -118,11 +113,13 @@ class PluginManager:
f"Plugin already exists: {module_info.name}! Check your plugin name"
)
module_spec = module_info.module_finder.find_spec(module_info.name, None)
if not module_spec:
if not (
module_spec := module_info.module_finder.find_spec(
module_info.name, None
)
):
continue
module_path = module_spec.origin
if not module_path:
if not (module_path := module_spec.origin):
continue
searched_plugins[module_info.name] = Path(module_path).resolve()
@@ -146,7 +143,7 @@ class PluginManager:
module = importlib.import_module(self._third_party_plugin_names[name])
elif name in self._searched_plugin_names:
module = importlib.import_module(
self._path_to_module_name(self._searched_plugin_names[name])
path_to_module_name(self._searched_plugin_names[name])
)
else:
raise RuntimeError(f"Plugin not found: {name}! Check your plugin name")
@@ -154,8 +151,7 @@ class PluginManager:
logger.opt(colors=True).success(
f'Succeeded to import "<y>{escape_tag(name)}</y>"'
)
plugin = getattr(module, "__plugin__", None)
if plugin is None:
if (plugin := getattr(module, "__plugin__", None)) is None:
raise RuntimeError(
f"Module {module.__name__} is not loaded as a plugin! "
"Make sure not to import it before loading."

View File

@@ -10,6 +10,7 @@ from types import ModuleType
from datetime import datetime, timedelta
from typing import Any, Set, Dict, List, Type, Tuple, Union, Optional
from nonebot.adapters import Event
from nonebot.matcher import Matcher
from nonebot.permission import Permission
from nonebot.dependencies import Dependent
@@ -19,6 +20,7 @@ from nonebot.rule import (
ArgumentParser,
regex,
command,
is_type,
keyword,
endswith,
fullmatch,
@@ -30,9 +32,8 @@ from .manager import _current_plugin_chain
def _store_matcher(matcher: Type[Matcher]) -> None:
plugins = _current_plugin_chain.get()
# only store the matcher defined in the plugin
if plugins:
if plugins := _current_plugin_chain.get():
plugins[-1].matcher.add(matcher)
@@ -368,7 +369,7 @@ def on_command(
state: 默认 state
"""
commands = set([cmd]) | (aliases or set())
commands = {cmd} | (aliases or set())
block = kwargs.pop("block", False)
return on_message(
command(*commands) & rule, block=block, **kwargs, _depth=_depth + 1
@@ -403,7 +404,7 @@ def on_shell_command(
state: 默认 state
"""
commands = set([cmd]) | (aliases or set())
commands = {cmd} | (aliases or set())
return on_message(
shell_command(*commands, parser=parser) & rule,
**kwargs,
@@ -437,7 +438,57 @@ def on_regex(
return on_message(regex(pattern, flags) & rule, **kwargs, _depth=_depth + 1)
class CommandGroup:
def on_type(
types: Union[Type[Event], Tuple[Type[Event]]],
rule: Optional[Union[Rule, T_RuleChecker]] = None,
*,
_depth: int = 0,
**kwargs,
) -> Type[Matcher]:
"""注册一个事件响应器,并且当事件为指定类型时响应。
参数:
types: 事件类型
rule: 事件响应规则
permission: 事件响应权限
handlers: 事件处理函数列表
temp: 是否为临时事件响应器(仅执行一次)
expire_time: 事件响应器最终有效时间点,过时即被删除
priority: 事件响应器优先级
block: 是否阻止事件向更低优先级传递
state: 默认 state
"""
event_types = types if isinstance(types, tuple) else (types,)
return on(rule=is_type(*event_types) & rule, **kwargs, _depth=_depth + 1)
class _Group:
def __init__(self, **kwargs):
"""创建一个事件响应器组合,参数为默认值,与 `on` 一致"""
self.matchers: List[Type[Matcher]] = []
"""组内事件响应器列表"""
self.base_kwargs: Dict[str, Any] = kwargs
"""其他传递给 `on` 的参数默认值"""
def _get_final_kwargs(
self, update: Dict[str, Any], *, exclude: Optional[Set[str]] = None
) -> Dict[str, Any]:
"""获取最终传递给 `on` 的参数
参数:
update: 更新的关键字参数
exclude: 需要排除的参数
"""
final_kwargs = self.base_kwargs.copy()
final_kwargs.update(update)
if exclude:
for key in exclude:
final_kwargs.pop(key, None)
final_kwargs["_depth"] = 1
return final_kwargs
class CommandGroup(_Group):
"""命令组,用于声明一组有相同名称前缀的命令。
参数:
@@ -453,12 +504,13 @@ class CommandGroup:
"""
def __init__(self, cmd: Union[str, Tuple[str, ...]], **kwargs):
self.basecmd: Tuple[str, ...] = (cmd,) if isinstance(cmd, str) else cmd
"""命令前缀"""
if "aliases" in kwargs:
del kwargs["aliases"]
self.base_kwargs: Dict[str, Any] = kwargs
"""其他传递给 `on_command` 的参数默认值"""
super().__init__(**kwargs)
self.basecmd: Tuple[str, ...] = (cmd,) if isinstance(cmd, str) else cmd
self.base_kwargs.pop("aliases", None)
def __repr__(self) -> str:
return f"CommandGroup(cmd={self.basecmd}, matchers={len(self.matchers)})"
def command(self, cmd: Union[str, Tuple[str, ...]], **kwargs) -> Type[Matcher]:
"""注册一个新的命令。新参数将会覆盖命令组默认值
@@ -477,10 +529,9 @@ class CommandGroup:
"""
sub_cmd = (cmd,) if isinstance(cmd, str) else cmd
cmd = self.basecmd + sub_cmd
final_kwargs = self.base_kwargs.copy()
final_kwargs.update(kwargs)
return on_command(cmd, **final_kwargs, _depth=1)
matcher = on_command(cmd, **self._get_final_kwargs(kwargs))
self.matchers.append(matcher)
return matcher
def shell_command(
self, cmd: Union[str, Tuple[str, ...]], **kwargs
@@ -502,21 +553,16 @@ class CommandGroup:
"""
sub_cmd = (cmd,) if isinstance(cmd, str) else cmd
cmd = self.basecmd + sub_cmd
final_kwargs = self.base_kwargs.copy()
final_kwargs.update(kwargs)
return on_shell_command(cmd, **final_kwargs, _depth=1)
matcher = on_shell_command(cmd, **self._get_final_kwargs(kwargs))
self.matchers.append(matcher)
return matcher
class MatcherGroup:
class MatcherGroup(_Group):
"""事件响应器组合,统一管理。为 `Matcher` 创建提供默认属性。"""
def __init__(self, **kwargs):
"""创建一个事件响应器组合,参数为默认值,与 `on` 一致"""
self.matchers: List[Type[Matcher]] = []
"""组内事件响应器列表"""
self.base_kwargs: Dict[str, Any] = kwargs
"""其他传递给 `on` 的参数默认值"""
def __repr__(self) -> str:
return f"MatcherGroup(matchers={len(self.matchers)})"
def on(self, **kwargs) -> Type[Matcher]:
"""注册一个基础事件响应器,可自定义类型。
@@ -532,9 +578,7 @@ class MatcherGroup:
block: 是否阻止事件向更低优先级传递
state: 默认 state
"""
final_kwargs = self.base_kwargs.copy()
final_kwargs.update(kwargs)
matcher = on(**final_kwargs, _depth=1)
matcher = on(**self._get_final_kwargs(kwargs))
self.matchers.append(matcher)
return matcher
@@ -550,11 +594,8 @@ class MatcherGroup:
block: 是否阻止事件向更低优先级传递
state: 默认 state
"""
final_kwargs = self.base_kwargs.copy()
final_kwargs.update(kwargs)
final_kwargs.pop("type", None)
final_kwargs.pop("permission", None)
matcher = on_metaevent(**final_kwargs, _depth=1)
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type", "permission"})
matcher = on_metaevent(**final_kwargs)
self.matchers.append(matcher)
return matcher
@@ -571,10 +612,8 @@ class MatcherGroup:
block: 是否阻止事件向更低优先级传递
state: 默认 state
"""
final_kwargs = self.base_kwargs.copy()
final_kwargs.update(kwargs)
final_kwargs.pop("type", None)
matcher = on_message(**final_kwargs, _depth=1)
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
matcher = on_message(**final_kwargs)
self.matchers.append(matcher)
return matcher
@@ -590,10 +629,8 @@ class MatcherGroup:
block: 是否阻止事件向更低优先级传递
state: 默认 state
"""
final_kwargs = self.base_kwargs.copy()
final_kwargs.update(kwargs)
final_kwargs.pop("type", None)
matcher = on_notice(**final_kwargs, _depth=1)
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type", "permission"})
matcher = on_notice(**final_kwargs)
self.matchers.append(matcher)
return matcher
@@ -609,10 +646,8 @@ class MatcherGroup:
block: 是否阻止事件向更低优先级传递
state: 默认 state
"""
final_kwargs = self.base_kwargs.copy()
final_kwargs.update(kwargs)
final_kwargs.pop("type", None)
matcher = on_request(**final_kwargs, _depth=1)
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type", "permission"})
matcher = on_request(**final_kwargs)
self.matchers.append(matcher)
return matcher
@@ -633,10 +668,8 @@ class MatcherGroup:
block: 是否阻止事件向更低优先级传递
state: 默认 state
"""
final_kwargs = self.base_kwargs.copy()
final_kwargs.update(kwargs)
final_kwargs.pop("type", None)
matcher = on_startswith(msg, **final_kwargs, _depth=1)
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
matcher = on_startswith(msg, **final_kwargs)
self.matchers.append(matcher)
return matcher
@@ -655,10 +688,8 @@ class MatcherGroup:
block: 是否阻止事件向更低优先级传递
state: 默认 state
"""
final_kwargs = self.base_kwargs.copy()
final_kwargs.update(kwargs)
final_kwargs.pop("type", None)
matcher = on_endswith(msg, **final_kwargs, _depth=1)
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
matcher = on_endswith(msg, **final_kwargs)
self.matchers.append(matcher)
return matcher
@@ -677,10 +708,8 @@ class MatcherGroup:
block: 是否阻止事件向更低优先级传递
state: 默认 state
"""
final_kwargs = self.base_kwargs.copy()
final_kwargs.update(kwargs)
final_kwargs.pop("type", None)
matcher = on_fullmatch(msg, **final_kwargs, _depth=1)
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
matcher = on_fullmatch(msg, **final_kwargs)
self.matchers.append(matcher)
return matcher
@@ -698,10 +727,8 @@ class MatcherGroup:
block: 是否阻止事件向更低优先级传递
state: 默认 state
"""
final_kwargs = self.base_kwargs.copy()
final_kwargs.update(kwargs)
final_kwargs.pop("type", None)
matcher = on_keyword(keywords, **final_kwargs, _depth=1)
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
matcher = on_keyword(keywords, **final_kwargs)
self.matchers.append(matcher)
return matcher
@@ -727,10 +754,8 @@ class MatcherGroup:
block: 是否阻止事件向更低优先级传递
state: 默认 state
"""
final_kwargs = self.base_kwargs.copy()
final_kwargs.update(kwargs)
final_kwargs.pop("type", None)
matcher = on_command(cmd, aliases=aliases, **final_kwargs, _depth=1)
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
matcher = on_command(cmd, aliases=aliases, **final_kwargs)
self.matchers.append(matcher)
return matcher
@@ -760,12 +785,8 @@ class MatcherGroup:
block: 是否阻止事件向更低优先级传递
state: 默认 state
"""
final_kwargs = self.base_kwargs.copy()
final_kwargs.update(kwargs)
final_kwargs.pop("type", None)
matcher = on_shell_command(
cmd, aliases=aliases, parser=parser, **final_kwargs, _depth=1
)
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
matcher = on_shell_command(cmd, aliases=aliases, parser=parser, **final_kwargs)
self.matchers.append(matcher)
return matcher
@@ -788,9 +809,28 @@ class MatcherGroup:
block: 是否阻止事件向更低优先级传递
state: 默认 state
"""
final_kwargs = self.base_kwargs.copy()
final_kwargs.update(kwargs)
final_kwargs.pop("type", None)
matcher = on_regex(pattern, flags=flags, **final_kwargs, _depth=1)
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
matcher = on_regex(pattern, flags=flags, **final_kwargs)
self.matchers.append(matcher)
return matcher
def on_type(
self, types: Union[Type[Event], Tuple[Type[Event]]], **kwargs
) -> Type[Matcher]:
"""注册一个事件响应器,并且当事件为指定类型时响应。
参数:
types: 事件类型
rule: 事件响应规则
permission: 事件响应权限
handlers: 事件处理函数列表
temp: 是否为临时事件响应器(仅执行一次)
expire_time: 事件响应器最终有效时间点,过时即被删除
priority: 事件响应器优先级
block: 是否阻止事件向更低优先级传递
state: 默认 state
"""
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
matcher = on_type(types, **final_kwargs)
self.matchers.append(matcher)
return matcher

View File

@@ -2,6 +2,7 @@ import re
from datetime import datetime, timedelta
from typing import Set, List, Type, Tuple, Union, Optional
from nonebot.adapters import Event
from nonebot.matcher import Matcher
from nonebot.permission import Permission
from nonebot.dependencies import Dependent
@@ -152,6 +153,18 @@ def on_regex(
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
def on_type(
types: Union[Type[Event], Tuple[Type[Event]]],
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
*,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
class CommandGroup:
def __init__(
@@ -171,8 +184,8 @@ class CommandGroup:
self,
cmd: Union[str, Tuple[str, ...]],
*,
aliases: Optional[Set[Union[str, Tuple[str, ...]]]],
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
temp: bool = ...,
@@ -186,7 +199,7 @@ class CommandGroup:
cmd: Union[str, Tuple[str, ...]],
*,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
aliases: Optional[Set[Union[str, Tuple[str, ...]]]],
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
parser: Optional[ArgumentParser] = ...,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
@@ -367,3 +380,16 @@ class MatcherGroup:
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
def on_type(
self,
types: Union[Type[Event], Tuple[Type[Event]]],
*,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...

View File

@@ -12,7 +12,6 @@ from pydantic import BaseModel
from nonebot.matcher import Matcher
from .export import Export
from . import _plugins as plugins # FIXME: backport for nonebug
if TYPE_CHECKING:
@@ -46,8 +45,6 @@ class Plugin:
"""点分割模块路径"""
manager: "PluginManager"
"""导入该插件的插件管理器"""
export: Export = field(default_factory=Export)
"""**Deprecated:** 插件内定义的导出内容"""
matcher: Set[Type[Matcher]] = field(default_factory=set)
"""插件内定义的 `Matcher`"""
parent_plugin: Optional["Plugin"] = None

View File

@@ -7,5 +7,5 @@ echo = on_command("echo", to_me())
@echo.handle()
async def echo_escape(message: Message = CommandArg()):
async def handle_echo(message: Message = CommandArg()):
await echo.send(message=message)

View File

@@ -15,8 +15,7 @@ async def matcher_mutex(event: Event) -> AsyncGenerator[bool, None]:
yield result
else:
current_event_id = id(event)
event_id = _running_matcher.get(session_id, None)
if event_id:
if event_id := _running_matcher.get(session_id, None):
result = event_id != current_event_id
else:
_running_matcher[session_id] = current_event_id

View File

@@ -10,11 +10,27 @@ FrontMatter:
import re
import shlex
from itertools import product
from argparse import Namespace
from typing_extensions import TypedDict
from argparse import Action
from argparse import ArgumentError
from itertools import chain, product
from argparse import Namespace as Namespace
from argparse import ArgumentParser as ArgParser
from typing import Any, List, Tuple, Union, Optional, Sequence, NamedTuple
from typing import (
IO,
TYPE_CHECKING,
Any,
List,
Type,
Tuple,
Union,
TypeVar,
Optional,
Sequence,
TypedDict,
NamedTuple,
cast,
overload,
)
from pygtrie import CharTrie
@@ -23,15 +39,8 @@ from nonebot.log import logger
from nonebot.typing import T_State
from nonebot.exception import ParserExit
from nonebot.internal.rule import Rule as Rule
from nonebot.params import Command, EventToMe, CommandArg
from nonebot.adapters import Bot, Event, Message, MessageSegment
from nonebot.params import (
Command,
EventToMe,
EventType,
CommandArg,
EventMessage,
EventPlainText,
)
from nonebot.consts import (
CMD_KEY,
PREFIX_KEY,
@@ -45,6 +54,8 @@ from nonebot.consts import (
REGEX_MATCHED,
)
T = TypeVar("T")
CMD_RESULT = TypedDict(
"CMD_RESULT",
{
@@ -83,8 +94,7 @@ class TrieRule:
message_seg: MessageSegment = message[0]
if message_seg.is_text():
segment_text = str(message_seg).lstrip()
pf = cls.prefix.longest_prefix(segment_text)
if pf:
if pf := cls.prefix.longest_prefix(segment_text):
value: TRIE_VALUE = pf.value
prefix[RAW_CMD_KEY] = pf.key
prefix[CMD_START_KEY] = value.command_start
@@ -113,10 +123,25 @@ class StartswithRule:
self.msg = msg
self.ignorecase = ignorecase
async def __call__(
self, type: str = EventType(), text: str = EventPlainText()
) -> Any:
if type != "message":
def __repr__(self) -> str:
return f"Startswith(msg={self.msg}, ignorecase={self.ignorecase})"
def __eq__(self, other: object) -> bool:
return (
isinstance(other, StartswithRule)
and frozenset(self.msg) == frozenset(other.msg)
and self.ignorecase == other.ignorecase
)
def __hash__(self) -> int:
return hash((frozenset(self.msg), self.ignorecase))
async def __call__(self, event: Event) -> bool:
if event.get_type() != "message":
return False
try:
text = event.get_plaintext()
except Exception:
return False
return bool(
re.match(
@@ -154,10 +179,25 @@ class EndswithRule:
self.msg = msg
self.ignorecase = ignorecase
async def __call__(
self, type: str = EventType(), text: str = EventPlainText()
) -> Any:
if type != "message":
def __repr__(self) -> str:
return f"Endswith(msg={self.msg}, ignorecase={self.ignorecase})"
def __eq__(self, other: object) -> bool:
return (
isinstance(other, EndswithRule)
and frozenset(self.msg) == frozenset(other.msg)
and self.ignorecase == other.ignorecase
)
def __hash__(self) -> int:
return hash((frozenset(self.msg), self.ignorecase))
async def __call__(self, event: Event) -> bool:
if event.get_type() != "message":
return False
try:
text = event.get_plaintext()
except Exception:
return False
return bool(
re.search(
@@ -192,17 +232,31 @@ class FullmatchRule:
__slots__ = ("msg", "ignorecase")
def __init__(self, msg: Tuple[str, ...], ignorecase: bool = False):
self.msg = frozenset(map(str.casefold, msg) if ignorecase else msg)
self.msg = tuple(map(str.casefold, msg) if ignorecase else msg)
self.ignorecase = ignorecase
async def __call__(
self, type_: str = EventType(), text: str = EventPlainText()
) -> bool:
def __repr__(self) -> str:
return f"Fullmatch(msg={self.msg}, ignorecase={self.ignorecase})"
def __eq__(self, other: object) -> bool:
return (
type_ == "message"
and (text.casefold() if self.ignorecase else text) in self.msg
isinstance(other, FullmatchRule)
and frozenset(self.msg) == frozenset(other.msg)
and self.ignorecase == other.ignorecase
)
def __hash__(self) -> int:
return hash((frozenset(self.msg), self.ignorecase))
async def __call__(self, event: Event) -> bool:
if event.get_type() != "message":
return False
try:
text = event.get_plaintext()
except Exception:
return False
return (text.casefold() if self.ignorecase else text) in self.msg
def fullmatch(msg: Union[str, Tuple[str, ...]], ignorecase: bool = False) -> Rule:
"""完全匹配消息。
@@ -229,10 +283,23 @@ class KeywordsRule:
def __init__(self, *keywords: str):
self.keywords = keywords
async def __call__(
self, type: str = EventType(), text: str = EventPlainText()
) -> bool:
if type != "message":
def __repr__(self) -> str:
return f"Keywords(keywords={self.keywords})"
def __eq__(self, other: object) -> bool:
return isinstance(other, KeywordsRule) and frozenset(
self.keywords
) == frozenset(other.keywords)
def __hash__(self) -> int:
return hash(frozenset(self.keywords))
async def __call__(self, event: Event) -> bool:
if event.get_type() != "message":
return False
try:
text = event.get_plaintext()
except Exception:
return False
return bool(text and any(keyword in text for keyword in self.keywords))
@@ -257,14 +324,22 @@ class CommandRule:
__slots__ = ("cmds",)
def __init__(self, cmds: List[Tuple[str, ...]]):
self.cmds = cmds
self.cmds = tuple(cmds)
def __repr__(self) -> str:
return f"Command(cmds={self.cmds})"
def __eq__(self, other: object) -> bool:
return isinstance(other, CommandRule) and frozenset(self.cmds) == frozenset(
other.cmds
)
def __hash__(self) -> int:
return hash((frozenset(self.cmds),))
async def __call__(self, cmd: Optional[Tuple[str, ...]] = Command()) -> bool:
return cmd in self.cmds
def __repr__(self):
return f"<Command {self.cmds}>"
def command(*cmds: Union[str, Tuple[str, ...]]) -> Rule:
"""匹配消息命令。
@@ -320,25 +395,48 @@ class ArgumentParser(ArgParser):
参考文档: [argparse](https://docs.python.org/3/library/argparse.html)
"""
def _print_message(self, message, file=None):
old_message: str = getattr(self, "message", "")
if old_message:
old_message += "\n"
old_message += message
setattr(self, "message", old_message)
if TYPE_CHECKING:
def exit(self, status: int = 0, message: Optional[str] = None):
raise ParserExit(
status=status, message=message or getattr(self, "message", None)
@overload
def parse_args(
self, args: Optional[Sequence[Union[str, MessageSegment]]] = ...
) -> Namespace:
...
@overload
def parse_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
) -> T:
...
def parse_args(
self,
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
namespace: Optional[T] = None,
) -> Union[Namespace, T]:
...
def _parse_optional(
self, arg_string: Union[str, MessageSegment]
) -> Optional[Tuple[Optional[Action], str, Optional[str]]]:
return (
super()._parse_optional(arg_string) if isinstance(arg_string, str) else None
)
def parse_args(
self,
args: Optional[Sequence[str]] = None,
namespace: Optional[Namespace] = None,
) -> Namespace:
setattr(self, "message", "")
return super().parse_args(args=args, namespace=namespace) # type: ignore
def _print_message(self, message: str, file: Optional[IO[str]] = None):
if message:
setattr(self, "_message", getattr(self, "_message", "") + message)
def exit(self, status: int = 0, message: Optional[str] = None):
if message:
self._print_message(message)
raise ParserExit(status=status, message=getattr(self, "_message", None))
class ShellCommandRule:
@@ -352,28 +450,48 @@ class ShellCommandRule:
__slots__ = ("cmds", "parser")
def __init__(self, cmds: List[Tuple[str, ...]], parser: Optional[ArgumentParser]):
self.cmds = cmds
self.cmds = tuple(cmds)
self.parser = parser
def __repr__(self) -> str:
return f"ShellCommand(cmds={self.cmds}, parser={self.parser})"
def __eq__(self, other: object) -> bool:
return (
isinstance(other, ShellCommandRule)
and frozenset(self.cmds) == frozenset(other.cmds)
and self.parser is other.parser
)
def __hash__(self) -> int:
return hash((frozenset(self.cmds), self.parser))
async def __call__(
self,
state: T_State,
cmd: Optional[Tuple[str, ...]] = Command(),
msg: Optional[Message] = CommandArg(),
) -> bool:
if cmd in self.cmds and msg is not None:
message = str(msg)
state[SHELL_ARGV] = shlex.split(message)
if self.parser:
try:
args = self.parser.parse_args(state[SHELL_ARGV])
state[SHELL_ARGS] = args
except ParserExit as e:
state[SHELL_ARGS] = e
return True
else:
if cmd not in self.cmds or msg is None:
return False
state[SHELL_ARGV] = list(
chain.from_iterable(
shlex.split(str(seg)) if cast(MessageSegment, seg).is_text() else (seg,)
for seg in msg
)
)
if self.parser:
try:
args = self.parser.parse_args(state[SHELL_ARGV])
state[SHELL_ARGS] = args
except ArgumentError as e:
state[SHELL_ARGS] = ParserExit(status=2, message=str(e))
except ParserExit as e:
state[SHELL_ARGS] = e
return True
def shell_command(
*cmds: Union[str, Tuple[str, ...]], parser: Optional[ArgumentParser] = None
@@ -452,16 +570,27 @@ class RegexRule:
self.regex = regex
self.flags = flags
async def __call__(
self,
state: T_State,
type: str = EventType(),
msg: Message = EventMessage(),
) -> bool:
if type != "message":
def __repr__(self) -> str:
return f"Regex(regex={self.regex!r}, flags={self.flags})"
def __eq__(self, other: object) -> bool:
return (
isinstance(other, RegexRule)
and self.regex == other.regex
and self.flags == other.flags
)
def __hash__(self) -> int:
return hash((self.regex, self.flags))
async def __call__(self, event: Event, state: T_State) -> bool:
if event.get_type() != "message":
return False
matched = re.search(self.regex, str(msg), self.flags)
if matched:
try:
msg = event.get_message()
except Exception:
return False
if matched := re.search(self.regex, str(msg), self.flags):
state[REGEX_MATCHED] = matched.group()
state[REGEX_GROUP] = matched.groups()
state[REGEX_DICT] = matched.groupdict()
@@ -498,6 +627,15 @@ class ToMeRule:
__slots__ = ()
def __repr__(self) -> str:
return "ToMe()"
def __eq__(self, other: object) -> bool:
return isinstance(other, ToMeRule)
def __hash__(self) -> int:
return hash((self.__class__,))
async def __call__(self, to_me: bool = EventToMe()) -> bool:
return to_me
@@ -508,6 +646,37 @@ def to_me() -> Rule:
return Rule(ToMeRule())
class IsTypeRule:
"""检查事件类型是否为指定类型。"""
__slots__ = ("types",)
def __init__(self, *types: Type[Event]):
self.types = types
def __repr__(self) -> str:
return f"IsType(types={tuple(type.__name__ for type in self.types)})"
def __eq__(self, other: object) -> bool:
return isinstance(other, IsTypeRule) and self.types == other.types
def __hash__(self) -> int:
return hash((self.types,))
async def __call__(self, event: Event) -> bool:
return isinstance(event, self.types)
def is_type(*types: Type[Event]) -> Rule:
"""匹配事件类型。
参数:
types: 事件类型
"""
return Rule(IsTypeRule(*types))
__autodoc__ = {
"Rule": True,
"Rule.__call__": True,

View File

@@ -11,6 +11,7 @@ FrontMatter:
sidebar_position: 11
description: nonebot.typing 模块
"""
from typing import (
TYPE_CHECKING,
Any,
@@ -28,6 +29,8 @@ if TYPE_CHECKING:
from nonebot.adapters import Bot
from nonebot.permission import Permission
T = TypeVar("T")
T_Wrapped = TypeVar("T_Wrapped", bound=Callable)
@@ -41,10 +44,14 @@ def overrides(InterfaceClass: object) -> Callable[[T_Wrapped], T_Wrapped]:
return overrider
# state
T_State = Dict[Any, Any]
"""事件处理状态 State 类型"""
T_BotConnectionHook = Callable[..., Awaitable[Any]]
_DependentCallable = Union[Callable[..., T], Callable[..., Awaitable[T]]]
# driver hooks
T_BotConnectionHook = _DependentCallable[Any]
"""Bot 连接建立时钩子函数
依赖参数:
@@ -53,7 +60,7 @@ T_BotConnectionHook = Callable[..., Awaitable[Any]]
- BotParam: Bot 对象
- DefaultParam: 带有默认值的参数
"""
T_BotDisconnectionHook = Callable[..., Awaitable[Any]]
T_BotDisconnectionHook = _DependentCallable[Any]
"""Bot 连接断开时钩子函数
依赖参数:
@@ -62,6 +69,8 @@ T_BotDisconnectionHook = Callable[..., Awaitable[Any]]
- BotParam: Bot 对象
- DefaultParam: 带有默认值的参数
"""
# api hooks
T_CallingAPIHook = Callable[["Bot", str, Dict[str, Any]], Awaitable[Any]]
"""`bot.call_api` 钩子函数"""
T_CalledAPIHook = Callable[
@@ -69,7 +78,8 @@ T_CalledAPIHook = Callable[
]
"""`bot.call_api` 后执行的函数,参数分别为 bot, exception, api, data, result"""
T_EventPreProcessor = Callable[..., Union[Any, Awaitable[Any]]]
# event hooks
T_EventPreProcessor = _DependentCallable[Any]
"""事件预处理函数 EventPreProcessor 类型
依赖参数:
@@ -80,7 +90,7 @@ T_EventPreProcessor = Callable[..., Union[Any, Awaitable[Any]]]
- StateParam: State 对象
- DefaultParam: 带有默认值的参数
"""
T_EventPostProcessor = Callable[..., Union[Any, Awaitable[Any]]]
T_EventPostProcessor = _DependentCallable[Any]
"""事件预处理函数 EventPostProcessor 类型
依赖参数:
@@ -91,7 +101,9 @@ T_EventPostProcessor = Callable[..., Union[Any, Awaitable[Any]]]
- StateParam: State 对象
- DefaultParam: 带有默认值的参数
"""
T_RunPreProcessor = Callable[..., Union[Any, Awaitable[Any]]]
# matcher run hooks
T_RunPreProcessor = _DependentCallable[Any]
"""事件响应器运行前预处理函数 RunPreProcessor 类型
依赖参数:
@@ -103,7 +115,7 @@ T_RunPreProcessor = Callable[..., Union[Any, Awaitable[Any]]]
- MatcherParam: Matcher 对象
- DefaultParam: 带有默认值的参数
"""
T_RunPostProcessor = Callable[..., Union[Any, Awaitable[Any]]]
T_RunPostProcessor = _DependentCallable[Any]
"""事件响应器运行后后处理函数 RunPostProcessor 类型
依赖参数:
@@ -117,7 +129,8 @@ T_RunPostProcessor = Callable[..., Union[Any, Awaitable[Any]]]
- DefaultParam: 带有默认值的参数
"""
T_RuleChecker = Callable[..., Union[bool, Awaitable[bool]]]
# rule, permission
T_RuleChecker = _DependentCallable[bool]
"""RuleChecker 即判断是否响应事件的处理函数。
依赖参数:
@@ -128,7 +141,7 @@ T_RuleChecker = Callable[..., Union[bool, Awaitable[bool]]]
- StateParam: State 对象
- DefaultParam: 带有默认值的参数
"""
T_PermissionChecker = Callable[..., Union[bool, Awaitable[bool]]]
T_PermissionChecker = _DependentCallable[bool]
"""PermissionChecker 即判断事件是否满足权限的处理函数。
依赖参数:
@@ -139,9 +152,9 @@ T_PermissionChecker = Callable[..., Union[bool, Awaitable[bool]]]
- DefaultParam: 带有默认值的参数
"""
T_Handler = Callable[..., Any]
T_Handler = _DependentCallable[Any]
"""Handler 处理函数。"""
T_TypeUpdater = Callable[..., Union[str, Awaitable[str]]]
T_TypeUpdater = _DependentCallable[str]
"""TypeUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新响应的事件类型。默认会更新为 `message`。
依赖参数:
@@ -153,7 +166,7 @@ T_TypeUpdater = Callable[..., Union[str, Awaitable[str]]]
- MatcherParam: Matcher 对象
- DefaultParam: 带有默认值的参数
"""
T_PermissionUpdater = Callable[..., Union["Permission", Awaitable["Permission"]]]
T_PermissionUpdater = _DependentCallable["Permission"]
"""PermissionUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新会话对象权限。默认会更新为当前事件的触发对象。
依赖参数:
@@ -165,5 +178,5 @@ T_PermissionUpdater = Callable[..., Union["Permission", Awaitable["Permission"]]
- MatcherParam: Matcher 对象
- DefaultParam: 带有默认值的参数
"""
T_DependencyCache = Dict[Callable[..., Any], "Task[Any]"]
T_DependencyCache = Dict[_DependentCallable[Any], "Task[Any]"]
"""依赖缓存, 用于存储依赖函数的返回值"""

View File

@@ -10,6 +10,7 @@ import json
import asyncio
import inspect
import dataclasses
from pathlib import Path
from functools import wraps, partial
from contextlib import asynccontextmanager
from typing_extensions import ParamSpec, get_args, get_origin
@@ -24,6 +25,7 @@ from typing import (
Coroutine,
AsyncGenerator,
ContextManager,
overload,
)
from pydantic.typing import is_union, is_none_type
@@ -129,11 +131,28 @@ async def run_sync_ctx_manager(
await run_sync(cm.__exit__)(None, None, None)
@overload
async def run_coro_with_catch(
coro: Coroutine[Any, Any, T],
exc: Tuple[Type[Exception], ...],
return_on_err: R = None,
) -> Union[T, None]:
...
@overload
async def run_coro_with_catch(
coro: Coroutine[Any, Any, T],
exc: Tuple[Type[Exception], ...],
return_on_err: R,
) -> Union[T, R]:
...
async def run_coro_with_catch(
coro: Coroutine[Any, Any, T],
exc: Tuple[Type[Exception], ...],
return_on_err: Optional[R] = None,
) -> Optional[Union[T, R]]:
try:
return await coro
except exc:
@@ -147,6 +166,14 @@ def get_name(obj: Any) -> str:
return obj.__class__.__name__
def path_to_module_name(path: Path) -> str:
rel_path = path.resolve().relative_to(Path(".").resolve())
if rel_path.stem == "__init__":
return ".".join(rel_path.parts[:-1])
else:
return ".".join(rel_path.parts[:-1] + (rel_path.stem,))
class DataclassEncoder(json.JSONEncoder):
"""在JSON序列化 {re}`nonebot.adapters._message.Message` (List[Dataclass]) 时使用的 `JSONEncoder`"""
@@ -173,7 +200,7 @@ def logger_wrapper(logger_name: str):
def log(level: str, message: str, exception: Optional[Exception] = None):
logger.opt(colors=True, exception=exception).log(
level, f"<m>{escape_tag(logger_name)}</m> | " + message
level, f"<m>{escape_tag(logger_name)}</m> | {message}"
)
return log

View File

@@ -17,7 +17,7 @@ _✨ NoneBot 本地文档插件 ✨_
<a href="https://pypi.python.org/pypi/nonebot-plugin-docs">
<img src="https://img.shields.io/pypi/v/nonebot-plugin-docs.svg" alt="pypi">
</a>
<img src="https://img.shields.io/badge/python-3.7+-blue.svg" alt="python">
<img src="https://img.shields.io/badge/python-3.8+-blue.svg" alt="python">
</p>
## 使用方式

View File

@@ -12,7 +12,7 @@ include = ["nonebot_plugin_docs/dist/**/*"]
[tool.poetry.dependencies]
python = "^3.7.3"
python = "^3.8"
nonebot2 = "^2.0.0-beta.1"
[tool.poetry.dev-dependencies]

470
poetry.lock generated
View File

@@ -11,15 +11,15 @@ pycares = ">=4.0.0"
[[package]]
name = "aiofiles"
version = "0.8.0"
version = "22.1.0"
description = "File support for asyncio."
category = "main"
optional = true
python-versions = ">=3.6,<4.0"
python-versions = ">=3.7,<4.0"
[[package]]
name = "aiohttp"
version = "3.8.1"
version = "3.8.3"
description = "Async http client/server framework (asyncio)"
category = "main"
optional = true
@@ -29,14 +29,12 @@ python-versions = ">=3.6"
aiodns = {version = "*", optional = true, markers = "extra == \"speedups\""}
aiosignal = ">=1.1.2"
async-timeout = ">=4.0.0a3,<5.0"
asynctest = {version = "0.13.0", markers = "python_version < \"3.8\""}
attrs = ">=17.3.0"
Brotli = {version = "*", optional = true, markers = "extra == \"speedups\""}
cchardet = {version = "*", optional = true, markers = "extra == \"speedups\""}
cchardet = {version = "*", optional = true, markers = "python_version < \"3.10\" and extra == \"speedups\""}
charset-normalizer = ">=2.0,<3.0"
frozenlist = ">=1.1.1"
multidict = ">=4.5,<7.0"
typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""}
yarl = ">=1.0,<2.0"
[package.extras]
@@ -64,7 +62,6 @@ python-versions = ">=3.6.2"
[package.dependencies]
idna = ">=2.8"
sniffio = ">=1.1"
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
[package.extras]
doc = ["packaging", "sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"]
@@ -79,9 +76,6 @@ category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
[package.extras]
tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"]
@@ -105,25 +99,6 @@ category = "main"
optional = true
python-versions = ">=3.6"
[package.dependencies]
typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""}
[[package]]
name = "asynctest"
version = "0.13.0"
description = "Enhance the standard unittest package with features for testing asyncio libraries"
category = "main"
optional = true
python-versions = ">=3.5"
[[package]]
name = "atomicwrites"
version = "1.4.1"
description = "Atomic file writes."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "attrs"
version = "21.4.0"
@@ -140,7 +115,7 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>
[[package]]
name = "black"
version = "22.6.0"
version = "22.8.0"
description = "The uncompromising code formatter."
category = "dev"
optional = false
@@ -152,7 +127,6 @@ mypy-extensions = ">=0.4.3"
pathspec = ">=0.9.0"
platformdirs = ">=2"
tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
[package.extras]
@@ -187,7 +161,7 @@ python-versions = "*"
[[package]]
name = "certifi"
version = "2022.6.15"
version = "2022.9.24"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
@@ -214,7 +188,7 @@ python-versions = ">=3.6.1"
[[package]]
name = "charset-normalizer"
version = "2.1.0"
version = "2.1.1"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main"
optional = false
@@ -233,7 +207,6 @@ python-versions = ">=3.7"
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
[[package]]
name = "colorama"
@@ -245,7 +218,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "coverage"
version = "6.4.2"
version = "6.5.0"
description = "Code coverage measurement for Python"
category = "dev"
optional = false
@@ -259,7 +232,7 @@ toml = ["tomli"]
[[package]]
name = "distlib"
version = "0.3.5"
version = "0.3.6"
description = "Distribution utilities"
category = "dev"
optional = false
@@ -278,7 +251,7 @@ testing = ["pre-commit"]
[[package]]
name = "fastapi"
version = "0.79.0"
version = "0.79.1"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
category = "main"
optional = false
@@ -296,15 +269,15 @@ test = ["pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "mypy (==0.91
[[package]]
name = "filelock"
version = "3.7.1"
version = "3.8.0"
description = "A platform independent file lock."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"]
testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"]
docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"]
testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"]
[[package]]
name = "frozenlist"
@@ -362,7 +335,7 @@ socks = ["socksio (>=1.0.0,<2.0.0)"]
[[package]]
name = "httptools"
version = "0.4.0"
version = "0.5.0"
description = "A collection of framework independent HTTP protocol utils."
category = "main"
optional = false
@@ -394,7 +367,7 @@ socks = ["socksio (>=1.0.0,<2.0.0)"]
[[package]]
name = "hypercorn"
version = "0.13.2"
version = "0.14.3"
description = "A ASGI Server based on Hyper libraries and inspired by Gunicorn"
category = "main"
optional = true
@@ -405,11 +378,11 @@ h11 = "*"
h2 = ">=3.1.0"
priority = "*"
toml = "*"
typing_extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""}
wsproto = ">=0.14.0"
[package.extras]
h3 = ["aioquic (>=0.9.0,<1.0)"]
docs = ["pydata-sphinx-theme"]
trio = ["trio (>=0.11.0)"]
uvloop = ["uvloop"]
@@ -423,7 +396,7 @@ python-versions = ">=3.6.1"
[[package]]
name = "identify"
version = "2.5.3"
version = "2.5.5"
description = "File identification library for Python"
category = "dev"
optional = false
@@ -434,29 +407,12 @@ license = ["ukkonen"]
[[package]]
name = "idna"
version = "3.3"
version = "3.4"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
python-versions = ">=3.5"
[[package]]
name = "importlib-metadata"
version = "4.12.0"
description = "Read metadata from Python packages"
category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
zipp = ">=0.5"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
perf = ["ipython"]
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"]
[[package]]
name = "iniconfig"
version = "1.1.1"
@@ -592,7 +548,7 @@ resolved_reference = "3cb2474a22bd1a633805dfa6edbe741e44ddc851"
[[package]]
name = "nonemoji"
version = "0.1.2"
version = "0.1.3"
description = "Simple gitmoji cli written in python"
category = "dev"
optional = false
@@ -625,11 +581,11 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
[[package]]
name = "pathspec"
version = "0.9.0"
version = "0.10.1"
description = "Utility library for gitignore style pattern matching of file paths."
category = "dev"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
python-versions = ">=3.7"
[[package]]
name = "platformdirs"
@@ -651,9 +607,6 @@ category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
@@ -669,7 +622,6 @@ python-versions = ">=3.7"
[package.dependencies]
cfgv = ">=2.0.0"
identify = ">=1.0.0"
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
nodeenv = ">=0.11.1"
pyyaml = ">=5.1"
toml = "*"
@@ -685,7 +637,7 @@ python-versions = ">=3.6.1"
[[package]]
name = "prompt-toolkit"
version = "3.0.30"
version = "3.0.31"
description = "Library for building powerful interactive command lines in Python"
category = "dev"
optional = false
@@ -704,7 +656,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "pycares"
version = "4.2.1"
version = "4.2.2"
description = "Python interface for c-ares"
category = "main"
optional = true
@@ -726,7 +678,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "pydantic"
version = "1.9.1"
version = "1.9.2"
description = "Data validation and settings management using python type hints"
category = "main"
optional = false
@@ -761,17 +713,15 @@ diagrams = ["railroad-diagrams", "jinja2"]
[[package]]
name = "pytest"
version = "7.1.2"
version = "7.1.3"
description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
attrs = ">=19.2.0"
colorama = {version = "*", markers = "sys_platform == \"win32\""}
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=0.12,<2.0"
@@ -791,7 +741,6 @@ python-versions = ">=3.7"
[package.dependencies]
pytest = ">=6.1.0"
typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""}
[package.extras]
testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)", "flaky (>=3.5.0)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"]
@@ -843,11 +792,11 @@ testing = ["filelock"]
[[package]]
name = "python-dotenv"
version = "0.20.0"
version = "0.21.0"
description = "Read key-value pairs from a .env file and set them as environment variables"
category = "main"
optional = false
python-versions = ">=3.5"
python-versions = ">=3.7"
[package.extras]
cli = ["click (>=5.0)"]
@@ -873,12 +822,10 @@ aiofiles = "*"
blinker = "*"
click = "*"
hypercorn = ">=0.11.2"
importlib_metadata = {version = "*", markers = "python_version < \"3.8\""}
itsdangerous = "*"
jinja2 = "*"
markupsafe = "*"
toml = "*"
typing_extensions = {version = "*", markers = "python_version < \"3.8\""}
werkzeug = ">=2.0.0"
[package.extras]
@@ -926,11 +873,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "sniffio"
version = "1.2.0"
version = "1.3.0"
description = "Sniff out which async library your code is running under"
category = "main"
optional = false
python-versions = ">=3.5"
python-versions = ">=3.7"
[[package]]
name = "starlette"
@@ -965,20 +912,12 @@ python-versions = ">=3.7"
[[package]]
name = "tomlkit"
version = "0.11.1"
version = "0.11.5"
description = "Style preserving TOML library"
category = "main"
optional = false
python-versions = ">=3.6,<4.0"
[[package]]
name = "typed-ast"
version = "1.5.4"
description = "a fork of Python 2 and 3 ast modules with type comment support"
category = "dev"
optional = false
python-versions = ">=3.6"
[[package]]
name = "typing-extensions"
version = "4.3.0"
@@ -989,7 +928,7 @@ python-versions = ">=3.7"
[[package]]
name = "urllib3"
version = "1.26.11"
version = "1.26.12"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "dev"
optional = false
@@ -997,12 +936,12 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*,
[package.extras]
brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "urllib3-secure-extra", "ipaddress"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "uvicorn"
version = "0.18.2"
version = "0.18.3"
description = "The lightning-fast ASGI server."
category = "main"
optional = false
@@ -1014,49 +953,47 @@ colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win
h11 = ">=0.8"
httptools = {version = ">=0.4.0", optional = true, markers = "extra == \"standard\""}
python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
PyYAML = {version = ">=5.1", optional = true, markers = "extra == \"standard\""}
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""}
uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""}
watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
websockets = {version = ">=10.0", optional = true, markers = "extra == \"standard\""}
[package.extras]
standard = ["websockets (>=10.0)", "httptools (>=0.4.0)", "watchfiles (>=0.13)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"]
standard = ["colorama (>=0.4)", "httptools (>=0.4.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.0)"]
[[package]]
name = "uvloop"
version = "0.16.0"
version = "0.17.0"
description = "Fast implementation of asyncio event loop on top of libuv"
category = "main"
optional = false
python-versions = ">=3.7"
[package.extras]
dev = ["Cython (>=0.29.24,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"]
dev = ["Cython (>=0.29.32,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=22.0.0,<22.1.0)", "mypy (>=0.800)", "aiohttp"]
docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)"]
test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"]
test = ["flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=22.0.0,<22.1.0)", "mypy (>=0.800)", "Cython (>=0.29.32,<0.30.0)", "aiohttp"]
[[package]]
name = "virtualenv"
version = "20.16.2"
version = "20.16.5"
description = "Virtual Python Environment builder"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
distlib = ">=0.3.1,<1"
filelock = ">=3.2,<4"
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
platformdirs = ">=2,<3"
distlib = ">=0.3.5,<1"
filelock = ">=3.4.1,<4"
platformdirs = ">=2.4,<3"
[package.extras]
docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"]
testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "packaging (>=20.0)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)"]
docs = ["proselint (>=0.13)", "sphinx (>=5.1.1)", "sphinx-argparse (>=0.3.1)", "sphinx-rtd-theme (>=1)", "towncrier (>=21.9)"]
testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"]
[[package]]
name = "watchfiles"
version = "0.16.1"
version = "0.17.0"
description = "Simple, modern and high performance file watching and code reload in python."
category = "main"
optional = false
@@ -1083,7 +1020,7 @@ python-versions = ">=3.7"
[[package]]
name = "werkzeug"
version = "2.2.1"
version = "2.2.2"
description = "The comprehensive WSGI web application library."
category = "main"
optional = true
@@ -1108,7 +1045,7 @@ dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"]
[[package]]
name = "wsproto"
version = "1.1.0"
version = "1.2.0"
description = "WebSockets state-machine based protocol implementation"
category = "main"
optional = true
@@ -1128,19 +1065,6 @@ python-versions = ">=3.7"
[package.dependencies]
idna = ">=2.0"
multidict = ">=4.0"
typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""}
[[package]]
name = "zipp"
version = "3.8.1"
description = "Backport of pathlib-compatible object wrapper for zip files"
category = "main"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"]
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"]
[extras]
aiohttp = ["aiohttp"]
@@ -1151,92 +1075,16 @@ websockets = ["websockets"]
[metadata]
lock-version = "1.1"
python-versions = "^3.7.3"
content-hash = "8775c8091ff054a3fb807281e0cfebde4a0409d18970a1001a7a06fad595addf"
python-versions = "^3.8"
content-hash = "3b21339de9113224b61700da359ea53ef866677d19c0bdbad7126e33ceceb856"
[metadata.files]
aiodns = [
{file = "aiodns-3.0.0-py3-none-any.whl", hash = "sha256:2b19bc5f97e5c936638d28e665923c093d8af2bf3aa88d35c43417fa25d136a2"},
{file = "aiodns-3.0.0.tar.gz", hash = "sha256:946bdfabe743fceeeb093c8a010f5d1645f708a241be849e17edfb0e49e08cd6"},
]
aiofiles = [
{file = "aiofiles-0.8.0-py3-none-any.whl", hash = "sha256:7a973fc22b29e9962d0897805ace5856e6a566ab1f0c8e5c91ff6c866519c937"},
{file = "aiofiles-0.8.0.tar.gz", hash = "sha256:8334f23235248a3b2e83b2c3a78a22674f39969b96397126cc93664d9a901e59"},
]
aiohttp = [
{file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8"},
{file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8"},
{file = "aiohttp-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a79004bb58748f31ae1cbe9fa891054baaa46fb106c2dc7af9f8e3304dc30316"},
{file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12de6add4038df8f72fac606dff775791a60f113a725c960f2bab01d8b8e6b15"},
{file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f0d5f33feb5f69ddd57a4a4bd3d56c719a141080b445cbf18f238973c5c9923"},
{file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eaba923151d9deea315be1f3e2b31cc39a6d1d2f682f942905951f4e40200922"},
{file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:099ebd2c37ac74cce10a3527d2b49af80243e2a4fa39e7bce41617fbc35fa3c1"},
{file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2e5d962cf7e1d426aa0e528a7e198658cdc8aa4fe87f781d039ad75dcd52c516"},
{file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fa0ffcace9b3aa34d205d8130f7873fcfefcb6a4dd3dd705b0dab69af6712642"},
{file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61bfc23df345d8c9716d03717c2ed5e27374e0fe6f659ea64edcd27b4b044cf7"},
{file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:31560d268ff62143e92423ef183680b9829b1b482c011713ae941997921eebc8"},
{file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:01d7bdb774a9acc838e6b8f1d114f45303841b89b95984cbb7d80ea41172a9e3"},
{file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97ef77eb6b044134c0b3a96e16abcb05ecce892965a2124c566af0fd60f717e2"},
{file = "aiohttp-3.8.1-cp310-cp310-win32.whl", hash = "sha256:c2aef4703f1f2ddc6df17519885dbfa3514929149d3ff900b73f45998f2532fa"},
{file = "aiohttp-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:713ac174a629d39b7c6a3aa757b337599798da4c1157114a314e4e391cd28e32"},
{file = "aiohttp-3.8.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:473d93d4450880fe278696549f2e7aed8cd23708c3c1997981464475f32137db"},
{file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b5eeae8e019e7aad8af8bb314fb908dd2e028b3cdaad87ec05095394cce632"},
{file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af642b43ce56c24d063325dd2cf20ee012d2b9ba4c3c008755a301aaea720ad"},
{file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3630c3ef435c0a7c549ba170a0633a56e92629aeed0e707fec832dee313fb7a"},
{file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4a4a4e30bf1edcad13fb0804300557aedd07a92cabc74382fdd0ba6ca2661091"},
{file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f8b01295e26c68b3a1b90efb7a89029110d3a4139270b24fda961893216c440"},
{file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a25fa703a527158aaf10dafd956f7d42ac6d30ec80e9a70846253dd13e2f067b"},
{file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5bfde62d1d2641a1f5173b8c8c2d96ceb4854f54a44c23102e2ccc7e02f003ec"},
{file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:51467000f3647d519272392f484126aa716f747859794ac9924a7aafa86cd411"},
{file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:03a6d5349c9ee8f79ab3ff3694d6ce1cfc3ced1c9d36200cb8f08ba06bd3b782"},
{file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:102e487eeb82afac440581e5d7f8f44560b36cf0bdd11abc51a46c1cd88914d4"},
{file = "aiohttp-3.8.1-cp36-cp36m-win32.whl", hash = "sha256:4aed991a28ea3ce320dc8ce655875e1e00a11bdd29fe9444dd4f88c30d558602"},
{file = "aiohttp-3.8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b0e20cddbd676ab8a64c774fefa0ad787cc506afd844de95da56060348021e96"},
{file = "aiohttp-3.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:37951ad2f4a6df6506750a23f7cbabad24c73c65f23f72e95897bb2cecbae676"},
{file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c23b1ad869653bc818e972b7a3a79852d0e494e9ab7e1a701a3decc49c20d51"},
{file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15b09b06dae900777833fe7fc4b4aa426556ce95847a3e8d7548e2d19e34edb8"},
{file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:477c3ea0ba410b2b56b7efb072c36fa91b1e6fc331761798fa3f28bb224830dd"},
{file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2f2f69dca064926e79997f45b2f34e202b320fd3782f17a91941f7eb85502ee2"},
{file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ef9612483cb35171d51d9173647eed5d0069eaa2ee812793a75373447d487aa4"},
{file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6d69f36d445c45cda7b3b26afef2fc34ef5ac0cdc75584a87ef307ee3c8c6d00"},
{file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:55c3d1072704d27401c92339144d199d9de7b52627f724a949fc7d5fc56d8b93"},
{file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b9d00268fcb9f66fbcc7cd9fe423741d90c75ee029a1d15c09b22d23253c0a44"},
{file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:07b05cd3305e8a73112103c834e91cd27ce5b4bd07850c4b4dbd1877d3f45be7"},
{file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c34dc4958b232ef6188c4318cb7b2c2d80521c9a56c52449f8f93ab7bc2a8a1c"},
{file = "aiohttp-3.8.1-cp37-cp37m-win32.whl", hash = "sha256:d2f9b69293c33aaa53d923032fe227feac867f81682f002ce33ffae978f0a9a9"},
{file = "aiohttp-3.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6ae828d3a003f03ae31915c31fa684b9890ea44c9c989056fea96e3d12a9fa17"},
{file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0c7ebbbde809ff4e970824b2b6cb7e4222be6b95a296e46c03cf050878fc1785"},
{file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b7ef7cbd4fec9a1e811a5de813311ed4f7ac7d93e0fda233c9b3e1428f7dd7b"},
{file = "aiohttp-3.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c3d6a4d0619e09dcd61021debf7059955c2004fa29f48788a3dfaf9c9901a7cd"},
{file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:718626a174e7e467f0558954f94af117b7d4695d48eb980146016afa4b580b2e"},
{file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:589c72667a5febd36f1315aa6e5f56dd4aa4862df295cb51c769d16142ddd7cd"},
{file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ed076098b171573161eb146afcb9129b5ff63308960aeca4b676d9d3c35e700"},
{file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:086f92daf51a032d062ec5f58af5ca6a44d082c35299c96376a41cbb33034675"},
{file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:11691cf4dc5b94236ccc609b70fec991234e7ef8d4c02dd0c9668d1e486f5abf"},
{file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:31d1e1c0dbf19ebccbfd62eff461518dcb1e307b195e93bba60c965a4dcf1ba0"},
{file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:11a67c0d562e07067c4e86bffc1553f2cf5b664d6111c894671b2b8712f3aba5"},
{file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:bb01ba6b0d3f6c68b89fce7305080145d4877ad3acaed424bae4d4ee75faa950"},
{file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:44db35a9e15d6fe5c40d74952e803b1d96e964f683b5a78c3cc64eb177878155"},
{file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:844a9b460871ee0a0b0b68a64890dae9c415e513db0f4a7e3cab41a0f2fedf33"},
{file = "aiohttp-3.8.1-cp38-cp38-win32.whl", hash = "sha256:7d08744e9bae2ca9c382581f7dce1273fe3c9bae94ff572c3626e8da5b193c6a"},
{file = "aiohttp-3.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:04d48b8ce6ab3cf2097b1855e1505181bdd05586ca275f2505514a6e274e8e75"},
{file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5315a2eb0239185af1bddb1abf472d877fede3cc8d143c6cddad37678293237"},
{file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a996d01ca39b8dfe77440f3cd600825d05841088fd6bc0144cc6c2ec14cc5f74"},
{file = "aiohttp-3.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:13487abd2f761d4be7c8ff9080de2671e53fff69711d46de703c310c4c9317ca"},
{file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea302f34477fda3f85560a06d9ebdc7fa41e82420e892fc50b577e35fc6a50b2"},
{file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2f635ce61a89c5732537a7896b6319a8fcfa23ba09bec36e1b1ac0ab31270d2"},
{file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e999f2d0e12eea01caeecb17b653f3713d758f6dcc770417cf29ef08d3931421"},
{file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0770e2806a30e744b4e21c9d73b7bee18a1cfa3c47991ee2e5a65b887c49d5cf"},
{file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d15367ce87c8e9e09b0f989bfd72dc641bcd04ba091c68cd305312d00962addd"},
{file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c7cefb4b0640703eb1069835c02486669312bf2f12b48a748e0a7756d0de33d"},
{file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:71927042ed6365a09a98a6377501af5c9f0a4d38083652bcd2281a06a5976724"},
{file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:28d490af82bc6b7ce53ff31337a18a10498303fe66f701ab65ef27e143c3b0ef"},
{file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b6613280ccedf24354406caf785db748bebbddcf31408b20c0b48cb86af76866"},
{file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81e3d8c34c623ca4e36c46524a3530e99c0bc95ed068fd6e9b55cb721d408fb2"},
{file = "aiohttp-3.8.1-cp39-cp39-win32.whl", hash = "sha256:7187a76598bdb895af0adbd2fb7474d7f6025d170bc0a1130242da817ce9e7d1"},
{file = "aiohttp-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac"},
{file = "aiohttp-3.8.1.tar.gz", hash = "sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578"},
]
aiofiles = []
aiohttp = []
aiosignal = [
{file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"},
{file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"},
@@ -1256,11 +1104,6 @@ async-timeout = [
{file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
{file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
]
asynctest = [
{file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"},
{file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"},
]
atomicwrites = []
attrs = [
{file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
@@ -1362,10 +1205,7 @@ cchardet = [
{file = "cchardet-2.1.7-cp39-cp39-win_amd64.whl", hash = "sha256:24974b3e40fee9e7557bb352be625c39ec6f50bc2053f44a3d1191db70b51675"},
{file = "cchardet-2.1.7.tar.gz", hash = "sha256:c428b6336545053c2589f6caf24ea32276c6664cb86db817e03a94c60afa0eaf"},
]
certifi = [
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
]
certifi = []
cffi = [
{file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
{file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"},
@@ -1436,10 +1276,7 @@ cfgv = [
{file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
{file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
]
charset-normalizer = [
{file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"},
{file = "charset_normalizer-2.1.0-py3-none-any.whl", hash = "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5"},
]
charset-normalizer = []
click = [
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
@@ -1455,10 +1292,7 @@ execnet = [
{file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"},
]
fastapi = []
filelock = [
{file = "filelock-3.7.1-py3-none-any.whl", hash = "sha256:37def7b658813cda163b56fc564cdc75e86d338246458c4c28ae84cabefa2404"},
{file = "filelock-3.7.1.tar.gz", hash = "sha256:3a0fd85166ad9dbab54c9aec96737b744106dc5f15c0b09a6744a445299fcf04"},
]
filelock = []
frozenlist = []
h11 = [
{file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"},
@@ -1476,60 +1310,18 @@ httpcore = [
{file = "httpcore-0.15.0-py3-none-any.whl", hash = "sha256:1105b8b73c025f23ff7c36468e4432226cbb959176eab66864b8e31c4ee27fa6"},
{file = "httpcore-0.15.0.tar.gz", hash = "sha256:18b68ab86a3ccf3e7dc0f43598eaddcf472b602aba29f9aa6ab85fe2ada3980b"},
]
httptools = [
{file = "httptools-0.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcddfe70553be717d9745990dfdb194e22ee0f60eb8f48c0794e7bfeda30d2d5"},
{file = "httptools-0.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1ee0b459257e222b878a6c09ccf233957d3a4dcb883b0847640af98d2d9aac23"},
{file = "httptools-0.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceafd5e960b39c7e0d160a1936b68eb87c5e79b3979d66e774f0c77d4d8faaed"},
{file = "httptools-0.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fdb9f9ed79bc6f46b021b3319184699ba1a22410a82204e6e89c774530069683"},
{file = "httptools-0.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:abe829275cdd4174b4c4e65ad718715d449e308d59793bf3a931ee1bf7e7b86c"},
{file = "httptools-0.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7af6bdbd21a2a25d6784f6d67f44f5df33ef39b6159543b9f9064d365c01f919"},
{file = "httptools-0.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:5d1fe6b6661022fd6cac541f54a4237496b246e6f1c0a6b41998ee08a1135afe"},
{file = "httptools-0.4.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:48e48530d9b995a84d1d89ae6b3ec4e59ea7d494b150ac3bbc5e2ac4acce92cd"},
{file = "httptools-0.4.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a113789e53ac1fa26edf99856a61e4c493868e125ae0dd6354cf518948fbbd5c"},
{file = "httptools-0.4.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8e2eb957787cbb614a0f006bfc5798ff1d90ac7c4dd24854c84edbdc8c02369e"},
{file = "httptools-0.4.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:7ee9f226acab9085037582c059d66769862706e8e8cd2340470ceb8b3850873d"},
{file = "httptools-0.4.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:701e66b59dd21a32a274771238025d58db7e2b6ecebbab64ceff51b8e31527ae"},
{file = "httptools-0.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6a1a7dfc1f9c78a833e2c4904757a0f47ce25d08634dd2a52af394eefe5f9777"},
{file = "httptools-0.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:903f739c9fb78dab8970b0f3ea51f21955b24b45afa77b22ff0e172fc11ef111"},
{file = "httptools-0.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54bbd295f031b866b9799dd39cb45deee81aca036c9bff9f58ca06726f6494f1"},
{file = "httptools-0.4.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3194f6d6443befa8d4db16c1946b2fc428a3ceb8ab32eb6f09a59f86104dc1a0"},
{file = "httptools-0.4.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cd1295f52971097f757edfbfce827b6dbbfb0f7a74901ee7d4933dff5ad4c9af"},
{file = "httptools-0.4.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:20a45bcf22452a10fa8d58b7dbdb474381f6946bf5b8933e3662d572bc61bae4"},
{file = "httptools-0.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d1f27bb0f75bef722d6e22dc609612bfa2f994541621cd2163f8c943b6463dfe"},
{file = "httptools-0.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7f7bfb74718f52d5ed47d608d507bf66d3bc01d4a8b3e6dd7134daaae129357b"},
{file = "httptools-0.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a522d12e2ddbc2e91842ffb454a1aeb0d47607972c7d8fc88bd0838d97fb8a2a"},
{file = "httptools-0.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2db44a0b294d317199e9f80123e72c6b005c55b625b57fae36de68670090fa48"},
{file = "httptools-0.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c286985b5e194ca0ebb2908d71464b9be8f17cc66d6d3e330e8d5407248f56ad"},
{file = "httptools-0.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3a4e165ca6204f34856b765d515d558dc84f1352033b8721e8d06c3e44930c3"},
{file = "httptools-0.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:72aa3fbe636b16d22e04b5a9d24711b043495e0ecfe58080addf23a1a37f3409"},
{file = "httptools-0.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:9967d9758df505975913304c434cb9ab21e2c609ad859eb921f2f615a038c8de"},
{file = "httptools-0.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f72b5d24d6730035128b238decdc4c0f2104b7056a7ca55cf047c106842ec890"},
{file = "httptools-0.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:29bf97a5c532da9c7a04de2c7a9c31d1d54f3abd65a464119b680206bbbb1055"},
{file = "httptools-0.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98993805f1e3cdb53de4eed02b55dcc953cdf017ba7bbb2fd89226c086a6d855"},
{file = "httptools-0.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d9b90bf58f3ba04e60321a23a8723a1ff2a9377502535e70495e5ada8e6e6722"},
{file = "httptools-0.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a99346ebcb801b213c591540837340bdf6fd060a8687518d01c607d338b7424"},
{file = "httptools-0.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:645373c070080e632480a3d251d892cb795be3d3a15f86975d0f1aca56fd230d"},
{file = "httptools-0.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:34d2903dd2a3dd85d33705b6fde40bf91fc44411661283763fd0746723963c83"},
{file = "httptools-0.4.0.tar.gz", hash = "sha256:2c9a930c378b3d15d6b695fb95ebcff81a7395b4f9775c4f10a076beb0b2c1ff"},
]
httptools = []
httpx = [
{file = "httpx-0.23.0-py3-none-any.whl", hash = "sha256:42974f577483e1e932c3cdc3cd2303e883cbfba17fe228b0f63589764d7b9c4b"},
{file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"},
]
hypercorn = [
{file = "Hypercorn-0.13.2-py3-none-any.whl", hash = "sha256:ca18f91ab3fa823cbe9e949738f9f2cc07027cd647c80d8f93e4b1a2a175f112"},
{file = "Hypercorn-0.13.2.tar.gz", hash = "sha256:6307be5cbdf6ba411967d4661202dc4f79bd511b5d318bc4eed88b09418427f8"},
]
hypercorn = []
hyperframe = [
{file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"},
{file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"},
]
identify = []
idna = [
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
]
importlib-metadata = []
idna = []
iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
@@ -1669,10 +1461,7 @@ packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
]
pathspec = [
{file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
]
pathspec = []
platformdirs = [
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
@@ -1686,97 +1475,23 @@ priority = [
{file = "priority-2.0.0-py3-none-any.whl", hash = "sha256:6f8eefce5f3ad59baf2c080a664037bb4725cd0a790d53d59ab4059288faf6aa"},
{file = "priority-2.0.0.tar.gz", hash = "sha256:c965d54f1b8d0d0b19479db3924c7c36cf672dbf2aec92d43fbdaf4492ba18c0"},
]
prompt-toolkit = [
{file = "prompt_toolkit-3.0.30-py3-none-any.whl", hash = "sha256:d8916d3f62a7b67ab353a952ce4ced6a1d2587dfe9ef8ebc30dd7c386751f289"},
{file = "prompt_toolkit-3.0.30.tar.gz", hash = "sha256:859b283c50bde45f5f97829f77a4674d1c1fcd88539364f1b28a37805cfd89c0"},
]
prompt-toolkit = []
py = [
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
]
pycares = [
{file = "pycares-4.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d83f193563b42360528167705b1c7bb91e2a09f990b98e3d6378835b72cd5c96"},
{file = "pycares-4.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b03f69df69f0ab3bfb8dbe54444afddff6ff9389561a08aade96b4f91207a655"},
{file = "pycares-4.2.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3b78bdee2f2f1351d5fccc2d1b667aea2d15a55d74d52cb9fd5bea8b5e74c4dc"},
{file = "pycares-4.2.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f05223de13467bb26f9a1594a1799ce2d08ad8ea241489fecd9d8ed3bbbfc672"},
{file = "pycares-4.2.1-cp310-cp310-win32.whl", hash = "sha256:1f37f762414680063b4dfec5be809a84f74cd8e203d939aaf3ba9c807a9e7013"},
{file = "pycares-4.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:1a9506d496efeb809a1b63647cb2f3f33c67fcf62bf80a2359af692fef2c1755"},
{file = "pycares-4.2.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2fd53eb5b441c4f6f9c78d7900e05883e9998b34a14b804be4fc4c6f9fea89f3"},
{file = "pycares-4.2.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:061dd4c80fec73feb150455b159704cd51a122f20d36790033bd6375d4198579"},
{file = "pycares-4.2.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a521d7f54f3e52ded4d34c306ba05cfe9eb5aaa2e5aaf83c96564b9369495588"},
{file = "pycares-4.2.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:99e00e397d07a79c9f43e4303e67f4f97bcabd013bda0d8f2d430509b7aef8a0"},
{file = "pycares-4.2.1-cp36-cp36m-win32.whl", hash = "sha256:d9cd826d8e0c270059450709bff994bfeb072f79d82fd3f11c701690ff65d0e7"},
{file = "pycares-4.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f8e6942965465ca98e212376c4afb9aec501d8129054929744b2f4a487c8c14b"},
{file = "pycares-4.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e75cbd4d3b3d9b02bba6e170846e39893a825e7a5fb1b96728fc6d7b964f8945"},
{file = "pycares-4.2.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2e8ec4c8e07c986b70a3cc8f5b297c53b08ac755e5b9797512002a466e2de86"},
{file = "pycares-4.2.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5333b51ef4ff3e8973b4a1b57cad5ada13e15552445ee3cd74bd77407dec9d44"},
{file = "pycares-4.2.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2113529004df4894783eaa61e9abc3a680756b6f033d942f2800301ae8c71c29"},
{file = "pycares-4.2.1-cp37-cp37m-win32.whl", hash = "sha256:e7a95763cdc20cf9ec357066e656ea30b8de6b03de6175cbb50890e22aa01868"},
{file = "pycares-4.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7a901776163a04de5d67c42bd63a287cff9cb05fc041668ad1681fe3daa36445"},
{file = "pycares-4.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:66b5390a4885a578e687d3f2683689c35e1d4573f4d0ecf217431f7bb55c49a0"},
{file = "pycares-4.2.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15dd5cf21bc73ad539e8aabf7afe370d1df8af7bc6944cd7298f3bfef0c1a27c"},
{file = "pycares-4.2.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4ee625d7571039038bca51ae049b047cbfcfc024b302aae6cc53d5d9aa8648a8"},
{file = "pycares-4.2.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:396ee487178e9de06ca4122a35a157474db3ce0a0db6038a31c831ebb9863315"},
{file = "pycares-4.2.1-cp38-cp38-win32.whl", hash = "sha256:e4dc37f732f7110ca6368e0128cbbd0a54f5211515a061b2add64da2ddb8e5ca"},
{file = "pycares-4.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:3636fccf643c5192c34ee0183c514a2d09419e3a76ca2717cef626638027cb21"},
{file = "pycares-4.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6724573e830ea2345f4bcf0f968af64cc6d491dc2133e9c617f603445dcdfa58"},
{file = "pycares-4.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9dbfcacbde6c21380c412c13d53ea44b257dea3f7b9d80be2c873bb20e21fee"},
{file = "pycares-4.2.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c8a46839da642b281ac5f56d3c6336528e128b3c41eab9c5330d250f22325e9d"},
{file = "pycares-4.2.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9b05c2cec644a6c66b55bcf6c24d4dfdaf2f7205b16e5c4ceee31db104fac958"},
{file = "pycares-4.2.1-cp39-cp39-win32.whl", hash = "sha256:8bd6ed3ad3a5358a635c1acf5d0f46be9afb095772b84427ff22283d2f31db1b"},
{file = "pycares-4.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:fbd53728d798d07811898e11991e22209229c090eab265a53d12270b95d70d1a"},
{file = "pycares-4.2.1.tar.gz", hash = "sha256:735b4f75fd0f595c4e9184da18cd87737f46bc81a64ea41f4edce2b6b68d46d2"},
]
pycares = []
pycparser = [
{file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
]
pydantic = [
{file = "pydantic-1.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8098a724c2784bf03e8070993f6d46aa2eeca031f8d8a048dff277703e6e193"},
{file = "pydantic-1.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c320c64dd876e45254bdd350f0179da737463eea41c43bacbee9d8c9d1021f11"},
{file = "pydantic-1.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18f3e912f9ad1bdec27fb06b8198a2ccc32f201e24174cec1b3424dda605a310"},
{file = "pydantic-1.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11951b404e08b01b151222a1cb1a9f0a860a8153ce8334149ab9199cd198131"},
{file = "pydantic-1.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8bc541a405423ce0e51c19f637050acdbdf8feca34150e0d17f675e72d119580"},
{file = "pydantic-1.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e565a785233c2d03724c4dc55464559639b1ba9ecf091288dd47ad9c629433bd"},
{file = "pydantic-1.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a4a88dcd6ff8fd47c18b3a3709a89adb39a6373f4482e04c1b765045c7e282fd"},
{file = "pydantic-1.9.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:447d5521575f18e18240906beadc58551e97ec98142266e521c34968c76c8761"},
{file = "pydantic-1.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:985ceb5d0a86fcaa61e45781e567a59baa0da292d5ed2e490d612d0de5796918"},
{file = "pydantic-1.9.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059b6c1795170809103a1538255883e1983e5b831faea6558ef873d4955b4a74"},
{file = "pydantic-1.9.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d12f96b5b64bec3f43c8e82b4aab7599d0157f11c798c9f9c528a72b9e0b339a"},
{file = "pydantic-1.9.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ae72f8098acb368d877b210ebe02ba12585e77bd0db78ac04a1ee9b9f5dd2166"},
{file = "pydantic-1.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:79b485767c13788ee314669008d01f9ef3bc05db9ea3298f6a50d3ef596a154b"},
{file = "pydantic-1.9.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:494f7c8537f0c02b740c229af4cb47c0d39840b829ecdcfc93d91dcbb0779892"},
{file = "pydantic-1.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0f047e11febe5c3198ed346b507e1d010330d56ad615a7e0a89fae604065a0e"},
{file = "pydantic-1.9.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:969dd06110cb780da01336b281f53e2e7eb3a482831df441fb65dd30403f4608"},
{file = "pydantic-1.9.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:177071dfc0df6248fd22b43036f936cfe2508077a72af0933d0c1fa269b18537"},
{file = "pydantic-1.9.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9bcf8b6e011be08fb729d110f3e22e654a50f8a826b0575c7196616780683380"},
{file = "pydantic-1.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a955260d47f03df08acf45689bd163ed9df82c0e0124beb4251b1290fa7ae728"},
{file = "pydantic-1.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9ce157d979f742a915b75f792dbd6aa63b8eccaf46a1005ba03aa8a986bde34a"},
{file = "pydantic-1.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0bf07cab5b279859c253d26a9194a8906e6f4a210063b84b433cf90a569de0c1"},
{file = "pydantic-1.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d93d4e95eacd313d2c765ebe40d49ca9dd2ed90e5b37d0d421c597af830c195"},
{file = "pydantic-1.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1542636a39c4892c4f4fa6270696902acb186a9aaeac6f6cf92ce6ae2e88564b"},
{file = "pydantic-1.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a9af62e9b5b9bc67b2a195ebc2c2662fdf498a822d62f902bf27cccb52dbbf49"},
{file = "pydantic-1.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fe4670cb32ea98ffbf5a1262f14c3e102cccd92b1869df3bb09538158ba90fe6"},
{file = "pydantic-1.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:9f659a5ee95c8baa2436d392267988fd0f43eb774e5eb8739252e5a7e9cf07e0"},
{file = "pydantic-1.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b83ba3825bc91dfa989d4eed76865e71aea3a6ca1388b59fc801ee04c4d8d0d6"},
{file = "pydantic-1.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1dd8fecbad028cd89d04a46688d2fcc14423e8a196d5b0a5c65105664901f810"},
{file = "pydantic-1.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02eefd7087268b711a3ff4db528e9916ac9aa18616da7bca69c1871d0b7a091f"},
{file = "pydantic-1.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7eb57ba90929bac0b6cc2af2373893d80ac559adda6933e562dcfb375029acee"},
{file = "pydantic-1.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4ce9ae9e91f46c344bec3b03d6ee9612802682c1551aaf627ad24045ce090761"},
{file = "pydantic-1.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:72ccb318bf0c9ab97fc04c10c37683d9eea952ed526707fabf9ac5ae59b701fd"},
{file = "pydantic-1.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:61b6760b08b7c395975d893e0b814a11cf011ebb24f7d869e7118f5a339a82e1"},
{file = "pydantic-1.9.1-py3-none-any.whl", hash = "sha256:4988c0f13c42bfa9ddd2fe2f569c9d54646ce84adc5de84228cfe83396f3bd58"},
{file = "pydantic-1.9.1.tar.gz", hash = "sha256:1ed987c3ff29fff7fd8c3ea3a3ea877ad310aae2ef9889a119e22d3f2db0691a"},
]
pydantic = []
pygtrie = []
pyparsing = [
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
]
pytest = [
{file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"},
{file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"},
]
pytest = []
pytest-asyncio = []
pytest-cov = [
{file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"},
@@ -1790,10 +1505,7 @@ pytest-xdist = [
{file = "pytest-xdist-2.5.0.tar.gz", hash = "sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf"},
{file = "pytest_xdist-2.5.0-py3-none-any.whl", hash = "sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65"},
]
python-dotenv = [
{file = "python-dotenv-0.20.0.tar.gz", hash = "sha256:b7e3b04a59693c42c36f9ab1cc2acc46fa5df8c78e178fc33a8d4cd05c8d498f"},
{file = "python_dotenv-0.20.0-py3-none-any.whl", hash = "sha256:d92a187be61fe482e4fd675b6d52200e7be63a12b724abbf931a40ce4fa92938"},
]
python-dotenv = []
pyyaml = [
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
@@ -1845,10 +1557,7 @@ six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
sniffio = [
{file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"},
{file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"},
]
sniffio = []
starlette = [
{file = "starlette-0.19.1-py3-none-any.whl", hash = "sha256:5a60c5c2d051f3a8eb546136aa0c9399773a689595e099e0877704d5888279bf"},
{file = "starlette-0.19.1.tar.gz", hash = "sha256:c6d21096774ecb9639acad41b86b7706e52ba3bf1dc13ea4ed9ad593d47e24c7"},
@@ -1862,56 +1571,13 @@ tomli = [
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
tomlkit = []
typed-ast = [
{file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"},
{file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"},
{file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"},
{file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"},
{file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"},
{file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"},
{file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"},
{file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"},
{file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"},
{file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"},
{file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"},
{file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"},
{file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"},
{file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"},
{file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"},
{file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"},
{file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"},
{file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"},
{file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"},
{file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"},
{file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"},
{file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"},
{file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"},
{file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
]
typing-extensions = [
{file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"},
{file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"},
]
urllib3 = []
uvicorn = []
uvloop = [
{file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d"},
{file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30ba9dcbd0965f5c812b7c2112a1ddf60cf904c1c160f398e7eed3a6b82dcd9c"},
{file = "uvloop-0.16.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bd53f7f5db562f37cd64a3af5012df8cac2c464c97e732ed556800129505bd64"},
{file = "uvloop-0.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:772206116b9b57cd625c8a88f2413df2fcfd0b496eb188b82a43bed7af2c2ec9"},
{file = "uvloop-0.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b572256409f194521a9895aef274cea88731d14732343da3ecdb175228881638"},
{file = "uvloop-0.16.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:04ff57aa137230d8cc968f03481176041ae789308b4d5079118331ab01112450"},
{file = "uvloop-0.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a19828c4f15687675ea912cc28bbcb48e9bb907c801873bd1519b96b04fb805"},
{file = "uvloop-0.16.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e814ac2c6f9daf4c36eb8e85266859f42174a4ff0d71b99405ed559257750382"},
{file = "uvloop-0.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bd8f42ea1ea8f4e84d265769089964ddda95eb2bb38b5cbe26712b0616c3edee"},
{file = "uvloop-0.16.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:647e481940379eebd314c00440314c81ea547aa636056f554d491e40503c8464"},
{file = "uvloop-0.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e0d26fa5875d43ddbb0d9d79a447d2ace4180d9e3239788208527c4784f7cab"},
{file = "uvloop-0.16.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6ccd57ae8db17d677e9e06192e9c9ec4bd2066b77790f9aa7dede2cc4008ee8f"},
{file = "uvloop-0.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:089b4834fd299d82d83a25e3335372f12117a7d38525217c2258e9b9f4578897"},
{file = "uvloop-0.16.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98d117332cc9e5ea8dfdc2b28b0a23f60370d02e1395f88f40d1effd2cb86c4f"},
{file = "uvloop-0.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e5f2e2ff51aefe6c19ee98af12b4ae61f5be456cd24396953244a30880ad861"},
{file = "uvloop-0.16.0.tar.gz", hash = "sha256:f74bc20c7b67d1c27c72601c78cf95be99d5c2cdd4514502b4f3eb0933ff1228"},
]
uvloop = []
virtualenv = []
watchfiles = []
wcwidth = [
@@ -1973,9 +1639,5 @@ win32-setctime = [
{file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"},
{file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"},
]
wsproto = [
{file = "wsproto-1.1.0-py3-none-any.whl", hash = "sha256:2218cb57952d90b9fca325c0dcfb08c3bda93e8fd8070b0a17f048e2e47a521b"},
{file = "wsproto-1.1.0.tar.gz", hash = "sha256:a2e56bfd5c7cd83c1369d83b5feccd6d37798b74872866e62616e0ecf111bda8"},
]
wsproto = []
yarl = []
zipp = []

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "nonebot2"
version = "2.0.0-beta.5"
version = "2.0.0-rc.1"
description = "An asynchronous python bot framework."
authors = ["yanyongyu <yyy@nonebot.dev>"]
license = "MIT"
@@ -22,7 +22,7 @@ packages = [
include = ["nonebot/py.typed"]
[tool.poetry.dependencies]
python = "^3.7.3"
python = "^3.8"
yarl = "^1.7.2"
loguru = "^0.6.0"
pygtrie = "^2.4.1"

View File

@@ -1,6 +1,7 @@
[report]
exclude_lines =
def __repr__
def __str__
pragma: no cover
if TYPE_CHECKING:
@(abc\.)?abstractmethod

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,2 @@
[tool]
nonebot = []

4
tests/plugins.json Normal file
View File

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

3
tests/plugins.toml Normal file
View File

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

View File

@@ -1,6 +1,2 @@
from nonebot import export
@export()
def test():
return "export"

View File

@@ -3,5 +3,7 @@ from datetime import datetime, timedelta
from nonebot.matcher import Matcher
test_temp_matcher = Matcher.new("test", temp=True)
test_datetime_matcher = Matcher.new("test", expire_time=datetime.now())
test_datetime_matcher = Matcher.new(
"test", expire_time=datetime.now() - timedelta(seconds=1)
)
test_timedelta_matcher = Matcher.new("test", expire_time=timedelta(seconds=-1))

View File

@@ -1,10 +1,14 @@
from nonebot.matcher import Matcher
from nonebot.permission import Permission
from nonebot.permission import USER, Permission
default_permission = Permission()
test_permission_updater = Matcher.new(permission=default_permission)
test_user_permission_updater = Matcher.new(
permission=USER("test", perm=default_permission)
)
test_custom_updater = Matcher.new(permission=default_permission)

View File

@@ -0,0 +1 @@
from . import matchers

View File

@@ -0,0 +1,243 @@
from datetime import datetime, timezone
from nonebot.adapters import Event
from nonebot import (
CommandGroup,
MatcherGroup,
on,
on_type,
on_regex,
on_notice,
on_command,
on_keyword,
on_message,
on_request,
on_endswith,
on_fullmatch,
on_metaevent,
on_startswith,
on_shell_command,
)
async def rule() -> bool:
return True
async def permission() -> bool:
return True
async def handler():
return
expire_time = datetime.now(timezone.utc)
priority = 100
state = {"test": "test"}
matcher_on = on(
"test",
rule=rule,
permission=permission,
handlers=[handler],
temp=True,
expire_time=expire_time,
priority=priority,
block=True,
state=state,
)
matcher_on_metaevent = on_metaevent(
rule=rule,
handlers=[handler],
temp=True,
expire_time=expire_time,
priority=priority,
block=True,
state=state,
)
matcher_on_message = on_message(
rule=rule,
permission=permission,
handlers=[handler],
temp=True,
expire_time=expire_time,
priority=priority,
block=True,
state=state,
)
matcher_on_notice = on_notice(
rule=rule,
handlers=[handler],
temp=True,
expire_time=expire_time,
priority=priority,
block=True,
state=state,
)
matcher_on_request = on_request(
rule=rule,
handlers=[handler],
temp=True,
expire_time=expire_time,
priority=priority,
block=True,
state=state,
)
matcher_on_startswith = on_startswith(
"test",
rule=rule,
permission=permission,
handlers=[handler],
temp=True,
expire_time=expire_time,
priority=priority,
block=True,
state=state,
)
matcher_on_endswith = on_endswith(
"test",
rule=rule,
permission=permission,
handlers=[handler],
temp=True,
expire_time=expire_time,
priority=priority,
block=True,
state=state,
)
matcher_on_fullmatch = on_fullmatch(
"test",
rule=rule,
permission=permission,
handlers=[handler],
temp=True,
expire_time=expire_time,
priority=priority,
block=True,
state=state,
)
matcher_on_keyword = on_keyword(
{"test"},
rule=rule,
permission=permission,
handlers=[handler],
temp=True,
expire_time=expire_time,
priority=priority,
block=True,
state=state,
)
matcher_on_command = on_command(
"test",
rule=rule,
permission=permission,
handlers=[handler],
temp=True,
expire_time=expire_time,
priority=priority,
block=True,
state=state,
)
matcher_on_shell_command = on_shell_command(
"test",
rule=rule,
permission=permission,
handlers=[handler],
temp=True,
expire_time=expire_time,
priority=priority,
block=True,
state=state,
)
matcher_on_regex = on_regex(
"test",
rule=rule,
permission=permission,
handlers=[handler],
temp=True,
expire_time=expire_time,
priority=priority,
block=True,
state=state,
)
class TestEvent(Event):
...
matcher_on_type = on_type(
TestEvent,
rule=rule,
permission=permission,
handlers=[handler],
temp=True,
expire_time=expire_time,
priority=priority,
block=True,
state=state,
)
cmd_group = CommandGroup(
"test",
rule=rule,
permission=permission,
handlers=[handler],
temp=True,
expire_time=expire_time,
priority=priority,
block=True,
state=state,
)
matcher_sub_cmd = cmd_group.command("sub")
matcher_sub_shell_cmd = cmd_group.shell_command("sub")
matcher_group = MatcherGroup(
rule=rule,
permission=permission,
handlers=[handler],
temp=True,
expire_time=expire_time,
priority=priority,
block=True,
state=state,
)
matcher_group_on = matcher_group.on(type="test")
matcher_group_on_metaevent = matcher_group.on_metaevent()
matcher_group_on_message = matcher_group.on_message()
matcher_group_on_notice = matcher_group.on_notice()
matcher_group_on_request = matcher_group.on_request()
matcher_group_on_startswith = matcher_group.on_startswith("test")
matcher_group_on_endswith = matcher_group.on_endswith("test")
matcher_group_on_fullmatch = matcher_group.on_fullmatch("test")
matcher_group_on_keyword = matcher_group.on_keyword({"test"})
matcher_group_on_command = matcher_group.on_command("test")
matcher_group_on_shell_command = matcher_group.on_shell_command("test")
matcher_group_on_regex = matcher_group.on_regex("test")
matcher_group_on_type = matcher_group.on_type(TestEvent)

View File

@@ -11,11 +11,11 @@ def test_template_basis():
def test_template_message():
Message = make_fake_message()
template = Message.template("{a:custom}{b:text}{c:image}")
template = Message.template("{a:custom}{b:text}{c:image}/{d}")
@template.add_format_spec
def custom(input: str) -> str:
return input + "-custom!"
return f"{input}-custom!"
try:
template.add_format_spec(custom)
@@ -24,12 +24,17 @@ def test_template_message():
else:
raise AssertionError("Should raise ValueError")
format_args = {"a": "custom", "b": "text", "c": "https://example.com/test"}
format_args = {
"a": "custom",
"b": "text",
"c": "https://example.com/test",
"d": 114,
}
formatted = template.format(**format_args)
assert template.format_map(format_args) == formatted
assert formatted.extract_plain_text() == "custom-custom!text"
assert str(formatted) == "custom-custom!text[fake:image]"
assert formatted.extract_plain_text() == "custom-custom!text/114"
assert str(formatted) == "custom-custom!text[fake:image]/114"
def test_rich_template_message():

View File

@@ -78,3 +78,27 @@ async def test_reverse_driver(app: App):
assert await ws.receive_bytes() == b"pong"
await ws.close()
@pytest.mark.asyncio
@pytest.mark.parametrize(
"nonebug_init, driver_type",
[
pytest.param(
{"driver": "nonebot.drivers.fastapi:Driver+nonebot.drivers.aiohttp:Mixin"},
"fastapi+aiohttp",
id="fastapi+aiohttp",
),
pytest.param(
{"driver": "~httpx:Driver+~websockets"},
"block_driver+httpx+websockets",
id="httpx+websockets",
),
],
indirect=["nonebug_init"],
)
async def test_combine_driver(app: App, driver_type: str):
import nonebot
driver = nonebot.get_driver()
assert driver.type == driver_type

View File

@@ -61,7 +61,7 @@ async def test_get(monkeypatch: pytest.MonkeyPatch, nonebug_clear):
with pytest.raises(ValueError):
get_bot()
monkeypatch.setattr(driver, "_clients", {"test": "test"})
monkeypatch.setattr(driver, "_bots", {"test": "test"})
assert get_bot() == "test"
assert get_bot("test") == "test"
assert get_bots() == {"test": "test"}

View File

@@ -104,6 +104,7 @@ async def test_permission_updater(app: App, load_plugin):
default_permission,
test_custom_updater,
test_permission_updater,
test_user_permission_updater,
)
event = make_fake_event(_session_id="test")()
@@ -119,6 +120,19 @@ async def test_permission_updater(app: App, load_plugin):
assert checker.users == ("test",)
assert checker.perm is default_permission
user_permission = list(test_user_permission_updater.permission.checkers)[0].call
assert isinstance(user_permission, User)
assert user_permission.perm is default_permission
async with app.test_api() as ctx:
bot = ctx.create_bot()
matcher = test_user_permission_updater()
new_perm = await matcher.update_permission(bot, event)
assert len(new_perm.checkers) == 1
checker = list(new_perm.checkers)[0].call
assert isinstance(checker, User)
assert checker.users == ("test",)
assert checker.perm is default_permission
assert test_custom_updater.permission is default_permission
async with app.test_api() as ctx:
bot = ctx.create_bot()

View File

@@ -1,3 +1,5 @@
from typing import Tuple, Optional
import pytest
from nonebug import App
@@ -142,10 +144,11 @@ async def test_metaevent(
("message", "test", True),
("message", "foo", False),
("message", "faketest", True),
("notice", "test", False),
("message", None, False),
("notice", "test", True),
],
)
async def test_startswith(
async def test_superuser(
app: App,
type: str,
user_id: str,
@@ -163,3 +166,29 @@ async def test_startswith(
async with app.test_api() as ctx:
bot = ctx.create_bot()
assert await dependent(bot=bot, event=event) == expected
@pytest.mark.asyncio
@pytest.mark.parametrize(
"session_ids,session_id,expected",
[
(("user", "foo"), "user", True),
(("user", "foo"), "bar", False),
(("user", "foo"), None, False),
],
)
async def test_user(
app: App, session_ids: Tuple[str, ...], session_id: Optional[str], expected: bool
):
from nonebot.permission import USER, User
dependent = list(USER(*session_ids).checkers)[0]
checker = dependent.call
assert isinstance(checker, User)
event = make_fake_event(_session_id=session_id)()
async with app.test_api() as ctx:
bot = ctx.create_bot()
assert await dependent(bot=bot, event=event) == expected

View File

@@ -1,4 +1,5 @@
import sys
from pathlib import Path
from dataclasses import asdict
from typing import TYPE_CHECKING, Set
@@ -10,7 +11,21 @@ if TYPE_CHECKING:
@pytest.mark.asyncio
async def test_load_plugin(app: App, load_plugin: Set["Plugin"]):
async def test_load_plugin(app: App):
import nonebot
# check regular
assert nonebot.load_plugin("plugins.metadata")
# check path
assert nonebot.load_plugin(Path("plugins/export"))
# check not found
assert nonebot.load_plugin("some_plugin_not_exist") is None
@pytest.mark.asyncio
async def test_load_plugins(app: App, load_plugin: Set["Plugin"]):
import nonebot
from nonebot.plugin import PluginManager
@@ -34,9 +49,6 @@ async def test_load_plugin(app: App, load_plugin: Set["Plugin"]):
with pytest.raises(RuntimeError):
PluginManager(search_path=["plugins"]).load_all_plugins()
# check not found
assert nonebot.load_plugin("some_plugin_not_exist") is None
@pytest.mark.asyncio
async def test_load_nested_plugin(app: App, load_plugin: Set["Plugin"]):
@@ -51,6 +63,29 @@ async def test_load_nested_plugin(app: App, load_plugin: Set["Plugin"]):
assert parent_plugin.sub_plugins == {sub_plugin, sub_plugin2}
@pytest.mark.asyncio
async def test_load_json(app: App):
import nonebot
nonebot.load_from_json("./plugins.json")
with pytest.raises(TypeError):
nonebot.load_from_json("./plugins.invalid.json")
@pytest.mark.asyncio
async def test_load_toml(app: App):
import nonebot
nonebot.load_from_toml("./plugins.toml")
with pytest.raises(ValueError):
nonebot.load_from_toml("./plugins.empty.toml")
with pytest.raises(TypeError):
nonebot.load_from_toml("./plugins.invalid.toml")
@pytest.mark.asyncio
async def test_bad_plugin(app: App):
import nonebot

View File

@@ -0,0 +1,116 @@
from typing import Type, Optional
import pytest
from nonebug import App
@pytest.mark.asyncio
async def test_on(app: App, load_plugin):
import nonebot
import plugins.plugin.matchers as module
from nonebot.typing import T_RuleChecker
from nonebot.matcher import Matcher, matchers
from nonebot.rule import (
RegexRule,
IsTypeRule,
CommandRule,
EndswithRule,
KeywordsRule,
FullmatchRule,
StartswithRule,
ShellCommandRule,
)
from plugins.plugin.matchers import (
TestEvent,
rule,
state,
handler,
priority,
matcher_on,
permission,
expire_time,
matcher_on_type,
matcher_sub_cmd,
matcher_group_on,
matcher_on_regex,
matcher_on_notice,
matcher_on_command,
matcher_on_keyword,
matcher_on_message,
matcher_on_request,
matcher_on_endswith,
matcher_on_fullmatch,
matcher_on_metaevent,
matcher_group_on_type,
matcher_on_startswith,
matcher_sub_shell_cmd,
matcher_group_on_regex,
matcher_group_on_notice,
matcher_group_on_command,
matcher_group_on_keyword,
matcher_group_on_message,
matcher_group_on_request,
matcher_on_shell_command,
matcher_group_on_endswith,
matcher_group_on_fullmatch,
matcher_group_on_metaevent,
matcher_group_on_startswith,
matcher_group_on_shell_command,
)
plugin = nonebot.get_plugin("plugin")
def _check(
matcher: Type[Matcher],
pre_rule: Optional[T_RuleChecker],
has_permission: bool,
):
assert {dependent.call for dependent in matcher.rule.checkers} == (
{pre_rule, rule} if pre_rule else {rule}
)
if has_permission:
assert {dependent.call for dependent in matcher.permission.checkers} == {
permission
}
else:
assert not matcher.permission.checkers
assert [dependent.call for dependent in matcher.handlers] == [handler]
assert matcher.temp is True
assert matcher.expire_time == expire_time
assert matcher in matchers[priority]
assert matcher.block is True
assert matcher._default_state == state
assert matcher.plugin is plugin
assert matcher.module is module
assert matcher.plugin_name == "plugin"
assert matcher.module_name == "plugins.plugin.matchers"
_check(matcher_on, None, True)
_check(matcher_on_metaevent, None, False)
_check(matcher_on_message, None, True)
_check(matcher_on_notice, None, False)
_check(matcher_on_request, None, False)
_check(matcher_on_startswith, StartswithRule(("test",)), True)
_check(matcher_on_endswith, EndswithRule(("test",)), True)
_check(matcher_on_fullmatch, FullmatchRule(("test",)), True)
_check(matcher_on_keyword, KeywordsRule("test"), True)
_check(matcher_on_command, CommandRule([("test",)]), True)
_check(matcher_on_shell_command, ShellCommandRule([("test",)], None), True)
_check(matcher_on_regex, RegexRule("test"), True)
_check(matcher_on_type, IsTypeRule(TestEvent), True)
_check(matcher_sub_cmd, CommandRule([("test", "sub")]), True)
_check(matcher_sub_shell_cmd, ShellCommandRule([("test", "sub")], None), True)
_check(matcher_group_on, None, True)
_check(matcher_group_on_metaevent, None, False)
_check(matcher_group_on_message, None, True)
_check(matcher_group_on_notice, None, False)
_check(matcher_group_on_request, None, False)
_check(matcher_group_on_startswith, StartswithRule(("test",)), True)
_check(matcher_group_on_endswith, EndswithRule(("test",)), True)
_check(matcher_group_on_fullmatch, FullmatchRule(("test",)), True)
_check(matcher_group_on_keyword, KeywordsRule("test"), True)
_check(matcher_group_on_command, CommandRule([("test",)]), True)
_check(matcher_group_on_shell_command, ShellCommandRule([("test",)], None), True)
_check(matcher_group_on_regex, RegexRule("test"), True)
_check(matcher_group_on_type, IsTypeRule(TestEvent), True)

View File

@@ -1,4 +1,5 @@
from typing import Tuple, Union
import sys
from typing import Dict, Tuple, Union, Optional
import pytest
from nonebug import App
@@ -51,6 +52,7 @@ async def test_rule(app: App):
("prefix", True, "message", "Prefix_", True),
("prefix", False, "message", "prefoo", False),
("prefix", False, "message", "fooprefix", False),
("prefix", False, "message", None, False),
(("prefix", "foo"), False, "message", "fooprefix", True),
("prefix", False, "notice", "foo", False),
],
@@ -60,7 +62,7 @@ async def test_startswith(
msg: Union[str, Tuple[str, ...]],
ignorecase: bool,
type: str,
text: str,
text: Optional[str],
expected: bool,
):
from nonebot.rule import StartswithRule, startswith
@@ -73,7 +75,7 @@ async def test_startswith(
assert checker.msg == (msg,) if isinstance(msg, str) else msg
assert checker.ignorecase == ignorecase
message = make_fake_message()(text)
message = text if text is None else make_fake_message()(text)
event = make_fake_event(_type=type, _message=message)()
assert await dependent(event=event) == expected
@@ -88,6 +90,7 @@ async def test_startswith(
("suffix", True, "message", "_Suffix", True),
("suffix", False, "message", "suffoo", False),
("suffix", False, "message", "suffixfoo", False),
("suffix", False, "message", None, False),
(("suffix", "foo"), False, "message", "suffixfoo", True),
("suffix", False, "notice", "foo", False),
],
@@ -97,7 +100,7 @@ async def test_endswith(
msg: Union[str, Tuple[str, ...]],
ignorecase: bool,
type: str,
text: str,
text: Optional[str],
expected: bool,
):
from nonebot.rule import EndswithRule, endswith
@@ -110,7 +113,7 @@ async def test_endswith(
assert checker.msg == (msg,) if isinstance(msg, str) else msg
assert checker.ignorecase == ignorecase
message = make_fake_message()(text)
message = text if text is None else make_fake_message()(text)
event = make_fake_event(_type=type, _message=message)()
assert await dependent(event=event) == expected
@@ -125,6 +128,7 @@ async def test_endswith(
("fullmatch", True, "message", "Fullmatch", True),
("fullmatch", False, "message", "fullfoo", False),
("fullmatch", False, "message", "_fullmatch_", False),
("fullmatch", False, "message", None, False),
(("fullmatch", "foo"), False, "message", "fullmatchfoo", False),
("fullmatch", False, "notice", "foo", False),
],
@@ -134,7 +138,7 @@ async def test_fullmatch(
msg: Union[str, Tuple[str, ...]],
ignorecase: bool,
type: str,
text: str,
text: Optional[str],
expected: bool,
):
from nonebot.rule import FullmatchRule, fullmatch
@@ -144,10 +148,10 @@ async def test_fullmatch(
checker = dependent.call
assert isinstance(checker, FullmatchRule)
assert checker.msg == {msg} if isinstance(msg, str) else {*msg}
assert checker.msg == ((msg,) if isinstance(msg, str) else msg)
assert checker.ignorecase == ignorecase
message = make_fake_message()(text)
message = text if text is None else make_fake_message()(text)
event = make_fake_event(_type=type, _message=message)()
assert await dependent(event=event) == expected
@@ -158,6 +162,7 @@ async def test_fullmatch(
[
(("key",), "message", "_key_", True),
(("key", "foo"), "message", "_foo_", True),
(("key",), "message", None, False),
(("key",), "notice", "foo", False),
],
)
@@ -165,7 +170,7 @@ async def test_keyword(
app: App,
kws: Tuple[str, ...],
type: str,
text: str,
text: Optional[str],
expected: bool,
):
from nonebot.rule import KeywordsRule, keyword
@@ -177,7 +182,7 @@ async def test_keyword(
assert isinstance(checker, KeywordsRule)
assert checker.keywords == kws
message = make_fake_message()(text)
message = text if text is None else make_fake_message()(text)
event = make_fake_event(_type=type, _message=message)()
assert await dependent(event=event) == expected
@@ -195,16 +200,157 @@ async def test_command(app: App, cmds: Tuple[Tuple[str, ...]]):
checker = dependent.call
assert isinstance(checker, CommandRule)
assert checker.cmds == list(cmds)
assert checker.cmds == cmds
for cmd in cmds:
state = {PREFIX_KEY: {CMD_KEY: cmd}}
assert await dependent(state=state)
# TODO: shell command
@pytest.mark.asyncio
async def test_shell_command(app: App):
from nonebot.typing import T_State
from nonebot.exception import ParserExit
from nonebot.consts import CMD_KEY, PREFIX_KEY, SHELL_ARGS, SHELL_ARGV, CMD_ARG_KEY
from nonebot.rule import Namespace, ArgumentParser, ShellCommandRule, shell_command
# TODO: regex
state: T_State
CMD = ("test",)
Message = make_fake_message()
MessageSegment = Message.get_segment_class()
test_not_cmd = shell_command(CMD)
dependent = list(test_not_cmd.checkers)[0]
checker = dependent.call
assert isinstance(checker, ShellCommandRule)
message = Message()
event = make_fake_event(_message=message)()
state = {PREFIX_KEY: {CMD_KEY: ("not",), CMD_ARG_KEY: message}}
assert not await dependent(event=event, state=state)
test_no_parser = shell_command(CMD)
dependent = list(test_no_parser.checkers)[0]
checker = dependent.call
assert isinstance(checker, ShellCommandRule)
message = Message()
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] == []
assert SHELL_ARGS not in state
parser = ArgumentParser("test")
parser.add_argument("-a", required=True)
test_simple_parser = shell_command(CMD, parser=parser)
dependent = list(test_simple_parser.checkers)[0]
checker = dependent.call
assert isinstance(checker, ShellCommandRule)
message = Message("-a 1")
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"]
assert state[SHELL_ARGS] == Namespace(a="1")
test_parser_help = shell_command(CMD, parser=parser)
dependent = list(test_parser_help.checkers)[0]
checker = dependent.call
assert isinstance(checker, ShellCommandRule)
message = Message("-h")
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] == ["-h"]
assert isinstance(state[SHELL_ARGS], ParserExit)
assert state[SHELL_ARGS].status == 0
assert state[SHELL_ARGS].message == parser.format_help()
test_parser_error = shell_command(CMD, parser=parser)
dependent = list(test_parser_error.checkers)[0]
checker = dependent.call
assert isinstance(checker, ShellCommandRule)
message = Message()
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] == []
assert isinstance(state[SHELL_ARGS], ParserExit)
assert state[SHELL_ARGS].status != 0
test_message_parser = shell_command(CMD, parser=parser)
dependent = list(test_message_parser.checkers)[0]
checker = dependent.call
assert isinstance(checker, ShellCommandRule)
message = MessageSegment.text("-a") + 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", MessageSegment.image("test")]
assert state[SHELL_ARGS] == Namespace(a=MessageSegment.image("test"))
if sys.version_info >= (3, 9):
parser = ArgumentParser("test", exit_on_error=False)
parser.add_argument("-a", required=True)
test_not_exit = shell_command(CMD, parser=parser)
dependent = list(test_not_exit.checkers)[0]
checker = dependent.call
assert isinstance(checker, ShellCommandRule)
message = Message()
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] == []
assert isinstance(state[SHELL_ARGS], ParserExit)
assert state[SHELL_ARGS].status != 0
@pytest.mark.asyncio
@pytest.mark.parametrize(
"pattern,type,text,expected,matched,group,dict",
[
(
r"(?P<key>key\d)",
"message",
"_key1_",
True,
"key1",
("key1",),
{"key": "key1"},
),
(r"foo", "message", None, False, None, None, None),
(r"foo", "notice", "foo", False, None, None, None),
],
)
async def test_regex(
app: App,
pattern: str,
type: str,
text: Optional[str],
expected: bool,
matched: Optional[str],
group: Optional[Tuple[str, ...]],
dict: Optional[Dict[str, str]],
):
from nonebot.typing import T_State
from nonebot.rule import RegexRule, regex
from nonebot.consts import REGEX_DICT, REGEX_GROUP, REGEX_MATCHED
test_regex = regex(pattern)
dependent = list(test_regex.checkers)[0]
checker = dependent.call
assert isinstance(checker, RegexRule)
assert checker.regex == pattern
message = text if text is None else make_fake_message()(text)
event = make_fake_event(_type=type, _message=message)()
state = {}
assert await dependent(event=event, state=state) == expected
assert state.get(REGEX_MATCHED) == matched
assert state.get(REGEX_GROUP) == group
assert state.get(REGEX_DICT) == dict
@pytest.mark.asyncio
@@ -212,11 +358,32 @@ async def test_command(app: App, cmds: Tuple[Tuple[str, ...]]):
async def test_to_me(app: App, expected: bool):
from nonebot.rule import ToMeRule, to_me
test_keyword = to_me()
dependent = list(test_keyword.checkers)[0]
test_to_me = to_me()
dependent = list(test_to_me.checkers)[0]
checker = dependent.call
assert isinstance(checker, ToMeRule)
event = make_fake_event(_to_me=expected)()
assert await dependent(event=event) == expected
@pytest.mark.asyncio
async def test_is_type(app: App):
from nonebot.rule import IsTypeRule, is_type
Event1 = make_fake_event()
Event2 = make_fake_event()
Event3 = make_fake_event()
test_type = is_type(Event1, Event2)
dependent = list(test_type.checkers)[0]
checker = dependent.call
assert isinstance(checker, IsTypeRule)
event = Event1()
assert await dependent(event=event)
event = Event3()
assert not await dependent(event=event)

View File

@@ -65,7 +65,7 @@ def make_fake_event(
_type: str = "message",
_name: str = "test",
_description: str = "test",
_user_id: str = "test",
_user_id: Optional[str] = "test",
_session_id: Optional[str] = "test",
_message: Optional["Message"] = None,
_to_me: bool = True,
@@ -86,7 +86,9 @@ def make_fake_event(
return _description
def get_user_id(self) -> str:
return _user_id
if _user_id is not None:
return _user_id
raise NotImplementedError
def get_session_id(self) -> str:
if _session_id is not None:

View File

@@ -8,7 +8,7 @@ slug: /
NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架,它基于 Python 的类型注解和异步特性,能够为你的需求实现提供便捷灵活的支持。
需要注意的是NoneBot2 仅支持 **Python 3.7.3 以上版本**
需要注意的是NoneBot2 仅支持 **Python 3.8 以上版本**
## 特色

View File

@@ -13,7 +13,7 @@ import Asciinema from "@site/src/components/Asciinema";
# 安装 NoneBot2
:::warning 注意
请确保你的 Python 版本 >= 3.7.3
请确保你的 Python 版本 >= 3.8
:::
:::warning 注意

View File

@@ -277,7 +277,7 @@ async def _(foo: str = CommandStart()): ...
### ShellCommandArgs
获取 shell 命令解析后的参数。
获取 shell 命令解析后的参数,支持 MessageSegment 富文本(如:图片)
:::tip 提示
如果参数解析失败,则为 [`ParserExit`](../../api/exception.md#ParserExit) 异常,并携带错误码与错误信息。
@@ -288,21 +288,28 @@ async def _(foo: str = CommandStart()): ...
```python {8,12}
from nonebot import on_shell_command
from nonebot.params import ShellCommandArgs
from nonebot.rule import Namespace, ArgumentParser
parser = ArgumentParser("demo")
# parser.add_argument ...
matcher = on_shell_command("cmd", parser)
# 解析失败
@matcher.handle()
async def _(foo: ParserExit = ShellCommandArgs()): ...
async def _(foo: ParserExit = ShellCommandArgs()):
if foo.status == 0:
foo.message # help message
else:
foo.message # error message
# 解析成功
@matcher.handle()
async def _(foo: Dict[str, Any] = ShellCommandArgs()): ...
async def _(foo: Namespace = ShellCommandArgs()): ...
```
### ShellCommandArgv
获取 shell 命令解析前的参数列表。
获取 shell 命令解析前的参数列表,支持 MessageSegment 富文本(如:图片)
```python {7}
from nonebot import on_shell_command
@@ -311,7 +318,7 @@ from nonebot.params import ShellCommandArgs
matcher = on_shell_command("cmd")
@matcher.handle()
async def _(foo: List[str] = ShellCommandArgv()): ...
async def _(foo: List[Union[str, MessageSegment]] = ShellCommandArgv()): ...
```
### RegexMatched

View File

@@ -5,6 +5,102 @@ toc_max_heading_level: 2
# 更新日志
## v2.0.0-rc.1
### 💥 破坏性变更
- Feature: `SUPERUSER` 权限匹配任意超管事件 [@AkiraXie](https://github.com/AkiraXie) ([#1275](https://github.com/nonebot/nonebot2/pull/1275))
- Remove: 移除过时的 State 注入参数 [@yanyongyu](https://github.com/yanyongyu) ([#1160](https://github.com/nonebot/nonebot2/pull/1160))
- Remove: 移除过时的 `nonebot.plugins` toml 配置 [@yanyongyu](https://github.com/yanyongyu) ([#1151](https://github.com/nonebot/nonebot2/pull/1151))
- Remove: 移除 Python 3.7 支持 [@yanyongyu](https://github.com/yanyongyu) ([#1148](https://github.com/nonebot/nonebot2/pull/1148))
- Remove: 删除过时的 Export 功能 [@yanyongyu](https://github.com/yanyongyu) ([#1125](https://github.com/nonebot/nonebot2/pull/1125))
### 🚀 新功能
- Feature: `SUPERUSER` 权限匹配任意超管事件 [@AkiraXie](https://github.com/AkiraXie) ([#1275](https://github.com/nonebot/nonebot2/pull/1275))
- Feature: 改进 `CommandGroup``MatcherGroup` 的结构 [@A-kirami](https://github.com/A-kirami) ([#1240](https://github.com/nonebot/nonebot2/pull/1240))
- Feature: 调整日志输出格式与等级 [@yanyongyu](https://github.com/yanyongyu) ([#1233](https://github.com/nonebot/nonebot2/pull/1233))
- Feature: 优化依赖注入结构 [@yanyongyu](https://github.com/yanyongyu) ([#1227](https://github.com/nonebot/nonebot2/pull/1227))
- Featue: `load_plugin` 支持 `pathlib.Path` [@Lancercmd](https://github.com/Lancercmd) ([#1194](https://github.com/nonebot/nonebot2/pull/1194))
- Feature: 新增事件类型过滤 rule [@yanyongyu](https://github.com/yanyongyu) ([#1183](https://github.com/nonebot/nonebot2/pull/1183))
- Feature: shell command 添加富文本支持 [@yanyongyu](https://github.com/yanyongyu) ([#1171](https://github.com/nonebot/nonebot2/pull/1171))
### 🐛 Bug 修复
- Fix: 内置规则和权限没有捕获错误 [@yanyongyu](https://github.com/yanyongyu) ([#1291](https://github.com/nonebot/nonebot2/pull/1291))
- Fix: 修复 User 会话权限更新嵌套问题 [@yanyongyu](https://github.com/yanyongyu) ([#1208](https://github.com/nonebot/nonebot2/pull/1208))
- Fix: 修复当消息与不支持的类型相加时抛出的异常类型错误 [@mnixry](https://github.com/mnixry) ([#1166](https://github.com/nonebot/nonebot2/pull/1166))
### 💫 杂项
- Fix: 修正 GenshinUID 的发布类型 [@A-kirami](https://github.com/A-kirami) ([#1243](https://github.com/nonebot/nonebot2/pull/1243))
- Remove: 移除未使用的导入 [@A-kirami](https://github.com/A-kirami) ([#1236](https://github.com/nonebot/nonebot2/pull/1236))
- Plugin: 更新插件米游社辅助工具 tag [@Ljzd-PRO](https://github.com/Ljzd-PRO) ([#1221](https://github.com/nonebot/nonebot2/pull/1221))
- Plugin: 修改插件多功能简易群管信息 [@HuYihe2008](https://github.com/HuYihe2008) ([#1180](https://github.com/nonebot/nonebot2/pull/1180))
- Plugin: 修改插件多功能简易群管信息 [@HuYihe2008](https://github.com/HuYihe2008) ([#1159](https://github.com/nonebot/nonebot2/pull/1159))
- Plugin: 修改 QQ 续火花插件信息 [@GC-ZF](https://github.com/GC-ZF) ([#1158](https://github.com/nonebot/nonebot2/pull/1158))
- Plugin: 修改插件多功能简易群管信息 [@HuYihe2008](https://github.com/HuYihe2008) ([#1154](https://github.com/nonebot/nonebot2/pull/1154))
### 🍻 插件发布
- Plugin: 文字识别 [@yanyongyu](https://github.com/yanyongyu) ([#1295](https://github.com/nonebot/nonebot2/pull/1295))
- Plugin: 在线编曲 [@yanyongyu](https://github.com/yanyongyu) ([#1293](https://github.com/nonebot/nonebot2/pull/1293))
- Plugin: 图灵机器人 [@yanyongyu](https://github.com/yanyongyu) ([#1289](https://github.com/nonebot/nonebot2/pull/1289))
- Plugin: PicStatus [@yanyongyu](https://github.com/yanyongyu) ([#1287](https://github.com/nonebot/nonebot2/pull/1287))
- Plugin: 阿里云盘福利码自动兑换 [@yanyongyu](https://github.com/yanyongyu) ([#1283](https://github.com/nonebot/nonebot2/pull/1283))
- Plugin: gal 角色语音生成 [@yanyongyu](https://github.com/yanyongyu) ([#1281](https://github.com/nonebot/nonebot2/pull/1281))
- Plugin: 漂流瓶 [@yanyongyu](https://github.com/yanyongyu) ([#1279](https://github.com/nonebot/nonebot2/pull/1279))
- Plugin: BWIKI 助手移植版 [@yanyongyu](https://github.com/yanyongyu) ([#1274](https://github.com/nonebot/nonebot2/pull/1274))
- Plugin: nonebot 物联网插件 [@yanyongyu](https://github.com/yanyongyu) ([#1265](https://github.com/nonebot/nonebot2/pull/1265))
- Plugin: 狼人杀插件 [@yanyongyu](https://github.com/yanyongyu) ([#1252](https://github.com/nonebot/nonebot2/pull/1252))
- Plugin: ayaka - 文字游戏开发辅助插件 [@yanyongyu](https://github.com/yanyongyu) ([#1254](https://github.com/nonebot/nonebot2/pull/1254))
- Plugin: 图像超分辨率重建 [@yanyongyu](https://github.com/yanyongyu) ([#1250](https://github.com/nonebot/nonebot2/pull/1250))
- Plugin: Minecraft Server 聊天同步 [@yanyongyu](https://github.com/yanyongyu) ([#1245](https://github.com/nonebot/nonebot2/pull/1245))
- Plugin: 查询 ETH 合并日期 [@yanyongyu](https://github.com/yanyongyu) ([#1232](https://github.com/nonebot/nonebot2/pull/1232))
- Plugin: 星际战甲事件查询 [@yanyongyu](https://github.com/yanyongyu) ([#1220](https://github.com/nonebot/nonebot2/pull/1220))
- Plugin: 米游社辅助工具 [@yanyongyu](https://github.com/yanyongyu) ([#1218](https://github.com/nonebot/nonebot2/pull/1218))
- Plugin: 原神每日材料查询 [@yanyongyu](https://github.com/yanyongyu) ([#1216](https://github.com/nonebot/nonebot2/pull/1216))
- Plugin: MC_QQ_MCRcon [@yanyongyu](https://github.com/yanyongyu) ([#1211](https://github.com/nonebot/nonebot2/pull/1211))
- Plugin: 原神角色展柜查询 [@yanyongyu](https://github.com/yanyongyu) ([#1209](https://github.com/nonebot/nonebot2/pull/1209))
- Plugin: 修仙模拟器 [@yanyongyu](https://github.com/yanyongyu) ([#1202](https://github.com/nonebot/nonebot2/pull/1202))
- Plugin: 赛博浅草寺 [@yanyongyu](https://github.com/yanyongyu) ([#1206](https://github.com/nonebot/nonebot2/pull/1206))
- Plugin: 不背单词 [@yanyongyu](https://github.com/yanyongyu) ([#1204](https://github.com/nonebot/nonebot2/pull/1204))
- Plugin: 自识别 todo [@yanyongyu](https://github.com/yanyongyu) ([#1193](https://github.com/nonebot/nonebot2/pull/1193))
- Plugin: 雨课堂自动签到 [@yanyongyu](https://github.com/yanyongyu) ([#1189](https://github.com/nonebot/nonebot2/pull/1189))
- Plugin: 反馈及通知 [@yanyongyu](https://github.com/yanyongyu) ([#1187](https://github.com/nonebot/nonebot2/pull/1187))
- Plugin: MagiaDice 骰娘及 TRPGLOG [@yanyongyu](https://github.com/yanyongyu) ([#1185](https://github.com/nonebot/nonebot2/pull/1185))
- Plugin: 面麻小助手 [@yanyongyu](https://github.com/yanyongyu) ([#1191](https://github.com/nonebot/nonebot2/pull/1191))
- Plugin: 话痨排行榜 [@yanyongyu](https://github.com/yanyongyu) ([#1182](https://github.com/nonebot/nonebot2/pull/1182))
- Plugin: 保存群聊闪照 [@yanyongyu](https://github.com/yanyongyu) ([#1179](https://github.com/nonebot/nonebot2/pull/1179))
- Plugin: 课表查询 [@yanyongyu](https://github.com/yanyongyu) ([#1168](https://github.com/nonebot/nonebot2/pull/1168))
- Plugin: 业余无线电助手 [@yanyongyu](https://github.com/yanyongyu) ([#1173](https://github.com/nonebot/nonebot2/pull/1173))
- Plugin: NoneBot 树形帮助插件 [@yanyongyu](https://github.com/yanyongyu) ([#1177](https://github.com/nonebot/nonebot2/pull/1177))
- Plugin: 工作性价比 [@yanyongyu](https://github.com/yanyongyu) ([#1175](https://github.com/nonebot/nonebot2/pull/1175))
- Plugin: 娶群友 [@yanyongyu](https://github.com/yanyongyu) ([#1170](https://github.com/nonebot/nonebot2/pull/1170))
- Plugin: PixivBot [@yanyongyu](https://github.com/yanyongyu) ([#1165](https://github.com/nonebot/nonebot2/pull/1165))
- Plugin: 日韩中 VITS 模型原神拟声 [@yanyongyu](https://github.com/yanyongyu) ([#1162](https://github.com/nonebot/nonebot2/pull/1162))
- Plugin: 每日人品 [@yanyongyu](https://github.com/yanyongyu) ([#1156](https://github.com/nonebot/nonebot2/pull/1156))
- Plugin: nonebot-plugin-drawer [@yanyongyu](https://github.com/yanyongyu) ([#1146](https://github.com/nonebot/nonebot2/pull/1146))
- Plugin: 小游戏合集 [@yanyongyu](https://github.com/yanyongyu) ([#1150](https://github.com/nonebot/nonebot2/pull/1150))
- Plugin: 简易群管(带入群欢迎) [@yanyongyu](https://github.com/yanyongyu) ([#1142](https://github.com/nonebot/nonebot2/pull/1142))
- Plugin: wiki 条目搜索、获取简介 [@yanyongyu](https://github.com/yanyongyu) ([#1133](https://github.com/nonebot/nonebot2/pull/1133))
- Plugin: bangumi 搜索 [@yanyongyu](https://github.com/yanyongyu) ([#1137](https://github.com/nonebot/nonebot2/pull/1137))
- Plugin: 疫情小助手-频道版 [@yanyongyu](https://github.com/yanyongyu) ([#1131](https://github.com/nonebot/nonebot2/pull/1131))
- Plugin: MC_QQ 通信 [@yanyongyu](https://github.com/yanyongyu) ([#1127](https://github.com/nonebot/nonebot2/pull/1127))
- Plugin: BAWiki [@yanyongyu](https://github.com/yanyongyu) ([#1129](https://github.com/nonebot/nonebot2/pull/1129))
### 🍻 机器人发布
- Bot: IdhagnBot [@yanyongyu](https://github.com/yanyongyu) ([#1267](https://github.com/nonebot/nonebot2/pull/1267))
- Bot: LittlePaimon [@yanyongyu](https://github.com/yanyongyu) ([#1256](https://github.com/nonebot/nonebot2/pull/1256))
- Bot: GenshinUID [@yanyongyu](https://github.com/yanyongyu) ([#1226](https://github.com/nonebot/nonebot2/pull/1226))
- Bot: 小白机器人 [@yanyongyu](https://github.com/yanyongyu) ([#1224](https://github.com/nonebot/nonebot2/pull/1224))
### 🍻 适配器发布
- Adapter: GitHub [@yanyongyu](https://github.com/yanyongyu) ([#1297](https://github.com/nonebot/nonebot2/pull/1297))
- Adapter: Console [@yanyongyu](https://github.com/yanyongyu) ([#1213](https://github.com/nonebot/nonebot2/pull/1213))
## v2.0.0-beta.5
### 🚀 新功能

View File

@@ -78,5 +78,25 @@
"homepage": "https://onebot.adapters.nonebot.dev/",
"tags": [],
"is_official": true
},
{
"module_name": "nonebot.adapters.console",
"project_link": "nonebot-adapter-console",
"name": "Console",
"desc": "基于终端的交互式适配器",
"author": "Melodyknit",
"homepage": "https://github.com/nonebot/adapter-console",
"tags": [],
"is_official": true
},
{
"module_name": "nonebot.adapters.github",
"project_link": "nonebot-adapter-github",
"name": "GitHub",
"desc": "GitHub APP & OAuth APP integration",
"author": "yanyongyu",
"homepage": "https://github.com/nonebot/adapter-github",
"tags": [],
"is_official": true
}
]

View File

@@ -233,5 +233,48 @@
}
],
"is_official": false
},
{
"name": "小白机器人",
"desc": "一个高度依赖数据库的群管理机器人",
"author": "SDIJF1521",
"homepage": "https://github.com/SDIJF1521/qqai",
"tags": [
{
"label": "群管理",
"color": "#ea5252"
},
{
"label": "新人作品",
"color": "#ea5252"
}
],
"is_official": false
},
{
"name": "LittlePaimon",
"desc": "小派蒙,多功能原神机器人。",
"author": "CMHopeSunshine",
"homepage": "https://github.com/CMHopeSunshine/LittlePaimon",
"tags": [
{
"label": "原神",
"color": "#7a52ea"
}
],
"is_official": false
},
{
"name": "IdhagnBot",
"desc": "🐱🤖 一个以娱乐功能为主的缝合怪划掉QQ机器人包含一定Furry要素但是不会卖萌就是逊啦",
"author": "su226",
"homepage": "https://github.com/su226/IdhagnBot",
"tags": [
{
"label": "a:OneBot",
"color": "#ea5252"
}
],
"is_official": false
}
]

View File

@@ -1974,7 +1974,7 @@
"module_name": "nonebot_plugin_firexN",
"project_link": "nonebot-plugin-firexN",
"name": "一起燚xN吧",
"desc": "QQ续火花可以自定义多个联系人、消息内容、发送时间",
"desc": "QQ续火花可以自定义多个联系人、消息内容、发送时间,支持群聊定时消息",
"author": "GC-ZF",
"homepage": "https://github.com/GC-ZF/nonebot_plugin_firexN",
"tags": [],
@@ -2125,5 +2125,757 @@
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_bawiki",
"project_link": "nonebot-plugin-bawiki",
"name": "BAWiki",
"desc": "基于 NoneBot2 的碧蓝档案 Wiki 插件",
"author": "lgc2333",
"homepage": "https://github.com/lgc2333/nonebot-plugin-bawiki/",
"tags": [
{
"label": "t:碧蓝档案",
"color": "#00d7fb"
},
{
"label": "t:蔚藍檔案",
"color": "#00d7fb"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_mcqq",
"project_link": "nonebot-plugin-mcqq",
"name": "MC_QQ通信",
"desc": "基于NoneBot的与Minecraft Server互通消息的插件",
"author": "17TheWord",
"homepage": "https://github.com/17TheWord/nonebot-plugin-mcqq",
"tags": [
{
"label": "Minecraft",
"color": "#52ea6f"
},
{
"label": "消息互通",
"color": "#52eadf"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_covid_19_by_guild",
"project_link": "nonebot-plugin-covid-19-by-guild",
"name": "疫情小助手-频道版",
"desc": "👉 疫情小助手 频道版👈",
"author": "bingqiu456",
"homepage": "https://github.com/bingqiu456/nonebot-plugin-covid-19-by-guild",
"tags": [
{
"label": "a:QQ频道",
"color": "#ea5252"
},
{
"label": "t:疫情小助手",
"color": "#526fea"
}
],
"is_official": false
},
{
"module_name": "nonebot-plugin-bangumi-search",
"project_link": "nonebot-plugin-bangumi-search",
"name": "bangumi搜索",
"desc": "搜索番剧,并且返回简介栏和收藏盒",
"author": "Ankhyty",
"homepage": "https://github.com/Ankhyty/nonebot-plugin-bangumi-search",
"tags": [
{
"label": "a:onebot",
"color": "#000000"
},
{
"label": "t:漫研",
"color": "#2f48e0"
},
{
"label": "bangumi",
"color": "#de2968"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_wiki",
"project_link": "nonebot-plugin-wiki",
"name": "wiki条目搜索、获取简介",
"desc": "通过mediawiki api进行条目搜索、生成简介biliwiki部分通过网页解析实现",
"author": "ZombieFly",
"homepage": "https://github.com/ZombieFly/nb2-wiki",
"tags": [],
"is_official": false
},
{
"module_name": "nonebot_plugin_groupmanager",
"project_link": "nonebot-plugin-groupmanager",
"name": "多功能群管",
"desc": "与简易群管属于同个产品,就是多了一个入群欢迎,感谢@幼稚园园长 开发的原版插件的帮助",
"author": "HuYihe2008",
"homepage": "https://github.com/HuYihe2008/nonebot_plugin_groupmanager",
"tags": [
{
"label": "简易群管",
"color": "#53e950"
},
{
"label": "插件改良",
"color": "#2b7be2"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_game_collection",
"project_link": "nonebot_plugin_game_collection",
"name": "小游戏合集",
"desc": "改自 nonebot_plugin_russian 合并了nonebot_plugin_horserace还有一些自编玩法的小游戏合集。",
"author": "KarisAya",
"homepage": "https://github.com/KarisAya/nonebot_plugin_game_collection",
"tags": [],
"is_official": false
},
{
"module_name": "nonebot_plugin_drawer",
"project_link": "nonebot-plugin-drawer",
"name": "nonebot-plugin-drawer",
"desc": "适用于 Nonebot2 的AI画画插件",
"author": "CrazyBoyM",
"homepage": "https://github.com/CrazyBoyM/nonebot-plugin-drawer",
"tags": [],
"is_official": false
},
{
"module_name": "nonebot_plugin_jrrp-n",
"project_link": "nonebot-plugin-jrrp-n",
"name": "每日人品",
"desc": "不基于random的每日人品插件",
"author": "SkyDynamic",
"homepage": "https://github.com/SkyDynamic/nonebot_plugin_jrrp",
"tags": [
{
"label": "每日人品",
"color": "#ea5252"
},
{
"label": "jrrp",
"color": "#529fea"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_moegoe",
"project_link": "nonebot-plugin-moegoe",
"name": "日韩中 VITS 模型原神拟声",
"desc": "现在是琪亚娜时间~",
"author": "Yiyuiii",
"homepage": "https://github.com/Yiyuiii/nonebot-plugin-moegoe",
"tags": [],
"is_official": false
},
{
"module_name": "nonebot_plugin_pixivbot",
"project_link": "nonebot-plugin-pixivbot",
"name": "PixivBot",
"desc": "NoneBot插件发送随机Pixiv插画、画师更新推送、定时订阅推送……",
"author": "ssttkkl",
"homepage": "https://github.com/ssttkkl/nonebot-plugin-pixivbot",
"tags": [],
"is_official": false
},
{
"module_name": "nonebot_plugin_groupmate_waifu",
"project_link": "nonebot_plugin_groupmate_waifu",
"name": "娶群友",
"desc": "娶群友纯爱版ntr版",
"author": "KarisAya",
"homepage": "https://github.com/KarisAya/nonebot_plugin_groupmate_waifu",
"tags": [
{
"label": "娶群友",
"color": "#529bea"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_workscore",
"project_link": "nonebot-plugin-workscore",
"name": "工作性价比",
"desc": "看看你的工作惨不惨",
"author": "yzyyz1387",
"homepage": "https://github.com/yzyyz1387/nonebot_plugin_workscore",
"tags": [
{
"label": "工作性价比计算器",
"color": "#3898fc"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_treehelp",
"project_link": "nonebot-plugin-treehelp",
"name": "NoneBot 树形帮助插件",
"desc": "利用插件元数据生成插件帮助,并支持插件嵌套。",
"author": "he0119",
"homepage": "https://github.com/he0119/nonebot-plugin-treehelp",
"tags": [],
"is_official": false
},
{
"module_name": "cqsat",
"project_link": "nonebot-plugin-cqsat",
"name": "业余无线电助手",
"desc": "一个业余无线电相关的插件",
"author": "yzyyz1387",
"homepage": "https://github.com/yzyyz1387/cqsat",
"tags": [
{
"label": "业余无线电",
"color": "#ea5252"
},
{
"label": "HAM",
"color": "#3898fc"
},
{
"label": "卫星追踪",
"color": "#fca638"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_course",
"project_link": "nonebot-plugin-course",
"name": "课表查询",
"desc": "本周课表,完整课表,下节课在哪上,今天还有什么课🤔",
"author": "InariInDream",
"homepage": "https://github.com/InariInDream/nonebot_plugin_course",
"tags": [
{
"label": "课表",
"color": "#6e9af2"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_flash_silentsave",
"project_link": "nonebot_plugin_flash_silentsave",
"name": "保存群聊闪照",
"desc": "静默保存群聊中的闪照",
"author": "KarisAya",
"homepage": "https://github.com/KarisAya/nonebot_plugin_flash_silentsave",
"tags": [
{
"label": "反闪照",
"color": "#52a5ea"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_dialectlist",
"project_link": "nonebot-plugin-dialectlist",
"name": "话痨排行榜",
"desc": "给话多的群友一个排行",
"author": "X-Skirt-X",
"homepage": "https://github.com/X-Skirt-X/nonebot_plugin_dialectlist",
"tags": [],
"is_official": false
},
{
"module_name": "nonebot_plugin_offline_mahjong_helper",
"project_link": "nonebot-plugin-offline-mahjong-helper",
"name": "面麻小助手",
"desc": "可约桌、算点、精算、雀魂查询的面麻助手。",
"author": "Nranphy",
"homepage": "https://github.com/Nranphy/nonebot_plugin_offline_mahjong_helper",
"tags": [
{
"label": "Mahjong",
"color": "#ea5252"
},
{
"label": "雀魂",
"color": "#eaa452"
},
{
"label": "线下约桌",
"color": "#52a6ea"
}
],
"is_official": false
},
{
"module_name": "nonebot-plugin-magiadice",
"project_link": "nonebot-plugin-magiadice",
"name": "MagiaDice骰娘及TRPGLOG",
"desc": "能够实时在网页围观的TRPG记录插件同时具有骰娘的功能",
"author": "sena-nana",
"homepage": "https://github.com/sena-nana/MutsukiBot/tree/main/nonebot-plugin-magiadice",
"tags": [
{
"label": "TRPG",
"color": "#ea5252"
}
],
"is_official": false
},
{
"module_name": "nonebot-plugin-send",
"project_link": "nonebot-plugin-send",
"name": "反馈及通知",
"desc": "用户通过bot向superuser发送反馈以及bot向全群发送通知",
"author": "sena-nana",
"homepage": "https://github.com/sena-nana/MutsukiBot/tree/main/nonebot-plugin-send",
"tags": [
{
"label": "send",
"color": "#ea5252"
},
{
"label": "notice",
"color": "#ea5252"
},
{
"label": "公告",
"color": "#ea5252"
}
],
"is_official": false
},
{
"module_name": "nonebot-plugin-ykt",
"project_link": "nonebot-plugin-ykt",
"name": "雨课堂自动签到",
"desc": "使用playwright模拟浏览器签到雨课堂并将腾讯会议号发送给superuser",
"author": "sena-nana",
"homepage": "https://github.com/sena-nana/MutsukiBot/tree/main/nonebot-plugin-ykt",
"tags": [
{
"label": "playwright",
"color": "#ea5252"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_todo_nlp",
"project_link": "nonebot-plugin-todo-nlp",
"name": "自识别todo",
"desc": "一款自动识别提醒内容可生成todo图片并定时推送的nonebot2插件v11适配器可用",
"author": "CofinCup",
"homepage": "https://github.com/CofinCup/nonebot-plugin-todo-nlp",
"tags": [
{
"label": "t:todo",
"color": "#499bdd"
},
{
"label": "t:nlp",
"color": "#83b279"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_wordsnorote",
"project_link": "nonebot-plugin-wordsnorote",
"name": "不背单词",
"desc": "哥们哥们,背单词么?哥们!",
"author": "GC-ZF",
"homepage": "https://github.com/GC-ZF/nonebot_plugin_wordsnorote",
"tags": [
{
"label": "四六级",
"color": "#24a0d8"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_CyberSensoji",
"project_link": "nonebot-plugin-CyberSensoji",
"name": "赛博浅草寺",
"desc": "随机抽取浅草寺的一签运势",
"author": "Raidenneox",
"homepage": "https://github.com/Raidenneox/nonebot_plugin_CyberSensoji",
"tags": [
{
"label": "t:抽签",
"color": "#52eadf"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_xiuxian",
"project_link": "nonebot-plugin-xiuxian",
"name": "修仙模拟器",
"desc": "加入群内修仙吧!",
"author": "s52047qwas",
"homepage": "https://github.com/s52047qwas/nonebot_plugin_xiuxian",
"tags": [],
"is_official": false
},
{
"module_name": "nonebot_plugin_gspanel",
"project_link": "nonebot-plugin-gspanel",
"name": "原神角色展柜查询",
"desc": "移植自 @yoimiya-kokomi/miao-plugin 的原神游戏内角色展柜数据查询",
"author": "monsterxcn",
"homepage": "https://github.com/monsterxcn/nonebot-plugin-gspanel",
"tags": [
{
"label": "t:Genshin",
"color": "#ffd49f"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_mcqq_mcrcon",
"project_link": "nonebot-plugin-mcqq-mcrcon",
"name": "MC_QQ_MCRcon",
"desc": "基于NoneBot的QQ群聊与Minecraft Server消息互通插件",
"author": "17TheWord",
"homepage": "https://github.com/17TheWord/nonebot-plugin-mcqq/tree/mcqq_mcrcon",
"tags": [
{
"label": "Minecraft",
"color": "#4aea7a"
},
{
"label": "消息互通",
"color": "#32ddea"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_gsmaterial",
"project_link": "nonebot-plugin-gsmaterial",
"name": "原神每日材料查询",
"desc": "原神每日天赋培养、武器突破材料查询,通过 Project Amber 数据库(支持部分尚未实装角色)自动更新每日数据~",
"author": "monsterxcn",
"homepage": "https://github.com/monsterxcn/nonebot-plugin-gsmaterial",
"tags": [
{
"label": "t:Genshin",
"color": "#ffd49f"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_mystool",
"project_link": "nonebot-plugin-mystool",
"name": "米游社辅助工具",
"desc": "每日完成米游币任务、游戏签到、商品兑换、免抓包登录",
"author": "Ljzd-PRO",
"homepage": "https://github.com/Ljzd-PRO/nonebot-plugin-mystool",
"tags": [
{
"label": "a:onebot",
"color": "#000000"
},
{
"label": "t:米游社",
"color": "#66e0ff"
},
{
"label": "t:原神",
"color": "#faf3c4"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_warframe",
"project_link": "nonebot-plugin-warframe",
"name": "星际战甲事件查询",
"desc": "基于NoneBot的星际战甲事件查询插件",
"author": "17TheWord",
"homepage": "https://github.com/17TheWord/nonebot-plugin-warframe",
"tags": [
{
"label": "星际战甲",
"color": "#ed3f3f"
},
{
"label": "WarFrame",
"color": "#edea3f"
}
],
"is_official": false
},
{
"module_name": "ETH_Terminal",
"project_link": "nonebot2-plugin-eth-terminal",
"name": "查询ETH合并日期",
"desc": "矿难!!!!",
"author": "Sclock",
"homepage": "https://github.com/Sclock/nonebot2_plugin_eth_terminal",
"tags": [
{
"label": "爬虫",
"color": "#ea5252"
},
{
"label": "ETH",
"color": "#5285ea"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_mcqq_server",
"project_link": "nonebot-plugin-mcqq-server",
"name": "Minecraft Server 聊天同步",
"desc": "采用本地读取log信息的方法的Minecraft Server互通消息的插件mcqq服主版。",
"author": "KarisAya",
"homepage": "https://github.com/KarisAya/nonebot_plugin_mcqq_server",
"tags": [
{
"label": "Minecraft",
"color": "#52ea64"
},
{
"label": "消息互通",
"color": "#52e5ea"
}
],
"is_official": false
},
{
"module_name": "GenshinUID",
"project_link": "git+https://github.com/KimigaiiWuyi/GenshinUID.git@nonebot2-beta1#egg=GenshinUID",
"name": "GenshinUID",
"desc": "原神Uid查询/原神Wiki/米社签到/树脂提醒插件",
"author": "KimigaiiWuyi",
"homepage": "https://github.com/KimigaiiWuyi/GenshinUID/tree/nonebot2-beta1",
"tags": [
{
"label": "原神",
"color": "#8edffb"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_RealESRGAN",
"project_link": "nonebot_plugin_RealESRGAN",
"name": "图像超分辨率重建",
"desc": "对图像进行超分辨率重建,可以理解成将图像变大变清晰,不过线上重建的效果没有本地的好,具体可以查阅仓库",
"author": "ppxxxg22",
"homepage": "https://github.com/ppxxxg22/nonebot_plugin_RealESRGAN",
"tags": [
{
"label": "图像超分辨率重建",
"color": "#ea5252"
}
],
"is_official": false
},
{
"module_name": "ayaka",
"project_link": "nonebot-plugin-ayaka",
"name": "ayaka - 文字游戏开发辅助插件",
"desc": "提供了状态机、命令隔离、缓存、固存、帮助指令等功能可以管理多个文字游戏避免命令冲突帮助开发者在nonebot2框架基础上快速开发qq文字游戏",
"author": "bridgeL",
"homepage": "https://github.com/bridgeL/nonebot-plugin-ayaka",
"tags": [
{
"label": "a:cqhttp",
"color": "#000000"
},
{
"label": "a:onebot",
"color": "#000000"
},
{
"label": "t:文字游戏",
"color": "#e36306"
}
],
"is_official": false
},
{
"module_name": "nonebot-plugin-wolf-kill",
"project_link": "nonebot-plugin-wolf-kill",
"name": "狼人杀插件",
"desc": "在群里玩狼人杀",
"author": "AbCooly",
"homepage": "https://github.com/AbCooly/nonebot_plugin_wolf_kill",
"tags": [],
"is_official": false
},
{
"module_name": "iot",
"project_link": "nonebot-plugin-iot",
"name": "nonebot物联网插件",
"desc": "为nonebot接入物联网提供方案目前支持天猫精灵终端后续会增加",
"author": "littlebutt",
"homepage": "https://github.com/littlebutt/nonebot-plugin-iot",
"tags": [
{
"label": "t:物联网",
"color": "#4b86d7"
},
{
"label": "t:天猫精灵",
"color": "#4b86d7"
},
{
"label": "t:IOT",
"color": "#4b86d7"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_bwiki_navigator",
"project_link": "nonebot-plugin-bwiki-navigator",
"name": "BWIKI助手移植版",
"desc": "BWIKI助手机器人Nonebot2插件移植版",
"author": "XZhouQD",
"homepage": "https://github.com/XZhouQD/nonebot-plugin-bwiki-navigator",
"tags": [
{
"label": "a:onebot",
"color": "#000000"
},
{
"label": "t:wiki",
"color": "#29a5e3"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_bottle",
"project_link": "nonebot-plugin-bottle",
"name": "漂流瓶",
"desc": "群与群互通的漂流瓶插件",
"author": "Todysheep",
"homepage": "https://github.com/Todysheep/nonebot_plugin_bottle",
"tags": [
{
"label": "漂流瓶",
"color": "#0893f2"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_tts_gal",
"project_link": "nonebot-plugin-tts-gal",
"name": "gal角色语音生成",
"desc": "能够根据发送角色名和文本生成对应角色语音",
"author": "dpm12345",
"homepage": "https://github.com/dpm12345/nonebot_plugin_tts_gal",
"tags": [
{
"label": "VITS",
"color": "#ea5252"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_alicdk_get",
"project_link": "nonebot-plugin-alicdk-get",
"name": "阿里云盘福利码自动兑换",
"desc": "基于nonebot2与aligo的阿里云盘兑换码自动获取和兑换插件。",
"author": "Kaguya233qwq",
"homepage": "https://github.com/Kaguya233qwq/nonebot_plugin_alicdk_get",
"tags": [
{
"label": "兑换码",
"color": "#595fd6"
},
{
"label": "auto",
"color": "#595fd6"
},
{
"label": "t:阿里云盘",
"color": "#595fd6"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_picstatus",
"project_link": "nonebot-plugin-picstatus",
"name": "PicStatus",
"desc": "服务器运行状态图片版",
"author": "lgc2333",
"homepage": "https://github.com/lgc2333/nonebot-plugin-picstatus",
"tags": [
{
"label": "t:server",
"color": "#8bff00"
},
{
"label": "a:OB V11",
"color": "#ff43c3"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_tuling",
"project_link": "nonebot-plugin-tuling",
"name": "图灵机器人",
"desc": "接入图灵机器人做聊天",
"author": "koking0",
"homepage": "https://github.com/Matrix-King-Studio/nonebot_plugin_tuling",
"tags": [],
"is_official": false
},
{
"module_name": "nonebot_plugin_makemidi",
"project_link": "nonebot_plugin_makemidi",
"name": "在线编曲",
"desc": "发送简谱生成音乐",
"author": "RandomEnch",
"homepage": "https://github.com/RandomEnch/nonebot_plugin_makemidi",
"tags": [
{
"label": "midi",
"color": "#6515a8"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_ocr",
"project_link": "nonebot-plugin-ocr",
"name": "文字识别",
"desc": "识别图片中的文字",
"author": "NewYearPrism",
"homepage": "https://github.com/NewYearPrism/nonebot-plugin-ocr",
"tags": [
{
"label": "ocr ",
"color": "#ea5252"
}
],
"is_official": false
}
]

View File

@@ -1,106 +0,0 @@
---
sidebar_position: 0
description: nonebot.dependencies 模块
---
# nonebot.dependencies
本模块模块实现了依赖注入的定义与处理。
## _abstract class_ `Param(default=PydanticUndefined, **kwargs)` {#Param}
- **说明**
依赖注入的基本单元 —— 参数。
继承自 `pydantic.fields.FieldInfo`,用于描述参数信息(不包括参数名)。
- **参数**
- `default` (Any)
- `**kwargs` (Any)
## _class_ `Dependent(*, call, pre_checkers=None, params=None, parameterless=None, allow_types=None)` {#Dependent}
- **说明**
依赖注入容器
- **参数**
- `call` ((\*Any, \*\*Any) -> Any): 依赖注入的可调用对象,可以是任何 Callable 对象
- `pre_checkers` (list[[Param](#Param)] | None): 依赖注入解析前的参数检查
- `params` (list[pydantic.fields.ModelField] | None): 具名参数列表
- `parameterless` (list[[Param](#Param)] | None): 匿名参数列表
- `allow_types` (list[Type[[Param](#Param)]] | None): 允许的参数类型
### _method_ `append_parameterless(self, value)` {#Dependent-append_parameterless}
- **参数**
- `value` (Any)
- **返回**
- None
### _classmethod_ `parse(cls, *, call, parameterless=None, allow_types=None)` {#Dependent-parse}
- **参数**
- `call` ((\*Any, \*\*Any) -> Any)
- `parameterless` (list[Any] | None)
- `allow_types` (list[Type[[Param](#Param)]] | None)
- **返回**
- (~ T)
### _method_ `parse_param(self, name, param)` {#Dependent-parse_param}
- **参数**
- `name` (str)
- `param` (inspect.Parameter)
- **返回**
- [Param](#Param)
### _method_ `parse_parameterless(self, value)` {#Dependent-parse_parameterless}
- **参数**
- `value` (Any)
- **返回**
- [Param](#Param)
### _method_ `prepend_parameterless(self, value)` {#Dependent-prepend_parameterless}
- **参数**
- `value` (Any)
- **返回**
- None
### _async method_ `solve(self, **params)` {#Dependent-solve}
- **参数**
- `**params` (Any)
- **返回**
- dict[str, Any]

View File

@@ -1,45 +0,0 @@
---
sidebar_position: 4
description: nonebot.plugin.export 模块
---
# nonebot.plugin.export
本模块定义了插件导出的内容对象。
在新版插件系统中,推荐优先使用直接 import 所需要的插件内容。
## _class_ `Export()` {#Export}
- **说明**
插件导出内容以使得其他插件可以获得。
- **用法**
```python
nonebot.export().default = "bar"
@nonebot.export()
def some_function():
pass
# this doesn't work before python 3.9
# use
# export = nonebot.export(); @export.sub
# instead
# See also PEP-614: https://www.python.org/dev/peps/pep-0614/
@nonebot.export().sub
def something_else():
pass
```
## _def_ `export()` {#export}
- **说明**
获取当前插件的导出内容对象
- **返回**
- [Export](#Export)

View File

@@ -8,7 +8,7 @@ slug: /
NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架,它基于 Python 的类型注解和异步特性,能够为你的需求实现提供便捷灵活的支持。
需要注意的是NoneBot2 仅支持 **Python 3.7.3 以上版本**
需要注意的是NoneBot2 仅支持 **Python 3.8 以上版本**
## 特色

View File

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

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