Repository: bot-ssttkkl/nonebot-plugin-pixivbot (language: python)

Issue:
添加pixiv链接嗅探功能



Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index b6bd1e8..7371091 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,53 @@ PixivBot中协议无关的通用部分。目前适配协议：
 
 开箱即用的Docker镜像：[ssttkkl/PixivBot](https://github.com/ssttkkl/PixivBot)
 
+## 触发语句
+
+### 普通语句
+
+所有数字参数均支持中文数字和罗马数字。
+
+- **看看<类型>榜<范围>**：查看pixiv榜单（<类型>可省略，<范围>应为a-b或a）
+  - 示例：看看榜、看看日榜、看看榜1-5、看看月榜一
+- **来<数量>张图**：从推荐插画随机抽选一张插画（<数量>可省略，下同）
+  - 示例：来张图、来五张图
+- **来<数量>张<关键字>图**：搜索关键字，从搜索结果随机抽选一张插画
+  - 示例：来张初音ミク图、来五张初音ミク图
+  - 注：默认开启关键字翻译功能。Bot会在平时的数据爬取时记录各个Tag的中文翻译。在搜索时，若关键字的日文翻译存在，则使用日文翻译代替关键字进行搜索。
+- **来<数量>张<用户>老师的图**：搜索用户，从插画列表中随机抽选一张插画
+  - 示例：来张Rella老师的图、来五张Rella老师的图
+- **看看图<插画ID>**：查看ID对应的插画
+  - 示例：看看图114514
+- **来<数量>张私家车**：从书签中随机抽选一张插画（发送者需绑定Pixiv账号，或者在配置中指定默认Pixiv账号）
+  - 示例：来张私家车、来五张私家车
+- **还要**：重复上一次请求
+- **不够色**：获取上一张插画的相关插画
+
+### 命令语句
+
+- **/pixivbot schedule \<type\> \<schedule\> [..args]**：为本群（本用户）订阅类型为<type>的定时推送功能，时间满足<schedule>时进行推送
+    - \<type\>：可选值有ranking, random_bookmark, random_recommended_illust, random_illust, random_user_illust
+    - \<schedule\>：有三种格式，*00:30\*x*为每隔30分钟进行一次推送，*12:00*为每天12:00进行一次推送，*00:10+00:30\*x*为从今天00:10开始每隔30分钟进行一次推送（开始时间若是一个过去的时间点，则从下一个开始推送的时间点进行推送）
+    - [..args]：
+      - \<type\>为ranking时，接受\<mode\> \<range\>
+      - \<type\>为random_bookmark时，接受\<pixiv_user_id\>（可空）
+      - \<type\>为random_illust时，接受\<word\>
+      - \<type\>为random_user_illust时，接受\<user\>
+- **/pixivbot schedule**：查看本群（本用户）的所有定时推送订阅
+- **/pixivbot unschedule <type>**：取消本群（本用户）的定时推送订阅
+- **/pixivbot watch \<type\> [..args]**：为本群（本用户）订阅类型为<type>的更新推送功能
+    - \<type\>：可选值有user_illusts, following_illusts
+    - [..args]：
+      - \<type\>为user_illusts时，接受\<pixiv_user_id\>/\<user\>
+      - \<type\>为following_illusts时，接受\<pixiv_user_id\>/\<user\>（可空）
+- **/pixivbot watch**：查看本群（本用户）的所有更新推送订阅
+- **/pixivbot unwatch \<type\> [..args]**：取消本群（本用户）的更新推送订阅
+- **/pixivbot bind \<pixiv_user_id\>**：绑定Pixiv账号（用于随机书签功能）
+- **/pixivbot unbind**：解绑Pixiv账号
+- **/pixivbot invalidate_cache**：清除缓存（只有超级用户能够发送此命令）
+- **/pixivbot**、**/pixivbot help**：查看帮助
+
+
 ## 环境配置
 
 事前准备：登录pixiv账号并获取refresh_token。（参考：[@ZipFile Pixiv OAuth Flow](https://gist.github.com/ZipFile/c9ebedb224406f4f11845ab700124362)）
@@ -41,6 +88,7 @@ pixiv_mongo_conn_url=  # MongoDB连接URL，格式：mongodb://<用户名>:<密
 pixiv_mongo_database_name=  # 连接的MongoDB数据库
 pixiv_proxy=None  # 代理URL
 pixiv_query_timeout=60  # 查询超时（单位：秒）
+pixiv_loading_prompt_delayed_time=5  # 加载提示消息的延迟时间（“努力加载中”的消息会在请求发出多少秒后发出）（单位：秒）
 pixiv_simultaneous_query=8  # 向Pixiv查询的并发数
 
 # 缓存过期时间（单位：秒）
diff --git a/docs/data_flow.drawio b/docs/data_flow.drawio
deleted file mode 100644
index 8bfb681..0000000
--- a/docs/data_flow.drawio
+++ /dev/null
@@ -1,1 +0,0 @@
-<mxfile host="Electron" modified="2022-07-06T15:58:38.807Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/19.0.3 Chrome/102.0.5005.63 Electron/19.0.3 Safari/537.36" etag="ySuIoO7BkunDIVxIqfuW" version="19.0.3" type="device"><diagram id="kTCxulU9FRBEiCpWeZAH" name="第 1 页">7VtLd+I2FP41WjbHsvBriYF0zumkkzMs2ulOsRVQIlscWxDSX18ZhI0lp1CDDZ3AJuZiX1nfd1+SbgAaJetfM7yYP/CYMGBb8RqgMbBtaFuB/FNI3rcSb+BsBbOMxuqmSjClfxMltJR0SWOS124UnDNBF3VhxNOURKImw1nG3+q3PXNWH3WBZ8QQTCPMTOkfNBbzrdS3vUr+hdDZfDcydNWEE7y7Wc0kn+OYv+2J0ASgUca52F4l6xFhBXg7XLbP3X/wa/liGUnFMQ/86b+M/xr9ePmOw/A359srfgnuf1FaVpgt1YTBxAPBAAx9MHGB74NQzTQX7ztM5EwWxeUyYcNI8AygcEUyQSVqX/ETYY88p4LyVN7yxIXgyd4NQ0ZnxQ+CL6R0LhImv0B5yZeC0ZSMSh4tKVRvJ58l6w+nDUswpRUSnhCRvctblMHZvsL/raIPKdF8jzlXybAymFmpqcJUXihY/wPEtgFxxJMEp7GBa8aXaUxiBcjbnAoyXeCo+PVNelcdr1OgUQ+Uc95BZZlQeX1ChQyopCkJidXFoUKoDhVyTaig3SdWAwOr33lKpL/ZFwcLonZ2VYbKs4PlGGB9kQ7ISHZxrFBwGKt+Dcs1U8JHCeCZkfWwSLISC5LG6nIcMZznNKpjVQFbBXYSG8n3IG57uDgNsOxkGWFY0FVdfRNWaoRHTuXAVWiEGi1ocOfUleR8mUVEPbefd3VVjq5KUyRwNiPCULQhr5x4ez69G58FfWfjE/mX5dO/8Skxt7V8HFjt2NQd3VDUMZuBweYjXdPVlGQrKrPOpdOT47VMT93l8h1hn9sBHPtsAc3RlwGmqo6dADYsQ1tTmsuXFX0zDa0GqkvhqVxrIcoK2lIN9bBpquqaanM5vAl4YyzwdDOLi8c8Ty/YriDmmSvjzxjztECF/NaOMNBV9Zz24a2KKzzNOlBKH0unZ5T3Tt9xzSzkPiOhA9NDPbclp9ZhXR2Tapul5neScEGuKFvBY3bbGtOV31W2ss1yboSj+VWjVm4QXA41szLaoPaAUzy7gp3KoK2ddVcW2ecsi36+dQO09CrHC+5cJ6g+XrvQDI11xAHFXcdp8zDkZgf7duAEd3vkBH7dkZEkz7Wqj9/WKuApw3RtI+YZUHsb+d8WaL6+eW1rpKB23Af6dtQBvV2TfTvEKkj5V38s6ulzJAPfO2WUrg3hnKdfP2FmMFpAZJB2/MptYdtcoB/QHFLctR2Y+y0PPJ3xcfihOUTvjEoyM3S4un7a0v71qRTg6HW2MYZv27YmJc+3/W3QOVejhV7gNaxi3F4XMeY2yKLY3r1LiTCQlvroIicFLjvMGV/GPfWoXN2yefdCt1h1XKwqFh21kNL2UMQ4TD6guONYhZqaMgcg9EEYgokD/OGmO9MHw/uiO3PighAC37QV6RyiTnkuMv5KRpwVPZvjlBdxKXymjGkirJo0I8k92W/v3HVvJjSOi2EaHbXJsE7sU3TqrtrQfNcU5VBnnmpu1UyjOYmX19BRprtJQ/9rv02dvZxdFZFrqhTyTMz5jKeYTSrp1VfreosSbH/MO9BOMGHPp1uoYT9GBq7hBATDIl7JRUDobkLZPQggmARAGqkPP08Eg0fWaS0imPxa/U/BltDqPzPQ5B8=</diagram></mxfile>
\ No newline at end of file
diff --git a/docs/data_flow.svg b/docs/data_flow.svg
deleted file mode 100644
index 58b6761..0000000
--- a/docs/data_flow.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Do not edit this file with editors other than diagrams.net -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1451px" height="341px" viewBox="-0.5 -0.5 1451 341" content="&lt;mxfile host=&quot;Electron&quot; modified=&quot;2022-07-06T15:58:30.264Z&quot; agent=&quot;5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/19.0.3 Chrome/102.0.5005.63 Electron/19.0.3 Safari/537.36&quot; etag=&quot;N_i86dUEEtQRIedjGkdN&quot; version=&quot;19.0.3&quot; type=&quot;device&quot;&gt;&lt;diagram id=&quot;kTCxulU9FRBEiCpWeZAH&quot; name=&quot;第 1 页&quot;&gt;7VtLd+I2FP41WjbHsvBriYF0zumkkzMs2ulOsRVQIlscWxDSX18ZhI0lp1CDDZ3AJuZiX1nfd1+SbgAaJetfM7yYP/CYMGBb8RqgMbBtaFuB/FNI3rcSb+BsBbOMxuqmSjClfxMltJR0SWOS124UnDNBF3VhxNOURKImw1nG3+q3PXNWH3WBZ8QQTCPMTOkfNBbzrdS3vUr+hdDZfDcydNWEE7y7Wc0kn+OYv+2J0ASgUca52F4l6xFhBXg7XLbP3X/wa/liGUnFMQ/86b+M/xr9ePmOw/A359srfgnuf1FaVpgt1YTBxAPBAAx9MHGB74NQzTQX7ztM5EwWxeUyYcNI8AygcEUyQSVqX/ETYY88p4LyVN7yxIXgyd4NQ0ZnxQ+CL6R0LhImv0B5yZeC0ZSMSh4tKVRvJ58l6w+nDUswpRUSnhCRvctblMHZvsL/raIPKdF8jzlXybAymFmpqcJUXihY/wPEtgFxxJMEp7GBa8aXaUxiBcjbnAoyXeCo+PVNelcdr1OgUQ+Uc95BZZlQeX1ChQyopCkJidXFoUKoDhVyTaig3SdWAwOr33lKpL/ZFwcLonZ2VYbKs4PlGGB9kQ7ISHZxrFBwGKt+Dcs1U8JHCeCZkfWwSLISC5LG6nIcMZznNKpjVQFbBXYSG8n3IG57uDgNsOxkGWFY0FVdfRNWaoRHTuXAVWiEGi1ocOfUleR8mUVEPbefd3VVjq5KUyRwNiPCULQhr5x4ez69G58FfWfjE/mX5dO/8Skxt7V8HFjt2NQd3VDUMZuBweYjXdPVlGQrKrPOpdOT47VMT93l8h1hn9sBHPtsAc3RlwGmqo6dADYsQ1tTmsuXFX0zDa0GqkvhqVxrIcoK2lIN9bBpquqaanM5vAl4YyzwdDOLi8c8Ty/YriDmmSvjzxjztECF/NaOMNBV9Zz24a2KKzzNOlBKH0unZ5T3Tt9xzSzkPiOhA9NDPbclp9ZhXR2Tapul5neScEGuKFvBY3bbGtOV31W2ss1yboSj+VWjVm4QXA41szLaoPaAUzy7gp3KoK2ddVcW2ecsi36+dQO09CrHC+5cJ6g+XrvQDI11xAHFXcdp8zDkZgf7duAEd3vkBH7dkZEkz7Wqj9/WKuApw3RtI+YZUHsb+d8WaL6+eW1rpKB23Af6dtQBvV2TfTvEKkj5V38s6ulzJAPfO2WUrg3hnKdfP2FmMFpAZJB2/MptYdtcoB/QHFLctR2Y+y0PPJ3xcfihOUTvjEoyM3S4un7a0v71qRTg6HW2MYZv27YmJc+3/W3QOVejhV7gNaxi3F4XMeY2yKLY3r1LiTCQlvroIicFLjvMGV/GPfWoXN2yefdCt1h1XKwqFh21kNL2UMQ4TD6guONYhZqaMgcg9EEYgokD/OGmO9MHw/uiO3PighAC37QV6RyiTnkuMv5KRpwVPZvjlBdxKXymjGkirJo0I8k92W/v3HVvJjSOi2EaHbXJsE7sU3TqrtrQfNcU5VBnnmpu1UyjOYmX19BRprtJQ/9rv02dvZxdFZFrqhTyTMz5jKeYTSrp1VfreosSbH/MO9BOMGHPp1uoYT9GBq7hBATDIl7JRUDobkLZPQggmARAGqkPP08Eg0fWaS0imPxa/U/BltDqPzPQ5B8=&lt;/diagram&gt;&lt;/mxfile&gt;" style="background-color: rgb(255, 255, 255);"><defs/><g><ellipse cx="15" cy="207.5" rx="7.5" ry="7.5" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><path d="M 15 215 L 15 240 M 15 220 L 0 220 M 15 220 L 30 220 M 15 240 L 0 260 M 15 240 L 30 260" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 267px; margin-left: 15px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">用户</div></div></div></foreignObject><text x="15" y="279" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">用户</text></switch></g><rect x="260" y="120" width="70" height="60" rx="9" ry="9" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 150px; margin-left: 261px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">command</div></div></div></foreignObject><text x="295" y="154" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">command</text></switch></g><rect x="330" y="280" width="120" height="60" rx="9" ry="9" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 310px; margin-left: 331px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">Postman</div></div></div></foreignObject><text x="390" y="314" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">Postman</text></switch></g><rect x="130" y="120" width="70" height="220" rx="10.5" ry="10.5" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 230px; margin-left: 131px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">Nonebot2</div></div></div></foreignObject><text x="165" y="234" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">Nonebot2</text></switch></g><rect x="390" y="120" width="120" height="60" rx="9" ry="9" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 150px; margin-left: 391px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">Handler</div></div></div></foreignObject><text x="450" y="154" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">Handler</text></switch></g><path d="M 210.44 159.51 L 210.56 149.51 L 230.56 149.76 L 230.7 139.26 L 249.5 154.99 L 230.31 170.26 L 230.44 159.76 Z" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 340.44 159.51 L 340.56 149.51 L 360.56 149.76 L 360.7 139.26 L 379.5 154.99 L 360.31 170.26 L 360.44 159.76 Z" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 319.5 305 L 319.5 315 L 229.5 315 L 229.5 325.5 L 210.5 310 L 229.5 294.5 L 229.5 305 Z" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="570" y="120" width="120" height="220" rx="18" ry="18" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 230px; margin-left: 571px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">PixivService</div></div></div></foreignObject><text x="630" y="234" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">PixivService</text></switch></g><path d="M 520.5 159.5 L 520.5 149.5 L 540.5 149.5 L 540.5 139 L 559.5 154.5 L 540.5 170 L 540.5 159.5 Z" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 69.5 234.5 L 69.5 245 L 50.5 229.5 L 69.5 214 L 69.5 224.5 L 100.5 224.5 L 100.5 214 L 119.5 229.5 L 100.5 245 L 100.5 234.5 Z" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="750" y="120" width="120" height="220" rx="18" ry="18" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 230px; margin-left: 751px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">PixivDataSource</div></div></div></foreignObject><text x="810" y="234" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">PixivDataSource</text></switch></g><path d="M 559.48 304.5 L 559.53 314.5 L 479.52 314.9 L 479.58 325.4 L 460.5 310 L 479.42 294.4 L 479.47 304.9 Z" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 700.44 160.01 L 700.56 150.01 L 720.56 150.26 L 720.7 139.76 L 739.5 155.49 L 720.31 170.76 L 720.44 160.26 Z" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 739.5 304.76 L 739.5 314.76 L 719.5 314.76 L 719.5 325.26 L 700.5 309.76 L 719.5 294.26 L 719.5 304.76 Z" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="1130" y="120" width="120" height="80" rx="12" ry="12" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 160px; margin-left: 1131px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">RemoteDataSource</div></div></div></foreignObject><text x="1190" y="164" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">RemoteDataSource</text></switch></g><rect x="1130" y="260" width="120" height="80" rx="12" ry="12" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 300px; margin-left: 1131px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">CacheDataSource</div></div></div></foreignObject><text x="1190" y="304" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">CacheDataSource</text></switch></g><rect x="930" y="120" width="120" height="220" rx="18" ry="18" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 230px; margin-left: 931px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">CacheManager</div></div></div></foreignObject><text x="990" y="234" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">CacheManager</text></switch></g><path d="M 1079.5 304.66 L 1079.5 315.16 L 1060.5 299.66 L 1079.5 284.16 L 1079.5 294.66 L 1100.5 294.66 L 1100.5 284.16 L 1119.5 299.66 L 1100.5 315.16 L 1100.5 304.66 Z" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 1079.5 164.66 L 1079.5 175.16 L 1060.5 159.66 L 1079.5 144.16 L 1079.5 154.66 L 1100.5 154.66 L 1100.5 144.16 L 1119.5 159.66 L 1100.5 175.16 L 1100.5 164.66 Z" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 880.5 154.66 L 880.5 144.66 L 900.5 144.66 L 900.5 134.16 L 919.5 149.66 L 900.5 165.16 L 900.5 154.66 Z" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 919.5 304.66 L 919.5 314.66 L 899.5 314.66 L 899.5 325.16 L 880.5 309.66 L 899.5 294.16 L 899.5 304.66 Z" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 1279.5 164.58 L 1279.5 175.08 L 1260.5 159.58 L 1279.5 144.08 L 1279.5 154.58 L 1300.5 154.58 L 1300.5 144.08 L 1319.5 159.58 L 1300.5 175.08 L 1300.5 164.58 Z" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 1360 275 C 1360 266.72 1373.43 260 1390 260 C 1397.96 260 1405.59 261.58 1411.21 264.39 C 1416.84 267.21 1420 271.02 1420 275 L 1420 325 C 1420 333.28 1406.57 340 1390 340 C 1373.43 340 1360 333.28 1360 325 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 1420 275 C 1420 283.28 1406.57 290 1390 290 C 1373.43 290 1360 283.28 1360 275" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 313px; margin-left: 1361px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">MongoDB</div></div></div></foreignObject><text x="1390" y="316" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">MongoDB</text></switch></g><path d="M 1360 140 C 1336 140 1330 160 1349.2 164 C 1330 172.8 1351.6 192 1367.2 184 C 1378 200 1414 200 1426 184 C 1450 184 1450 168 1435 160 C 1450 144 1426 128 1405 136 C 1390 124 1366 124 1360 140 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 160px; margin-left: 1331px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">pixiv.net</div></div></div></foreignObject><text x="1390" y="164" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">pixiv.net</text></switch></g><path d="M 1279.5 304.58 L 1279.5 315.08 L 1260.5 299.58 L 1279.5 284.08 L 1279.5 294.58 L 1300.5 294.58 L 1300.5 284.08 L 1319.5 299.58 L 1300.5 315.08 L 1300.5 304.58 Z" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="265" y="180" width="60" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 195px; margin-left: 266px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">主动请求</div></div></div></foreignObject><text x="295" y="199" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">主动请求</text></switch></g><rect x="260" y="0" width="70" height="60" rx="9" ry="9" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 30px; margin-left: 261px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">Scheduler</div></div></div></foreignObject><text x="295" y="34" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">Scheduler</text></switch></g><path d="M 340.5 34.5 L 340.5 24.5 L 455 24.5 L 455 90.5 L 465.5 90.5 L 450 109.5 L 434.5 90.5 L 445 90.5 L 445 34.5 Z" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="1.42" pointer-events="all"/><path d="M 455 90.5 L 465.5 90.5 L 450 109.5 L 434.5 90.5 L 445 90.5" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="4" pointer-events="all"/><rect x="265" y="60" width="60" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 75px; margin-left: 266px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">定时发送</div></div></div></foreignObject><text x="295" y="79" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">定时发送</text></switch></g></g><switch><g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/><a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Text is not SVG - cannot display</text></a></switch></svg>
\ No newline at end of file
diff --git a/docs/development_manual.md b/docs/development_manual.md
deleted file mode 100644
index 2d74909..0000000
--- a/docs/development_manual.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# 开发手册
-
-数据流图：
-
-![数据流图](data_flow.svg)
\ No newline at end of file
diff --git a/poetry.lock b/poetry.lock
index 6d92e2f..d28362d 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,14 +1,14 @@
 [[package]]
 name = "aiofiles"
-version = "0.8.0"
+version = "22.1.0"
 description = "File support for asyncio."
 category = "main"
 optional = false
-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 = false
@@ -52,7 +52,7 @@ frozenlist = ">=1.1.0"
 
 [[package]]
 name = "anyio"
-version = "3.6.1"
+version = "3.6.2"
 description = "High level compatibility layer for multiple asynchronous event loop implementations"
 category = "main"
 optional = false
@@ -65,7 +65,7 @@ sniffio = ">=1.1"
 [package.extras]
 doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
 test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"]
-trio = ["trio (>=0.16)"]
+trio = ["trio (>=0.16,<0.22)"]
 
 [[package]]
 name = "apscheduler"
@@ -158,6 +158,21 @@ docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"]
 tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"]
 tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
 
+[[package]]
+name = "beanie"
+version = "1.12.1"
+description = "Asynchronous Python ODM for MongoDB"
+category = "main"
+optional = false
+python-versions = ">=3.7,<4.0"
+
+[package.dependencies]
+click = ">=7"
+motor = ">=2.5,<4.0"
+pydantic = ">=1.9.0"
+toml = "*"
+yarl = ">=1.6"
+
 [[package]]
 name = "bleach"
 version = "5.0.1"
@@ -184,7 +199,7 @@ python-versions = "~=3.7"
 
 [[package]]
 name = "certifi"
-version = "2022.6.15"
+version = "2022.9.24"
 description = "Python package for providing Mozilla's CA Bundle."
 category = "dev"
 optional = false
@@ -242,9 +257,23 @@ python-versions = "*"
 [package.extras]
 test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"]
 
+[[package]]
+name = "coverage"
+version = "6.5.0"
+description = "Code coverage measurement for Python"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
+
+[package.extras]
+toml = ["tomli"]
+
 [[package]]
 name = "cryptography"
-version = "37.0.4"
+version = "38.0.1"
 description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
 category = "dev"
 optional = false
@@ -254,10 +283,10 @@ python-versions = ">=3.6"
 cffi = ">=1.12"
 
 [package.extras]
-docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx_rtd_theme"]
+docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
 docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"]
 pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
-sdist = ["setuptools_rust (>=0.11.4)"]
+sdist = ["setuptools-rust (>=0.11.4)"]
 ssh = ["bcrypt (>=3.1.5)"]
 test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"]
 
@@ -275,6 +304,22 @@ wrapt = ">=1.10,<2"
 [package.extras]
 dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version (<1)", "configparser (<5)", "importlib-metadata (<3)", "importlib-resources (<4)", "sphinx (<2)", "sphinxcontrib-websupport (<2)", "tox", "zipp (<2)"]
 
+[[package]]
+name = "dnspython"
+version = "2.2.1"
+description = "DNS toolkit"
+category = "main"
+optional = false
+python-versions = ">=3.6,<4.0"
+
+[package.extras]
+curio = ["curio (>=1.2,<2.0)", "sniffio (>=1.1,<2.0)"]
+dnssec = ["cryptography (>=2.6,<37.0)"]
+doh = ["h2 (>=4.1.0)", "httpx (>=0.21.1)", "requests (>=2.23.0,<3.0.0)", "requests-toolbelt (>=0.9.1,<0.10.0)"]
+idna = ["idna (>=2.1,<4.0)"]
+trio = ["trio (>=0.14,<0.20)"]
+wmi = ["wmi (>=1.5.1,<2.0.0)"]
+
 [[package]]
 name = "docutils"
 version = "0.19"
@@ -332,15 +377,15 @@ python-versions = ">=3.7"
 
 [[package]]
 name = "h11"
-version = "0.13.0"
+version = "0.14.0"
 description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
 category = "main"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 
 [[package]]
 name = "httptools"
-version = "0.4.0"
+version = "0.5.0"
 description = "A collection of framework independent HTTP protocol utils."
 category = "main"
 optional = false
@@ -351,7 +396,7 @@ test = ["Cython (>=0.29.24,<0.30.0)"]
 
 [[package]]
 name = "idna"
-version = "3.3"
+version = "3.4"
 description = "Internationalized Domain Names in Applications (IDNA)"
 category = "main"
 optional = false
@@ -359,7 +404,7 @@ python-versions = ">=3.5"
 
 [[package]]
 name = "importlib-metadata"
-version = "4.12.0"
+version = "5.0.0"
 description = "Read metadata from Python packages"
 category = "dev"
 optional = false
@@ -369,9 +414,9 @@ python-versions = ">=3.7"
 zipp = ">=0.5"
 
 [package.extras]
-docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"]
+docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"]
 perf = ["ipython"]
-testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
+testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
 
 [[package]]
 name = "iniconfig"
@@ -381,6 +426,21 @@ category = "dev"
 optional = false
 python-versions = "*"
 
+[[package]]
+name = "jaraco-classes"
+version = "3.2.3"
+description = "Utility functions for Python class constructs"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+more-itertools = "*"
+
+[package.extras]
+docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"]
+testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
+
 [[package]]
 name = "jeepney"
 version = "0.8.0"
@@ -395,7 +455,7 @@ trio = ["async_generator", "trio"]
 
 [[package]]
 name = "keyring"
-version = "23.8.2"
+version = "23.9.3"
 description = "Store and access your passwords safely."
 category = "dev"
 optional = false
@@ -403,6 +463,7 @@ python-versions = ">=3.7"
 
 [package.dependencies]
 importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""}
+"jaraco.classes" = "*"
 jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""}
 pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_platform == \"win32\""}
 SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""}
@@ -445,6 +506,14 @@ category = "dev"
 optional = false
 python-versions = ">=3.6"
 
+[[package]]
+name = "more-itertools"
+version = "9.0.0"
+description = "More routines for operating on iterables, beyond itertools"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
 [[package]]
 name = "motor"
 version = "3.0.0"
@@ -496,11 +565,11 @@ nonebot2 = ">=2.0.0-beta.1,<3.0.0"
 
 [[package]]
 name = "nonebot-adapter-onebot"
-version = "2.1.3"
+version = "2.1.5"
 description = "OneBot(CQHTTP) adapter for nonebot2"
 category = "main"
 optional = true
-python-versions = ">=3.7.3,<4.0.0"
+python-versions = ">=3.8,<4.0"
 
 [package.dependencies]
 msgpack = ">=1.0.3,<2.0.0"
@@ -508,15 +577,15 @@ nonebot2 = ">=2.0.0-beta.3,<3.0.0"
 
 [[package]]
 name = "nonebot-plugin-apscheduler"
-version = "0.1.4"
+version = "0.2.0"
 description = "APScheduler Support for NoneBot2"
 category = "main"
 optional = false
-python-versions = ">=3.7.3,<4.0.0"
+python-versions = ">=3.8,<4.0"
 
 [package.dependencies]
 apscheduler = ">=3.7.0,<4.0.0"
-nonebot2 = ">=2.0.0-alpha.8,<3.0.0"
+nonebot2 = ">=2.0.0-rc.1,<3.0.0"
 
 [[package]]
 name = "nonebot-plugin-pixivbot-kook"
@@ -534,7 +603,7 @@ nonebot2 = ">=2.0.0-beta.4,<3.0.0"
 
 [[package]]
 name = "nonebot-plugin-pixivbot-onebot-v11"
-version = "1.3.0"
+version = "1.3.1"
 description = "Nonebot Plugin PixivBot (Onebot V11)"
 category = "main"
 optional = true
@@ -546,11 +615,11 @@ nonebot2 = ">=2.0.0-beta.4,<3.0.0"
 
 [[package]]
 name = "nonebot2"
-version = "2.0.0b5"
+version = "2.0.0rc1"
 description = "An asynchronous python bot framework."
 category = "main"
 optional = false
-python-versions = ">=3.7.3,<4.0.0"
+python-versions = ">=3.8,<4.0"
 
 [package.dependencies]
 fastapi = ">=0.79.0,<0.80.0"
@@ -721,19 +790,21 @@ python-versions = "*"
 
 [[package]]
 name = "pymongo"
-version = "4.2.0"
+version = "4.3.2"
 description = "Python driver for MongoDB <http://www.mongodb.org>"
 category = "main"
 optional = false
 python-versions = ">=3.7"
 
+[package.dependencies]
+dnspython = ">=1.16.0,<3.0.0"
+
 [package.extras]
 aws = ["pymongo-auth-aws (<2.0.0)"]
 encryption = ["pymongocrypt (>=1.3.0,<2.0.0)"]
 gssapi = ["pykerberos"]
 ocsp = ["certifi", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"]
 snappy = ["python-snappy"]
-srv = ["dnspython (>=1.16.0,<3.0.0)"]
 zstd = ["zstandard"]
 
 [[package]]
@@ -782,6 +853,21 @@ pytest = ">=5.4.0"
 [package.extras]
 testing = ["coverage", "hypothesis (>=5.7.1)"]
 
+[[package]]
+name = "pytest-cov"
+version = "4.0.0"
+description = "Pytest plugin for measuring coverage."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+coverage = {version = ">=5.2.1", extras = ["toml"]}
+pytest = ">=4.6"
+
+[package.extras]
+testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
+
 [[package]]
 name = "pytest-mock"
 version = "3.10.0"
@@ -798,11 +884,11 @@ dev = ["pre-commit", "pytest-asyncio", "tox"]
 
 [[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)"]
@@ -826,7 +912,7 @@ trio = ["trio (>=0.16.0)"]
 
 [[package]]
 name = "pytz"
-version = "2022.2.1"
+version = "2022.5"
 description = "World timezone definitions, modern and historical"
 category = "main"
 optional = false
@@ -861,7 +947,7 @@ python-versions = ">=3.6"
 
 [[package]]
 name = "readme-renderer"
-version = "37.0"
+version = "37.2"
 description = "readme_renderer is a library for rendering \"readme\" descriptions for Warehouse"
 category = "dev"
 optional = false
@@ -895,11 +981,11 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
 
 [[package]]
 name = "requests-toolbelt"
-version = "0.9.1"
+version = "0.10.0"
 description = "A utility belt for advanced users of python-requests"
 category = "dev"
 optional = false
-python-versions = "*"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 
 [package.dependencies]
 requests = ">=2.0.1,<3.0.0"
@@ -917,7 +1003,7 @@ idna2008 = ["idna"]
 
 [[package]]
 name = "rich"
-version = "12.5.1"
+version = "12.6.0"
 description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
 category = "dev"
 optional = false
@@ -944,14 +1030,14 @@ jeepney = ">=0.6"
 
 [[package]]
 name = "setuptools"
-version = "65.3.0"
+version = "65.5.0"
 description = "Easily download, build, install, upgrade, and uninstall Python packages"
 category = "main"
 optional = false
 python-versions = ">=3.7"
 
 [package.extras]
-docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
+docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
 testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
 testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
 
@@ -965,11 +1051,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"
@@ -990,13 +1076,21 @@ full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"]
 name = "toml"
 version = "0.10.2"
 description = "Python Library for Tom's Obvious, Minimal Language"
-category = "dev"
+category = "main"
 optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
 
+[[package]]
+name = "tomli"
+version = "2.0.1"
+description = "A lil' TOML parser"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
 [[package]]
 name = "tomlkit"
-version = "0.11.4"
+version = "0.11.5"
 description = "Style preserving TOML library"
 category = "main"
 optional = false
@@ -1023,7 +1117,7 @@ urllib3 = ">=1.26.0"
 
 [[package]]
 name = "typing-extensions"
-version = "4.3.0"
+version = "4.4.0"
 description = "Backported and Experimental Type Hints for Python 3.7+"
 category = "main"
 optional = false
@@ -1031,7 +1125,7 @@ python-versions = ">=3.7"
 
 [[package]]
 name = "tzdata"
-version = "2022.2"
+version = "2022.5"
 description = "Provider of IANA time zone data"
 category = "main"
 optional = false
@@ -1068,7 +1162,7 @@ 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
@@ -1080,30 +1174,30 @@ 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\""}
+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 = ["PyYAML (>=5.1)", "colorama (>=0.4)", "httptools (>=0.4.0)", "python-dotenv (>=0.13)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.0)"]
+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)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=19.0.0,<19.1.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=3.6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
+dev = ["Cython (>=0.29.32,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=3.6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
 docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
-test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=19.0.0,<19.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"]
+test = ["Cython (>=0.29.32,<0.30.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"]
 
 [[package]]
 name = "watchfiles"
-version = "0.16.1"
+version = "0.18.0"
 description = "Simple, modern and high performance file watching and code reload in python."
 category = "main"
 optional = false
@@ -1161,15 +1255,15 @@ multidict = ">=4.0"
 
 [[package]]
 name = "zipp"
-version = "3.8.1"
+version = "3.9.0"
 description = "Backport of pathlib-compatible object wrapper for zip files"
 category = "dev"
 optional = false
 python-versions = ">=3.7"
 
 [package.extras]
-docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"]
-testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
+docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"]
+testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
 
 [extras]
 kook = ["nonebot-plugin-pixivbot-kook"]
@@ -1178,86 +1272,101 @@ onebot = ["nonebot-plugin-pixivbot-onebot-v11"]
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.9"
-content-hash = "69a7185358461ac7c1cee3baf880469868e533b42cf4458c00736a21a35583ed"
+content-hash = "e227cd26f578a2bafd469142543ac003c1918a6c0c12f998ed3176a60b4be61e"
 
 [metadata.files]
 aiofiles = [
-    {file = "aiofiles-0.8.0-py3-none-any.whl", hash = "sha256:7a973fc22b29e9962d0897805ace5856e6a566ab1f0c8e5c91ff6c866519c937"},
-    {file = "aiofiles-0.8.0.tar.gz", hash = "sha256:8334f23235248a3b2e83b2c3a78a22674f39969b96397126cc93664d9a901e59"},
+    {file = "aiofiles-22.1.0-py3-none-any.whl", hash = "sha256:1142fa8e80dbae46bb6339573ad4c8c0841358f79c6eb50a493dceca14621bad"},
+    {file = "aiofiles-22.1.0.tar.gz", hash = "sha256:9107f1ca0b2a5553987a94a3c9959fe5b491fdf731389aa5b7b1bd0733e32de6"},
 ]
 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"},
+    {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ba71c9b4dcbb16212f334126cc3d8beb6af377f6703d9dc2d9fb3874fd667ee9"},
+    {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d24b8bb40d5c61ef2d9b6a8f4528c2f17f1c5d2d31fed62ec860f6006142e83e"},
+    {file = "aiohttp-3.8.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f88df3a83cf9df566f171adba39d5bd52814ac0b94778d2448652fc77f9eb491"},
+    {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97decbb3372d4b69e4d4c8117f44632551c692bb1361b356a02b97b69e18a62"},
+    {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:309aa21c1d54b8ef0723181d430347d7452daaff93e8e2363db8e75c72c2fb2d"},
+    {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad5383a67514e8e76906a06741febd9126fc7c7ff0f599d6fcce3e82b80d026f"},
+    {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20acae4f268317bb975671e375493dbdbc67cddb5f6c71eebdb85b34444ac46b"},
+    {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05a3c31c6d7cd08c149e50dc7aa2568317f5844acd745621983380597f027a18"},
+    {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d6f76310355e9fae637c3162936e9504b4767d5c52ca268331e2756e54fd4ca5"},
+    {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:256deb4b29fe5e47893fa32e1de2d73c3afe7407738bd3c63829874661d4822d"},
+    {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:5c59fcd80b9049b49acd29bd3598cada4afc8d8d69bd4160cd613246912535d7"},
+    {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:059a91e88f2c00fe40aed9031b3606c3f311414f86a90d696dd982e7aec48142"},
+    {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2feebbb6074cdbd1ac276dbd737b40e890a1361b3cc30b74ac2f5e24aab41f7b"},
+    {file = "aiohttp-3.8.3-cp310-cp310-win32.whl", hash = "sha256:5bf651afd22d5f0c4be16cf39d0482ea494f5c88f03e75e5fef3a85177fecdeb"},
+    {file = "aiohttp-3.8.3-cp310-cp310-win_amd64.whl", hash = "sha256:653acc3880459f82a65e27bd6526e47ddf19e643457d36a2250b85b41a564715"},
+    {file = "aiohttp-3.8.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:86fc24e58ecb32aee09f864cb11bb91bc4c1086615001647dbfc4dc8c32f4008"},
+    {file = "aiohttp-3.8.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75e14eac916f024305db517e00a9252714fce0abcb10ad327fb6dcdc0d060f1d"},
+    {file = "aiohttp-3.8.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d1fde0f44029e02d02d3993ad55ce93ead9bb9b15c6b7ccd580f90bd7e3de476"},
+    {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ab94426ddb1ecc6a0b601d832d5d9d421820989b8caa929114811369673235c"},
+    {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89d2e02167fa95172c017732ed7725bc8523c598757f08d13c5acca308e1a061"},
+    {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02f9a2c72fc95d59b881cf38a4b2be9381b9527f9d328771e90f72ac76f31ad8"},
+    {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c7149272fb5834fc186328e2c1fa01dda3e1fa940ce18fded6d412e8f2cf76d"},
+    {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:512bd5ab136b8dc0ffe3fdf2dfb0c4b4f49c8577f6cae55dca862cd37a4564e2"},
+    {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7018ecc5fe97027214556afbc7c502fbd718d0740e87eb1217b17efd05b3d276"},
+    {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:88c70ed9da9963d5496d38320160e8eb7e5f1886f9290475a881db12f351ab5d"},
+    {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:da22885266bbfb3f78218dc40205fed2671909fbd0720aedba39b4515c038091"},
+    {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:e65bc19919c910127c06759a63747ebe14f386cda573d95bcc62b427ca1afc73"},
+    {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:08c78317e950e0762c2983f4dd58dc5e6c9ff75c8a0efeae299d363d439c8e34"},
+    {file = "aiohttp-3.8.3-cp311-cp311-win32.whl", hash = "sha256:45d88b016c849d74ebc6f2b6e8bc17cabf26e7e40c0661ddd8fae4c00f015697"},
+    {file = "aiohttp-3.8.3-cp311-cp311-win_amd64.whl", hash = "sha256:96372fc29471646b9b106ee918c8eeb4cca423fcbf9a34daa1b93767a88a2290"},
+    {file = "aiohttp-3.8.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c971bf3786b5fad82ce5ad570dc6ee420f5b12527157929e830f51c55dc8af77"},
+    {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff25f48fc8e623d95eca0670b8cc1469a83783c924a602e0fbd47363bb54aaca"},
+    {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e381581b37db1db7597b62a2e6b8b57c3deec95d93b6d6407c5b61ddc98aca6d"},
+    {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db19d60d846283ee275d0416e2a23493f4e6b6028825b51290ac05afc87a6f97"},
+    {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25892c92bee6d9449ffac82c2fe257f3a6f297792cdb18ad784737d61e7a9a85"},
+    {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:398701865e7a9565d49189f6c90868efaca21be65c725fc87fc305906be915da"},
+    {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4a4fbc769ea9b6bd97f4ad0b430a6807f92f0e5eb020f1e42ece59f3ecfc4585"},
+    {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:b29bfd650ed8e148f9c515474a6ef0ba1090b7a8faeee26b74a8ff3b33617502"},
+    {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:1e56b9cafcd6531bab5d9b2e890bb4937f4165109fe98e2b98ef0dcfcb06ee9d"},
+    {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ec40170327d4a404b0d91855d41bfe1fe4b699222b2b93e3d833a27330a87a6d"},
+    {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2df5f139233060578d8c2c975128fb231a89ca0a462b35d4b5fcf7c501ebdbe1"},
+    {file = "aiohttp-3.8.3-cp36-cp36m-win32.whl", hash = "sha256:f973157ffeab5459eefe7b97a804987876dd0a55570b8fa56b4e1954bf11329b"},
+    {file = "aiohttp-3.8.3-cp36-cp36m-win_amd64.whl", hash = "sha256:437399385f2abcd634865705bdc180c8314124b98299d54fe1d4c8990f2f9494"},
+    {file = "aiohttp-3.8.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:09e28f572b21642128ef31f4e8372adb6888846f32fecb288c8b0457597ba61a"},
+    {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f3553510abdbec67c043ca85727396ceed1272eef029b050677046d3387be8d"},
+    {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e168a7560b7c61342ae0412997b069753f27ac4862ec7867eff74f0fe4ea2ad9"},
+    {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db4c979b0b3e0fa7e9e69ecd11b2b3174c6963cebadeecfb7ad24532ffcdd11a"},
+    {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e164e0a98e92d06da343d17d4e9c4da4654f4a4588a20d6c73548a29f176abe2"},
+    {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8a78079d9a39ca9ca99a8b0ac2fdc0c4d25fc80c8a8a82e5c8211509c523363"},
+    {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:21b30885a63c3f4ff5b77a5d6caf008b037cb521a5f33eab445dc566f6d092cc"},
+    {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4b0f30372cef3fdc262f33d06e7b411cd59058ce9174ef159ad938c4a34a89da"},
+    {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:8135fa153a20d82ffb64f70a1b5c2738684afa197839b34cc3e3c72fa88d302c"},
+    {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:ad61a9639792fd790523ba072c0555cd6be5a0baf03a49a5dd8cfcf20d56df48"},
+    {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:978b046ca728073070e9abc074b6299ebf3501e8dee5e26efacb13cec2b2dea0"},
+    {file = "aiohttp-3.8.3-cp37-cp37m-win32.whl", hash = "sha256:0d2c6d8c6872df4a6ec37d2ede71eff62395b9e337b4e18efd2177de883a5033"},
+    {file = "aiohttp-3.8.3-cp37-cp37m-win_amd64.whl", hash = "sha256:21d69797eb951f155026651f7e9362877334508d39c2fc37bd04ff55b2007091"},
+    {file = "aiohttp-3.8.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ca9af5f8f5812d475c5259393f52d712f6d5f0d7fdad9acdb1107dd9e3cb7eb"},
+    {file = "aiohttp-3.8.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d90043c1882067f1bd26196d5d2db9aa6d268def3293ed5fb317e13c9413ea4"},
+    {file = "aiohttp-3.8.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d737fc67b9a970f3234754974531dc9afeea11c70791dcb7db53b0cf81b79784"},
+    {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebf909ea0a3fc9596e40d55d8000702a85e27fd578ff41a5500f68f20fd32e6c"},
+    {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5835f258ca9f7c455493a57ee707b76d2d9634d84d5d7f62e77be984ea80b849"},
+    {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da37dcfbf4b7f45d80ee386a5f81122501ec75672f475da34784196690762f4b"},
+    {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87f44875f2804bc0511a69ce44a9595d5944837a62caecc8490bbdb0e18b1342"},
+    {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:527b3b87b24844ea7865284aabfab08eb0faf599b385b03c2aa91fc6edd6e4b6"},
+    {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d5ba88df9aa5e2f806650fcbeedbe4f6e8736e92fc0e73b0400538fd25a4dd96"},
+    {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e7b8813be97cab8cb52b1375f41f8e6804f6507fe4660152e8ca5c48f0436017"},
+    {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:2dea10edfa1a54098703cb7acaa665c07b4e7568472a47f4e64e6319d3821ccf"},
+    {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:713d22cd9643ba9025d33c4af43943c7a1eb8547729228de18d3e02e278472b6"},
+    {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2d252771fc85e0cf8da0b823157962d70639e63cb9b578b1dec9868dd1f4f937"},
+    {file = "aiohttp-3.8.3-cp38-cp38-win32.whl", hash = "sha256:66bd5f950344fb2b3dbdd421aaa4e84f4411a1a13fca3aeb2bcbe667f80c9f76"},
+    {file = "aiohttp-3.8.3-cp38-cp38-win_amd64.whl", hash = "sha256:84b14f36e85295fe69c6b9789b51a0903b774046d5f7df538176516c3e422446"},
+    {file = "aiohttp-3.8.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16c121ba0b1ec2b44b73e3a8a171c4f999b33929cd2397124a8c7fcfc8cd9e06"},
+    {file = "aiohttp-3.8.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8d6aaa4e7155afaf994d7924eb290abbe81a6905b303d8cb61310a2aba1c68ba"},
+    {file = "aiohttp-3.8.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43046a319664a04b146f81b40e1545d4c8ac7b7dd04c47e40bf09f65f2437346"},
+    {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599418aaaf88a6d02a8c515e656f6faf3d10618d3dd95866eb4436520096c84b"},
+    {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92a2964319d359f494f16011e23434f6f8ef0434acd3cf154a6b7bec511e2fb7"},
+    {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73a4131962e6d91109bca6536416aa067cf6c4efb871975df734f8d2fd821b37"},
+    {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598adde339d2cf7d67beaccda3f2ce7c57b3b412702f29c946708f69cf8222aa"},
+    {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75880ed07be39beff1881d81e4a907cafb802f306efd6d2d15f2b3c69935f6fb"},
+    {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0239da9fbafd9ff82fd67c16704a7d1bccf0d107a300e790587ad05547681c8"},
+    {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4e3a23ec214e95c9fe85a58470b660efe6534b83e6cbe38b3ed52b053d7cb6ad"},
+    {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:47841407cc89a4b80b0c52276f3cc8138bbbfba4b179ee3acbd7d77ae33f7ac4"},
+    {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:54d107c89a3ebcd13228278d68f1436d3f33f2dd2af5415e3feaeb1156e1a62c"},
+    {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c37c5cce780349d4d51739ae682dec63573847a2a8dcb44381b174c3d9c8d403"},
+    {file = "aiohttp-3.8.3-cp39-cp39-win32.whl", hash = "sha256:f178d2aadf0166be4df834c4953da2d7eef24719e8aec9a65289483eeea9d618"},
+    {file = "aiohttp-3.8.3-cp39-cp39-win_amd64.whl", hash = "sha256:88e5be56c231981428f4f506c68b6a46fa25c4123a2e86d156c58a8369d31ab7"},
+    {file = "aiohttp-3.8.3.tar.gz", hash = "sha256:3828fb41b7203176b82fe5d699e0d845435f2374750a44b480ea6b930f6be269"},
 ]
 aiohttp-socks = [
     {file = "aiohttp_socks-0.7.1-py3-none-any.whl", hash = "sha256:94bcff5ef73611c6c6231c2ffc1be4af1599abec90dbd2fdbbd63233ec2fb0ff"},
@@ -1268,8 +1377,8 @@ aiosignal = [
     {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"},
 ]
 anyio = [
-    {file = "anyio-3.6.1-py3-none-any.whl", hash = "sha256:cb29b9c70620506a9a8f87a309591713446953302d7d995344d0d7c6c0c9a7be"},
-    {file = "anyio-3.6.1.tar.gz", hash = "sha256:413adf95f93886e442aea925f3ee43baa5a765a64a0f52c6081894f9992fdd0b"},
+    {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"},
+    {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"},
 ]
 apscheduler = [
     {file = "APScheduler-3.9.1-py2.py3-none-any.whl", hash = "sha256:ddc25a0ddd899de44d7f451f4375fb971887e65af51e41e5dcf681f59b8b2c9a"},
@@ -1296,6 +1405,10 @@ attrs = [
     {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"},
     {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"},
 ]
+beanie = [
+    {file = "beanie-1.12.1-py3-none-any.whl", hash = "sha256:3e6ad60eb4df439d6dfc5bb6725ecb9500efaefc39061c20b3840b93bb64d637"},
+    {file = "beanie-1.12.1.tar.gz", hash = "sha256:1c171104bf787eebdc43fc8c5171b064f03b7ca2ff2245a7c966556c965ede1e"},
+]
 bleach = [
     {file = "bleach-5.0.1-py3-none-any.whl", hash = "sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a"},
     {file = "bleach-5.0.1.tar.gz", hash = "sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c"},
@@ -1305,8 +1418,8 @@ cachetools = [
     {file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"},
 ]
 certifi = [
-    {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
-    {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
+    {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"},
+    {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"},
 ]
 cffi = [
     {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
@@ -1390,34 +1503,94 @@ commonmark = [
     {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"},
     {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"},
 ]
+coverage = [
+    {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"},
+    {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"},
+    {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"},
+    {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"},
+    {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"},
+    {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"},
+    {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"},
+    {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"},
+    {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"},
+    {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"},
+    {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"},
+    {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"},
+    {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"},
+    {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"},
+    {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"},
+    {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"},
+    {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"},
+    {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"},
+    {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"},
+    {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"},
+    {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"},
+    {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"},
+    {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"},
+    {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"},
+    {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"},
+    {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"},
+    {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"},
+    {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"},
+    {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"},
+    {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"},
+    {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"},
+    {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"},
+    {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"},
+    {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"},
+    {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"},
+    {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"},
+    {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"},
+    {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"},
+    {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"},
+    {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"},
+    {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"},
+    {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"},
+    {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"},
+    {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"},
+    {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"},
+    {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"},
+    {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"},
+    {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"},
+    {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"},
+    {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"},
+]
 cryptography = [
-    {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884"},
-    {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6"},
-    {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046"},
-    {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5"},
-    {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b"},
-    {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8"},
-    {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280"},
-    {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3"},
-    {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59"},
-    {file = "cryptography-37.0.4-cp36-abi3-win32.whl", hash = "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157"},
-    {file = "cryptography-37.0.4-cp36-abi3-win_amd64.whl", hash = "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327"},
-    {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b"},
-    {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"},
-    {file = "cryptography-37.0.4-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67"},
-    {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d"},
-    {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282"},
-    {file = "cryptography-37.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa"},
-    {file = "cryptography-37.0.4-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441"},
-    {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596"},
-    {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a"},
-    {file = "cryptography-37.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab"},
-    {file = "cryptography-37.0.4.tar.gz", hash = "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82"},
+    {file = "cryptography-38.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f"},
+    {file = "cryptography-38.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad"},
+    {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153"},
+    {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407"},
+    {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e"},
+    {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0"},
+    {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd"},
+    {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6"},
+    {file = "cryptography-38.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a"},
+    {file = "cryptography-38.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294"},
+    {file = "cryptography-38.0.1-cp36-abi3-win32.whl", hash = "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0"},
+    {file = "cryptography-38.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a"},
+    {file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d"},
+    {file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9"},
+    {file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d"},
+    {file = "cryptography-38.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818"},
+    {file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6"},
+    {file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750"},
+    {file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013"},
+    {file = "cryptography-38.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5"},
+    {file = "cryptography-38.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61"},
+    {file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac"},
+    {file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb"},
+    {file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a"},
+    {file = "cryptography-38.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b"},
+    {file = "cryptography-38.0.1.tar.gz", hash = "sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7"},
 ]
 deprecated = [
     {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"},
     {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"},
 ]
+dnspython = [
+    {file = "dnspython-2.2.1-py3-none-any.whl", hash = "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"},
+    {file = "dnspython-2.2.1.tar.gz", hash = "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e"},
+]
 docutils = [
     {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"},
     {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"},
@@ -1511,64 +1684,75 @@ frozenlist = [
     {file = "frozenlist-1.3.1.tar.gz", hash = "sha256:3a735e4211a04ccfa3f4833547acdf5d2f863bfeb01cfd3edaffbc251f15cec8"},
 ]
 h11 = [
-    {file = "h11-0.13.0-py3-none-any.whl", hash = "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442"},
-    {file = "h11-0.13.0.tar.gz", hash = "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06"},
+    {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
+    {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
 ]
 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"},
+    {file = "httptools-0.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8f470c79061599a126d74385623ff4744c4e0f4a0997a353a44923c0b561ee51"},
+    {file = "httptools-0.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e90491a4d77d0cb82e0e7a9cb35d86284c677402e4ce7ba6b448ccc7325c5421"},
+    {file = "httptools-0.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1d2357f791b12d86faced7b5736dea9ef4f5ecdc6c3f253e445ee82da579449"},
+    {file = "httptools-0.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f90cd6fd97c9a1b7fe9215e60c3bd97336742a0857f00a4cb31547bc22560c2"},
+    {file = "httptools-0.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5230a99e724a1bdbbf236a1b58d6e8504b912b0552721c7c6b8570925ee0ccde"},
+    {file = "httptools-0.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3a47a34f6015dd52c9eb629c0f5a8a5193e47bf2a12d9a3194d231eaf1bc451a"},
+    {file = "httptools-0.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:24bb4bb8ac3882f90aa95403a1cb48465de877e2d5298ad6ddcfdebec060787d"},
+    {file = "httptools-0.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e67d4f8734f8054d2c4858570cc4b233bf753f56e85217de4dfb2495904cf02e"},
+    {file = "httptools-0.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e5eefc58d20e4c2da82c78d91b2906f1a947ef42bd668db05f4ab4201a99f49"},
+    {file = "httptools-0.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0297822cea9f90a38df29f48e40b42ac3d48a28637368f3ec6d15eebefd182f9"},
+    {file = "httptools-0.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:557be7fbf2bfa4a2ec65192c254e151684545ebab45eca5d50477d562c40f986"},
+    {file = "httptools-0.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:54465401dbbec9a6a42cf737627fb0f014d50dc7365a6b6cd57753f151a86ff0"},
+    {file = "httptools-0.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4d9ebac23d2de960726ce45f49d70eb5466725c0087a078866043dad115f850f"},
+    {file = "httptools-0.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:e8a34e4c0ab7b1ca17b8763613783e2458e77938092c18ac919420ab8655c8c1"},
+    {file = "httptools-0.5.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f659d7a48401158c59933904040085c200b4be631cb5f23a7d561fbae593ec1f"},
+    {file = "httptools-0.5.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1616b3ba965cd68e6f759eeb5d34fbf596a79e84215eeceebf34ba3f61fdc7"},
+    {file = "httptools-0.5.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3625a55886257755cb15194efbf209584754e31d336e09e2ffe0685a76cb4b60"},
+    {file = "httptools-0.5.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:72ad589ba5e4a87e1d404cc1cb1b5780bfcb16e2aec957b88ce15fe879cc08ca"},
+    {file = "httptools-0.5.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:850fec36c48df5a790aa735417dca8ce7d4b48d59b3ebd6f83e88a8125cde324"},
+    {file = "httptools-0.5.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f222e1e9d3f13b68ff8a835574eda02e67277d51631d69d7cf7f8e07df678c86"},
+    {file = "httptools-0.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3cb8acf8f951363b617a8420768a9f249099b92e703c052f9a51b66342eea89b"},
+    {file = "httptools-0.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550059885dc9c19a072ca6d6735739d879be3b5959ec218ba3e013fd2255a11b"},
+    {file = "httptools-0.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a04fe458a4597aa559b79c7f48fe3dceabef0f69f562daf5c5e926b153817281"},
+    {file = "httptools-0.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d0c1044bce274ec6711f0770fd2d5544fe392591d204c68328e60a46f88843b"},
+    {file = "httptools-0.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c6eeefd4435055a8ebb6c5cc36111b8591c192c56a95b45fe2af22d9881eee25"},
+    {file = "httptools-0.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5b65be160adcd9de7a7e6413a4966665756e263f0d5ddeffde277ffeee0576a5"},
+    {file = "httptools-0.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fe9c766a0c35b7e3d6b6939393c8dfdd5da3ac5dec7f971ec9134f284c6c36d6"},
+    {file = "httptools-0.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85b392aba273566c3d5596a0a490978c085b79700814fb22bfd537d381dd230c"},
+    {file = "httptools-0.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5e3088f4ed33947e16fd865b8200f9cfae1144f41b64a8cf19b599508e096bc"},
+    {file = "httptools-0.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c2a56b6aad7cc8f5551d8e04ff5a319d203f9d870398b94702300de50190f63"},
+    {file = "httptools-0.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b571b281a19762adb3f48a7731f6842f920fa71108aff9be49888320ac3e24d"},
+    {file = "httptools-0.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa47ffcf70ba6f7848349b8a6f9b481ee0f7637931d91a9860a1838bfc586901"},
+    {file = "httptools-0.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:bede7ee075e54b9a5bde695b4fc8f569f30185891796b2e4e09e2226801d09bd"},
+    {file = "httptools-0.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:64eba6f168803a7469866a9c9b5263a7463fa8b7a25b35e547492aa7322036b6"},
+    {file = "httptools-0.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4b098e4bb1174096a93f48f6193e7d9aa7071506a5877da09a783509ca5fff42"},
+    {file = "httptools-0.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9423a2de923820c7e82e18980b937893f4aa8251c43684fa1772e341f6e06887"},
+    {file = "httptools-0.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca1b7becf7d9d3ccdbb2f038f665c0f4857e08e1d8481cbcc1a86a0afcfb62b2"},
+    {file = "httptools-0.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:50d4613025f15f4b11f1c54bbed4761c0020f7f921b95143ad6d58c151198142"},
+    {file = "httptools-0.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8ffce9d81c825ac1deaa13bc9694c0562e2840a48ba21cfc9f3b4c922c16f372"},
+    {file = "httptools-0.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:1af91b3650ce518d226466f30bbba5b6376dbd3ddb1b2be8b0658c6799dd450b"},
+    {file = "httptools-0.5.0.tar.gz", hash = "sha256:295874861c173f9101960bba332429bb77ed4dcd8cdf5cee9922eb00e4f6bc09"},
 ]
 idna = [
-    {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
-    {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
+    {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
+    {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
 ]
 importlib-metadata = [
-    {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"},
-    {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"},
+    {file = "importlib_metadata-5.0.0-py3-none-any.whl", hash = "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43"},
+    {file = "importlib_metadata-5.0.0.tar.gz", hash = "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab"},
 ]
 iniconfig = [
     {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
     {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
 ]
+jaraco-classes = [
+    {file = "jaraco.classes-3.2.3-py3-none-any.whl", hash = "sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158"},
+    {file = "jaraco.classes-3.2.3.tar.gz", hash = "sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a"},
+]
 jeepney = [
     {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"},
     {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"},
 ]
 keyring = [
-    {file = "keyring-23.8.2-py3-none-any.whl", hash = "sha256:10d2a8639663fe2090705a00b8c47c687cacdf97598ea9c11456679fa974473a"},
-    {file = "keyring-23.8.2.tar.gz", hash = "sha256:0d9973f8891850f1ade5f26aafd06bb16865fbbae3fc56b0defb6a14a2624003"},
+    {file = "keyring-23.9.3-py3-none-any.whl", hash = "sha256:69732a15cb1433bdfbc3b980a8a36a04878a6cfd7cb99f497b573f31618001c0"},
+    {file = "keyring-23.9.3.tar.gz", hash = "sha256:69b01dd83c42f590250fe7a1f503fc229b14de83857314b1933a3ddbf595c4a5"},
 ]
 lazy = [
     {file = "lazy-1.5-py2.py3-none-any.whl", hash = "sha256:7b8bc7e0136f96722bf9e41eec7472495059526bfa1195db61d43e8da6bbe604"},
@@ -1582,6 +1766,10 @@ mccabe = [
     {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
     {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
 ]
+more-itertools = [
+    {file = "more-itertools-9.0.0.tar.gz", hash = "sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab"},
+    {file = "more_itertools-9.0.0-py3-none-any.whl", hash = "sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41"},
+]
 motor = [
     {file = "motor-3.0.0-py3-none-any.whl", hash = "sha256:b076de44970f518177f0eeeda8b183f52eafa557775bfe3294e93bda18867a71"},
     {file = "motor-3.0.0.tar.gz", hash = "sha256:3e36d29406c151b61342e6a8fa5e90c00c4723b76e30f11276a4373ea2064b7d"},
@@ -1706,24 +1894,24 @@ nonebot-adapter-kaiheila = [
     {file = "nonebot_adapter_kaiheila-0.0.6-py3-none-any.whl", hash = "sha256:16fb79f17ded20be3c02c93782b03181a96b9bd79cec374d06866bbb9c360655"},
 ]
 nonebot-adapter-onebot = [
-    {file = "nonebot-adapter-onebot-2.1.3.tar.gz", hash = "sha256:2090847c58d0058604077b9404be78e6bee225d4972ab59f7223685f79bd5a86"},
-    {file = "nonebot_adapter_onebot-2.1.3-py3-none-any.whl", hash = "sha256:d44eb7b119fdcc3f7936c77e2c0922728c959ad6d6b36ebd004e94856d9ccf49"},
+    {file = "nonebot_adapter_onebot-2.1.5-py3-none-any.whl", hash = "sha256:d2a46694c402d5f3c5f6951955a78bbf779e17039d282c612cc1fcff77bc6044"},
+    {file = "nonebot_adapter_onebot-2.1.5.tar.gz", hash = "sha256:e740422cceebc1f0379da788ccddaa7937f7d88c140e2a5ea9ba0e91ddee867e"},
 ]
 nonebot-plugin-apscheduler = [
-    {file = "nonebot-plugin-apscheduler-0.1.4.tar.gz", hash = "sha256:7541cfb2af48f244b74b66fd14f6486ef39a6d79f17b15c31cf1c6ad9a9632ca"},
-    {file = "nonebot_plugin_apscheduler-0.1.4-py3-none-any.whl", hash = "sha256:c904c344c22c8fb2bafec9875ee195b7be7d1f0b758a04e96da6758d62cf4e83"},
+    {file = "nonebot-plugin-apscheduler-0.2.0.tar.gz", hash = "sha256:7b63e99a611b657533b48fcf1f8c6627c18c2eb3fa820a906cd4ec4666c0ceb0"},
+    {file = "nonebot_plugin_apscheduler-0.2.0-py3-none-any.whl", hash = "sha256:9285ee84ca1cfa4db73c86cedb5911bbbd25a21ec0cd5f22447cd12f89e48fb4"},
 ]
 nonebot-plugin-pixivbot-kook = [
     {file = "nonebot-plugin-pixivbot-kook-1.3.0.post2.tar.gz", hash = "sha256:20b5270cfaed42238eff9d75cb9ab77e64a2d1b16f40d91b995be13628545ef4"},
     {file = "nonebot_plugin_pixivbot_kook-1.3.0.post2-py3-none-any.whl", hash = "sha256:1e1e32398144e3ea03a24959f205435143585fac5910ed620a3fa83e3bda9705"},
 ]
 nonebot-plugin-pixivbot-onebot-v11 = [
-    {file = "nonebot-plugin-pixivbot-onebot-v11-1.3.0.tar.gz", hash = "sha256:271fa48fa6a98a503305e64f220ff62e319770abdc24401c52db1780789dd3a5"},
-    {file = "nonebot_plugin_pixivbot_onebot_v11-1.3.0-py3-none-any.whl", hash = "sha256:2eb027cf440195d08e54862a30f7ec496410c9e4a9e394a55425cc6821ed7d1c"},
+    {file = "nonebot_plugin_pixivbot_onebot_v11-1.3.1-py3-none-any.whl", hash = "sha256:ddd30a53fcea907c6a8968be0fd3e187e0a831e9f23ecfec89339d38c4bb2dc6"},
+    {file = "nonebot_plugin_pixivbot_onebot_v11-1.3.1.tar.gz", hash = "sha256:c1a2ecfe8b773ea2e67422f9300388aed8fe97d958d39bae93a061e2f01cdc22"},
 ]
 nonebot2 = [
-    {file = "nonebot2-2.0.0b5-py3-none-any.whl", hash = "sha256:5cfbfcd62ffcf55f884f9fb750a71cd92c97e347c99e891ef617da4d445a1f22"},
-    {file = "nonebot2-2.0.0b5.tar.gz", hash = "sha256:fccb5af749cceb289d756901b7abc7a679e5952dbe88c8b6ad8b2d906715c3ee"},
+    {file = "nonebot2-2.0.0rc1-py3-none-any.whl", hash = "sha256:99c70300b9d0755bba6ed50b7399f44f6213e119157bc026b960b3e157a38416"},
+    {file = "nonebot2-2.0.0rc1.tar.gz", hash = "sha256:6e5a0abf24086d05d5c2a56c971a551ece2a343ff29f83b76b398bf717b92e16"},
 ]
 nonebug = [
     {file = "nonebug-0.2.1-py3-none-any.whl", hash = "sha256:f4d59effd50e400ee866df57902e4d749227a76857be26a0607fc2a5f6a05f7c"},
@@ -1897,73 +2085,80 @@ pygtrie = [
     {file = "pygtrie-2.5.0.tar.gz", hash = "sha256:203514ad826eb403dab1d2e2ddd034e0d1534bbe4dbe0213bb0593f66beba4e2"},
 ]
 pymongo = [
-    {file = "pymongo-4.2.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:b9e4981a65f8500a3a46bb3a1e81b9feb45cf0b2115ad9c4f8d517326d026940"},
-    {file = "pymongo-4.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1c81414b706627f15e921e29ae2403aab52e33e36ed92ed989c602888d7c3b90"},
-    {file = "pymongo-4.2.0-cp310-cp310-manylinux1_i686.whl", hash = "sha256:c549bb519456ee230e92f415c5b4d962094caac0fdbcc4ed22b576f66169764e"},
-    {file = "pymongo-4.2.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:70216ec4c248213ae95ea499b6314c385ce01a5946c448fb22f6c8395806e740"},
-    {file = "pymongo-4.2.0-cp310-cp310-manylinux2014_i686.whl", hash = "sha256:8a86e8c2ac2ec87141e1c6cb00bdb18a4560f06e5f96769abcd1dda24dc0e764"},
-    {file = "pymongo-4.2.0-cp310-cp310-manylinux2014_ppc64le.whl", hash = "sha256:314b556afd72eb21a6a10bd1f45ef252509f014f80207db59c97372103c88237"},
-    {file = "pymongo-4.2.0-cp310-cp310-manylinux2014_s390x.whl", hash = "sha256:902e2c9030cb042c49750bc70d72d830d42c64ea0df5ff8630c171e065c93dd7"},
-    {file = "pymongo-4.2.0-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:c69ef5906dcd6ec565d4d887ba97ceb2a84f3b614307ee3b4780cb1ea40b1867"},
-    {file = "pymongo-4.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07564178ecc203a84f63e72972691af6c0c82d2dc0c9da66ba711695276089ba"},
-    {file = "pymongo-4.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f47d5f10922cf7f7dfcd1406bd0926cef6d866a75953c3745502dffd7ac197dd"},
-    {file = "pymongo-4.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4cadaaa5c19ad23fc84559e90284f2eb003c36958ebb2c06f286b678f441285f"},
-    {file = "pymongo-4.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d94f535df9f539615bc3dbbef185ded3b609373bb44ca1afffcabac70202678a"},
-    {file = "pymongo-4.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:147a23cd96feb67606ac957744d8d25b013426cdc3c7164a4f99bd8253f649e3"},
-    {file = "pymongo-4.2.0-cp310-cp310-win32.whl", hash = "sha256:ecdcb0d4e9b08b739035f57a09330efc6f464bd7f942b63897395d996ca6ebd5"},
-    {file = "pymongo-4.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:8c223aea52c359cc8fdee5bd3475532590755c269ec4d4fe581acd47a44e9952"},
-    {file = "pymongo-4.2.0-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:fe0820d169635e41c14a5d21514282e0b93347878666ec9d5d3bf0eed0649948"},
-    {file = "pymongo-4.2.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e39cacee70a98758f9b2da53ee175378f07c60113b1fa4fae40cbaee5583181e"},
-    {file = "pymongo-4.2.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:701d331060dae72bf3ebdb82924405d14136a69282ccb00c89fc69dee21340b4"},
-    {file = "pymongo-4.2.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e08fe1731f5429435b8dea1db9663f9ed1812915ff803fc9991c7c4841ed62ad"},
-    {file = "pymongo-4.2.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:60c470a58c5b62b1b12a5f5458f8e2f2f67b94e198d03dc5352f854d9230c394"},
-    {file = "pymongo-4.2.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:b211e161b6cc2790e0d640ad38e0429d06c944e5da23410f4dc61809dba25095"},
-    {file = "pymongo-4.2.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:ed90a9de4431cbfb2f3b2ef0c5fd356e61c85117b2be4db3eae28cb409f6e2d5"},
-    {file = "pymongo-4.2.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:68e1e49a5675748233f7b05330f092582cd52f2850b4244939fd75ba640593ed"},
-    {file = "pymongo-4.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:764fc15418d94bce5c2f8ebdbf66544f96f42efb1364b61e715e5b33281b388d"},
-    {file = "pymongo-4.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e64442aba81ed4df1ca494b87bf818569a1280acaa73071c68014f7a884e83f1"},
-    {file = "pymongo-4.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:83168126ae2457d1a19b2af665cafa7ef78c2dcff192d7d7b5dad6b36c73ae24"},
-    {file = "pymongo-4.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69d0180bca594e81cdb4a2af328bdb4046f59e10aaeef7619496fe64f2ec918c"},
-    {file = "pymongo-4.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80cbf0b043061451660099fff9001a7faacb2c9c983842b4819526e2f944dc6c"},
-    {file = "pymongo-4.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e1b8f5e2f9637492b0da4d51f78ecb17786e61d6c461ead8542c944750faf4f9"},
-    {file = "pymongo-4.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1a957cdc2b26eeed4d8f1889a40c6023dd1bd94672dd0f5ce327314f2caaefd4"},
-    {file = "pymongo-4.2.0-cp37-cp37m-win32.whl", hash = "sha256:6bd5888997ea3eae9830c6cc7964b61dcfbc50eb3a5a6ce56ad5f86d5579b11c"},
-    {file = "pymongo-4.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:dc24737d24ce0de762bee9c2a884639819485f679bbac8ab5be9c161ef6f9b2c"},
-    {file = "pymongo-4.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:193cc97d44b1e6d2253ea94e30c6f94f994efb7166e2452af4df55825266e88b"},
-    {file = "pymongo-4.2.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e152c26ffc30331e9d57591fc4c05453c209aa20ba299d1deb7173f7d1958c22"},
-    {file = "pymongo-4.2.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8a9bc4dcfc2bda69ee88cdb7a89b03f2b8eca668519b704384a264dea2db4209"},
-    {file = "pymongo-4.2.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8cbb868e88c4eee1c53364bb343d226a3c0e959e791e6828030cb78f46cfcbe3"},
-    {file = "pymongo-4.2.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:2bfe6b59f431f40fa545547616f4acf0c0c4b64518b1f951083e3bad06eb368b"},
-    {file = "pymongo-4.2.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:ff66014687598823b6b23751884b4aa67eb934445406d95894dfc60cb7bfcc18"},
-    {file = "pymongo-4.2.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:31c50da4a080166bc29403aa91f4c76e0889b4f24928d1b60508a37c1bf87f9a"},
-    {file = "pymongo-4.2.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ccfdc7722df445c49dc6b5d514c3544cad99b53189165f7546793933050ac7fb"},
-    {file = "pymongo-4.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc7ebc37b03956a070260665079665eae69e5e96007694214f3a2107af96816a"},
-    {file = "pymongo-4.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8b4a782aac43948308087b962c9ecb030ba98886ce6dee3ad7aafe8c5e1ce80"},
-    {file = "pymongo-4.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1c23527f8e13f526fededbb96f2e7888f179fe27c51d41c2724f7059b75b2fa"},
-    {file = "pymongo-4.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83cc3c35aeeceb67143914db67f685206e1aa37ea837d872f4bc28d7f80917c9"},
-    {file = "pymongo-4.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e09cdf5aad507c8faa30d97884cc42932ed3a9c2b7f22cc3ccc607bae03981b3"},
-    {file = "pymongo-4.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0f53253f4777cbccc426e669a2af875f26c95bd090d88593287b9a0a8ac7fa25"},
-    {file = "pymongo-4.2.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:21238b19243a42f9a34a6d39e7580ceebc6da6d2f3cf729c1cff9023cb61a5f1"},
-    {file = "pymongo-4.2.0-cp38-cp38-win32.whl", hash = "sha256:766acb5b1a19eae0f7467bcd3398748f110ea5309cdfc59faa5185dcc7fd4dca"},
-    {file = "pymongo-4.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:10f09c4f09757c2e2a707ad7304f5d69cb8fdf7cbfb644dbacfe5bbe8afe311b"},
-    {file = "pymongo-4.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a6bf01b9237f794fa3bdad5089474067d28be7e199b356a18d3f247a45775f26"},
-    {file = "pymongo-4.2.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d8bb745321716e7a11220a67c88212ecedde4021e1de4802e563baef9df921d2"},
-    {file = "pymongo-4.2.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3be53e9888e759c49ae35d747ff77a04ff82b894dd64601e0f3a5a159b406245"},
-    {file = "pymongo-4.2.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:a3efdf154844244e0dabe902cf1827fdced55fa5b144adec2a86e5ce50a99b97"},
-    {file = "pymongo-4.2.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:a7eb5b06744b911b6668b427c8abc71b6d624e72d3dfffed00988fa1b4340f97"},
-    {file = "pymongo-4.2.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:b0be613d926c5dbb0d3fc6b58e4f2be4979f80ae76fda6e47309f011b388fe0c"},
-    {file = "pymongo-4.2.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:e7dcb73f683c155885a3488646fcead3a895765fed16e93c9b80000bc69e96cb"},
-    {file = "pymongo-4.2.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:b537dd282de1b53d9ae7cf9f3df36420c8618390f2da92100391f3ba8f3c141a"},
-    {file = "pymongo-4.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d98d2a8283c9928a9e5adf2f3c0181e095579e9732e1613aaa55d386e2bcb6c5"},
-    {file = "pymongo-4.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76892bbce743eb9f90360b3626ea92f13d338010a1004b4488e79e555b339921"},
-    {file = "pymongo-4.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:124d0e880b66f9b0778613198e89984984fdd37a3030a9007e5f459a42dfa2d3"},
-    {file = "pymongo-4.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:773467d25c293f8e981b092361dab5fd800e1ba318403b7959d35004c67faedc"},
-    {file = "pymongo-4.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6673ab3fbf3135cc1a8c0f70d480db5b2378c3a70af8d602f73f76b8338bdf97"},
-    {file = "pymongo-4.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:153b8f8705970756226dfeeb7bb9637e0ad54a4d79b480b4c8244e34e16e1662"},
-    {file = "pymongo-4.2.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:01721da74558f2f64a9f162ee063df403ed656b7d84229268d8e4ae99cfba59c"},
-    {file = "pymongo-4.2.0-cp39-cp39-win32.whl", hash = "sha256:a25c0eb2d610b20e276e684be61c337396813b636b69373c17314283cb1a3b14"},
-    {file = "pymongo-4.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:44b36ccb90aac5ea50be23c1a6e8f24fbfc78afabdef114af16c6e0a80981364"},
-    {file = "pymongo-4.2.0.tar.gz", hash = "sha256:72f338f6aabd37d343bd9d1fdd3de921104d395766bcc5cdc4039e4c2dd97766"},
+    {file = "pymongo-4.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:68320e5326e2b1e49dcd901e6dcbe3009b8a0fd0da0c618579a2be7cf5f2d7be"},
+    {file = "pymongo-4.3.2-cp310-cp310-manylinux1_i686.whl", hash = "sha256:3f41781c8310fe1ae3ed0b809e2d7be6ebba9f0954c08e1d18ac443916b82b29"},
+    {file = "pymongo-4.3.2-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:372307185d8e17ea31d2f3ff6943e213a6c379ccf547f18b05a58a1620d6f92a"},
+    {file = "pymongo-4.3.2-cp310-cp310-manylinux2014_i686.whl", hash = "sha256:1be15568e4b2be4c75bc54a542276c857628e09cbc283befcf4c45a0a22c1eec"},
+    {file = "pymongo-4.3.2-cp310-cp310-manylinux2014_ppc64le.whl", hash = "sha256:cad31512e6956c95210fbd585d5b80df28425251260387164c6382894f0c6eca"},
+    {file = "pymongo-4.3.2-cp310-cp310-manylinux2014_s390x.whl", hash = "sha256:b510843ea70e5bc9c096a93f683b28e8d43f1ad89da0126502d88b3d90f07ebe"},
+    {file = "pymongo-4.3.2-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:253faefea46482ffa87c77fdd01cd95d430cc84aae8d7a78ba920ea6cebcf3c7"},
+    {file = "pymongo-4.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a06c9ca15a2133478d1c775c4e7e5e782961b6254a3fc81ab5d0fb3cf9b8e358"},
+    {file = "pymongo-4.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5231eb29e8174509250bc5fc609d6e8eceebfb209bf37bd6e014cbd7b6554344"},
+    {file = "pymongo-4.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:178ffaa833d473b16fbd65c4a485af56484a50e2a201e8d0547f98cf5007f133"},
+    {file = "pymongo-4.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f41c4e3b9e315655d6d1136d904ceda24fe5ea2d273ec6f9d66dbef06f3446"},
+    {file = "pymongo-4.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:352bc034e112c9f6a408e2796e74bae900d3167a804224b2c24ea75b5d57e9f9"},
+    {file = "pymongo-4.3.2-cp310-cp310-win32.whl", hash = "sha256:28ab644adc92c21a249570e2d677ebf4f2ef374630ddec98f19d2630dcb154c6"},
+    {file = "pymongo-4.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:6049927b50c39e7dc51e75b5bb30c8501fbf1f08414b3447bcc9f9f967c116ed"},
+    {file = "pymongo-4.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:77436db17ab2baec2356cf38db32d13c7cd11267c8137864c67391f2dfdcc5e4"},
+    {file = "pymongo-4.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cefd851fdea191fc4db780157a28a11e0a80bccd34c454a73f252a287d28b2c7"},
+    {file = "pymongo-4.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c22ad464688a807bec103734cbdf712489c74d439cdd346e6f12095070bfbf5"},
+    {file = "pymongo-4.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db94b741dde2cc44ec038495d041c8f6dd4d510bb4e5d0be1b9f9aae4fbb28c6"},
+    {file = "pymongo-4.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21e1cfa3e73cd253afcad32e2a46a277f52553635ccc0dd4d643f5824af88428"},
+    {file = "pymongo-4.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5db3bddbbc2657aa76088e76d24a616aefc98883c48dc27f3c3829ddb2ca10d"},
+    {file = "pymongo-4.3.2-cp311-cp311-win32.whl", hash = "sha256:006799ddba1f2e73ce27689f016791ab80e51876c52ae2265d8c76016baaa10e"},
+    {file = "pymongo-4.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:0c8f061eabef3a6b3696f7f7be3eaed7928864ff84a2248429f9c7eb564343cc"},
+    {file = "pymongo-4.3.2-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:80bdfc7039674c670e1afbf95849ce2075731785527eeac7e3850e862dec239b"},
+    {file = "pymongo-4.3.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:8d81f6f5f6e66481aadd2fc087a937833312de23cd94b5ea1b225f35fafb0a00"},
+    {file = "pymongo-4.3.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:02140c1a9f2107a16c074c9e558a556faafb0dc3c2e9332c6685c5506823ab9d"},
+    {file = "pymongo-4.3.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b4b683a40cf07b6d16704ead92a7aee24208d3af83d55d31248cdac003f8591c"},
+    {file = "pymongo-4.3.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:dab1d89f969046057be2b904a7bbf40df114f43aebfb3ccdceb054d9c40ec56d"},
+    {file = "pymongo-4.3.2-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:0f48c2562a1d1426b6db7567511dc62817df43357041e1fd4ea5c68278bfa11b"},
+    {file = "pymongo-4.3.2-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:ce14598b8fa93e51aed0f400e446fddd6b26297ba5965fd0c0585614b60b9fc0"},
+    {file = "pymongo-4.3.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:af46f635513c7339419374f46f4f662cee7140bfb86de4377885a2c1de2278d4"},
+    {file = "pymongo-4.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96483316a799923f13bb61170f05feab22e8bd8630bf8cdcd440c78f307039a"},
+    {file = "pymongo-4.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:07f58d05d2289f93e16ddc93be6e0453fa67afd33c1b015f6bd3d9741c0963ff"},
+    {file = "pymongo-4.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8817b17db2013354aa7f187d5825d65da0d7720b5ca697af37ff5efdf97e7f62"},
+    {file = "pymongo-4.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36e7a74bdab9aa19f5ac94dfd74111d2164ccea752afbef0aa039d1266e7c404"},
+    {file = "pymongo-4.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6db95d3e955aa5dbe42db691dd77cdddc0bc15f9883aa1def51f3ca40d49c1d6"},
+    {file = "pymongo-4.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aee8fafea8bb669deb0dd4878d947f79b2ef298e60f06e1fe799598929b68be2"},
+    {file = "pymongo-4.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cea32bd14d8c0725e22e5fcc607a81e3636650c689697c12423a34f9a125c7e2"},
+    {file = "pymongo-4.3.2-cp37-cp37m-win32.whl", hash = "sha256:3966dcba4b80dbc0eb4dd08d6f7127e3b1701cd829b6c13507a956c878b78546"},
+    {file = "pymongo-4.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6461d29a967e1980ba7798e4da8178dbe4245fe4a66ebb3aa07339c9da383c3a"},
+    {file = "pymongo-4.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36da8aeb95cc1abea7b80e578fb6bcdbe395638d16b1b0068bc121e2111a00f"},
+    {file = "pymongo-4.3.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:315fe5f628e9aee67cc4c17b91ddf08c5c0917b764f433a5acf9aed33164a8f0"},
+    {file = "pymongo-4.3.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:07e05784578bf7f8ecdfc6d0fd1e684e6259e9b5fdb5439a58c4f0df950fae29"},
+    {file = "pymongo-4.3.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f84b8428a41d7d7f2931762c27b09ffa8b3bc51e3b5dab40ab2b1d008091247e"},
+    {file = "pymongo-4.3.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:39308580bcdbc368a2664c48761226c06b1d3368cc3ab3492d3cca88dc2e5e27"},
+    {file = "pymongo-4.3.2-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:7f907daec92208d748db4ea04568aa33e9254e0c27e4e40ac287e1b1ca8b12b5"},
+    {file = "pymongo-4.3.2-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:cb47ba9c19da8fb4174f9d7bbbdb1796ad288c61dda35c96fb45d69e61d3a5cb"},
+    {file = "pymongo-4.3.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:0d7ad2112a705e992ca0cca98ccbb874276c495f8d9df627438c2ee94f810a3d"},
+    {file = "pymongo-4.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4082d1b660e70d9df71da00050f7adb902b73a2287216e69ada124bd2f89636"},
+    {file = "pymongo-4.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26dd79e60f883b6467b91c8af0be484147365b18cebf9248f8e72c035aecb693"},
+    {file = "pymongo-4.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:715ad027daff84e213ab74fa3ec98cad8dabb669653a71daa0dd6f80a1c32dd0"},
+    {file = "pymongo-4.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ceded83530f5507dadd873f8d004b56f996de44d9c3f56b7f26c22ca823f12ee"},
+    {file = "pymongo-4.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f968621d019ed165f1cb5b037875ce3425ea7704407234895c7c52ad32190da"},
+    {file = "pymongo-4.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4f7763c9e37e6d59406ce2defc25266980b24a86708ec6db753b02459db45715"},
+    {file = "pymongo-4.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f3861081540e1f06d1e5d131d1419b9fc507834b6865407e0f56735b4082566c"},
+    {file = "pymongo-4.3.2-cp38-cp38-win32.whl", hash = "sha256:7424b7c59b16e7889a720a5b2e2dda518753c6fec6c6582ab2fcedf97df3df75"},
+    {file = "pymongo-4.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:64b010681019c0b312f342e3aae1f3091a7dc7ff4b7a3dec72fc0e7238be9477"},
+    {file = "pymongo-4.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cc7b269af274ac0d5d9a5c8d035b03ccc34438baa01705bf8ec7cc6a31093ace"},
+    {file = "pymongo-4.3.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:68213f4c1531b95dcfef40f79dd95e94484f69ec5949b7f42f82ad2bee135f7f"},
+    {file = "pymongo-4.3.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:11630f5b3287375c85f5b7a788d3a7241671af24fda2b49a3396bc53cbf1c0c6"},
+    {file = "pymongo-4.3.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:35e9eec45a212306143367b0702c2aff75c375290015af00fa8b653641c20b34"},
+    {file = "pymongo-4.3.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:94639935caf13af551429bd13e4cb20e7c110a57d07f0c6a84a9bf3c2c9000ad"},
+    {file = "pymongo-4.3.2-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:7ce5d43c011e03cd1a42a4dcc0d5c8772f18533cdfe672a63607942d62581df4"},
+    {file = "pymongo-4.3.2-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:98fd65c2aee7a55615dda1a1b0340ae8d756151983cb5040ea59a730083221e7"},
+    {file = "pymongo-4.3.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:c8e82d6cc2f1cf5017485f55d67375bacf73d95c40903759e46024a987bab86f"},
+    {file = "pymongo-4.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab7f49c5ca3db7ae94743b0da1b21c5e7402a561a0614c1b0fba718aad591611"},
+    {file = "pymongo-4.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:404bc7f7190e8975f41f0c7498e303e9cb291f6384e1889ac4333448652a83d6"},
+    {file = "pymongo-4.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c821c897498e3e3c3254f7a90195f71473361f502201fd396281869d8108857"},
+    {file = "pymongo-4.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6498ae9a76ad64617703373a43e3cd8454271bca0d7d395b393b4f31aa68f734"},
+    {file = "pymongo-4.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b2a8b2b7d9196d46e5181f88632eeca5bf79a69ca2e9911229c58f66aebfbeb"},
+    {file = "pymongo-4.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f423e066de040f4f93dcac0e6ceec37ffc25cc591a609ecc3ab20adfdbb787ae"},
+    {file = "pymongo-4.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:04597a5d877a984b5e3059e942b02d68f8af9bb4328592abca27e82015560112"},
+    {file = "pymongo-4.3.2-cp39-cp39-win32.whl", hash = "sha256:53dd2c034fb92c019e5e581cd361ed3fa9833abb56cc76725d56dcba169746fe"},
+    {file = "pymongo-4.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:d7bdfac2f3c87d0971691f2a091427f55bb6b94b23d74213ed2de87d8facba85"},
+    {file = "pymongo-4.3.2.tar.gz", hash = "sha256:95913659d6c5fc714e662533d014836c988cc1561684f07b6a0a8343651afa66"},
 ]
 pyparsing = [
     {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
@@ -1977,21 +2172,25 @@ pytest-asyncio = [
     {file = "pytest-asyncio-0.16.0.tar.gz", hash = "sha256:7496c5977ce88c34379df64a66459fe395cd05543f0a2f837016e7144391fcfb"},
     {file = "pytest_asyncio-0.16.0-py3-none-any.whl", hash = "sha256:5f2a21273c47b331ae6aa5b36087047b4899e40f03f18397c0e65fa5cca54e9b"},
 ]
+pytest-cov = [
+    {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"},
+    {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"},
+]
 pytest-mock = [
     {file = "pytest-mock-3.10.0.tar.gz", hash = "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"},
     {file = "pytest_mock-3.10.0-py3-none-any.whl", hash = "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b"},
 ]
 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"},
+    {file = "python-dotenv-0.21.0.tar.gz", hash = "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"},
+    {file = "python_dotenv-0.21.0-py3-none-any.whl", hash = "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5"},
 ]
 python-socks = [
     {file = "python-socks-2.0.3.tar.gz", hash = "sha256:e3a9ca8e554733862ce4d8ce1d10efb480fd3a3acdafd03393943ec00c98ba8a"},
     {file = "python_socks-2.0.3-py3-none-any.whl", hash = "sha256:950723f27d2cf401e193a9e0a0d45baab848341298f5b397d27fda0c4635e9a9"},
 ]
 pytz = [
-    {file = "pytz-2022.2.1-py2.py3-none-any.whl", hash = "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197"},
-    {file = "pytz-2022.2.1.tar.gz", hash = "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"},
+    {file = "pytz-2022.5-py2.py3-none-any.whl", hash = "sha256:335ab46900b1465e714b4fda4963d87363264eb662aab5e65da039c25f1f5b22"},
+    {file = "pytz-2022.5.tar.gz", hash = "sha256:c4d88f472f54d615e9cd582a5004d1e5f624854a6a27a6211591c251f22a6914"},
 ]
 pytz-deprecation-shim = [
     {file = "pytz_deprecation_shim-0.1.0.post0-py2.py3-none-any.whl", hash = "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6"},
@@ -2044,40 +2243,40 @@ pyyaml = [
     {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
 ]
 readme-renderer = [
-    {file = "readme_renderer-37.0-py3-none-any.whl", hash = "sha256:9fa416704703e509eeb900696751c908ddeb2011319d93700d8f18baff887a69"},
-    {file = "readme_renderer-37.0.tar.gz", hash = "sha256:07b7ea234e03e58f77cc222e206e6abb8f4c0435becce5104794ee591f9301c5"},
+    {file = "readme_renderer-37.2-py3-none-any.whl", hash = "sha256:d3f06a69e8c40fca9ab3174eca48f96d9771eddb43517b17d96583418427b106"},
+    {file = "readme_renderer-37.2.tar.gz", hash = "sha256:e8ad25293c98f781dbc2c5a36a309929390009f902f99e1798c761aaf04a7923"},
 ]
 requests = [
     {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
     {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
 ]
 requests-toolbelt = [
-    {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"},
-    {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"},
+    {file = "requests-toolbelt-0.10.0.tar.gz", hash = "sha256:f695d6207931200b46c8ef6addbc8a921fb5d77cc4cd209c2e7d39293fcd2b30"},
+    {file = "requests_toolbelt-0.10.0-py2.py3-none-any.whl", hash = "sha256:64c6b8c51b515d123f9f708a29743f44eb70c4479440641ed2df8c4dea56d985"},
 ]
 rfc3986 = [
     {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"},
     {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"},
 ]
 rich = [
-    {file = "rich-12.5.1-py3-none-any.whl", hash = "sha256:2eb4e6894cde1e017976d2975ac210ef515d7548bc595ba20e195fb9628acdeb"},
-    {file = "rich-12.5.1.tar.gz", hash = "sha256:63a5c5ce3673d3d5fbbf23cd87e11ab84b6b451436f1b7f19ec54b6bc36ed7ca"},
+    {file = "rich-12.6.0-py3-none-any.whl", hash = "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e"},
+    {file = "rich-12.6.0.tar.gz", hash = "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0"},
 ]
 secretstorage = [
     {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"},
     {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"},
 ]
 setuptools = [
-    {file = "setuptools-65.3.0-py3-none-any.whl", hash = "sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82"},
-    {file = "setuptools-65.3.0.tar.gz", hash = "sha256:7732871f4f7fa58fb6bdcaeadb0161b2bd046c85905dbaa066bdcbcc81953b57"},
+    {file = "setuptools-65.5.0-py3-none-any.whl", hash = "sha256:f62ea9da9ed6289bfe868cd6845968a2c854d1427f8548d52cae02a42b4f0356"},
+    {file = "setuptools-65.5.0.tar.gz", hash = "sha256:512e5536220e38146176efb833d4a62aa726b7bbff82cfbc8ba9eaa3996e0b17"},
 ]
 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"},
+    {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
+    {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
 ]
 starlette = [
     {file = "starlette-0.19.1-py3-none-any.whl", hash = "sha256:5a60c5c2d051f3a8eb546136aa0c9399773a689595e099e0877704d5888279bf"},
@@ -2087,21 +2286,25 @@ toml = [
     {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
     {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
 ]
+tomli = [
+    {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
+    {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
+]
 tomlkit = [
-    {file = "tomlkit-0.11.4-py3-none-any.whl", hash = "sha256:25d4e2e446c453be6360c67ddfb88838cfc42026322770ba13d1fbd403a93a5c"},
-    {file = "tomlkit-0.11.4.tar.gz", hash = "sha256:3235a9010fae54323e727c3ac06fb720752fe6635b3426e379daec60fbd44a83"},
+    {file = "tomlkit-0.11.5-py3-none-any.whl", hash = "sha256:f2ef9da9cef846ee027947dc99a45d6b68a63b0ebc21944649505bf2e8bc5fe7"},
+    {file = "tomlkit-0.11.5.tar.gz", hash = "sha256:571854ebbb5eac89abcb4a2e47d7ea27b89bf29e09c35395da6f03dd4ae23d1c"},
 ]
 twine = [
     {file = "twine-4.0.1-py3-none-any.whl", hash = "sha256:42026c18e394eac3e06693ee52010baa5313e4811d5a11050e7d48436cf41b9e"},
     {file = "twine-4.0.1.tar.gz", hash = "sha256:96b1cf12f7ae611a4a40b6ae8e9570215daff0611828f5fe1f37a16255ab24a0"},
 ]
 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"},
+    {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"},
+    {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"},
 ]
 tzdata = [
-    {file = "tzdata-2022.2-py2.py3-none-any.whl", hash = "sha256:c3119520447d68ef3eb8187a55a4f44fa455f30eb1b4238fa5691ba094f2b05b"},
-    {file = "tzdata-2022.2.tar.gz", hash = "sha256:21f4f0d7241572efa7f7a4fdabb052e61b55dc48274e6842697ccdf5253e5451"},
+    {file = "tzdata-2022.5-py2.py3-none-any.whl", hash = "sha256:323161b22b7802fdc78f20ca5f6073639c64f1a7227c40cd3e19fd1d0ce6650a"},
+    {file = "tzdata-2022.5.tar.gz", hash = "sha256:e15b2b3005e2546108af42a0eb4ccab4d9e225e2dfbf4f77aad50c70a4b1f3ab"},
 ]
 tzlocal = [
     {file = "tzlocal-4.2-py3-none-any.whl", hash = "sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745"},
@@ -2112,46 +2315,60 @@ urllib3 = [
     {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"},
 ]
 uvicorn = [
-    {file = "uvicorn-0.18.2-py3-none-any.whl", hash = "sha256:c19a057deb1c5bb060946e2e5c262fc01590c6529c0af2c3d9ce941e89bc30e0"},
-    {file = "uvicorn-0.18.2.tar.gz", hash = "sha256:cade07c403c397f9fe275492a48c1b869efd175d5d8a692df649e6e7e2ed8f4e"},
+    {file = "uvicorn-0.18.3-py3-none-any.whl", hash = "sha256:0abd429ebb41e604ed8d2be6c60530de3408f250e8d2d84967d85ba9e86fe3af"},
+    {file = "uvicorn-0.18.3.tar.gz", hash = "sha256:9a66e7c42a2a95222f76ec24a4b754c158261c4696e683b9dadc72b590e0311b"},
 ]
 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"},
+    {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce9f61938d7155f79d3cb2ffa663147d4a76d16e08f65e2c66b77bd41b356718"},
+    {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:68532f4349fd3900b839f588972b3392ee56042e440dd5873dfbbcd2cc67617c"},
+    {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0949caf774b9fcefc7c5756bacbbbd3fc4c05a6b7eebc7c7ad6f825b23998d6d"},
+    {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3d00b70ce95adce264462c930fbaecb29718ba6563db354608f37e49e09024"},
+    {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a5abddb3558d3f0a78949c750644a67be31e47936042d4f6c888dd6f3c95f4aa"},
+    {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8efcadc5a0003d3a6e887ccc1fb44dec25594f117a94e3127954c05cf144d811"},
+    {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3378eb62c63bf336ae2070599e49089005771cc651c8769aaad72d1bd9385a7c"},
+    {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6aafa5a78b9e62493539456f8b646f85abc7093dd997f4976bb105537cf2635e"},
+    {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c686a47d57ca910a2572fddfe9912819880b8765e2f01dc0dd12a9bf8573e539"},
+    {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:864e1197139d651a76c81757db5eb199db8866e13acb0dfe96e6fc5d1cf45fc4"},
+    {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2a6149e1defac0faf505406259561bc14b034cdf1d4711a3ddcdfbaa8d825a05"},
+    {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6708f30db9117f115eadc4f125c2a10c1a50d711461699a0cbfaa45b9a78e376"},
+    {file = "uvloop-0.17.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:23609ca361a7fc587031429fa25ad2ed7242941adec948f9d10c045bfecab06b"},
+    {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2deae0b0fb00a6af41fe60a675cec079615b01d68beb4cc7b722424406b126a8"},
+    {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45cea33b208971e87a31c17622e4b440cac231766ec11e5d22c76fab3bf9df62"},
+    {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9b09e0f0ac29eee0451d71798878eae5a4e6a91aa275e114037b27f7db72702d"},
+    {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dbbaf9da2ee98ee2531e0c780455f2841e4675ff580ecf93fe5c48fe733b5667"},
+    {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a4aee22ece20958888eedbad20e4dbb03c37533e010fb824161b4f05e641f738"},
+    {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:307958f9fc5c8bb01fad752d1345168c0abc5d62c1b72a4a8c6c06f042b45b20"},
+    {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ebeeec6a6641d0adb2ea71dcfb76017602ee2bfd8213e3fcc18d8f699c5104f"},
+    {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1436c8673c1563422213ac6907789ecb2b070f5939b9cbff9ef7113f2b531595"},
+    {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8887d675a64cfc59f4ecd34382e5b4f0ef4ae1da37ed665adba0c2badf0d6578"},
+    {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3db8de10ed684995a7f34a001f15b374c230f7655ae840964d51496e2f8a8474"},
+    {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7d37dccc7ae63e61f7b96ee2e19c40f153ba6ce730d8ba4d3b4e9738c1dccc1b"},
+    {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cbbe908fda687e39afd6ea2a2f14c2c3e43f2ca88e3a11964b297822358d0e6c"},
+    {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d97672dc709fa4447ab83276f344a165075fd9f366a97b712bdd3fee05efae8"},
+    {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1e507c9ee39c61bfddd79714e4f85900656db1aec4d40c6de55648e85c2799c"},
+    {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c092a2c1e736086d59ac8e41f9c98f26bbf9b9222a76f21af9dfe949b99b2eb9"},
+    {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:30babd84706115626ea78ea5dbc7dd8d0d01a2e9f9b306d24ca4ed5796c66ded"},
+    {file = "uvloop-0.17.0.tar.gz", hash = "sha256:0ddf6baf9cf11a1a22c71487f39f15b2cf78eb5bde7e5b45fbb99e8a9d91b9e1"},
 ]
 watchfiles = [
-    {file = "watchfiles-0.16.1-cp37-abi3-macosx_10_7_x86_64.whl", hash = "sha256:1e41c8b4bf3e07c18aa51775b36b718830fa727929529a7d6e5b38cf845a06b4"},
-    {file = "watchfiles-0.16.1-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:b2c7ad91a867dd688b9a12097dd6a4f89397b43fccee871152aa67197cc94398"},
-    {file = "watchfiles-0.16.1-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:75a4b9cec1b1c337ea77d4428b29861553d6bf8179923b1bc7e825e217460e2c"},
-    {file = "watchfiles-0.16.1-cp37-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2a3debb19912072799d7ca53e99fc5f090f77948f5601392623b2a416b4c86be"},
-    {file = "watchfiles-0.16.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35f3e411822e14a35f2ef656535aad4e6e79670d6b6ef8e53db958e28916b1fe"},
-    {file = "watchfiles-0.16.1-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9a7a6dc63684ff5ba11f0be0e64f744112c3c7a0baf4ec8f6794f9a6257d21e"},
-    {file = "watchfiles-0.16.1-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e939a2693404ac11e055f9d1237db8ad7635e2185a6143bde00116e691ea2983"},
-    {file = "watchfiles-0.16.1-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cd7d2fd9a8f28066edc8db5278f3632eb94d10596af760fa0601631f32b1a41e"},
-    {file = "watchfiles-0.16.1-cp37-abi3-win32.whl", hash = "sha256:f91035a273001390093a09e52274a34695b0d15ee8736183b640bbc3b8a432ab"},
-    {file = "watchfiles-0.16.1-cp37-abi3-win_amd64.whl", hash = "sha256:a8a1809bf910672aa0b7ed6e6045d4fc2cf1e0718b99bc443ef17faa5697b68a"},
-    {file = "watchfiles-0.16.1-cp37-abi3-win_arm64.whl", hash = "sha256:baa6d0c1c5140e1dcf6ff802dd7b09fcd95b358e50d42fabc83d83f719451c54"},
-    {file = "watchfiles-0.16.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:5741246ae399a03395aa5ee35480083a4f29d58ffd41dd3395594f8805f8cdbc"},
-    {file = "watchfiles-0.16.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:44c6aff58b8a70a26431737e483a54e8e224279b21873388571ed184fe7c91a7"},
-    {file = "watchfiles-0.16.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d1b2d0cf060e5222a930a3e2f40f6577da1d18c085c32741b98a128dc1e72c"},
-    {file = "watchfiles-0.16.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:70159e759f52b65a50c498182dece80364bfd721e839c254c328cbc7a1716616"},
-    {file = "watchfiles-0.16.1-pp39-pypy39_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:22af3b915f928ef59d427d7228668f87ac8054ed8200808c73fbcaa4f82d5572"},
-    {file = "watchfiles-0.16.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a6a1ac96edf5bc3f8e36f4462fc1daad0ec3769ff2adb920571e120e37c91c5"},
-    {file = "watchfiles-0.16.1.tar.gz", hash = "sha256:aed7575e24434c8fec2f2bbb0cecb1521ea1240234d9108db7915a3424d92394"},
+    {file = "watchfiles-0.18.0-cp37-abi3-macosx_10_7_x86_64.whl", hash = "sha256:76a4c4a8e25a2c9a4f7fa3d373bbaf5558c17b97b4cf8411d33de368fe6b68a9"},
+    {file = "watchfiles-0.18.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:d5d799614d4c56d29c5ba56f4f619f967210dc10a0d6965b62d326b9e2f72c9e"},
+    {file = "watchfiles-0.18.0-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:39b932b044fef6c43e813e0bef908e0edf185bf7b5d8d53246651cb7ac9efe79"},
+    {file = "watchfiles-0.18.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1686bc4ac40ffde7256b6543b0f9a2cc8b531ae45243786f1d3f1dda2fe39e24"},
+    {file = "watchfiles-0.18.0-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:320bcde0adaa972403ed3b70f784409437325a1a4df2de54ba0672203d8847e5"},
+    {file = "watchfiles-0.18.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ba6d8c2f957cae3e888bc250bc60ed09fe869b3f55f09d020ed3fecbefb6a4c"},
+    {file = "watchfiles-0.18.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fd4215badad1e3d1ad5fb79f21432dd5157e2e7b0765d27a19dc2a28580c6979"},
+    {file = "watchfiles-0.18.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cfdbfc4b6797c28dd1a8524581fed00ca333971b4111af8cd42fb7a92dcdc227"},
+    {file = "watchfiles-0.18.0-cp37-abi3-win32.whl", hash = "sha256:8eddc2d19bf6f49aee224072ec0f4f3258125a49f11b5dcff1448e68718a745e"},
+    {file = "watchfiles-0.18.0-cp37-abi3-win_amd64.whl", hash = "sha256:be87c9b1fe2b02105a9ac6d9df7500a110652bbd97cf46b13964eeaef9a6c89c"},
+    {file = "watchfiles-0.18.0-cp37-abi3-win_arm64.whl", hash = "sha256:184799818c4fa7dbc6a1e4ca20bcbc6b85e4e0db07ce4554ea2f29b75ccd0cdc"},
+    {file = "watchfiles-0.18.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:7f39fcdac5d5b9815a0c2ab9005d39854296b11fa15386a9a69c09cbbc5dde2c"},
+    {file = "watchfiles-0.18.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78b1e7c29b92dfc8fc32f15949019232b493767d236c2bff31848df13fdb9e8a"},
+    {file = "watchfiles-0.18.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27d64a6ed5e0aebef97c70fa3899a6958d4f7f049effc659e7dc3e81f3170a7b"},
+    {file = "watchfiles-0.18.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4bbc8bfa0f3871b1867af42837a5635a9c1cbb2b68d039754b4750642c34aaee"},
+    {file = "watchfiles-0.18.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d3a12e4de5446fb6e286b720d0cb3a080811caf0ef43e556c2db5fe10ef0342"},
+    {file = "watchfiles-0.18.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e611a90482ac14ef3ec234c1604ed921d1b0c68970eba82f1cf0d59a3e4eb76"},
+    {file = "watchfiles-0.18.0.tar.gz", hash = "sha256:bbe10d134eef1666451382015e48f092c941a6d4562a98ffa1a288f79a897c46"},
 ]
 webencodings = [
     {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},
@@ -2339,6 +2556,6 @@ yarl = [
     {file = "yarl-1.8.1.tar.gz", hash = "sha256:af887845b8c2e060eb5605ff72b6f2dd2aab7a761379373fd89d314f4752abbf"},
 ]
 zipp = [
-    {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"},
-    {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"},
+    {file = "zipp-3.9.0-py3-none-any.whl", hash = "sha256:972cfa31bc2fedd3fa838a51e9bc7e64b7fb725a8c00e7431554311f180e9980"},
+    {file = "zipp-3.9.0.tar.gz", hash = "sha256:3a7af91c3db40ec72dd9d154ae18e008c69efe8ca88dde4f9a731bb82fe2f9eb"},
 ]
diff --git a/pyproject.toml b/pyproject.toml
index 34d3105..1d4490b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "nonebot-plugin-pixivbot"
-version = "1.3.3"
+version = "1.4.0rc1"
 description = "Nonebot Plugin PixivBot"
 authors = ["ssttkkl <huang.wen.long@hotmail.com>"]
 readme = "README.md"
@@ -12,8 +12,8 @@ packages = [
 
 [tool.poetry.dependencies]
 python = "^3.9"
-nonebot2 = "^2.0.0b4"
-nonebot_plugin_apscheduler = "^0.1.3"
+nonebot2 = "^2.0.0rc1"
+nonebot_plugin_apscheduler = "^0.2.0"
 PixivPy-Async = "^1.2.14"
 Pillow = "^9.2.0"
 motor = "^3.0.0"
@@ -22,23 +22,21 @@ lazy = "^1.4"
 aiohttp-socks = "^0.7.1"
 frozendict = "^2.3.4"
 tzlocal = "^4.2"
-nonebot-plugin-pixivbot-onebot-v11 = { version = "^1.3.0", optional = true }
+nonebot-plugin-pixivbot-onebot-v11 = { version = "^1.3.1", optional = true }
 nonebot-plugin-pixivbot-kook = { version = "^1.3.0.post2", optional = true }
+beanie = "^1.12.0"
 
 [tool.poetry.dev-dependencies]
 flake8 = "^5.0.4"
 twine = "^4.0.1"
 nonebug = "^0.2.1"
 pytest-mock = "^3.10.0"
+pytest-cov = "^4.0.0"
 
 [tool.poetry.extras]
 onebot = ["nonebot-plugin-pixivbot-onebot-v11"]
 kook = ["nonebot-plugin-pixivbot-kook"]
 
-#[[tool.poetry.source]]
-#name = "tsinghua"
-#url = "https://pypi.tuna.tsinghua.edu.cn/simple/"
-
 [build-system]
 requires = ["poetry-core>=1.0.0"]
 build-backend = "poetry.core.masonry.api"
diff --git a/src/nonebot_plugin_pixivbot/__init__.py b/src/nonebot_plugin_pixivbot/__init__.py
index c16d309..7f5d8f8 100644
--- a/src/nonebot_plugin_pixivbot/__init__.py
+++ b/src/nonebot_plugin_pixivbot/__init__.py
@@ -12,7 +12,7 @@
 from nonebot import logger
 
 from .global_context import context
-from .providers import provide
+from .nb_providers import provide
 
 provide(context)
 
diff --git a/src/nonebot_plugin_pixivbot/config.py b/src/nonebot_plugin_pixivbot/config.py
index c747c41..761e377 100644
--- a/src/nonebot_plugin_pixivbot/config.py
+++ b/src/nonebot_plugin_pixivbot/config.py
@@ -16,7 +16,8 @@ class Config(BaseSettings):
     pixiv_mongo_conn_url: str
     pixiv_mongo_database_name: str
     pixiv_proxy: Optional[str]
-    pixiv_query_timeout: int = 60
+    pixiv_query_timeout: float = 60.0
+    pixiv_loading_prompt_delayed_time: float = 5.0
     pixiv_simultaneous_query: int = 8
 
     pixiv_download_cache_expires_in = 3600 * 24 * 7
@@ -24,7 +25,9 @@ class Config(BaseSettings):
     pixiv_user_detail_cache_expires_in = 3600 * 24 * 7
     pixiv_illust_ranking_cache_expires_in = 3600 * 6
     pixiv_search_illust_cache_expires_in = 3600 * 24
+    pixiv_search_illust_cache_delete_in = 3600 * 24 * 30
     pixiv_search_user_cache_expires_in = 3600 * 24
+    pixiv_search_user_cache_delete_in = 3600 * 24 * 30
     pixiv_user_illusts_cache_expires_in = 3600 * 24
     pixiv_user_illusts_cache_delete_in = 3600 * 24 * 30
     pixiv_user_bookmarks_cache_expires_in = 3600 * 24
diff --git a/src/nonebot_plugin_pixivbot/data/errors.py b/src/nonebot_plugin_pixivbot/data/errors.py
index 3259ad6..287c0b8 100644
--- a/src/nonebot_plugin_pixivbot/data/errors.py
+++ b/src/nonebot_plugin_pixivbot/data/errors.py
@@ -1,2 +1,9 @@
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from nonebot_plugin_pixivbot.data.pixiv_repo.abstract_repo import PixivRepoMetadata
+
+
 class DataSourceNotReadyError(RuntimeError):
     pass
+
diff --git a/src/nonebot_plugin_pixivbot/data/local_tag_repo.py b/src/nonebot_plugin_pixivbot/data/local_tag_repo.py
index 9a5c213..5bb6755 100644
--- a/src/nonebot_plugin_pixivbot/data/local_tag_repo.py
+++ b/src/nonebot_plugin_pixivbot/data/local_tag_repo.py
@@ -1,5 +1,7 @@
-import typing
+from typing import Optional, Iterable
 
+from beanie import Document, BulkWriter
+from beanie.odm.operators.update.general import SetOnInsert
 from pymongo import *
 
 from nonebot_plugin_pixivbot.global_context import context
@@ -7,44 +9,46 @@
 from .source import MongoDataSource
 
 
-@context.inject
+class LocalTag(Tag, Document):
+    class Settings:
+        name = "local_tags"
+        indexes = [
+            IndexModel([("name", 1)], unique=True),
+            IndexModel([("translated_name", 1)])
+        ]
+
+
+context.require(MongoDataSource).document_models.append(LocalTag)
+
+
 @context.register_singleton()
 class LocalTagRepo:
-    mongo: MongoDataSource
-
-    async def insert(self, tag: Tag) -> typing.NoReturn:
-        await self.mongo.db.local_tags.updateOne(
-            {"name": tag.name},
-            {"$setOnInsert": {"translated_name": tag.translated_name}},
-            upsert=True
+    @classmethod
+    async def find_by_name(cls, name: str) -> Optional[Tag]:
+        result = await LocalTag.find_one(LocalTag.name == name)
+        return result
+
+    @classmethod
+    async def find_by_translated_name(cls, translated_name: str) -> Optional[Tag]:
+        result = await LocalTag.find_one(LocalTag.translated_name == translated_name)
+        return result
+
+    @classmethod
+    async def update_one(cls, tag: Tag):
+        await LocalTag.find_one(LocalTag.name == tag.name).upsert(
+            SetOnInsert({LocalTag.translated_name: tag.translated_name}),
+            on_insert=LocalTag(**tag.dict()),
         )
 
-    async def insert_many(self, tags: typing.Iterable[Tag]) -> typing.NoReturn:
-        opt = []
-
-        for tag in tags:
-            opt.append(UpdateOne(
-                {"name": tag.name},
-                {"$setOnInsert": {"translated_name": tag.translated_name}},
-                upsert=True
-            ))
-
-        if len(opt) != 0:
-            await self.mongo.db.local_tags.bulk_write(opt, ordered=False)
-
-    async def get_by_name(self, name: str) -> typing.Optional[Tag]:
-        result = await self.mongo.db.local_tags.find_one({"name": name})
-        if result:
-            return Tag.parse_obj(result)
-        else:
-            return None
-
-    async def get_by_translated_name(self, translated_name: str) -> typing.Optional[Tag]:
-        result = await self.mongo.db.local_tags.find_one({"translated_name": translated_name})
-        if result:
-            return Tag.parse_obj(result)
-        else:
-            return None
+    @classmethod
+    async def update_many(cls, tags: Iterable[Tag]):
+        async with BulkWriter() as bw:
+            for tag in tags:
+                await LocalTag.find_one(LocalTag.name == tag.name).upsert(
+                    SetOnInsert({LocalTag.translated_name: tag.translated_name}),
+                    on_insert=LocalTag(**tag.dict()),
+                    bulk_writer=bw
+                )
 
 
 __all__ = ("LocalTagRepo",)
diff --git a/src/nonebot_plugin_pixivbot/data/pixiv_binding_repo.py b/src/nonebot_plugin_pixivbot/data/pixiv_binding_repo.py
index 59b770f..f40721c 100644
--- a/src/nonebot_plugin_pixivbot/data/pixiv_binding_repo.py
+++ b/src/nonebot_plugin_pixivbot/data/pixiv_binding_repo.py
@@ -1,4 +1,8 @@
-from typing import TypeVar, Optional
+from typing import TypeVar, Optional, Any
+
+from beanie import Document
+from beanie.odm.operators.update.general import Set
+from pymongo import IndexModel
 
 from nonebot_plugin_pixivbot.global_context import context
 from nonebot_plugin_pixivbot.model import PixivBinding
@@ -7,33 +11,43 @@
 UID = TypeVar("UID")
 
 
-@context.inject
+class PixivBindingDocument(PixivBinding[Any], Document):
+    class Settings:
+        name = "pixiv_binding"
+        indexes = [
+            IndexModel([("adapter", 1), ("user_id", 1)], unique=True)
+        ]
+
+
+context.require(MongoDataSource).document_models.append(PixivBindingDocument)
+
+
 @context.register_singleton()
 class PixivBindingRepo:
-    mongo: MongoDataSource
-
-    async def get(self, adapter: str, user_id: UID) -> Optional[PixivBinding]:
-        doc = await self.mongo.db.pixiv_binding.find_one(
-            {"adapter": adapter, "user_id": user_id}
+    @classmethod
+    async def get(cls, adapter: str, user_id: UID) -> Optional[PixivBinding]:
+        result = await PixivBindingDocument.find_one(
+            PixivBindingDocument.adapter == adapter,
+            PixivBindingDocument.user_id == user_id
         )
-        if doc:
-            return PixivBinding.parse_obj(doc)
-        else:
-            return None
-
-    async def update(self, binding: PixivBinding):
-        await self.mongo.db.pixiv_binding.update_one(
-            {"adapter": binding.adapter, "user_id": binding.user_id},
-            {"$set": {
-                "pixiv_user_id": binding.pixiv_user_id
-            }},
-            upsert=True
+        return result
+
+    @classmethod
+    async def update(cls, binding: PixivBinding):
+        await PixivBindingDocument.find_one(
+            PixivBindingDocument.adapter == binding.adapter,
+            PixivBindingDocument.user_id == binding.user_id
+        ).upsert(
+            Set({PixivBindingDocument.pixiv_user_id: binding.pixiv_user_id}),
+            on_insert=PixivBindingDocument(**binding.dict())
         )
 
-    async def remove(self, adapter: str, user_id: UID) -> bool:
-        cnt = await self.mongo.db.pixiv_binding.delete_one(
-            {"adapter": adapter, "user_id": user_id}
-        )
+    @classmethod
+    async def remove(cls, adapter: str, user_id: UID) -> bool:
+        cnt = await PixivBindingDocument.find_one(
+            PixivBindingDocument.adapter == adapter,
+            PixivBindingDocument.user_id == user_id
+        ).delete()
         return cnt == 1
 
 
diff --git a/src/nonebot_plugin_pixivbot/data/pixiv_repo/abstract_repo.py b/src/nonebot_plugin_pixivbot/data/pixiv_repo/abstract_repo.py
index c8afb2e..ff2ba22 100644
--- a/src/nonebot_plugin_pixivbot/data/pixiv_repo/abstract_repo.py
+++ b/src/nonebot_plugin_pixivbot/data/pixiv_repo/abstract_repo.py
@@ -1,18 +1,10 @@
 from abc import ABC, abstractmethod
-from datetime import datetime, timezone
-from typing import Union, Optional, AsyncGenerator
-
-from pydantic import BaseModel
+from typing import Union, AsyncGenerator
 
 from nonebot_plugin_pixivbot.enums import RankingMode
 from nonebot_plugin_pixivbot.model import Illust, User
 from .lazy_illust import LazyIllust
-
-
-class PixivRepoMetadata(BaseModel):
-    update_time: datetime = datetime.now(timezone.utc)
-    pages: Optional[int]
-    next_qs: Optional[dict]
+from .models import PixivRepoMetadata
 
 
 class AbstractPixivRepo(ABC):
@@ -58,4 +50,4 @@ def image(self, illust: Illust) -> AsyncGenerator[Union[bytes, PixivRepoMetadata
         raise NotImplementedError()
 
 
-__all__ = ("AbstractPixivRepo", "PixivRepoMetadata",)
+__all__ = ("AbstractPixivRepo",)
diff --git a/src/nonebot_plugin_pixivbot/data/pixiv_repo/errors.py b/src/nonebot_plugin_pixivbot/data/pixiv_repo/errors.py
new file mode 100644
index 0000000..c9cd729
--- /dev/null
+++ b/src/nonebot_plugin_pixivbot/data/pixiv_repo/errors.py
@@ -0,0 +1,14 @@
+from .models import PixivRepoMetadata
+
+
+class LocalPixivRepoError(RuntimeError):
+    pass
+
+
+class NoSuchItemError(LocalPixivRepoError):
+    pass
+
+
+class CacheExpiredError(LocalPixivRepoError):
+    def __init__(self, metadata: PixivRepoMetadata):
+        self.metadata = metadata
diff --git a/src/nonebot_plugin_pixivbot/data/pixiv_repo/local_repo.py b/src/nonebot_plugin_pixivbot/data/pixiv_repo/local_repo.py
index 0d8614f..349eb62 100644
--- a/src/nonebot_plugin_pixivbot/data/pixiv_repo/local_repo.py
+++ b/src/nonebot_plugin_pixivbot/data/pixiv_repo/local_repo.py
@@ -1,43 +1,47 @@
 from datetime import datetime, timedelta, timezone
-from typing import List, Union, Sequence, Any, Mapping, AsyncGenerator
+from typing import List, Union, Sequence, Any, AsyncGenerator, Optional, Type, Mapping
 
 import bson
+from beanie import BulkWriter
+from beanie.odm.operators.find.comparison import In
+from beanie.odm.operators.find.logical import And
+from beanie.odm.operators.update.array import AddToSet
+from beanie.odm.operators.update.general import Set
 from nonebot import logger
-from pymongo import UpdateOne
 
+from nonebot_plugin_pixivbot.config import Config
 from nonebot_plugin_pixivbot.enums import RankingMode
 from nonebot_plugin_pixivbot.model import Illust, User
-from .abstract_repo import AbstractPixivRepo, PixivRepoMetadata
+from .abstract_repo import AbstractPixivRepo
+from .errors import CacheExpiredError, NoSuchItemError
 from .lazy_illust import LazyIllust
+from .models import PixivRepoMetadata, UserDetailCache, PixivRepoCache, IllustDetailCache, IllustSetCache, UserSetCache, \
+    SearchIllustCache, SearchUserCache, UserIllustsCache, UserBookmarksCache, OtherIllustCache, RelatedIllustsCache, \
+    IllustRankingCache, DownloadCache
 from .pkg_context import context
 from ..local_tag_repo import LocalTagRepo
 from ..source import MongoDataSource
-from ...config import Config
 
 
-class LocalPixivRepoError(RuntimeError):
-    pass
+def _handle_expires_in(metadata: PixivRepoMetadata, expires_in: int):
+    if datetime.now(timezone.utc) - metadata.update_time >= timedelta(seconds=expires_in):
+        raise CacheExpiredError(metadata)
 
 
-class NoSuchItemError(LocalPixivRepoError):
-    pass
+async def _add_to_local_tags(illusts: List[Union[LazyIllust, Illust]]):
+    tags = {}
+    for x in illusts:
+        if isinstance(x, LazyIllust):
+            if not x.loaded:
+                continue
+            x = x.content
+        for t in x.tags:
+            if t.translated_name:
+                tags[t.name] = t
 
+    await LocalTagRepo.update_many(tags.values())
 
-class CacheExpiredError(LocalPixivRepoError):
-    def __init__(self, metadata: PixivRepoMetadata):
-        self.metadata = metadata
-
-
-def handle_expires_in(doc: Mapping, expires_in: int):
-    if datetime.now(timezone.utc) - doc["metadata"]["update_time"] >= timedelta(seconds=expires_in):
-        raise CacheExpiredError(PixivRepoMetadata.parse_obj(doc["metadata"]))
-
-
-def metadata_to_text(metadata: PixivRepoMetadata):
-    if metadata:
-        return '(' + ', '.join(map(lambda kv: kv[0] + '=' + str(kv[1]), metadata.dict(exclude_none=True).items())) + ')'
-    else:
-        return ''
+    logger.debug(f"[local] add {len(tags)} local tags")
 
 
 @context.inject
@@ -45,28 +49,13 @@ def metadata_to_text(metadata: PixivRepoMetadata):
 class LocalPixivRepo(AbstractPixivRepo):
     conf: Config
     mongo: MongoDataSource
-    local_tags: LocalTagRepo
-
-    async def _add_to_local_tags(self, illusts: List[Union[LazyIllust, Illust]]):
-        tags = {}
-        for x in illusts:
-            if isinstance(x, LazyIllust):
-                if not x.loaded:
-                    continue
-                x = x.content
-            for t in x.tags:
-                if t.translated_name:
-                    tags[t.name] = t
-
-        await self.local_tags.insert_many(tags.values())
-        logger.debug(f"[local] add {len(tags)} local tags")
-
-    async def _illusts_agen(self, collection_name: str,
-                            query: dict,
-                            *, offset: int = 0) -> AsyncGenerator[LazyIllust, None]:
+
+    async def _illusts_agen(self, doc_type: Type[PixivRepoCache],
+                            *criteria: Union[Mapping[str, Any], bool],
+                            offset: int = 0) -> AsyncGenerator[LazyIllust, None]:
         aggregation = [
             {
-                "$match": query
+                "$match": And(*criteria).query
             },
             {
                 "$replaceWith": {"illust_id": "$illust_id"}
@@ -82,7 +71,7 @@ async def _illusts_agen(self, collection_name: str,
         aggregation.extend([
             {
                 "$lookup": {
-                    "from": "illust_detail_cache",
+                    "from": IllustDetailCache.Settings.name,
                     "localField": "illust_id",
                     "foreignField": "illust.id",
                     "as": "illusts"
@@ -101,13 +90,11 @@ async def _illusts_agen(self, collection_name: str,
             }
         ])
 
-        result = self.mongo.db[collection_name].aggregate(aggregation)
-
         total = 0
         broken = 0
 
         try:
-            async for x in result:
+            async for x in doc_type.aggregate(aggregation):
                 total += 1
                 if "illust" in x and x["illust"] is not None:
                     yield LazyIllust(x["illust_id"], Illust.parse_obj(x["illust"]))
@@ -117,12 +104,12 @@ async def _illusts_agen(self, collection_name: str,
         finally:
             logger.debug(f"[local] {total} got, illust_detail of {broken} are missed")
 
-    async def _users_agen(self, collection_name: str,
-                          query: dict,
-                          *, offset: int = 0) -> AsyncGenerator[User, None]:
+    async def _users_agen(self, doc_type: Type[PixivRepoCache],
+                          *criteria: Union[Mapping[str, Any], bool],
+                          offset: int = 0) -> AsyncGenerator[User, None]:
         aggregation = [
             {
-                "$match": query
+                "$match": And(*criteria).query
             },
             {
                 "$replaceWith": {"user_id": "$user_id"}
@@ -138,7 +125,7 @@ async def _users_agen(self, collection_name: str,
         aggregation.extend([
             {
                 "$lookup": {
-                    "from": "user_detail_cache",
+                    "from": UserDetailCache.Settings.name,
                     "localField": "user_id",
                     "foreignField": "user.id",
                     "as": "users"
@@ -157,385 +144,366 @@ async def _users_agen(self, collection_name: str,
             }
         ])
 
-        result = self.mongo.db[collection_name].aggregate(aggregation)
-
-        async for x in result:
+        async for x in doc_type.aggregate(aggregation):
             if "user" in x and x["user"] is not None:
                 yield User.parse_obj(x["user"])
             else:
                 yield User(id=x["user_id"], name="", account="")
 
-    async def _get_illusts(self, collection_name: str,
-                           query: dict,
-                           *, expired_in: int,
+    async def _get_illusts(self, doc_type: Type[PixivRepoCache],
+                           *criteria: Union[Mapping[str, Any], bool],
+                           expired_in: int,
                            offset: int = 0) \
             -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], Any]:
-        doc = await self.mongo.db[collection_name].find_one(query, {"metadata": 1})
+        doc: Optional[PixivRepoCache] = await doc_type.find_one(*criteria)
         if not doc:
             raise NoSuchItemError()
-        handle_expires_in(doc, expired_in)
+        _handle_expires_in(doc.metadata, expired_in)
 
-        metadata = PixivRepoMetadata.parse_obj(doc["metadata"])
+        metadata = doc.metadata
         yield metadata.copy(update={"pages": 0})
-        async for x in self._illusts_agen(collection_name, query, offset=offset):
+        async for x in self._illusts_agen(doc_type, *criteria, offset=offset):
             yield x
         yield metadata
 
-    async def _get_users(self, collection_name: str,
-                         query: dict,
-                         *, expired_in: int,
+    async def _get_users(self, doc_type: Type[PixivRepoCache],
+                         *criteria: Union[Mapping[str, Any], bool],
+                         expired_in: int,
                          offset: int = 0) \
             -> AsyncGenerator[Union[User, PixivRepoMetadata], Any]:
-        doc = await self.mongo.db[collection_name].find_one(query, {"metadata": 1})
+        doc: Optional[PixivRepoCache] = await doc_type.find_one(*criteria)
         if not doc:
             raise NoSuchItemError()
-        handle_expires_in(doc, expired_in)
+        _handle_expires_in(doc.metadata, expired_in)
 
-        metadata = PixivRepoMetadata.parse_obj(doc["metadata"])
+        metadata = doc.metadata
         yield metadata.copy(update={"pages": 0})
-        async for x in self._users_agen(collection_name, query, offset=offset):
+        async for x in self._users_agen(doc_type, *criteria, offset=offset):
             yield x
         yield metadata
 
-    async def _check_illusts_exists(self, collection_name: str,
-                                    query: dict,
+    async def _check_illusts_exists(self, doc_type: Type[IllustSetCache],
+                                    *criteria: Union[Mapping[str, Any], bool],
                                     illust_id: Union[int, Sequence[int]]) -> bool:
         if isinstance(illust_id, int):
-            return await self.mongo.db[collection_name].count_documents({**query, "illust_id": illust_id}) != 0
+            return await doc_type.find(*criteria, In(doc_type.illust_id, illust_id)).count() != 0
         else:
             agg = [
-                {'$match': query},
+                {'$match': And(*criteria).query},
                 {'$unwind': {'path': '$illust_id'}},
                 {'$match': {'illust_id': {'$in': illust_id}}},
                 {'$count': 'count'}
             ]
 
-            async for x in self.mongo.db[collection_name].aggregate(agg):
-                exists = x["count"]
-                return exists != 0
+            async for result in doc_type.aggregate(agg):
+                return result["count"] != 0
 
-    async def _check_users_exists(self, collection_name: str,
-                                  query: dict,
+    async def _check_users_exists(self, doc_type: Type[UserSetCache],
+                                  *criteria: Union[Mapping[str, Any], bool],
                                   user_id: Union[int, Sequence[int]]) -> bool:
         if isinstance(user_id, int):
-            return await self.mongo.db[collection_name].count_documents({**query, "user_id": user_id}) != 0
+            return await doc_type.find(*criteria, In(doc_type.user_id, user_id)).count() != 0
         else:
             agg = [
-                {'$match': query},
+                {'$match': And(*criteria).query},
                 {'$unwind': {'path': '$user_id'}},
                 {'$match': {'user_id': {'$in': user_id}}},
                 {'$count': 'count'}
             ]
 
-            async for x in self.mongo.db[collection_name].aggregate(agg):
-                exists = x["count"]
-                return exists != 0
+            async for result in doc_type.aggregate(agg):
+                return result["count"] != 0
 
-    async def _update_illusts(self, collection_name: str,
-                              query: dict,
+    async def _update_illusts(self, doc_type: Type[IllustSetCache],
+                              *criteria: Union[Mapping[str, Any], bool],
                               content: List[Union[Illust, LazyIllust]],
                               metadata: PixivRepoMetadata,
                               append: bool = False):
         if append:
-            await self.mongo.db[collection_name].update_one(
-                query,
-                {
-                    "$set": {
-                        "metadata": metadata.dict(exclude_none=True)
-                    },
-                    "$addToSet": {
-                        "illust_id": {
-                            "$each": [illust.id for illust in content]
-                        }
+            await doc_type.find_one(*criteria).update(
+                Set({
+                    doc_type.metadata: metadata
+                }),
+                AddToSet({
+                    doc_type.illust_id: {
+                        "$each": [illust.id for illust in content]
                     }
-                },
+                }),
                 upsert=True
             )
         else:
-            await self.mongo.db[collection_name].update_one(
-                query,
-                {
-                    "$set": {
-                        "illust_id": [illust.id for illust in content],
-                        "metadata": metadata.dict(exclude_none=True)
-                    }
-                },
+            await doc_type.find_one(*criteria).update(
+                Set({
+                    doc_type.illust_id: [illust.id for illust in content],
+                    doc_type.metadata: metadata
+                }),
                 upsert=True
             )
 
-        opt = []
-        for illust in content:
-            if isinstance(illust, LazyIllust) and illust.content is not None:
-                illust = illust.content
-
-            if isinstance(illust, Illust):
-                opt.append(UpdateOne(
-                    {"illust.id": illust.id},
-                    {"$set": {
-                        "illust": illust.dict(exclude_none=True),
-                        "metadata": {"update_time": metadata.update_time}
-                    }},
-                    upsert=True
-                ))
-        if len(opt) != 0:
-            await self.mongo.db.illust_detail_cache.bulk_write(opt, ordered=False)
+        async with BulkWriter() as bw:
+            illust_metadata = PixivRepoMetadata(update_time=metadata.update_time)
+
+            for illust in content:
+                if isinstance(illust, LazyIllust) and illust.content is not None:
+                    illust = illust.content
+
+                if isinstance(illust, Illust):
+                    IllustDetailCache.find_one(
+                        IllustDetailCache.illust.id == illust.id
+                    ).update(
+                        Set({
+                            IllustDetailCache.illust: illust,
+                            IllustDetailCache.metadata: illust_metadata
+                        }),
+                        upsert=True,
+                        bulk_writer=bw
+                    )
 
         if self.conf.pixiv_tag_translation_enabled:
-            await self._add_to_local_tags(content)
+            await _add_to_local_tags(content)
 
-    async def _update_users(self, collection_name: str,
-                            query: dict,
+    async def _update_users(self, doc_type: Type[UserSetCache],
+                            *criteria: Union[Mapping[str, Any], bool],
                             content: List[User],
                             metadata: PixivRepoMetadata,
                             append: bool = False):
         if append:
-            await self.mongo.db[collection_name].update_one(
-                query,
-                {
-                    "$set": {
-                        "metadata": metadata.dict(exclude_none=True)
-                    },
-                    "$addToSet": {
-                        "user_id": {
-                            "$each": [user.id for user in content]
-                        }
+            await doc_type.find_one(*criteria).update(
+                Set({
+                    doc_type.metadata: metadata
+                }),
+                AddToSet({
+                    doc_type.user_id: {
+                        "$each": [user.id for user in content]
                     }
-                },
+                }),
                 upsert=True
             )
         else:
-            await self.mongo.db[collection_name].update_one(
-                query,
-                {"$set": {
-                    "user_id": [user.id for user in content],
-                    "metadata": metadata.dict(exclude_none=True)
-                }},
+            await doc_type.find_one(*criteria).update(
+                Set({
+                    doc_type.user_id: [user.id for user in content],
+                    doc_type.metadata: metadata
+                }),
                 upsert=True
             )
 
-        opt = []
-        for user in content:
-            opt.append(UpdateOne(
-                {"user.id": user.id},
-                {"$set": {
-                    "user": user.dict(exclude_none=True),
-                    "metadata": {"update_time": metadata.update_time}
-                }},
-                upsert=True
-            ))
-        if len(opt) != 0:
-            await self.mongo.db.user_detail_cache.bulk_write(opt, ordered=False)
-
-    async def _append_and_check_illusts(self, collection_name: str,
-                                        query: dict,
+        async with BulkWriter() as bw:
+            user_metadata = PixivRepoMetadata(update_time=metadata.update_time)
+
+            for user in content:
+                UserDetailCache.find_one(
+                    UserDetailCache.user.id == user.id
+                ).update(
+                    Set({
+                        UserDetailCache.user: user,
+                        UserDetailCache.metadata: user_metadata
+                    }),
+                    upsert=True,
+                    bulk_writer=bw
+                )
+
+    async def _append_and_check_illusts(self, doc_type: Type[IllustSetCache],
+                                        *criteria: Union[Mapping[str, Any], bool],
                                         content: List[Union[Illust, LazyIllust]],
                                         metadata: PixivRepoMetadata):
-        exists = await self._check_illusts_exists(collection_name, query, [x.id for x in content])
-        await self._update_illusts(collection_name, query, content, metadata, True)
+        exists = await self._check_illusts_exists(doc_type, *criteria, illust_id=[x.id for x in content])
+        await self._update_illusts(doc_type, *criteria, content=content, metadata=metadata, append=True)
         return exists
 
-    async def _append_and_check_users(self, collection_name: str,
-                                      query: dict,
+    async def _append_and_check_users(self, doc_type: Type[UserSetCache],
+                                      *criteria: Union[Mapping[str, Any], bool],
                                       content: List[User],
                                       metadata: PixivRepoMetadata):
-        exists = await self._check_users_exists(collection_name, query, [x.id for x in content])
-        await self._update_users(collection_name, query, content, metadata, True)
+        exists = await self._check_users_exists(doc_type, *criteria, user_id=[x.id for x in content])
+        await self._update_users(doc_type, *criteria, content=content, metadata=metadata, append=True)
         return exists
 
     # ================ illust_detail ================
     async def illust_detail(self, illust_id: int) \
             -> AsyncGenerator[Union[Illust, PixivRepoMetadata], None]:
         logger.debug(f"[local] illust_detail {illust_id}")
-        doc = await self.mongo.db.illust_detail_cache.find_one({"illust.id": illust_id})
+        doc = await IllustDetailCache.find_one(IllustDetailCache.illust.id == illust_id)
         if doc is not None:
-            handle_expires_in(doc, self.conf.pixiv_illust_detail_cache_expires_in)
+            _handle_expires_in(doc.metadata, self.conf.pixiv_illust_detail_cache_expires_in)
 
-            yield PixivRepoMetadata.parse_obj(doc["metadata"])
-            yield Illust.parse_obj(doc["illust"])
+            yield doc.metadata
+            yield doc.illust
         else:
             raise NoSuchItemError()
 
-    async def update_illust_detail(self, illust: Illust, metadata: PixivRepoMetadata = PixivRepoMetadata()):
-        logger.debug(f"[local] update illust_detail {illust.id} {metadata_to_text(metadata)}")
+    async def update_illust_detail(self, illust: Illust, metadata: PixivRepoMetadata):
+        logger.debug(f"[local] update illust_detail {illust.id} {metadata}")
 
-        await self.mongo.db.illust_detail_cache.update_one(
-            {"illust.id": illust.id},
-            {"$set": {
-                "illust": illust.dict(exclude_none=True),
-                "metadata": metadata.dict(exclude_none=True)
-            }},
+        await IllustDetailCache.find_one(
+            IllustDetailCache.illust.id == illust.id
+        ).update(
+            Set({
+                IllustDetailCache.illust: illust,
+                IllustDetailCache.metadata: metadata
+            }),
             upsert=True
         )
 
         if self.conf.pixiv_tag_translation_enabled:
-            await self._add_to_local_tags([illust])
+            await _add_to_local_tags([illust])
 
     # ================ user_detail ================
+
     async def user_detail(self, user_id: int) \
             -> AsyncGenerator[Union[User, PixivRepoMetadata], None]:
         logger.debug(f"[local] user_detail {user_id}")
-        doc = await self.mongo.db.user_detail_cache.find_one({"user.id": user_id})
+        doc: Optional[UserDetailCache] = await UserDetailCache.find_one(UserDetailCache.user.id == user_id)
         if doc is not None:
-            handle_expires_in(doc, self.conf.pixiv_user_detail_cache_expires_in)
+            _handle_expires_in(doc.metadata, self.conf.pixiv_user_detail_cache_expires_in)
 
-            yield PixivRepoMetadata.parse_obj(doc["metadata"])
-            yield User.parse_obj(doc["user"])
+            yield doc.metadata
+            yield doc.user
         else:
             raise NoSuchItemError()
 
-    async def update_user_detail(self, user: User, metadata: PixivRepoMetadata = PixivRepoMetadata()):
-        logger.debug(f"[local] update user_detail {user.id} {metadata_to_text(metadata)}")
+    async def update_user_detail(self, user: User, metadata: PixivRepoMetadata):
+        logger.debug(f"[local] update user_detail {user.id} {metadata}")
 
         if not metadata:
             metadata = PixivRepoMetadata(update_time=datetime.now(timezone.utc))
 
-        await self.mongo.db.user_detail_cache.update_one(
-            {"user.id": user.id},
-            {"$set": {
-                "user": user.dict(exclude_none=True),
-                "metadata": metadata.dict(exclude_none=True)
-            }},
+        await UserDetailCache.find_one(
+            UserDetailCache.user.id == user.id
+        ).update_one(
+            Set({
+                UserDetailCache.user: user,
+                UserDetailCache.metadata: metadata
+            }),
             upsert=True
         )
 
     # ================ search_illust ================
+
     def search_illust(self, word: str, *, offset: int = 0) \
             -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
         logger.debug(f"[local] search_illust {word}")
-        return self._get_illusts("search_illust_cache", {"word": word},
-                                 expired_in=self.conf.pixiv_search_illust_cache_expires_in,
-                                 offset=offset)
+        return self._get_illusts(SearchIllustCache, SearchIllustCache.word == word,
+                                 expired_in=self.conf.pixiv_search_illust_cache_expires_in, offset=offset)
 
-    async def update_search_illust(self, word: str, content: List[Union[Illust, LazyIllust]],
-                                   metadata: PixivRepoMetadata):
-        logger.debug(f"[local] update search_illust {word} "
-                     f"({len(content)} items) "
-                     f"{metadata_to_text(metadata)}")
-        await self._update_illusts("search_illust_cache", {"word": word}, content, metadata)
+    async def invalidate_search_illust(self, word: str):
+        logger.debug(f"[local] invalidate search_illust {word}")
+        await self.mongo.db.search_illust_cache.delete_one({"word": word})
 
     async def append_search_illust(self, word: str, content: List[Union[Illust, LazyIllust]],
                                    metadata: PixivRepoMetadata) -> bool:
+        # 返回值表示content中是否有已经存在于集合的文档，下同
         logger.debug(f"[local] append search_illust {word} "
                      f"({len(content)} items) "
-                     f"{metadata_to_text(metadata)}")
-        return await self._append_and_check_illusts("search_illust_cache", {"word": word}, content, metadata)
+                     f"{metadata}")
+        return await self._append_and_check_illusts(SearchIllustCache, SearchIllustCache.word == word,
+                                                    content=content, metadata=metadata)
 
     # ================ search_user ================
+
     def search_user(self, word: str, *, offset: int = 0) \
             -> AsyncGenerator[Union[User, PixivRepoMetadata], None]:
         logger.debug(f"[local] search_user {word}")
-        return self._get_users("search_user_cache", {"word": word},
-                               expired_in=self.conf.pixiv_search_user_cache_expires_in,
-                               offset=offset)
+        return self._get_users(SearchUserCache, SearchUserCache.word == word,
+                               expired_in=self.conf.pixiv_search_user_cache_expires_in, offset=offset)
 
-    async def update_search_user(self, word: str, content: List[User],
-                                 metadata: PixivRepoMetadata):
-        logger.debug(f"[local] update search_user {word} "
-                     f"({len(content)} items) "
-                     f"{metadata_to_text(metadata)}")
-        await self._update_users("search_user_cache", {"word": word}, content, metadata)
+    async def invalidate_search_user(self, word: str):
+        logger.debug(f"[local] invalidate search_user {word}")
+        await SearchUserCache.find_one(SearchUserCache.word == word).delete()
 
     async def append_search_user(self, word: str, content: List[User],
                                  metadata: PixivRepoMetadata) -> bool:
         logger.debug(f"[local] append search_user {word} "
                      f"({len(content)} items) "
-                     f"{metadata_to_text(metadata)}")
-        return await self._append_and_check_users("search_user_cache", {"word": word}, content, metadata)
+                     f"{metadata}")
+        return await self._append_and_check_users(SearchUserCache, SearchUserCache.word == word,
+                                                  content=content, metadata=metadata)
 
     # ================ user_illusts ================
+
     def user_illusts(self, user_id: int, *, offset: int = 0) \
             -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
         logger.debug(f"[local] user_illusts {user_id}")
-        return self._get_illusts("user_illusts_cache", {"user_id": user_id},
-                                 expired_in=self.conf.pixiv_user_illusts_cache_expires_in,
-                                 offset=offset)
+        return self._get_illusts(UserIllustsCache, UserIllustsCache.user_id == user_id,
+                                 expired_in=self.conf.pixiv_user_illusts_cache_expires_in, offset=offset)
 
-    async def update_user_illusts(self, user_id: int,
-                                  content: List[Union[Illust, LazyIllust]],
-                                  metadata: PixivRepoMetadata):
-        logger.debug(f"[local] update user_illusts {user_id} "
-                     f"({len(content)} items) "
-                     f"{metadata_to_text(metadata)}")
-        await self._update_illusts("user_illusts_cache", {"user_id": user_id}, content, metadata)
+    async def invalidate_user_illusts(self, user_id: int):
+        logger.debug(f"[local] invalidate user_illusts {user_id}")
+        await UserIllustsCache.find_one(UserIllustsCache.user_id == user_id).delete()
 
     async def append_user_illusts(self, user_id: int,
                                   content: List[Union[Illust, LazyIllust]],
                                   metadata: PixivRepoMetadata) -> bool:
         logger.debug(f"[local] append user_illusts {user_id} "
                      f"({len(content)} items) "
-                     f"{metadata_to_text(metadata)}")
-        return await self._append_and_check_illusts("user_illusts_cache", {"user_id": user_id}, content, metadata)
+                     f"{metadata}")
+        return await self._append_and_check_illusts(UserIllustsCache, UserIllustsCache.user_id == user_id,
+                                                    content=content, metadata=metadata)
 
     # ================ user_bookmarks ================
+
     def user_bookmarks(self, user_id: int = 0, *, offset: int = 0) \
             -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
         logger.debug(f"[local] user_bookmarks {user_id}")
-        return self._get_illusts("user_bookmarks_cache", {"user_id": user_id},
-                                 expired_in=self.conf.pixiv_user_bookmarks_cache_expires_in,
-                                 offset=offset)
+        return self._get_illusts(UserBookmarksCache, UserBookmarksCache.user_id == user_id,
+                                 expired_in=self.conf.pixiv_user_bookmarks_cache_expires_in, offset=offset)
 
-    async def update_user_bookmarks(self, user_id: int,
-                                    content: List[Union[Illust, LazyIllust]],
-                                    metadata: PixivRepoMetadata):
-        logger.debug(f"[local] update user_bookmarks {user_id} "
-                     f"({len(content)} items) "
-                     f"{metadata_to_text(metadata)}")
-        await self._update_illusts("user_bookmarks_cache", {"user_id": user_id}, content, metadata)
+    async def invalidate_user_bookmarks(self, user_id: int):
+        logger.debug(f"[local] invalidate user_bookmarks {user_id}")
+        await UserBookmarksCache.find_one(UserBookmarksCache.user_id == user_id).delete()
 
     async def append_user_bookmarks(self, user_id: int,
                                     content: List[Union[Illust, LazyIllust]],
                                     metadata: PixivRepoMetadata) -> bool:
         logger.debug(f"[local] append user_bookmarks {user_id} "
                      f"({len(content)} items) "
-                     f"{metadata_to_text(metadata)}")
-        return await self._append_and_check_illusts("user_bookmarks_cache", {"user_id": user_id}, content, metadata)
+                     f"{metadata}")
+        return await self._append_and_check_illusts(UserBookmarksCache, UserBookmarksCache.user_id == user_id,
+                                                    content=content, metadata=metadata)
 
     # ================ recommended_illusts ================
+
     def recommended_illusts(self, *, offset: int = 0) \
             -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
         logger.debug(f"[local] recommended_illusts")
-        return self._get_illusts("other_cache", {"type": "recommended_illusts"},
-                                 expired_in=self.conf.pixiv_other_cache_expires_in,
-                                 offset=offset)
+        return self._get_illusts(OtherIllustCache, OtherIllustCache.type == "recommended_illusts",
+                                 expired_in=self.conf.pixiv_other_cache_expires_in, offset=offset)
 
-    async def update_recommended_illusts(self, content: List[Union[Illust, LazyIllust]],
-                                         metadata: PixivRepoMetadata):
-        logger.debug(f"[local] update recommended_illusts "
-                     f"({len(content)} items) "
-                     f"{metadata_to_text(metadata)}")
-        await self._update_illusts("other_cache", {"type": "recommended_illusts"}, content, metadata)
+    async def invalidate_recommended_illusts(self):
+        logger.debug(f"[local] invalidate recommended_illusts")
+        await OtherIllustCache.find_one(OtherIllustCache.type == "recommended_illusts").delete()
 
     async def append_recommended_illusts(self, content: List[Union[Illust, LazyIllust]],
                                          metadata: PixivRepoMetadata) -> bool:
         logger.debug(f"[local] append recommended_illusts "
                      f"({len(content)} items) "
-                     f"{metadata_to_text(metadata)}")
-        return await self._append_and_check_illusts("other_cache", {"type": "recommended_illusts"}, content, metadata)
+                     f"{metadata}")
+        return await self._append_and_check_illusts(OtherIllustCache, OtherIllustCache.type == "recommended_illusts",
+                                                    content=content, metadata=metadata)
 
     # ================ related_illusts ================
+
     def related_illusts(self, illust_id: int, *, offset: int = 0) \
             -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
         logger.debug(f"[local] related_illusts {illust_id}")
-        return self._get_illusts("related_illusts_cache", {"original_illust_id": illust_id},
-                                 expired_in=self.conf.pixiv_related_illusts_cache_expires_in,
-                                 offset=offset)
+        return self._get_illusts(RelatedIllustsCache, RelatedIllustsCache.original_illust_id == illust_id,
+                                 expired_in=self.conf.pixiv_related_illusts_cache_expires_in, offset=offset)
 
-    async def update_related_illusts(self, illust_id: int, content: List[Union[Illust, LazyIllust]],
-                                     metadata: PixivRepoMetadata):
-        logger.debug(f"[local] update related_illusts {illust_id} "
-                     f"({len(content)} items) "
-                     f"{metadata_to_text(metadata)}")
-        await self._update_illusts("related_illusts_cache", {"original_illust_id": illust_id}, content, metadata)
+    async def invalidate_related_illusts(self, illust_id: int):
+        logger.debug(f"[local] invalidate related_illusts")
+        await RelatedIllustsCache.find_one(RelatedIllustsCache.original_illust_id == illust_id).delete()
 
     async def append_related_illusts(self, illust_id: int, content: List[Union[Illust, LazyIllust]],
                                      metadata: PixivRepoMetadata) -> bool:
         logger.debug(f"[local] append related_illusts {illust_id} "
                      f"({len(content)} items) "
-                     f"{metadata_to_text(metadata)}")
-        return await self._append_and_check_illusts("related_illusts_cache", {"original_illust_id": illust_id}, content,
-                                                    metadata)
+                     f"{metadata}")
+        return await self._append_and_check_illusts(RelatedIllustsCache,
+                                                    RelatedIllustsCache.original_illust_id == illust_id,
+                                                    content=content, metadata=metadata)
 
     # ================ illust_ranking ================
+
     def illust_ranking(self, mode: Union[str, RankingMode], *, offset: int = 0) \
             -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
         if isinstance(mode, str):
@@ -543,51 +511,50 @@ def illust_ranking(self, mode: Union[str, RankingMode], *, offset: int = 0) \
 
         logger.debug(f"[local] illust_ranking {mode}")
 
-        return self._get_illusts("other_cache", {"type": mode.name + "_ranking"},
-                                 expired_in=self.conf.pixiv_illust_ranking_cache_expires_in,
-                                 offset=offset)
+        return self._get_illusts(IllustRankingCache, IllustRankingCache.mode == mode,
+                                 expired_in=self.conf.pixiv_illust_ranking_cache_expires_in, offset=offset)
 
-    async def update_illust_ranking(self, mode: RankingMode, content: List[Union[Illust, LazyIllust]],
-                                    metadata: PixivRepoMetadata):
-        logger.debug(f"[local] update illust_ranking {mode} "
-                     f"({len(content)} items) "
-                     f"{metadata_to_text(metadata)}")
-        await self._update_illusts("other_cache", {"type": mode.name + "_ranking"}, content, metadata)
+    async def invalidate_illust_ranking(self, mode: RankingMode):
+        logger.debug(f"[local] invalidate illust_ranking")
+        await IllustRankingCache.find_one(IllustRankingCache.mode == mode).delete()
 
     async def append_illust_ranking(self, mode: RankingMode, content: List[Union[Illust, LazyIllust]],
                                     metadata: PixivRepoMetadata) -> bool:
         logger.debug(f"[local] append illust_ranking {mode} "
                      f"({len(content)} items) "
-                     f"{metadata_to_text(metadata)}")
-        return await self._append_and_check_illusts("other_cache", {"type": mode.name + "_ranking"}, content, metadata)
+                     f"{metadata}")
+        return await self._append_and_check_illusts(IllustRankingCache, IllustRankingCache.mode == mode,
+                                                    content=content, metadata=metadata)
 
     # ================ image ================
+
     async def image(self, illust: Illust) -> AsyncGenerator[Union[bytes, PixivRepoMetadata], None]:
         logger.debug(f"[local] image {illust.id}")
-        doc = await self.mongo.db.download_cache.find_one({"illust_id": illust.id})
+        doc = await DownloadCache.find_one(DownloadCache.illust_id == illust.id)
         if doc is not None:
-            handle_expires_in(doc, self.conf.pixiv_download_cache_expires_in)
-            yield PixivRepoMetadata.parse_obj(doc["metadata"])
-            yield doc["content"]
+            _handle_expires_in(doc.metadata, self.conf.pixiv_download_cache_expires_in)
+            yield doc.metadata
+            yield doc.content
         else:
             raise NoSuchItemError()
 
     async def update_image(self, illust_id: int, content: bytes,
-                           metadata: PixivRepoMetadata = PixivRepoMetadata()):
+                           metadata: PixivRepoMetadata):
         logger.debug(f"[local] update image {illust_id} "
-                     f"{metadata_to_text(metadata)}")
-
-        await self.mongo.db.download_cache.update_one(
-            {"illust_id": illust_id},
-            {"$set": {
-                "content": bson.Binary(content),
-                "metadata": metadata.dict(exclude_none=True)
-            }},
+                     f"{metadata}")
+
+        await DownloadCache.find_one(
+            DownloadCache.illust_id == illust_id
+        ).update(
+            Set({
+                DownloadCache.content: bson.Binary(content),
+                DownloadCache.metadata: metadata
+            }),
             upsert=True
         )
 
-    async def invalidate_cache(self):
-        logger.debug(f"[local] invalidate_cache")
+    async def invalidate_all(self):
+        logger.debug(f"[local] invalidate_all")
         await self.mongo.db.download_cache.delete_many({})
         await self.mongo.db.illust_detail_cache.delete_many({})
         await self.mongo.db.user_detail_cache.delete_many({})
@@ -597,3 +564,6 @@ async def invalidate_cache(self):
         await self.mongo.db.user_illusts_cache.delete_many({})
         await self.mongo.db.user_bookmarks_cache.delete_many({})
         await self.mongo.db.other_cache.delete_many({})
+
+
+__all__ = ("LocalPixivRepo",)
diff --git a/src/nonebot_plugin_pixivbot/data/pixiv_repo/mediator.py b/src/nonebot_plugin_pixivbot/data/pixiv_repo/mediator.py
index c990c04..960801a 100644
--- a/src/nonebot_plugin_pixivbot/data/pixiv_repo/mediator.py
+++ b/src/nonebot_plugin_pixivbot/data/pixiv_repo/mediator.py
@@ -2,7 +2,7 @@
 from typing import List, AsyncGenerator, TypeVar, Union, Callable, Awaitable, Optional, Any, Mapping
 
 from .abstract_repo import PixivRepoMetadata
-from .local_repo import NoSuchItemError, CacheExpiredError
+from .errors import NoSuchItemError, CacheExpiredError
 
 T = TypeVar("T")
 
@@ -37,7 +37,7 @@ async def mediate_single(cache_factory: Callable[..., AsyncGenerator[Union[T, Pi
             raise RuntimeError("no metadata")
 
 
-async def _load_many_from_local_and_remote_and_update(
+async def _load_many_from_local_and_remote_and_append(
         cache_factory: Callable[..., AsyncGenerator[Union[T, PixivRepoMetadata], None]],
         remote_factory: Callable[..., AsyncGenerator[Union[T, PixivRepoMetadata], None]],
         query_kwargs: Mapping[str, Any],
@@ -109,6 +109,7 @@ async def _load_many_from_remote_and_append(
 async def mediate_many(cache_factory: Callable[..., AsyncGenerator[Union[T, PixivRepoMetadata], None]],
                        remote_factory: Callable[..., AsyncGenerator[Union[T, PixivRepoMetadata], None]],
                        query_kwargs: Mapping[str, Any],
+                       cache_invalidator: Callable[[], Awaitable[Any]],
                        cache_appender: Callable[[List[T], Optional[PixivRepoMetadata]], Awaitable[Any]],
                        max_item: int = 2 ** 31,
                        max_page: int = 2 ** 31,
@@ -117,11 +118,16 @@ async def mediate_many(cache_factory: Callable[..., AsyncGenerator[Union[T, Pixi
         if force_expiration:
             raise NoSuchItemError()
 
-        async for x in _load_many_from_local_and_remote_and_update(cache_factory, remote_factory, query_kwargs,
-                                                                   cache_appender,
-                                                                   max_item, max_page):
+        async for x in _load_many_from_local_and_remote_and_append(cache_factory, remote_factory, query_kwargs,
+                                                                   cache_appender, max_item, max_page):
             yield x
-    except (CacheExpiredError, NoSuchItemError):
+    except NoSuchItemError:
+        async for x in _load_many_from_remote_and_append(remote_factory, query_kwargs,
+                                                         cache_appender,
+                                                         max_item, max_page):
+            yield x
+    except CacheExpiredError:
+        await cache_invalidator()
         async for x in _load_many_from_remote_and_append(remote_factory, query_kwargs,
                                                          cache_appender,
                                                          max_item, max_page):
@@ -148,9 +154,8 @@ async def mediate_append(cache_factory: Callable[..., AsyncGenerator[Union[T, Pi
             else:
                 raise CacheExpiredError(metadata)
 
-        async for x in _load_many_from_local_and_remote_and_update(cache_factory, remote_factory, query_kwargs,
-                                                                   cache_appender,
-                                                                   max_item, max_page):
+        async for x in _load_many_from_local_and_remote_and_append(cache_factory, remote_factory, query_kwargs,
+                                                                   cache_appender, max_item, max_page):
             yield x
     except NoSuchItemError:
         async for x in _load_many_from_remote_and_append(remote_factory, query_kwargs,
@@ -184,9 +189,8 @@ async def mediate_append(cache_factory: Callable[..., AsyncGenerator[Union[T, Pi
                 loaded_items += 1
                 buffer.append(x)
 
-        async for x in _load_many_from_local_and_remote_and_update(cache_factory, remote_factory, query_kwargs,
-                                                                   cache_appender,
-                                                                   max_item, max_page):
+        async for x in _load_many_from_local_and_remote_and_append(cache_factory, remote_factory, query_kwargs,
+                                                                   cache_appender, max_item, max_page):
             yield x
 
 
diff --git a/src/nonebot_plugin_pixivbot/data/pixiv_repo/models.py b/src/nonebot_plugin_pixivbot/data/pixiv_repo/models.py
new file mode 100644
index 0000000..4b43bf3
--- /dev/null
+++ b/src/nonebot_plugin_pixivbot/data/pixiv_repo/models.py
@@ -0,0 +1,159 @@
+from datetime import datetime, timezone
+from typing import Optional, List
+
+from beanie import Document
+from pydantic import BaseModel
+from pymongo import IndexModel
+
+from nonebot_plugin_pixivbot import context
+from nonebot_plugin_pixivbot.config import Config
+from nonebot_plugin_pixivbot.data.source import MongoDataSource
+from nonebot_plugin_pixivbot.enums import RankingMode
+from nonebot_plugin_pixivbot.model import Illust, User
+
+conf = context.require(Config)
+
+
+class PixivRepoMetadata(BaseModel):
+    update_time: datetime = datetime.now(timezone.utc)
+    pages: Optional[int]
+    next_qs: Optional[dict]
+
+    def __str__(self):
+        return '(' + ', '.join(map(lambda kv: kv[0] + '=' + str(kv[1]), self.dict(exclude_none=True).items())) + ')'
+
+
+class PixivRepoCache(Document):
+    metadata: PixivRepoMetadata
+
+
+class IllustSetCache(PixivRepoCache):
+    illust_id: List[int]
+
+
+class UserSetCache(PixivRepoCache):
+    user_id: List[int]
+
+
+class DownloadCache(PixivRepoCache):
+    illust_id: int
+    content: bytes
+
+    class Settings:
+        name = "download_cache"
+        indexes = [
+            IndexModel([("illust_id", 1)], unique=True),
+            IndexModel([("update_time", 1)], expireAfterSeconds=conf.pixiv_download_cache_expires_in)
+        ]
+
+
+class IllustDetailCache(PixivRepoCache):
+    illust: Illust
+
+    class Settings:
+        name = "illust_detail_cache"
+        indexes = [
+            IndexModel([("illust.id", 1)], unique=True),
+            IndexModel([("update_time", 1)], expireAfterSeconds=conf.pixiv_illust_detail_cache_expires_in)
+        ]
+
+
+class IllustRankingCache(IllustSetCache):
+    mode: RankingMode
+
+    class Settings:
+        name = "illust_ranking_cache"
+        indexes = [
+            IndexModel([("mode", 1)], unique=True),
+            IndexModel([("update_time", 1)], expireAfterSeconds=conf.pixiv_illust_ranking_cache_expires_in)
+        ]
+
+
+class OtherIllustCache(IllustSetCache):
+    type: str
+
+    class Settings:
+        name = "other_cache"
+        indexes = [
+            IndexModel([("type", 1)], unique=True),
+            IndexModel([("update_time", 1)], expireAfterSeconds=conf.pixiv_other_cache_expires_in)
+        ]
+
+
+class RelatedIllustsCache(IllustSetCache):
+    original_illust_id: int
+
+    class Settings:
+        name = "related_illusts_cache"
+        indexes = [
+            IndexModel([("original_illust_id", 1)], unique=True),
+            IndexModel([("update_time", 1)], expireAfterSeconds=conf.pixiv_related_illusts_cache_expires_in)
+        ]
+
+
+class SearchIllustCache(IllustSetCache):
+    word: str
+
+    class Settings:
+        name = "search_illust_cache"
+        indexes = [
+            IndexModel([("word", 1)], unique=True),
+            IndexModel([("update_time", 1)], expireAfterSeconds=conf.pixiv_search_illust_cache_delete_in)
+        ]
+
+
+class SearchUserCache(UserSetCache):
+    word: str
+    user_id: List[int]
+
+    class Settings:
+        name = "search_user_cache"
+        indexes = [
+            IndexModel([("word", 1)], unique=True),
+            IndexModel([("update_time", 1)], expireAfterSeconds=conf.pixiv_search_user_cache_delete_in)
+        ]
+
+
+class UserBookmarksCache(IllustSetCache):
+    user_id: int
+
+    class Settings:
+        name = "user_bookmarks_cache"
+        indexes = [
+            IndexModel([("user_id", 1)], unique=True),
+            IndexModel([("update_time", 1)], expireAfterSeconds=conf.pixiv_user_bookmarks_cache_delete_in)
+        ]
+
+
+class UserDetailCache(PixivRepoCache):
+    user: User
+
+    class Settings:
+        name = "user_detail_cache"
+        indexes = [
+            IndexModel([("user.id", 1)], unique=True),
+            IndexModel([("update_time", 1)], expireAfterSeconds=conf.pixiv_user_detail_cache_expires_in)
+        ]
+
+
+class UserIllustsCache(IllustSetCache):
+    user_id: int
+
+    class Settings:
+        name = "user_illusts_cache"
+        indexes = [
+            IndexModel([("user_id", 1)], unique=True),
+            IndexModel([("update_time", 1)], expireAfterSeconds=conf.pixiv_user_illusts_cache_delete_in)
+        ]
+
+
+context.require(MongoDataSource).document_models.extend([
+    DownloadCache, IllustDetailCache, IllustRankingCache, OtherIllustCache,
+    RelatedIllustsCache, SearchIllustCache, SearchUserCache,
+    UserBookmarksCache, UserDetailCache, UserIllustsCache
+])
+
+__all__ = ("PixivRepoMetadata", "PixivRepoCache", "IllustSetCache",
+           "DownloadCache", "IllustDetailCache", "IllustRankingCache", "OtherIllustCache",
+           "RelatedIllustsCache", "SearchIllustCache", "SearchUserCache",
+           "UserBookmarksCache", "UserDetailCache", "UserIllustsCache")
diff --git a/src/nonebot_plugin_pixivbot/data/pixiv_repo/remote_repo.py b/src/nonebot_plugin_pixivbot/data/pixiv_repo/remote_repo.py
index 4d40145..87c3c75 100644
--- a/src/nonebot_plugin_pixivbot/data/pixiv_repo/remote_repo.py
+++ b/src/nonebot_plugin_pixivbot/data/pixiv_repo/remote_repo.py
@@ -1,4 +1,6 @@
 from asyncio import sleep, create_task, CancelledError, Semaphore, Task
+from contextlib import asynccontextmanager
+from datetime import datetime, timedelta
 from functools import wraps
 from io import BytesIO
 from typing import TypeVar, Optional, Awaitable, List, Callable, Tuple, AsyncGenerator, Union
@@ -8,10 +10,9 @@
 from pixivpy_async.error import TokenError
 
 from nonebot_plugin_pixivbot.config import Config
-from nonebot_plugin_pixivbot.data.local_tag_repo import LocalTagRepo
 from nonebot_plugin_pixivbot.enums import DownloadQuantity, RankingMode
 from nonebot_plugin_pixivbot.model import Illust, User, UserPreview
-from nonebot_plugin_pixivbot.utils.errors import QueryError
+from nonebot_plugin_pixivbot.utils.errors import QueryError, RateLimitError
 from nonebot_plugin_pixivbot.utils.lifecycler import on_startup, on_shutdown
 from .abstract_repo import AbstractPixivRepo, PixivRepoMetadata
 from .compressor import Compressor
@@ -21,7 +22,7 @@
 T = TypeVar("T")
 
 
-def auto_retry(func):
+def _auto_retry(func):
     @wraps(func)
     async def wrapped(*args, **kwargs):
         err = None
@@ -30,6 +31,8 @@ async def wrapped(*args, **kwargs):
                 return await func(*args, **kwargs)
             except CancelledError as e:
                 raise e
+            except QueryError as e:
+                raise e
             except Exception as e:
                 logger.debug(f"Retrying... {t + 1}/10")
                 logger.exception(e)
@@ -40,17 +43,10 @@ async def wrapped(*args, **kwargs):
     return wrapped
 
 
-def check_error_in_raw_result(raw_result: dict):
-    if "error" in raw_result:
-        raise QueryError(raw_result["error"]["user_message"]
-                         or raw_result["error"]["message"] or raw_result["error"]["reason"])
-
-
 @context.inject
 @context.register_eager_singleton()
 class RemotePixivRepo(AbstractPixivRepo):
     _conf: Config
-    _local_tags: LocalTagRepo
     _compressor: Compressor
 
     # noinspection PyTypeChecker
@@ -60,6 +56,8 @@ def __init__(self):
         self._papi: AppPixivAPI = None
         self._refresh_daemon: Task = None
 
+        self._got_rate_limit: Optional[datetime] = None
+
         self.user_id = 0
 
         on_startup(self.start, replay=True)
@@ -129,11 +127,43 @@ async def shutdown(self):
         await self._pclient.close()
         self._refresh_daemon.cancel()
 
-    async def load_page(self, papi_search_func: Callable[..., Awaitable[dict]],
-                        element_list_name: str,
-                        *, mapper: Optional[Callable[[dict], T]] = None,
-                        filter_item: Optional[Callable[[T], bool]] = None,
-                        **kwargs) -> Tuple[List[T], PixivRepoMetadata]:
+    def _check_error_in_raw_result(self, raw_result: dict):
+        if "error" in raw_result:
+            message = raw_result["error"]["user_message"] \
+                      or raw_result["error"]["message"] \
+                      or raw_result["error"]["reason"]
+            if message == "Rate Limit":
+                # 处理RateLimit
+                self._got_rate_limit = datetime.utcnow()
+                raise RateLimitError()
+            else:
+                raise QueryError(message)
+
+    @asynccontextmanager
+    async def _query(self):
+        # RateLimit期间不进行查询
+        if self._got_rate_limit is not None:
+            now = datetime.utcnow()
+            if now - self._got_rate_limit >= timedelta(minutes=2):
+                self._got_rate_limit = None
+            else:
+                raise RateLimitError()
+
+        async with self._sema:
+            yield
+
+    async def _load_raw_page(self, papi_search_func: Callable[..., Awaitable[dict]],
+                             **kwargs):
+        async with self._query():
+            raw_result = await papi_search_func(**kwargs)
+            self._check_error_in_raw_result(raw_result)
+            return raw_result
+
+    async def _load_page(self, papi_search_func: Callable[..., Awaitable[dict]],
+                         element_list_name: str,
+                         *, mapper: Optional[Callable[[dict], T]] = None,
+                         filter_item: Optional[Callable[[T], bool]] = None,
+                         **kwargs) -> Tuple[List[T], PixivRepoMetadata]:
         """
         加载一页
         :param papi_search_func: PixivPy-Async的加载方法
@@ -143,12 +173,7 @@ async def load_page(self, papi_search_func: Callable[..., Awaitable[dict]],
         :param kwargs: 传给papi_search_func的参数
         :return: 加载结果
         """
-        papi_search_func = auto_retry(papi_search_func)
-
-        async with self._sema:
-            raw_result = await papi_search_func(**kwargs)
-
-        check_error_in_raw_result(raw_result)
+        raw_result = await self._load_raw_page(papi_search_func, **kwargs)
 
         pending = []
         for x in raw_result[element_list_name]:
@@ -162,11 +187,11 @@ async def load_page(self, papi_search_func: Callable[..., Awaitable[dict]],
 
         return pending, metadata
 
-    async def load_many_pages(self, papi_search_func: Callable[..., Awaitable[dict]],
-                              element_list_name: str,
-                              *, mapper: Optional[Callable[[dict], T]] = None,
-                              filter_item: Optional[Callable[[T], bool]] = None,
-                              **kwargs) -> AsyncGenerator[Tuple[List[T], PixivRepoMetadata], None]:
+    async def _load_many_pages(self, papi_search_func: Callable[..., Awaitable[dict]],
+                               element_list_name: str,
+                               *, mapper: Optional[Callable[[dict], T]] = None,
+                               filter_item: Optional[Callable[[T], bool]] = None,
+                               **kwargs) -> AsyncGenerator[Tuple[List[T], PixivRepoMetadata], None]:
         """
         一次加载多页
         :param papi_search_func: PixivPy-Async的加载方法
@@ -181,9 +206,8 @@ async def load_many_pages(self, papi_search_func: Callable[..., Awaitable[dict]]
         next_qs = kwargs
 
         while True:
-            page, metadata = await self.load_page(papi_search_func, element_list_name,
-                                                  mapper=mapper, filter_item=filter_item,
-                                                  **next_qs)
+            page, metadata = await self._load_page(papi_search_func, element_list_name, mapper=mapper,
+                                                   filter_item=filter_item, **next_qs)
 
             loaded_pages = loaded_pages + 1
             loaded_items += len(page)
@@ -234,10 +258,9 @@ def illust_filter(illust: Illust) -> bool:
         broken = 0
 
         yield PixivRepoMetadata(pages=0, next_qs=kwargs)
-        async for page, metadata in self.load_many_pages(papi_search_func, "illusts",
-                                                         mapper=lambda x: Illust.parse_obj(x),
-                                                         filter_item=illust_filter,
-                                                         **kwargs):
+        async for page, metadata in self._load_many_pages(papi_search_func, "illusts",
+                                                          mapper=lambda x: Illust.parse_obj(x),
+                                                          filter_item=illust_filter, **kwargs):
             for item in page:
                 total += 1
                 if "limit_unknown_360.png" in item.image_urls.large:
@@ -252,60 +275,56 @@ async def _get_user_previews(self, papi_search_func: Callable[[], Awaitable[dict
                                  **kwargs) \
             -> AsyncGenerator[Union[PixivRepoMetadata, UserPreview], None]:
         yield PixivRepoMetadata(pages=0, next_qs=kwargs)
-        async with self._sema:
-            async for page, metadata in self.load_many_pages(papi_search_func, "user_previews",
-                                                             mapper=lambda x: UserPreview.parse_obj(x),
-                                                             **kwargs):
-                for item in page:
-                    item: UserPreview
-                    if block_tags is not None:
-                        illusts = []
-                        for x in item.illusts:
-                            for tag in block_tags:
-                                if x.has_tag(tag):
-                                    break
-                            else:
-                                illusts.append(x)
-                        item.illusts = illusts
-                    yield item
-                yield metadata
+        async for page, metadata in self._load_many_pages(papi_search_func, "user_previews",
+                                                          mapper=lambda x: UserPreview.parse_obj(x), **kwargs):
+            for item in page:
+                item: UserPreview
+                if block_tags is not None:
+                    illusts = []
+                    for x in item.illusts:
+                        for tag in block_tags:
+                            if x.has_tag(tag):
+                                break
+                        else:
+                            illusts.append(x)
+                    item.illusts = illusts
+                yield item
+            yield metadata
 
     async def _get_users(self, papi_search_func: Callable[[], Awaitable[dict]], **kwargs) \
             -> AsyncGenerator[Union[PixivRepoMetadata, User], None]:
         yield PixivRepoMetadata(pages=0, next_qs=kwargs)
-        async with self._sema:
-            async for page, metadata in self.load_many_pages(papi_search_func, "user_previews",
-                                                             mapper=lambda x: User.parse_obj(x["user"]),
-                                                             **kwargs):
-                for item in page:
-                    yield item
-                yield metadata
-
-    @auto_retry
-    async def _illust_detail(self, illust_id: int, **kwargs) -> Illust:
-        logger.debug(f"[remote] illust_detail {illust_id}")
-        async with self._sema:
+        async for page, metadata in self._load_many_pages(papi_search_func, "user_previews",
+                                                          mapper=lambda x: User.parse_obj(x["user"]), **kwargs):
+            for item in page:
+                yield item
+            yield metadata
+
+    @_auto_retry
+    async def _raw_illust_detail(self, illust_id: int, **kwargs) -> dict:
+        async with self._query():
             raw_result = await self._papi.illust_detail(illust_id, **kwargs)
-            check_error_in_raw_result(raw_result)
-            return Illust.parse_obj(raw_result["illust"])
+            self._check_error_in_raw_result(raw_result)
+            return raw_result
 
     async def illust_detail(self, illust_id: int, **kwargs) -> AsyncGenerator[Illust, None]:
-        data = await self._illust_detail(illust_id, **kwargs)
+        logger.debug(f"[remote] illust_detail {illust_id}")
+        raw_result = await self._raw_illust_detail(illust_id, **kwargs)
         yield PixivRepoMetadata()
-        yield data
+        yield Illust.parse_obj(raw_result["illust"])
 
-    @auto_retry
-    async def _user_detail(self, user_id: int, **kwargs) -> User:
-        logger.debug(f"[remote] user_detail {user_id}")
-        async with self._sema:
+    @_auto_retry
+    async def _raw_user_detail(self, user_id: int, **kwargs) -> dict:
+        async with self._query():
             raw_result = await self._papi.user_detail(user_id, **kwargs)
-            check_error_in_raw_result(raw_result)
-            return User.parse_obj(raw_result["user"])
+            self._check_error_in_raw_result(raw_result)
+            return raw_result
 
     async def user_detail(self, user_id: int, **kwargs) -> AsyncGenerator[User, None]:
-        data = await self._user_detail(user_id, **kwargs)
+        logger.debug(f"[remote] user_detail {user_id}")
+        raw_result = await self._raw_user_detail(user_id, **kwargs)
         yield PixivRepoMetadata()
-        yield data
+        yield User.parse_obj(raw_result["user"])
 
     def search_illust(self, word: str, **kwargs) \
             -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
@@ -395,40 +414,40 @@ def illust_ranking(self, mode: Union[str, RankingMode], **kwargs) \
         if isinstance(mode, str):
             mode = RankingMode[mode]
 
-        logger.debug(f"[repo] illust_ranking {mode}")
+        logger.debug(f"[remote] illust_ranking {mode}")
         return self._get_illusts(self._papi.illust_ranking,
                                  block_tags=self._conf.pixiv_block_tags,
                                  mode=mode.name, **kwargs)
 
-    @auto_retry
-    async def _image(self, illust: Illust, **kwargs) -> bytes:
-        logger.debug(f"[remote] image {illust.id}")
-        async with self._sema:
-            download_quantity = self._conf.pixiv_download_quantity
-            custom_domain = self._conf.pixiv_download_custom_domain
+    @_auto_retry
+    async def _raw_image(self, illust: Illust, **kwargs) -> bytes:
+        download_quantity = self._conf.pixiv_download_quantity
+        custom_domain = self._conf.pixiv_download_custom_domain
 
-            if download_quantity == DownloadQuantity.original:
-                if len(illust.meta_pages) > 0:
-                    url = illust.meta_pages[0].image_urls.original
-                else:
-                    url = illust.meta_single_page.original_image_url
+        if download_quantity == DownloadQuantity.original:
+            if len(illust.meta_pages) > 0:
+                url = illust.meta_pages[0].image_urls.original
             else:
-                url = getattr(illust.image_urls, download_quantity.name)
+                url = illust.meta_single_page.original_image_url
+        else:
+            url = getattr(illust.image_urls, download_quantity.name)
 
-            if custom_domain is not None:
-                url = url.replace("i.pximg.net", custom_domain)
+        if custom_domain is not None:
+            url = url.replace("i.pximg.net", custom_domain)
 
+        async with self._query():
             with BytesIO() as bio:
                 await self._papi.download(url, fname=bio, **kwargs)
                 content = bio.getvalue()
-                content = await self._compressor.compress(content)
                 return content
 
     async def image(self, illust: Illust, **kwargs) \
             -> AsyncGenerator[Union[bytes, PixivRepoMetadata], None]:
-        data = await self._image(illust, **kwargs)
+        logger.debug(f"[remote] image {illust.id}")
+        content = await self._raw_image(illust, **kwargs)
+        content = await self._compressor.compress(content)
         yield PixivRepoMetadata()
-        yield data
+        yield content
 
 
 __all__ = ("RemotePixivRepo",)
diff --git a/src/nonebot_plugin_pixivbot/data/pixiv_repo/repo.py b/src/nonebot_plugin_pixivbot/data/pixiv_repo/repo.py
index a67d1e5..b959fd6 100644
--- a/src/nonebot_plugin_pixivbot/data/pixiv_repo/repo.py
+++ b/src/nonebot_plugin_pixivbot/data/pixiv_repo/repo.py
@@ -9,12 +9,12 @@
 from nonebot_plugin_pixivbot.enums import RankingMode
 from nonebot_plugin_pixivbot.model import Illust, User, UserPreview
 from nonebot_plugin_pixivbot.utils.shared_agen import SharedAsyncGeneratorManager
-from . import LazyIllust
 from .abstract_repo import AbstractPixivRepo
-from .abstract_repo import PixivRepoMetadata
 from .enums import PixivResType, CacheStrategy
+from .lazy_illust import LazyIllust
 from .local_repo import LocalPixivRepo
 from .mediator import mediate_single, mediate_many, mediate_append
+from .models import PixivRepoMetadata
 from .pkg_context import context
 from .remote_repo import RemotePixivRepo
 
@@ -64,7 +64,7 @@ def user_detail_factory(self, user_id: int,
 
     def search_illust_factory(self, word: str,
                               cache_strategy: CacheStrategy) -> AsyncGenerator[LazyIllust, None]:
-        return mediate_many(
+        return mediate_append(
             cache_factory=self.local.search_illust,
             remote_factory=self.remote.search_illust,
             query_kwargs={"word": word},
@@ -76,7 +76,7 @@ def search_illust_factory(self, word: str,
 
     def search_user_factory(self, word: str,
                             cache_strategy: CacheStrategy) -> AsyncGenerator[User, None]:
-        return mediate_many(
+        return mediate_append(
             cache_factory=self.local.search_user,
             remote_factory=self.remote.search_user,
             query_kwargs={"word": word},
@@ -114,6 +114,7 @@ def recommended_illusts_factory(self, cache_strategy: CacheStrategy) -> AsyncGen
             cache_factory=self.local.recommended_illusts,
             remote_factory=self.remote.recommended_illusts,
             query_kwargs={},
+            cache_invalidator=self.local.invalidate_recommended_illusts,
             cache_appender=lambda data, metadata: self.local.append_recommended_illusts(data, metadata),
             max_item=self.conf.pixiv_random_recommended_illust_max_item,
             max_page=self.conf.pixiv_random_recommended_illust_max_page,
@@ -126,6 +127,7 @@ def related_illusts_factory(self, illust_id: int,
             cache_factory=self.local.related_illusts,
             remote_factory=self.remote.related_illusts,
             query_kwargs={"illust_id": illust_id},
+            cache_invalidator=lambda: self.local.invalidate_related_illusts(illust_id),
             cache_appender=lambda data, metadata: self.local.append_related_illusts(illust_id, data, metadata),
             max_item=self.conf.pixiv_random_related_illust_max_item,
             max_page=self.conf.pixiv_random_related_illust_max_page,
@@ -138,6 +140,7 @@ def illust_ranking_factory(self, mode: RankingMode,
             cache_factory=self.local.illust_ranking,
             remote_factory=self.remote.illust_ranking,
             query_kwargs={"mode": mode},
+            cache_invalidator=lambda: self.local.invalidate_illust_ranking(mode),
             cache_appender=lambda data, metadata: self.local.append_illust_ranking(mode, data, metadata),
             max_item=self.conf.pixiv_ranking_fetch_item,
             force_expiration=cache_strategy == CacheStrategy.FORCE_EXPIRATION,
@@ -166,9 +169,7 @@ def image_factory(self, illust_id: int, illust: Illust,
         PixivResType.IMAGE: image_factory,
     }
 
-    def agen_factory(self, identifier: SharedAgenIdentifier,
-                     cache_strategy: CacheStrategy = CacheStrategy.NORMAL,
-                     **kwargs) -> AsyncGenerator[Any, None]:
+    def agen(self, identifier: SharedAgenIdentifier, cache_strategy: CacheStrategy, **kwargs) -> AsyncGenerator[Any, None]:
         if identifier.type in self.factories:
             merged_kwargs = identifier.kwargs | kwargs
             # noinspection PyTypeChecker
@@ -210,7 +211,7 @@ class PixivRepo(AbstractPixivRepo):
 
     async def invalidate_cache(self):
         self._shared_agen_mgr.invalidate_all()
-        await self._local.invalidate_cache()
+        await self._local.invalidate_all()
 
     async def illust_detail(self, illust_id: int,
                             cache_strategy: CacheStrategy = CacheStrategy.NORMAL) -> AsyncGenerator[Illust, None]:
diff --git a/src/nonebot_plugin_pixivbot/data/source/mongo/meta_info.py b/src/nonebot_plugin_pixivbot/data/source/mongo/meta_info.py
new file mode 100644
index 0000000..1a2da54
--- /dev/null
+++ b/src/nonebot_plugin_pixivbot/data/source/mongo/meta_info.py
@@ -0,0 +1,21 @@
+from typing import Any
+
+from beanie import Document
+from pymongo import IndexModel
+
+from nonebot_plugin_pixivbot import context
+from nonebot_plugin_pixivbot.data.source import MongoDataSource
+
+
+class MetaInfo(Document):
+    key: str
+    value: Any
+
+    class Settings:
+        name = "meta_info"
+        indexes = [
+            IndexModel([("key", 1)], unique=True),
+        ]
+
+
+context.require(MongoDataSource).document_models.append(MetaInfo)
diff --git a/src/nonebot_plugin_pixivbot/data/source/mongo/mongo.py b/src/nonebot_plugin_pixivbot/data/source/mongo/mongo.py
index 689392e..8b7f7e3 100644
--- a/src/nonebot_plugin_pixivbot/data/source/mongo/mongo.py
+++ b/src/nonebot_plugin_pixivbot/data/source/mongo/mongo.py
@@ -1,6 +1,11 @@
+from asyncio import create_task, gather
+from typing import List, Type
+
+from beanie import init_beanie, Document
 from bson import CodecOptions
 from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase
 from nonebot import logger
+from pymongo import IndexModel
 from pymongo.errors import OperationFailure
 
 from nonebot_plugin_pixivbot.config import Config
@@ -21,6 +26,8 @@ def __init__(self):
         self._client = None
         self._db = None
 
+        self.document_models: List[Type[Document]] = []
+
         on_startup(self.initialize, replay=True)
         on_shutdown(self.finalize)
 
@@ -36,7 +43,7 @@ def db(self):
             raise DataSourceNotReadyError()
 
     @staticmethod
-    async def _get_db_version(db: AsyncIOMotorDatabase) -> int:
+    async def _raw_get_db_version(db: AsyncIOMotorDatabase) -> int:
         version = await db["meta_info"].find_one({"key": "db_version"})
         if version is None:
             await db["meta_info"].insert_one({"key": "db_version", "value": 1})
@@ -45,7 +52,7 @@ async def _get_db_version(db: AsyncIOMotorDatabase) -> int:
             return version["value"]
 
     @staticmethod
-    async def _set_db_version(db_version: int, db: AsyncIOMotorDatabase):
+    async def _raw_set_db_version(db: AsyncIOMotorDatabase, db_version: int):
         await db["meta_info"].update_one({"key": "db_version"},
                                          {"$set": {
                                              "value": db_version
@@ -53,27 +60,19 @@ async def _set_db_version(db_version: int, db: AsyncIOMotorDatabase):
                                          upsert=True)
 
     @staticmethod
-    async def _ensure_index(db: AsyncIOMotorDatabase, coll_name: str, indexes: list[tuple[str, int]], **kwargs):
-        try:
-            await db[coll_name].create_index(indexes, **kwargs)
-        except OperationFailure:
-            logger.info(f"Index in {coll_name}: recreated")
-            await db[coll_name].drop_index(indexes)
-            await db[coll_name].create_index(indexes, **kwargs)
-
-    @staticmethod
-    async def _ensure_ttl_index(db: AsyncIOMotorDatabase, coll_name: str, expires_in: int):
+    async def _ensure_ttl_index(db: AsyncIOMotorDatabase, coll_name: str, index: IndexModel):
         try:
-            await db[coll_name].create_index([("update_time", 1)], expireAfterSeconds=expires_in)
+            await db[coll_name].create_indexes([index])
         except OperationFailure:
             await db.command({
                 "collMod": coll_name,
                 "index": {
-                    "keyPattern": {"update_time": 1},
-                    "expireAfterSeconds": expires_in,
+                    "keyPattern": index.document["key"],
+                    "expireAfterSeconds": index.document["expireAfterSeconds"]
                 }
             })
-            logger.success(f"TTL Index in {coll_name}: expireAfterSeconds changed to {expires_in}")
+            logger.success(
+                f"Index in {coll_name}: expireAfterSeconds changed to {index.document['expireAfterSeconds']}")
 
     async def initialize(self):
         client = AsyncIOMotorClient(self.conf.pixiv_mongo_conn_url)
@@ -81,55 +80,29 @@ async def initialize(self):
         db = client[self.conf.pixiv_mongo_database_name].with_options(options)
 
         # migrate
-        db_version = await self._get_db_version(db)
+        db_version = await self._raw_get_db_version(db)
         await self.mongo_migration_mgr.perform_migration(db, db_version, self.app_db_version)
-        await self._set_db_version(self.app_db_version, db)
-
-        # ensure index
-        await self._ensure_index(db, 'meta_info', [("key", 1)], unique=True)
-
-        await self._ensure_index(db, 'pixiv_binding', [("adapter", 1), ("user_id", 1)], unique=True)
-
-        await self._ensure_index(db, 'subscription', [("subscriber.adapter", 1)])
-        await self._ensure_index(db, 'subscription', [("subscriber", 1),
-                                                      ("type", 1)], unique=True)
-
-        await self._ensure_index(db, 'watch_task', [("subscriber.adapter", 1)])
-        await self._ensure_index(db, 'watch_task', [("subscriber.adapter", 1),
-                                                    ("type", 1)])
-
-        await self._ensure_index(db, 'local_tags', [("name", 1)], unique=True)
-        await self._ensure_index(db, 'local_tags', [("translated_name", 1)])
-
-        await self._ensure_index(db, 'download_cache', [("illust_id", 1)], unique=True)
-        await self._ensure_ttl_index(db, 'download_cache', self.conf.pixiv_download_cache_expires_in)
-
-        await self._ensure_index(db, 'illust_detail_cache', [("illust.id", 1)], unique=True)
-        await self._ensure_ttl_index(db, 'illust_detail_cache', self.conf.pixiv_illust_detail_cache_expires_in)
-
-        await self._ensure_index(db, 'user_detail_cache', [("user.id", 1)], unique=True)
-        await self._ensure_ttl_index(db, 'user_detail_cache', self.conf.pixiv_user_detail_cache_expires_in)
-
-        await self._ensure_index(db, 'illust_ranking_cache', [("mode", 1)], unique=True)
-        await self._ensure_ttl_index(db, 'illust_ranking_cache', self.conf.pixiv_illust_ranking_cache_expires_in)
-
-        await self._ensure_index(db, 'search_illust_cache', [("word", 1)], unique=True)
-        await self._ensure_ttl_index(db, 'search_illust_cache', self.conf.pixiv_search_illust_cache_expires_in)
-
-        await self._ensure_index(db, 'search_user_cache', [("word", 1)], unique=True)
-        await self._ensure_ttl_index(db, 'search_user_cache', self.conf.pixiv_search_user_cache_expires_in)
-
-        await self._ensure_index(db, 'user_illusts_cache', [("user_id", 1)], unique=True)
-        await self._ensure_ttl_index(db, 'user_illusts_cache', self.conf.pixiv_user_illusts_cache_delete_in)
-
-        await self._ensure_index(db, 'user_bookmarks_cache', [("user_id", 1)], unique=True)
-        await self._ensure_ttl_index(db, 'user_bookmarks_cache', self.conf.pixiv_user_bookmarks_cache_delete_in)
-
-        await self._ensure_index(db, 'related_illusts_cache', [("original_illust_id", 1)], unique=True)
-        await self._ensure_ttl_index(db, 'related_illusts_cache', self.conf.pixiv_related_illusts_cache_expires_in)
-
-        await self._ensure_index(db, 'other_cache', [("type", 1)], unique=True)
-        await self._ensure_ttl_index(db, 'other_cache', self.conf.pixiv_other_cache_expires_in)
+        await self._raw_set_db_version(db, self.app_db_version)
+
+        # ensure ttl indexes (before init beanie)
+        index_tasks = []
+        for model in self.document_models:
+            name = model.__name__
+
+            settings = getattr(model, "Settings", None)
+            if settings:
+                settings_name = getattr(settings, "name")
+                if settings_name:
+                    name = settings_name
+
+                settings_indexes = getattr(settings, "indexes")
+                if settings_indexes:
+                    for index in settings_indexes:
+                        if isinstance(index, IndexModel) and index.document.get("expireAfterSeconds", None):
+                            index_tasks.append(create_task(self._ensure_ttl_index(db, name, index)))
+        await gather(*index_tasks)
+
+        await init_beanie(database=db, document_models=self.document_models, allow_index_dropping=True)
 
         self._client = client
         self._db = db
diff --git a/src/nonebot_plugin_pixivbot/data/subscription_repo.py b/src/nonebot_plugin_pixivbot/data/subscription_repo.py
index ada7dbe..b359ddd 100644
--- a/src/nonebot_plugin_pixivbot/data/subscription_repo.py
+++ b/src/nonebot_plugin_pixivbot/data/subscription_repo.py
@@ -1,6 +1,7 @@
-from typing import TypeVar, AsyncGenerator, Optional
+from typing import TypeVar, AsyncGenerator, Optional, List, Any
 
-from pymongo import ReturnDocument
+from beanie import Document, BulkWriter
+from pymongo import IndexModel, ReturnDocument
 
 from nonebot_plugin_pixivbot.global_context import context
 from nonebot_plugin_pixivbot.model import Subscription, PostIdentifier, ScheduleType
@@ -13,26 +14,36 @@
 ID = PostIdentifier[UID, GID]
 
 
+class SubscriptionDocument(Subscription[Any, Any], Document):
+    class Settings:
+        name = "subscription"
+        indexes = [
+            IndexModel([("subscriber.adapter", 1)]),
+            IndexModel([("subscriber", 1), ("type", 1)], unique=True)
+        ]
+
+
+context.require(MongoDataSource).document_models.append(SubscriptionDocument)
+
+
 @context.inject
 @context.register_singleton()
 class SubscriptionRepo:
     mongo: MongoDataSource
 
-    async def get_by_subscriber(self, subscriber: ID) -> AsyncGenerator[Subscription, None]:
-        query = {
-            "subscriber": process_subscriber(subscriber).dict()
-        }
-        async for obj in self.mongo.db.subscription.find(query):
-            yield Subscription.parse_obj(obj)
+    @classmethod
+    async def get_by_subscriber(cls, subscriber: ID) -> AsyncGenerator[Subscription, None]:
+        subscriber = process_subscriber(subscriber)
+        async for doc in SubscriptionDocument.find(SubscriptionDocument.subscriber == subscriber):
+            yield doc
 
-    async def get_by_adapter(self, adapter: str) -> AsyncGenerator[Subscription, None]:
-        query = {
-            "subscriber.adapter": adapter
-        }
-        async for obj in self.mongo.db.subscription.find(query):
-            yield Subscription.parse_obj(obj)
+    @classmethod
+    async def get_by_adapter(cls, adapter: str) -> AsyncGenerator[Subscription, None]:
+        async for doc in SubscriptionDocument.find(SubscriptionDocument.subscriber.adapter == adapter):
+            yield doc
 
     async def update(self, subscription: Subscription) -> Optional[Subscription]:
+        # beanie不支持原子性的find_one_and_replace操作
         subscription.subscriber = process_subscriber(subscription.subscriber)
 
         query = {
@@ -52,6 +63,7 @@ async def update(self, subscription: Subscription) -> Optional[Subscription]:
             return None
 
     async def delete_one(self, subscriber: ID, type: ScheduleType) -> Optional[Subscription]:
+        # beanie不支持原子性的find_one_and_delete操作
         query = {
             "type": type.value,
             "subscriber": process_subscriber(subscriber).dict()
@@ -62,11 +74,19 @@ async def delete_one(self, subscriber: ID, type: ScheduleType) -> Optional[Subsc
         else:
             return None
 
-    async def delete_many_by_subscriber(self, subscriber: ID):
-        query = {
-            "subscriber": process_subscriber(subscriber)
-        }
-        await self.mongo.db.subscription.delete_many(query)
+    @classmethod
+    async def delete_many_by_subscriber(cls, subscriber: ID) -> List[Subscription]:
+        subscriber = process_subscriber(subscriber)
+
+        old_doc = await SubscriptionDocument.find(
+            SubscriptionDocument.subscriber == subscriber
+        ).to_list()
+
+        async with BulkWriter() as bw:
+            for x in old_doc:
+                await x.delete(bulk_writer=bw)
+
+        return old_doc
 
 
 __all__ = ("SubscriptionRepo",)
diff --git a/src/nonebot_plugin_pixivbot/data/watch_task_repo.py b/src/nonebot_plugin_pixivbot/data/watch_task_repo.py
index cab6735..57ef6e1 100644
--- a/src/nonebot_plugin_pixivbot/data/watch_task_repo.py
+++ b/src/nonebot_plugin_pixivbot/data/watch_task_repo.py
@@ -1,6 +1,7 @@
-from typing import TypeVar, AsyncGenerator, Optional, Any, Dict
+from typing import TypeVar, AsyncGenerator, Optional, Any, Dict, List
 
-from pymongo import ReturnDocument
+from beanie import Document, BulkWriter
+from pymongo import ReturnDocument, IndexModel
 
 from nonebot_plugin_pixivbot.global_context import context
 from nonebot_plugin_pixivbot.model import WatchTask, WatchType, PostIdentifier
@@ -13,38 +14,36 @@
 ID = PostIdentifier[UID, GID]
 
 
+class WatchTaskDocument(WatchTask[Any, Any], Document):
+    class Settings:
+        name = "watch_task"
+        indexes = [
+            IndexModel([("subscriber.adapter", 1)]),
+            IndexModel([("subscriber", 1), ("type", 1), ("kwargs", 1)], unique=True)
+        ]
+
+
+context.require(MongoDataSource).document_models.append(WatchTaskDocument)
+
+
 @context.inject
 @context.register_singleton()
 class WatchTaskRepo:
     mongo: MongoDataSource
 
-    # async def get(self, type: WatchType,
-    #               args: Dict[str, Any],
-    #               subscriber: ID):
-    #     query = {
-    #         "type": type.value,
-    #         "args": args,
-    #         "subscriber": subscriber.dict(),
-    #     }
-    #     return WatchTask.parse_obj(
-    #         await self.mongo.db.watch_task.find_one(query)
-    #     )
-
-    async def get_by_subscriber(self, subscriber: ID) -> AsyncGenerator[WatchTask, None]:
-        query = {
-            "subscriber": process_subscriber(subscriber).dict()
-        }
-        async for obj in self.mongo.db.watch_task.find(query):
-            yield WatchTask.parse_obj(obj)
+    @classmethod
+    async def get_by_subscriber(cls, subscriber: ID) -> AsyncGenerator[WatchTask, None]:
+        subscriber = process_subscriber(subscriber)
+        async for doc in WatchTaskDocument.find(WatchTaskDocument.subscriber == subscriber):
+            yield doc
 
-    async def get_by_adapter(self, adapter: str) -> AsyncGenerator[WatchTask, None]:
-        query = {
-            "subscriber.adapter": adapter
-        }
-        async for obj in self.mongo.db.watch_task.find(query):
-            yield WatchTask.parse_obj(obj)
+    @classmethod
+    async def get_by_adapter(cls, adapter: str) -> AsyncGenerator[WatchTask, None]:
+        async for doc in WatchTaskDocument.find(WatchTaskDocument.subscriber.adapter == adapter):
+            yield doc
 
     async def update(self, task: WatchTask) -> Optional[WatchTask]:
+        # beanie不支持原子性的find_one_and_replace操作
         task.subscriber = process_subscriber(task.subscriber)
 
         query = {
@@ -67,6 +66,7 @@ async def update(self, task: WatchTask) -> Optional[WatchTask]:
     async def delete_one(self, type: WatchType,
                          kwargs: Dict[str, Any],
                          subscriber: PostIdentifier[UID, GID]) -> Optional[WatchTask]:
+        # beanie不支持原子性的find_one_and_delete操作
         query = {
             "type": type.value,
             "kwargs": kwargs,
@@ -78,11 +78,19 @@ async def delete_one(self, type: WatchType,
         else:
             return None
 
-    async def delete_many_by_subscriber(self, subscriber: ID):
-        query = {
-            "subscriber": process_subscriber(subscriber)
-        }
-        await self.mongo.db.watch_task.delete_many(query)
+    @classmethod
+    async def delete_many_by_subscriber(cls, subscriber: ID) -> List[WatchTask]:
+        subscriber = process_subscriber(subscriber)
+
+        old_doc = await WatchTaskDocument.find(
+            WatchTaskDocument.subscriber == subscriber
+        ).to_list()
+
+        async with BulkWriter() as bw:
+            for x in old_doc:
+                await x.delete(bulk_writer=bw)
+
+        return old_doc
 
 
 __all__ = ("WatchTaskRepo",)
diff --git a/src/nonebot_plugin_pixivbot/enums.py b/src/nonebot_plugin_pixivbot/enums.py
index 1df2e08..7df2d28 100644
--- a/src/nonebot_plugin_pixivbot/enums.py
+++ b/src/nonebot_plugin_pixivbot/enums.py
@@ -3,27 +3,27 @@
 __all__ = ("BlockAction", "DownloadQuantity", "RandomIllustMethod", "RankingMode",)
 
 
-class BlockAction(Enum):
+class BlockAction(str, Enum):
     no_image = 'no_image'
     completely_block = 'completely_block'
     no_reply = 'no_reply'
 
 
-class DownloadQuantity(Enum):
+class DownloadQuantity(str, Enum):
     original = 'original'
     square_medium = 'square_medium'
     medium = 'medium'
     large = 'large'
 
 
-class RandomIllustMethod(Enum):
+class RandomIllustMethod(str, Enum):
     uniform = 'uniform'
     bookmark_proportion = 'bookmark_proportion'
     view_proportion = 'view_proportion'
     timedelta_proportion = 'timedelta_proportion'
 
 
-class RankingMode(Enum):
+class RankingMode(str, Enum):
     day = 'day'
     week = 'week'
     month = 'month'
diff --git a/src/nonebot_plugin_pixivbot/handler/command/help.py b/src/nonebot_plugin_pixivbot/handler/command/help.py
index 9c5e624..51d3ab0 100644
--- a/src/nonebot_plugin_pixivbot/handler/command/help.py
+++ b/src/nonebot_plugin_pixivbot/handler/command/help.py
@@ -24,7 +24,7 @@ class HelpHandler(SubCommandHandler):
 - /pixivbot help：查看本帮助
 - /pixivbot bind：绑定Pixiv账号
 
-更多功能：参见https://github.com/ssttkkl/PixivBot
+更多功能：参见https://github.com/ssttkkl/nonebot-plugin-pixivbot
 """
 
     @classmethod
diff --git a/src/nonebot_plugin_pixivbot/handler/common/common.py b/src/nonebot_plugin_pixivbot/handler/common/common.py
index 88c0daf..c626d29 100644
--- a/src/nonebot_plugin_pixivbot/handler/common/common.py
+++ b/src/nonebot_plugin_pixivbot/handler/common/common.py
@@ -6,7 +6,7 @@
 from .recorder import Recorder
 from ..entry_handler import EntryHandler
 from ..interceptor.cooldown_interceptor import CooldownInterceptor
-from ..interceptor.loading_interceptor import LoadingInterceptor
+from ..interceptor.loading_prompt_interceptor import LoadingPromptInterceptor
 from ..interceptor.timeout_interceptor import TimeoutInterceptor
 from ...model import Illust
 from ...model.message import IllustMessageModel, IllustMessagesModel
@@ -51,7 +51,7 @@ def __init__(self):
         self.postman_manager = RecordPostmanManager(self.postman_manager)
         self.add_interceptor(context.require(CooldownInterceptor))
         self.add_interceptor(context.require(TimeoutInterceptor))
-        self.add_interceptor(context.require(LoadingInterceptor))
+        self.add_interceptor(context.require(LoadingPromptInterceptor))
 
     async def post_illust(self, illust: Illust, *,
                           header: Optional[str] = None,
diff --git a/src/nonebot_plugin_pixivbot/handler/interceptor/loading_interceptor.py b/src/nonebot_plugin_pixivbot/handler/interceptor/loading_prompt_interceptor.py
similarity index 71%
rename from src/nonebot_plugin_pixivbot/handler/interceptor/loading_interceptor.py
rename to src/nonebot_plugin_pixivbot/handler/interceptor/loading_prompt_interceptor.py
index 7847ce4..3f5738c 100644
--- a/src/nonebot_plugin_pixivbot/handler/interceptor/loading_interceptor.py
+++ b/src/nonebot_plugin_pixivbot/handler/interceptor/loading_prompt_interceptor.py
@@ -4,14 +4,19 @@
 from nonebot import logger
 
 from nonebot_plugin_pixivbot import context
+from nonebot_plugin_pixivbot.config import Config
 from nonebot_plugin_pixivbot.protocol_dep.post_dest import PostDestination
 from .interceptor import Interceptor, UID, GID
 
 
+@context.inject
 @context.register_singleton()
-class LoadingInterceptor(Interceptor):
-    async def send_delayed_loading(self, post_dest: PostDestination[UID, GID]):
-        await sleep(3.0)
+class LoadingPromptInterceptor(Interceptor):
+    conf: Config
+
+    async def send_delayed_loading_prompt(self, post_dest: PostDestination[UID, GID]):
+        await sleep(self.conf.pixiv_loading_prompt_delayed_time)
+
         logger.debug(f"send delayed loading to {post_dest.identifier}")
         await shield(self.post_plain_text("努力加载中", post_dest))
 
@@ -21,7 +26,7 @@ async def intercept(self, wrapped_func: Callable,
                         **kwargs):
         task: Optional[Task] = None
         if not silently:
-            task = create_task(self.send_delayed_loading(post_dest))
+            task = create_task(self.send_delayed_loading_prompt(post_dest))
 
         try:
             await wrapped_func(*args, post_dest=post_dest, silently=silently, **kwargs)
diff --git a/src/nonebot_plugin_pixivbot/model/subscription.py b/src/nonebot_plugin_pixivbot/model/subscription.py
index a684e0b..118924d 100644
--- a/src/nonebot_plugin_pixivbot/model/subscription.py
+++ b/src/nonebot_plugin_pixivbot/model/subscription.py
@@ -10,7 +10,7 @@
 GID = TypeVar("GID")
 
 
-class ScheduleType(Enum):
+class ScheduleType(str, Enum):
     random_bookmark = "random_bookmark"
     random_recommended_illust = "random_recommended_illust"
     random_illust = "random_illust"
diff --git a/src/nonebot_plugin_pixivbot/model/watch_task.py b/src/nonebot_plugin_pixivbot/model/watch_task.py
index e584c91..9ad1e7c 100644
--- a/src/nonebot_plugin_pixivbot/model/watch_task.py
+++ b/src/nonebot_plugin_pixivbot/model/watch_task.py
@@ -3,6 +3,7 @@
 from typing import Any, TypeVar, Generic, Dict
 
 from pydantic import BaseModel
+from pydantic.generics import GenericModel
 
 from nonebot_plugin_pixivbot.model import PostIdentifier
 
@@ -10,12 +11,12 @@
 GID = TypeVar("GID")
 
 
-class WatchType(Enum):
+class WatchType(str, Enum):
     user_illusts = "user_illusts"
     following_illusts = "following_illusts"
 
 
-class WatchTask(BaseModel, Generic[UID, GID]):
+class WatchTask(GenericModel, Generic[UID, GID]):
     type: WatchType
     kwargs: Dict[str, Any]
     subscriber: PostIdentifier[UID, GID]
diff --git a/src/nonebot_plugin_pixivbot/nb_providers.py b/src/nonebot_plugin_pixivbot/nb_providers.py
index 037420d..f1077b5 100644
--- a/src/nonebot_plugin_pixivbot/nb_providers.py
+++ b/src/nonebot_plugin_pixivbot/nb_providers.py
@@ -1,16 +1,21 @@
-from apscheduler.schedulers.asyncio import AsyncIOScheduler
-from apscheduler.schedulers.base import BaseScheduler
-from nonebot import require
-
 from nonebot_plugin_pixivbot.context import Context
 
 
 def asyncio_scheduler_provider(context: Context):
     # 改成register_lazy以后Scheduler不工作，不懂为啥
-    context.register(AsyncIOScheduler, require("nonebot_plugin_apscheduler").scheduler)
+    from nonebot import require
+
+    require("nonebot_plugin_apscheduler")
+
+    from nonebot_plugin_apscheduler import scheduler
+    from apscheduler.schedulers.asyncio import AsyncIOScheduler
+
+    context.register(AsyncIOScheduler, scheduler)
 
 
 def base_scheduler_provider(context: Context):
+    from apscheduler.schedulers.base import BaseScheduler
+    from apscheduler.schedulers.asyncio import AsyncIOScheduler
     context.bind_to(BaseScheduler, AsyncIOScheduler)
 
 
diff --git a/src/nonebot_plugin_pixivbot/protocol_dep/protocol_dep.py b/src/nonebot_plugin_pixivbot/protocol_dep/protocol_dep.py
index 3a6b130..a421839 100644
--- a/src/nonebot_plugin_pixivbot/protocol_dep/protocol_dep.py
+++ b/src/nonebot_plugin_pixivbot/protocol_dep/protocol_dep.py
@@ -25,7 +25,10 @@ def register(self, cls: Type[T]):
         return cls
 
     def require(self, adapter: str):
-        return context.require(self.factories[adapter])
+        t = self.factories.get(adapter, None)
+        if t is None:
+            raise RuntimeError(f"未找到{adapter}协议的特化插件，请尝试安装nonebot-plugin-pixivbot-{adapter}")
+        return context.require(t)
 
     def __getitem__(self, adapter: str):
         return self.require(adapter)
diff --git a/src/nonebot_plugin_pixivbot/providers.py b/src/nonebot_plugin_pixivbot/providers.py
deleted file mode 100644
index be9636e..0000000
--- a/src/nonebot_plugin_pixivbot/providers.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from . import nb_providers
-from .context import Context
-
-
-def provide(context: Context):
-    nb_providers.provide(context)
diff --git a/src/nonebot_plugin_pixivbot/query/__init__.py b/src/nonebot_plugin_pixivbot/query/__init__.py
index 6857211..0f75e8d 100644
--- a/src/nonebot_plugin_pixivbot/query/__init__.py
+++ b/src/nonebot_plugin_pixivbot/query/__init__.py
@@ -1,6 +1,7 @@
 from .command import CommandQuery
 from .delegateion_query import DelegationQuery
 from .illust import IllustQuery
+from .illust_link import IllustLinkQuery
 from .more import MoreQuery
 from .query import Query, register_query
 from .random_bookmark import RandomBookmarkQuery
@@ -11,6 +12,6 @@
 from .ranking import RankingQuery
 
 __all__ = (
-    "CommandQuery", "DelegationQuery", "IllustQuery", "MoreQuery", "Query", "RandomBookmarkQuery", "RandomIllustQuery",
-    "RandomRecommendedIllustQuery", "RandomRelatedIllustQuery", "RandomUserIllustQuery", "RankingQuery",
-    "register_query")
+    "CommandQuery", "DelegationQuery", "IllustQuery", "IllustLinkQuery", "MoreQuery", "Query", "RandomBookmarkQuery",
+    "RandomIllustQuery", "RandomRecommendedIllustQuery", "RandomRelatedIllustQuery", "RandomUserIllustQuery",
+    "RankingQuery", "register_query")
diff --git a/src/nonebot_plugin_pixivbot/query/illust_link.py b/src/nonebot_plugin_pixivbot/query/illust_link.py
new file mode 100644
index 0000000..ed8f47d
--- /dev/null
+++ b/src/nonebot_plugin_pixivbot/query/illust_link.py
@@ -0,0 +1,26 @@
+from lazy import lazy
+from nonebot import on_regex
+from nonebot.adapters import Bot, Event
+from nonebot.matcher import Matcher
+from nonebot.typing import T_State
+
+from nonebot_plugin_pixivbot.global_context import context
+from nonebot_plugin_pixivbot.handler.common import IllustHandler
+from .query import Query, register_query
+from .utils import get_common_query_rule, get_post_dest
+
+
+@context.inject
+@register_query(context)
+class IllustLinkQuery(Query):
+    handler: IllustHandler
+
+    @lazy
+    def matcher(self):
+        return on_regex(r"^(http://|https://)?(www.)?pixiv\.net/artworks/([1-9][0-9]*)/?$",
+                        rule=get_common_query_rule(), priority=5)
+
+    async def on_match(self, bot: Bot, event: Event, state: T_State, matcher: Matcher):
+        illust_id = state["_matched_groups"][2]
+
+        await self.handler.handle(illust_id, post_dest=get_post_dest(bot, event))
diff --git a/src/nonebot_plugin_pixivbot/query/query.py b/src/nonebot_plugin_pixivbot/query/query.py
index 779b3e3..4d48c0d 100644
--- a/src/nonebot_plugin_pixivbot/query/query.py
+++ b/src/nonebot_plugin_pixivbot/query/query.py
@@ -12,7 +12,7 @@
 class Query(ABC):
     @property
     @abstractmethod
-    def matcher(self) -> Matcher:
+    def matcher(self) -> Type[Matcher]:
         raise NotImplementedError()
 
     @abstractmethod
diff --git a/src/nonebot_plugin_pixivbot/service/pixiv_service.py b/src/nonebot_plugin_pixivbot/service/pixiv_service.py
index 2e9322a..e8837a8 100644
--- a/src/nonebot_plugin_pixivbot/service/pixiv_service.py
+++ b/src/nonebot_plugin_pixivbot/service/pixiv_service.py
@@ -3,7 +3,7 @@
 from nonebot import logger
 
 from nonebot_plugin_pixivbot.config import Config
-from nonebot_plugin_pixivbot.data.local_tag_repo import LocalTagRepo
+from nonebot_plugin_pixivbot.data.local_tag_repo import LocalTag, LocalTagRepo
 from nonebot_plugin_pixivbot.data.pixiv_repo import LazyIllust, PixivRepo
 from nonebot_plugin_pixivbot.enums import RandomIllustMethod, RankingMode
 from nonebot_plugin_pixivbot.global_context import context
@@ -17,7 +17,7 @@
 class PixivService:
     conf: Config
     repo: PixivRepo
-    local_tags: LocalTagRepo
+    local_tag_repo: LocalTagRepo
 
     async def _choice_and_load(self, illusts: List[LazyIllust], random_method: RandomIllustMethod, count: int) \
             -> List[Illust]:
@@ -51,9 +51,9 @@ async def illust_detail(self, illust: int) -> Illust:
     async def random_illust(self, word: str, *, count: int = 1) -> List[Illust]:
         if self.conf.pixiv_tag_translation_enabled:
             # 只有原word不是标签时获取翻译（例子：唐可可）
-            tag = await self.local_tags.get_by_name(word)
+            tag = await self.local_tag_repo.find_by_name(word)
             if not tag:
-                tag = await self.local_tags.get_by_translated_name(word)
+                tag = await self.local_tag_repo.find_by_translated_name(word)
                 if tag:
                     logger.info(f"[pixiv_service] found translation {word} -> {tag.name}")
                     word = tag.name
diff --git a/src/nonebot_plugin_pixivbot/service/scheduler.py b/src/nonebot_plugin_pixivbot/service/scheduler.py
index 1da9b8f..a6e7176 100644
--- a/src/nonebot_plugin_pixivbot/service/scheduler.py
+++ b/src/nonebot_plugin_pixivbot/service/scheduler.py
@@ -148,22 +148,24 @@ async def schedule(self, type: ScheduleType,
         self._add_job(post_dest.normalized(), sub)
 
     async def unschedule(self, type: ScheduleType, subscriber: PostIdentifier[UID, GID]) -> bool:
-        subscriber = process_subscriber(subscriber)
-        subscription = await self.repo.delete_one(subscriber, type)
-        if subscription:
-            self._remove_job(subscription)
+        # subscriber = process_subscriber(subscriber)
+        sub = await self.repo.delete_one(subscriber, type)
+        if sub:
+            self._remove_job(sub)
+            logger.success(f"[scheduler] successfully removed subscription {sub}")
             return True
         else:
             return False
 
     async def unschedule_all_by_subscriber(self, subscriber: PostIdentifier[UID, GID]):
-        subscriber = process_subscriber(subscriber)
-        async for sub in self.repo.get_by_subscriber(subscriber):
+        # subscriber = process_subscriber(subscriber)
+        old = await self.repo.delete_many_by_subscriber(subscriber)
+        for sub in old:
             self._remove_job(sub)
-        await self.repo.delete_many_by_subscriber(subscriber)
+            logger.success(f"[scheduler] successfully removed subscription {sub}")
 
     async def get_by_subscriber(self, subscriber: PostIdentifier[UID, GID]) -> List[Subscription]:
-        subscriber = process_subscriber(subscriber)
+        # subscriber = process_subscriber(subscriber)
         return [x async for x in self.repo.get_by_subscriber(subscriber)]
 
 
diff --git a/src/nonebot_plugin_pixivbot/service/watchman/shared_agen.py b/src/nonebot_plugin_pixivbot/service/watchman/shared_agen.py
index 89dc6bf..8e4979e 100644
--- a/src/nonebot_plugin_pixivbot/service/watchman/shared_agen.py
+++ b/src/nonebot_plugin_pixivbot/service/watchman/shared_agen.py
@@ -41,9 +41,7 @@ class WatchmanSharedAsyncGeneratorManager(SharedAsyncGeneratorManager[WatchmanSh
     pixiv: PixivRepo
     remote_pixiv: RemotePixivRepo
 
-    async def agen_factory(self, identifier: WatchmanSharedAgenIdentifier,
-                           cache_strategy: CacheStrategy,  # 这里的cache_strategy和PixivRepo没关系
-                           *args, **kwargs) -> AsyncGenerator[Illust, None]:
+    async def agen(self, identifier: WatchmanSharedAgenIdentifier, cache_strategy: CacheStrategy, **kwargs) -> AsyncGenerator[Illust, None]:
         self.set_expires_time(identifier, datetime.now(timezone.utc) + timedelta(seconds=30))  # 保证每分钟的所有task都能共享
         if identifier.type == WatchType.user_illusts:
             async for x in self.pixiv.user_illusts(cache_strategy=CacheStrategy.FORCE_EXPIRATION, **identifier.kwargs):
diff --git a/src/nonebot_plugin_pixivbot/service/watchman/watchman.py b/src/nonebot_plugin_pixivbot/service/watchman/watchman.py
index 15aa8b5..ff7670e 100644
--- a/src/nonebot_plugin_pixivbot/service/watchman/watchman.py
+++ b/src/nonebot_plugin_pixivbot/service/watchman/watchman.py
@@ -1,5 +1,5 @@
 from datetime import datetime, timedelta, timezone
-from typing import Dict, Any, TypeVar, Optional
+from typing import Dict, Any, TypeVar, Optional, List
 
 from apscheduler.schedulers.asyncio import AsyncIOScheduler
 from apscheduler.triggers.interval import IntervalTrigger
@@ -83,8 +83,15 @@ async def _get_user_illusts(self, task: WatchTask, post_dest: PD):
         WatchType.user_illusts: _get_user_illusts
     }
 
-    async def _handle(self, task: WatchTask, post_dest: PD):
-        # logger.info(f"[watchman] handle user_illust {pixiv_user_id}")
+    async def _on_trigger(self, task: WatchTask, post_dest: PD):
+        job_id = self._make_job_id(task.type, task.kwargs, task.subscriber)
+        logger.info(f"[watchman] triggered {job_id}")
+
+        # 先保存checkpoint，避免一次异常后下一次重复推送
+        # 但是会存在丢失推送的问题
+        task.checkpoint = datetime.now(timezone.utc)
+        await self.repo.update(task)
+
         ctx_mgr = await Watchman._ctx_mgr_factory[task.type](self, task, post_dest)
         if ctx_mgr:
             with ctx_mgr as iter:
@@ -96,9 +103,6 @@ async def _handle(self, task: WatchTask, post_dest: PD):
                     else:
                         break
 
-        task.checkpoint = datetime.now(timezone.utc)
-        await self.repo.update(task)
-
     @staticmethod
     def _make_job_id(type: WatchType,
                      kwargs: Dict[str, Any],
@@ -118,7 +122,7 @@ def _make_job_trigger(self, task: WatchTask):
     def _add_job(self, task: WatchTask, post_dest: PD):
         job_id = self._make_job_id(task.type, task.kwargs, task.subscriber)
         trigger = self._make_job_trigger(task)
-        self.apscheduler.add_job(self._handle, id=job_id, trigger=trigger,
+        self.apscheduler.add_job(self._on_trigger, id=job_id, trigger=trigger,
                                  args=[task, post_dest],
                                  max_instances=1)
         logger.success(f"[watchman] add job \"{job_id}\" on {trigger}")
@@ -158,20 +162,22 @@ async def watch(self, type: WatchType,
     async def unwatch(self, type: WatchType,
                       kwargs: Dict[str, Any],
                       subscriber: ID) -> bool:
-        subscriber = process_subscriber(subscriber)
+        # subscriber = process_subscriber(subscriber)
         task = await self.repo.delete_one(type, kwargs, subscriber)
         if task:
             self._remove_job(task)
+            logger.success(f"[scheduler] successfully removed subscription {task}")
             return True
         else:
             return False
 
     async def unwatch_all_by_subscriber(self, subscriber: ID):
-        subscriber = process_subscriber(subscriber)
-        async for task in self.repo.get_by_subscriber(subscriber):
+        # subscriber = process_subscriber(subscriber)
+        old = await self.repo.delete_many_by_subscriber(subscriber)
+        for task in old:
             self._remove_job(task)
-        await self.repo.delete_many_by_subscriber(subscriber)
+            logger.success(f"[scheduler] successfully removed subscription {task}")
 
-    async def get_by_subscriber(self, subscriber: ID):
-        subscriber = process_subscriber(subscriber)
+    async def get_by_subscriber(self, subscriber: ID) -> List[WatchTask]:
+        # subscriber = process_subscriber(subscriber)
         return [x async for x in self.repo.get_by_subscriber(subscriber)]
diff --git a/src/nonebot_plugin_pixivbot/utils/errors.py b/src/nonebot_plugin_pixivbot/utils/errors.py
index bf13d5d..fbb60ad 100644
--- a/src/nonebot_plugin_pixivbot/utils/errors.py
+++ b/src/nonebot_plugin_pixivbot/utils/errors.py
@@ -6,6 +6,11 @@ def __str__(self):
         return self.message
 
 
+class RateLimitError(QueryError):
+    def __init__(self):
+        super(RateLimitError).__init__("Rate Limit")
+
+
 class BadRequestError(Exception):
     def __init__(self, message=None):
         super().__init__()
@@ -15,4 +20,4 @@ def __str__(self):
         return self.message
 
 
-__all__ = ("QueryError", "BadRequestError")
+__all__ = ("QueryError", "RateLimitError", "BadRequestError")
diff --git a/src/nonebot_plugin_pixivbot/utils/shared_agen.py b/src/nonebot_plugin_pixivbot/utils/shared_agen.py
index 3672e13..e79ec3f 100644
--- a/src/nonebot_plugin_pixivbot/utils/shared_agen.py
+++ b/src/nonebot_plugin_pixivbot/utils/shared_agen.py
@@ -17,10 +17,10 @@
 
 class SharedAsyncGeneratorContextManager(AbstractContextManager, Generic[T_ITEM]):
     def __init__(self, origin: AsyncGenerator[T_ITEM, None],
-                 on_each: Callable[[T_ITEM], Union[None, Awaitable[None]]],
-                 on_stop: Callable[[List[T_ITEM]],
-                                   Union[None, Awaitable[None]]],
-                 on_consumers_changed: Callable[["SharedAsyncGeneratorContextManager", int], None]):
+                 *, on_each: Optional[Callable[[T_ITEM], Union[None, Awaitable[None]]]] = None,
+                 on_error: Optional[Callable[[Exception], Union[None, Awaitable[None]]]] = None,
+                 on_stop: Optional[Callable[[List[T_ITEM]], Union[None, Awaitable[None]]]] = None,
+                 on_consumers_changed: Optional[Callable[["SharedAsyncGeneratorContextManager", int], None]] = None):
         super().__init__()
         self._origin = origin
         self._stopped = False  # whether origin has raised a StopIteration
@@ -29,6 +29,7 @@ def __init__(self, origin: AsyncGenerator[T_ITEM, None],
         self._mutex = Lock()  # to solve race
         self._consumers = 0  # count of consumer
         self._on_each = on_each  # callback on origin yields
+        self._on_error = on_error  # callback on origin throws
         self._on_stop = on_stop  # callback on origin stops
         self._on_consumers_changed = on_consumers_changed  # callback on a consumer enters or exits
 
@@ -36,7 +37,7 @@ def __init__(self, origin: AsyncGenerator[T_ITEM, None],
     def consumers(self) -> int:
         return self._consumers
 
-    async def _generator_factory(self) -> AsyncGenerator[T_ITEM, None]:
+    async def _generator(self) -> AsyncGenerator[T_ITEM, None]:
         cur = 0
         while True:
             if cur < self._got:
@@ -50,30 +51,51 @@ async def _generator_factory(self) -> AsyncGenerator[T_ITEM, None]:
                     if self._stopped:
                         break
 
-                    try:
-                        if cur == self._got:
+                    if cur == self._got:
+                        try:
                             new_data = await self._origin.__anext__()
-                            await self._on_each(new_data)
-                            self._got_items.append(new_data)
-                            self._got += 1
-                        yield self._got_items[cur]
-                        cur += 1
-                    except StopAsyncIteration:
-                        self._stopped = True
-                        x = self._on_stop(self._got_items)
-                        if isawaitable(x):
-                            await x
+                        except StopAsyncIteration:
+                            self._stopped = True
+
+                            if self._on_stop:
+                                ret = self._on_stop(self._got_items)
+                                if isawaitable(ret):
+                                    await ret
+
+                            break
+                        except Exception as e:
+                            if self._on_error:
+                                ret = self._on_error(e)
+                                if isawaitable(ret):
+                                    await ret
+                            raise e
+
+                        if self._on_each:
+                            ret = self._on_each(new_data)
+                            if isawaitable(ret):
+                                await ret
+
+                        self._got_items.append(new_data)
+                        self._got += 1
+
+                    yield self._got_items[cur]
+                    cur += 1
 
     def __enter__(self) -> AsyncGenerator[T_ITEM, None]:
         self._consumers += 1
-        self._on_consumers_changed(self, self._consumers)
-        return self._generator_factory()
+
+        if self._on_consumers_changed:
+            self._on_consumers_changed(self, self._consumers)
+
+        return self._generator()
 
     def __exit__(self, exc_type: Optional[Type[BaseException]],
                  exc_value: Optional[BaseException],
                  traceback: Optional[TracebackType]) -> None:
         self._consumers -= 1
-        self._on_consumers_changed(self, self._consumers)
+
+        if self._on_consumers_changed:
+            self._on_consumers_changed(self, self._consumers)
 
     async def aclose(self):
         return await self._origin.aclose()
@@ -91,9 +113,9 @@ def __init__(self):
         self._expires_time: Dict[T_ID, datetime] = {}
 
     @abstractmethod
-    def agen_factory(self, identifier: T_ID,
-                     cache_strategy: CacheStrategy,
-                     **kwargs) -> AsyncGenerator[T_ITEM, None]:
+    def agen(self, identifier: T_ID,
+             cache_strategy: CacheStrategy,
+             **kwargs) -> AsyncGenerator[T_ITEM, None]:
         raise NotImplementedError()
 
     async def on_agen_next(self, identifier: T_ID, item: T_ITEM):
@@ -102,6 +124,9 @@ async def on_agen_next(self, identifier: T_ID, item: T_ITEM):
     async def on_agen_stop(self, identifier: T_ID, items: List[T_ITEM]):
         pass
 
+    async def on_agen_error(self, identifier: T_ID, e: Exception):
+        self.invalidate(identifier)
+
     def _cleanup(self, identifier: T_ID, ctx_mgr: SharedAsyncGeneratorContextManager[T_ITEM]):
         logger.debug(f"[{self.log_tag}] {identifier} was popped")
         ctx_mgr.close()
@@ -173,11 +198,12 @@ def get(self, identifier: Any,
         elif identifier in self._ctx_mgr:
             return self._ctx_mgr[identifier]
         else:
-            origin = self.agen_factory(identifier, cache_strategy, **kwargs)
+            origin = self.agen(identifier, cache_strategy, **kwargs)
             self._ctx_mgr[identifier] = SharedAsyncGeneratorContextManager(
                 origin=origin,
                 on_each=lambda item: self.on_agen_next(identifier, item),
                 on_stop=lambda items: self.on_agen_stop(identifier, items),
+                on_error=lambda e: self.on_agen_error(identifier, e),
                 on_consumers_changed=lambda ctx_mgr, consumers: self.on_consumers_changed(
                     identifier, ctx_mgr, consumers)
             )


Test patch:
diff --git a/src/tests/data/pixiv_repo/fake_pixiv_repo.py b/src/tests/data/pixiv_repo/fake_pixiv_repo.py
index abc5ca0..7348d74 100644
--- a/src/tests/data/pixiv_repo/fake_pixiv_repo.py
+++ b/src/tests/data/pixiv_repo/fake_pixiv_repo.py
@@ -3,9 +3,6 @@
 
 import pytest
 
-from nonebot_plugin_pixivbot.data.pixiv_repo import LazyIllust
-from nonebot_plugin_pixivbot.enums import RankingMode
-from nonebot_plugin_pixivbot.model import Illust, User
 from tests import MyTest
 
 
@@ -13,8 +10,10 @@ class FakePixivRepoMixin(MyTest):
     @pytest.fixture
     def fake_pixiv_repo(self, load_pixivbot):
         from nonebot_plugin_pixivbot import context
-        from nonebot_plugin_pixivbot.data.pixiv_repo import PixivRepo
+        from nonebot_plugin_pixivbot.data.pixiv_repo import PixivRepo, LazyIllust
         from nonebot_plugin_pixivbot.data.pixiv_repo.abstract_repo import AbstractPixivRepo
+        from nonebot_plugin_pixivbot.enums import RankingMode
+        from nonebot_plugin_pixivbot.model import Illust, User
 
         @context.bind_singleton_to(PixivRepo)
         class FakePixivRepo(AbstractPixivRepo):
diff --git a/src/tests/data/pixiv_repo/test_mediator.py b/src/tests/data/pixiv_repo/test_mediator.py
index aa24c59..6b66741 100644
--- a/src/tests/data/pixiv_repo/test_mediator.py
+++ b/src/tests/data/pixiv_repo/test_mediator.py
@@ -31,6 +31,7 @@ def cache_factory_with_expired_cache(self):
         async def agen():
             await sleep(0.1)
             raise CacheExpiredError(cache_metadata)
+            # noinspection PyUnreachableCode
             yield None  # to mark it as an AsyncGenerator
 
         return agen
@@ -55,6 +56,7 @@ async def test_with_cache(self, cache_factory_with_cache, remote_factory):
 
         content = None
         metadata = None
+        # noinspection PyTypeChecker
         async for x in mediate_single(cache_factory_with_cache, remote_factory, {}, cache_updater):
             if isinstance(x, PixivRepoMetadata):
                 metadata = x
@@ -74,6 +76,7 @@ async def test_with_remote(self, cache_factory_with_expired_cache, remote_factor
 
         content = None
         metadata = None
+        # noinspection PyTypeChecker
         async for x in mediate_single(cache_factory_with_expired_cache, remote_factory, {}, cache_updater):
             if isinstance(x, PixivRepoMetadata):
                 metadata = x
@@ -141,6 +144,7 @@ def cache_factory_with_no_cache(self):
         async def agen():
             await sleep(0.1)
             raise NoSuchItemError()
+            # noinspection PyUnreachableCode
             yield None  # to mark it as an AsyncGenerator
 
         return agen
@@ -157,6 +161,7 @@ def cache_factory_with_expired_cache(self):
         async def agen():
             await sleep(0.1)
             raise CacheExpiredError(cache_metadata)
+            # noinspection PyUnreachableCode
             yield None  # to mark it as an AsyncGenerator
 
         return agen
@@ -187,11 +192,14 @@ async def test_with_cache_and_empty_next_qs(self, cache_factory_with_cache_and_e
         from nonebot_plugin_pixivbot.data.pixiv_repo.mediator import mediate_many
         from nonebot_plugin_pixivbot.data.pixiv_repo.abstract_repo import PixivRepoMetadata
 
+        cache_invalidator = AsyncMock()
         cache_appender = AsyncMock()
 
         items = []
         metadata = None
-        async for x in mediate_many(cache_factory_with_cache_and_empty_next_qs, remote_factory, {}, cache_appender):
+        # noinspection PyTypeChecker
+        async for x in mediate_many(cache_factory_with_cache_and_empty_next_qs, remote_factory, {},
+                                    cache_invalidator, cache_appender):
             if not isinstance(x, PixivRepoMetadata):
                 items.append(x)
             else:
@@ -199,6 +207,7 @@ async def test_with_cache_and_empty_next_qs(self, cache_factory_with_cache_and_e
 
         assert items == [x for x in range(30)]
         assert metadata.update_time == datetime.fromtimestamp(0)  # assert metadata comes from cache
+        cache_invalidator.assert_not_awaited()
         cache_appender.assert_not_awaited()
 
     @pytest.mark.asyncio
@@ -206,6 +215,7 @@ async def test_with_cache_and_remote(self, cache_factory_with_cache, remote_fact
         from nonebot_plugin_pixivbot.data.pixiv_repo.mediator import mediate_many
         from nonebot_plugin_pixivbot.data.pixiv_repo.abstract_repo import PixivRepoMetadata
 
+        cache_invalidator = AsyncMock()
         cache_appender_calls = []
 
         async def cache_appender(items, metadata):
@@ -213,7 +223,8 @@ async def cache_appender(items, metadata):
 
         items = []
         metadata = None
-        async for x in mediate_many(cache_factory_with_cache, remote_factory, {}, cache_appender):
+        async for x in mediate_many(cache_factory_with_cache, remote_factory, {},
+                                    cache_invalidator, cache_appender):
             if not isinstance(x, PixivRepoMetadata):
                 items.append(x)
             else:
@@ -221,6 +232,7 @@ async def cache_appender(items, metadata):
 
         assert items == [x for x in range(90)]
         assert metadata.update_time != datetime.fromtimestamp(0)  # assert metadata comes from remote
+        cache_invalidator.assert_not_awaited()
         assert cache_appender_calls == [
             ([x for x in range(30, 50)], PixivRepoMetadata(pages=6, next_qs={"next": 50})),
             ([x for x in range(50, 70)], PixivRepoMetadata(pages=7, next_qs={"next": 70})),
@@ -232,6 +244,7 @@ async def test_with_remote(self, cache_factory_with_no_cache, remote_factory):
         from nonebot_plugin_pixivbot.data.pixiv_repo.mediator import mediate_many
         from nonebot_plugin_pixivbot.data.pixiv_repo.abstract_repo import PixivRepoMetadata
 
+        cache_invalidator = AsyncMock()
         cache_appender_calls = []
 
         async def cache_appender(items, metadata):
@@ -239,7 +252,8 @@ async def cache_appender(items, metadata):
 
         items = []
         metadata = None
-        async for x in mediate_many(cache_factory_with_no_cache, remote_factory, {}, cache_appender):
+        async for x in mediate_many(cache_factory_with_no_cache, remote_factory, {},
+                                    cache_invalidator, cache_appender):
             if not isinstance(x, PixivRepoMetadata):
                 items.append(x)
             else:
@@ -247,6 +261,36 @@ async def cache_appender(items, metadata):
 
         assert items == [x for x in range(60)]
         assert metadata.update_time != datetime.fromtimestamp(0)  # assert metadata comes from remote
+        cache_invalidator.assert_not_awaited()
+        assert cache_appender_calls == [
+            ([x for x in range(0, 20)], PixivRepoMetadata(pages=1, next_qs={"next": 20})),
+            ([x for x in range(20, 40)], PixivRepoMetadata(pages=2, next_qs={"next": 40})),
+            ([x for x in range(40, 60)], PixivRepoMetadata(pages=3, next_qs={"next": 60})),
+        ]
+
+    @pytest.mark.asyncio
+    async def test_with_expired_cache_and_remote(self, cache_factory_with_expired_cache, remote_factory):
+        from nonebot_plugin_pixivbot.data.pixiv_repo.mediator import mediate_many
+        from nonebot_plugin_pixivbot.data.pixiv_repo.abstract_repo import PixivRepoMetadata
+
+        cache_invalidator = AsyncMock()
+        cache_appender_calls = []
+
+        async def cache_appender(items, metadata):
+            cache_appender_calls.append((list(items), metadata))
+
+        items = []
+        metadata = None
+        async for x in mediate_many(cache_factory_with_expired_cache, remote_factory, {},
+                                    cache_invalidator, cache_appender):
+            if not isinstance(x, PixivRepoMetadata):
+                items.append(x)
+            else:
+                metadata = x
+
+        assert items == [x for x in range(60)]
+        assert metadata.update_time != datetime.fromtimestamp(0)  # assert metadata comes from remote
+        cache_invalidator.assert_awaited_once()
         assert cache_appender_calls == [
             ([x for x in range(0, 20)], PixivRepoMetadata(pages=1, next_qs={"next": 20})),
             ([x for x in range(20, 40)], PixivRepoMetadata(pages=2, next_qs={"next": 40})),
@@ -264,6 +308,7 @@ async def test_with_cache_and_empty_next_qs(self, cache_factory_with_cache_and_e
 
         items = []
         metadata = None
+        # noinspection PyTypeChecker
         async for x in mediate_append(cache_factory_with_cache_and_empty_next_qs, remote_factory, {}, cache_appender):
             if not isinstance(x, PixivRepoMetadata):
                 items.append(x)
diff --git a/src/tests/handler/command/test_invalidate_cache.py b/src/tests/handler/command/test_invalidate_cache.py
index 750dae5..352a09d 100644
--- a/src/tests/handler/command/test_invalidate_cache.py
+++ b/src/tests/handler/command/test_invalidate_cache.py
@@ -1,25 +1,15 @@
-from unittest.mock import AsyncMock
-
 import pytest
 
 from tests import MyTest
+from tests.data.pixiv_repo.fake_pixiv_repo import FakePixivRepoMixin
 from tests.protocol_dep.fake_post_dest import FakePostDestinationMixin
 from tests.protocol_dep.fake_postman import FakePostmanManagerMixin
 
 
 class TestInvalidateCache(FakePostDestinationMixin,
                           FakePostmanManagerMixin,
+                          FakePixivRepoMixin,
                           MyTest):
-    @pytest.fixture
-    def fake_pixiv_repo(self, load_pixivbot):
-        from nonebot_plugin_pixivbot import context
-        from nonebot_plugin_pixivbot.data.pixiv_repo import PixivRepo
-
-        @context.bind_singleton_to(PixivRepo)
-        class PixivRepo:
-            invalidate_cache = AsyncMock()
-
-        return PixivRepo
 
     @pytest.fixture(autouse=True)
     def remove_interceptor(self, load_pixivbot):
diff --git a/src/tests/handler/common/test_ranking.py b/src/tests/handler/common/test_ranking.py
new file mode 100644
index 0000000..9e2f5b2
--- /dev/null
+++ b/src/tests/handler/common/test_ranking.py
@@ -0,0 +1,66 @@
+import pytest
+
+from tests import MyTest
+from tests.model.mock_message import MockMessageModelMixin
+from tests.protocol_dep.fake_post_dest import FakePostDestinationMixin
+from tests.protocol_dep.fake_postman import FakePostmanManagerMixin
+from tests.service.fake_pixiv_service import FakePixivServiceMixin
+
+
+class TestRankingHandler(FakePixivServiceMixin,
+                         MockMessageModelMixin,
+                         FakePostDestinationMixin,
+                         FakePostmanManagerMixin,
+                         MyTest):
+    @pytest.fixture(autouse=True)
+    def remove_interceptor(self, load_pixivbot):
+        from nonebot_plugin_pixivbot import context
+        from nonebot_plugin_pixivbot.handler.common import RankingHandler
+
+        context.require(RankingHandler).interceptor = None
+
+    @pytest.mark.asyncio
+    async def test_handle(self, fake_post_destination,
+                          fake_pixiv_service,
+                          fake_postman_manager,
+                          mock_illust_message_model):
+        from nonebot_plugin_pixivbot import context
+        from nonebot_plugin_pixivbot.handler.common import RankingHandler
+        from nonebot_plugin_pixivbot.model.message import IllustMessagesModel
+
+        post_dest = fake_post_destination(123456, 56789)
+
+        await context.require(RankingHandler).handle("day", "1-3", post_dest=post_dest)
+
+        illusts = context.require(fake_pixiv_service).spy_illust_ranking.spy_return
+        except_model = await IllustMessagesModel.from_illusts(illusts, header="这是您点的日榜", number=1)
+
+        context.require(fake_postman_manager).assert_call(post_dest, except_model)
+
+    @pytest.mark.asyncio
+    async def test_handle_invalid_arg_1(self, fake_post_destination,
+                                      fake_postman_manager):
+        from nonebot_plugin_pixivbot import context
+        from nonebot_plugin_pixivbot.handler.common import RankingHandler
+        from nonebot_plugin_pixivbot.utils.errors import BadRequestError
+
+        post_dest = fake_post_destination(123456, 56789)
+        except_msg = "abcdef不是合法的榜单类型"
+
+        with pytest.raises(BadRequestError) as e:
+            await context.require(RankingHandler).handle("abcdef", post_dest=post_dest)
+        assert e.value.message == except_msg
+
+    @pytest.mark.asyncio
+    async def test_handle_invalid_arg_2(self, fake_post_destination,
+                                        fake_postman_manager):
+        from nonebot_plugin_pixivbot import context
+        from nonebot_plugin_pixivbot.handler.common import RankingHandler
+        from nonebot_plugin_pixivbot.utils.errors import BadRequestError
+
+        post_dest = fake_post_destination(123456, 56789)
+        except_msg = "abcdef不是合法的范围"
+
+        with pytest.raises(BadRequestError) as e:
+            await context.require(RankingHandler).handle("day", "abcdef", post_dest=post_dest)
+        assert e.value.message == except_msg
diff --git a/src/tests/utils/test_shared_agen.py b/src/tests/utils/test_shared_agen.py
index b8a381c..4fe154a 100644
--- a/src/tests/utils/test_shared_agen.py
+++ b/src/tests/utils/test_shared_agen.py
@@ -23,53 +23,78 @@ async def agen():
         return agen()
 
     @pytest.fixture
-    def ctx_mgr(self, origin_agen):
-        from nonebot_plugin_pixivbot.utils.shared_agen import SharedAsyncGeneratorContextManager
+    def origin_agen_raises(self):
+        cnt = 0
 
-        on_each = AsyncMock()
-        on_stop = AsyncMock()
-        on_consumers_changed = MagicMock()
+        async def agen():
+            nonlocal cnt
+            for i in range(10):
+                await sleep(0.1)
+                yield cnt
+                cnt += 1
+            raise RuntimeError()
 
-        return SharedAsyncGeneratorContextManager(
-            origin_agen,
-            on_each,
-            on_stop,
-            on_consumers_changed
-        )
+        return agen()
 
-    @pytest.mark.asyncio
-    async def test_on_each_and_on_stop(self, origin_agen):
+    @pytest.fixture
+    def ctx_mgr_factory(self):
         from nonebot_plugin_pixivbot.utils.shared_agen import SharedAsyncGeneratorContextManager
 
-        on_each = AsyncMock()
-        on_stop = AsyncMock()
-        on_consumers_changed = MagicMock()
+        def factory(origin_agen):
+            on_each = AsyncMock()
+            on_stop = AsyncMock()
+            on_error = AsyncMock()
+            on_consumers_changed = MagicMock()
+
+            # noinspection PyTypeChecker
+            return SharedAsyncGeneratorContextManager(
+                origin_agen,
+                on_each=on_each,
+                on_stop=on_stop,
+                on_error=on_error,
+                on_consumers_changed=on_consumers_changed
+            )
 
-        ctx_mgr = SharedAsyncGeneratorContextManager(
-            origin_agen,
-            on_each,
-            on_stop,
-            on_consumers_changed
-        )
+        return factory
 
+    @pytest.mark.asyncio
+    async def test_on_each_and_on_stop(self, ctx_mgr_factory, origin_agen):
+        ctx_mgr = ctx_mgr_factory(origin_agen)
         with ctx_mgr as iter:
             items = [i async for i in iter]
 
-        on_each.assert_has_awaits([call(i) for i in range(10)])
-        on_stop.assert_awaited_once_with([i for i in range(10)])
+        # noinspection PyUnresolvedReferences
+        ctx_mgr._on_each.assert_has_awaits([call(i) for i in range(10)])
+        # noinspection PyUnresolvedReferences
+        ctx_mgr._on_stop.assert_awaited_once_with([i for i in range(10)])
 
     @pytest.mark.asyncio
-    async def test_single_consumer(self, ctx_mgr):
+    async def test_on_error(self, ctx_mgr_factory, origin_agen_raises):
+        ctx_mgr = ctx_mgr_factory(origin_agen_raises)
+        with pytest.raises(RuntimeError) as e:
+            with ctx_mgr as iter:
+                items = [i async for i in iter]
+
+            # noinspection PyUnresolvedReferences
+            ctx_mgr._on_error.assert_awaited_once_with(e)
+
+    @pytest.mark.asyncio
+    async def test_single_consumer(self, ctx_mgr_factory, origin_agen):
+        ctx_mgr = ctx_mgr_factory(origin_agen)
         with ctx_mgr as iter:
             items = [i async for i in iter]
 
         assert items == [i for i in range(10)]
 
-        # noinspection PyUnresolvedReferences
-        ctx_mgr._on_consumers_changed.assert_has_calls([call(ctx_mgr, 1), call(ctx_mgr, 0)])
+        # noinspection PyTypeChecker
+        on_consumers_changed: MagicMock = ctx_mgr._on_consumers_changed
+        assert on_consumers_changed.call_args_list[0].args == (ctx_mgr, 1)
+        assert on_consumers_changed.call_args_list[1].args == (ctx_mgr, 0)
 
     @pytest.mark.asyncio
-    async def test_multi_consumer(self, ctx_mgr):
+    async def test_multi_consumer(self, ctx_mgr_factory, origin_agen):
+        ctx_mgr = ctx_mgr_factory(origin_agen)
+
         async def consume():
             with ctx_mgr as iter:
                 items = [i async for i in iter]
@@ -89,13 +114,18 @@ async def consume():
 
         await gather(consumer1, consumer2, consumer3)
 
-        # noinspection PyUnresolvedReferences
-        ctx_mgr._on_consumers_changed.assert_has_calls(
-            [call(ctx_mgr, 1), call(ctx_mgr, 2), call(ctx_mgr, 1), call(ctx_mgr, 0), call(ctx_mgr, 1),
-             call(ctx_mgr, 0)])
+        # noinspection PyTypeChecker
+        on_consumers_changed: MagicMock = ctx_mgr._on_consumers_changed
+        assert on_consumers_changed.call_args_list[0].args == (ctx_mgr, 1)
+        assert on_consumers_changed.call_args_list[1].args == (ctx_mgr, 2)
+        assert on_consumers_changed.call_args_list[2].args == (ctx_mgr, 1)
+        assert on_consumers_changed.call_args_list[3].args == (ctx_mgr, 0)
+        assert on_consumers_changed.call_args_list[4].args == (ctx_mgr, 1)
+        assert on_consumers_changed.call_args_list[5].args == (ctx_mgr, 0)
 
     @pytest.mark.asyncio
-    async def test_close(self, ctx_mgr):
+    async def test_close(self, ctx_mgr_factory, origin_agen):
+        ctx_mgr = ctx_mgr_factory(origin_agen)
         with ctx_mgr as iter:
             await iter.__anext__()
             await iter.__anext__()
@@ -115,7 +145,7 @@ def shared_agen_mgr(self):
         from nonebot_plugin_pixivbot.utils.shared_agen import SharedAsyncGeneratorManager
 
         class SharedAsyncGeneratorManagerImpl(SharedAsyncGeneratorManager[int, int]):
-            def agen_factory(self, identifier: int, *args, **kwargs) -> AsyncGenerator[int, None]:
+            def agen(self, identifier: int, *args, **kwargs) -> AsyncGenerator[int, None]:
                 async def agen():
                     cnt = identifier
                     for i in range(10):