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

Issue:
可持续缓存



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/python-build.yml b/.github/workflows/python-build.yml
index be2f287..032b254 100644
--- a/.github/workflows/python-build.yml
+++ b/.github/workflows/python-build.yml
@@ -16,6 +16,10 @@ jobs:
   deploy:
 
     runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        python-version: ["3.9", "3.10"]
 
     steps:
       #----------------------------------------------
@@ -27,7 +31,7 @@ jobs:
         id: setup-python
         uses: actions/setup-python@v2
         with:
-          python-version: 3.x
+          python-version: ${{ matrix.python-version }}
       #----------------------------------------------
       #  -----  install & configure poetry  -----
       #----------------------------------------------
@@ -35,13 +39,9 @@ jobs:
         uses: actions/cache@v2
         with:
           path: ~/.local  # the path depends on the OS
-          key: poetry-0  # increment to reset cache
+          key: poetry-2  # increment to reset cache
       - name: Install Poetry
         uses: snok/install-poetry@v1
-        with:
-          virtualenvs-create: true
-          virtualenvs-in-project: true
-          installer-parallel: true
 
       #----------------------------------------------
       #       load cached venv if cache exists
@@ -59,6 +59,18 @@ jobs:
         if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
         run: poetry install --no-interaction --no-root
       #----------------------------------------------
+      #               lint & test
+      #----------------------------------------------
+      - name: Lint with flake8
+        run: |
+          # stop the build if there are Python syntax errors or undefined names
+          poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
+          # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
+          poetry run flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
+      - name: Test with pytest
+        run: |
+          poetry run pytest
+      #----------------------------------------------
       #               build & publish
       #----------------------------------------------
       - name: Build package
@@ -67,4 +79,4 @@ jobs:
         uses: actions/upload-artifact@v3
         with:
           name: distribution
-          path: dist/*
+          path: dist/*
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 5698bdc..a798895 100644
--- a/.gitignore
+++ b/.gitignore
@@ -142,4 +142,5 @@ dmypy.json
 
 .idea
 
-poetry.lock
\ No newline at end of file
+.env.prod
+.env.dev
\ No newline at end of file
diff --git a/poetry.lock b/poetry.lock
new file mode 100644
index 0000000..64cfbb7
--- /dev/null
+++ b/poetry.lock
@@ -0,0 +1,2153 @@
+[[package]]
+name = "aiofiles"
+version = "0.8.0"
+description = "File support for asyncio."
+category = "main"
+optional = false
+python-versions = ">=3.6,<4.0"
+
+[[package]]
+name = "aiohttp"
+version = "3.8.1"
+description = "Async http client/server framework (asyncio)"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+aiosignal = ">=1.1.2"
+async-timeout = ">=4.0.0a3,<5.0"
+attrs = ">=17.3.0"
+charset-normalizer = ">=2.0,<3.0"
+frozenlist = ">=1.1.1"
+multidict = ">=4.5,<7.0"
+yarl = ">=1.0,<2.0"
+
+[package.extras]
+speedups = ["aiodns", "brotli", "cchardet"]
+
+[[package]]
+name = "aiohttp-socks"
+version = "0.7.1"
+description = "Proxy connector for aiohttp"
+category = "main"
+optional = true
+python-versions = "*"
+
+[package.dependencies]
+aiohttp = ">=2.3.2"
+attrs = ">=19.2.0"
+python-socks = {version = ">=2.0.0,<3.0.0", extras = ["asyncio"]}
+
+[[package]]
+name = "aioreactive"
+version = "0.16.0"
+description = "sync/await Reactive Tools for Python 3.9+"
+category = "main"
+optional = false
+python-versions = ">=3.9,<3.11"
+
+[package.dependencies]
+Expression = ">=2.0.0,<3.0.0"
+typing-extensions = ">=4.1.1,<5.0.0"
+
+[[package]]
+name = "aiosignal"
+version = "1.2.0"
+description = "aiosignal: a list of registered asynchronous callbacks"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+frozenlist = ">=1.1.0"
+
+[[package]]
+name = "anyio"
+version = "3.6.1"
+description = "High level compatibility layer for multiple asynchronous event loop implementations"
+category = "main"
+optional = false
+python-versions = ">=3.6.2"
+
+[package.dependencies]
+idna = ">=2.8"
+sniffio = ">=1.1"
+
+[package.extras]
+doc = ["packaging", "sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"]
+test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "contextlib2", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"]
+trio = ["trio (>=0.16)"]
+
+[[package]]
+name = "apscheduler"
+version = "3.9.1"
+description = "In-process task scheduler with Cron-like capabilities"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
+
+[package.dependencies]
+pytz = "*"
+six = ">=1.4.0"
+tzlocal = ">=2.0,<3.0.0 || >=4.0.0"
+
+[package.extras]
+asyncio = ["trollius"]
+doc = ["sphinx", "sphinx-rtd-theme"]
+gevent = ["gevent"]
+mongodb = ["pymongo (>=3.0)"]
+redis = ["redis (>=3.0)"]
+rethinkdb = ["rethinkdb (>=2.4.0)"]
+sqlalchemy = ["sqlalchemy (>=0.8)"]
+testing = ["pytest", "pytest-cov", "pytest-tornado5", "mock", "pytest-asyncio (<0.6)", "pytest-asyncio"]
+tornado = ["tornado (>=4.3)"]
+twisted = ["twisted"]
+zookeeper = ["kazoo"]
+
+[[package]]
+name = "asgiref"
+version = "3.5.2"
+description = "ASGI specs, helper code, and adapters"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"]
+
+[[package]]
+name = "async-asgi-testclient"
+version = "1.4.11"
+description = "Async client for testing ASGI web applications"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+multidict = ">=4.0,<7.0"
+requests = ">=2.21,<3.0"
+
+[[package]]
+name = "async-timeout"
+version = "4.0.2"
+description = "Timeout context manager for asyncio programs"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "atomicwrites"
+version = "1.4.1"
+description = "Atomic file writes."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "attrs"
+version = "22.1.0"
+description = "Classes Without Boilerplate"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[package.extras]
+dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"]
+docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
+tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
+tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "cloudpickle"]
+
+[[package]]
+name = "bleach"
+version = "5.0.1"
+description = "An easy safelist-based HTML-sanitizing tool."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+six = ">=1.9.0"
+webencodings = "*"
+
+[package.extras]
+css = ["tinycss2 (>=1.1.0,<1.2)"]
+dev = ["build (==0.8.0)", "flake8 (==4.0.1)", "hashin (==0.17.0)", "pip-tools (==6.6.2)", "pytest (==7.1.2)", "Sphinx (==4.3.2)", "tox (==3.25.0)", "twine (==4.0.1)", "wheel (==0.37.1)", "black (==22.3.0)", "mypy (==0.961)"]
+
+[[package]]
+name = "certifi"
+version = "2022.6.15"
+description = "Python package for providing Mozilla's CA Bundle."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "cffi"
+version = "1.15.1"
+description = "Foreign Function Interface for Python calling C code."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+pycparser = "*"
+
+[[package]]
+name = "charset-normalizer"
+version = "2.1.0"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+category = "main"
+optional = false
+python-versions = ">=3.6.0"
+
+[package.extras]
+unicode_backport = ["unicodedata2"]
+
+[[package]]
+name = "click"
+version = "8.1.3"
+description = "Composable command line interface toolkit"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[[package]]
+name = "colorama"
+version = "0.4.5"
+description = "Cross-platform colored terminal text."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "commonmark"
+version = "0.9.1"
+description = "Python parser for the CommonMark Markdown spec"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.extras]
+test = ["hypothesis (==3.55.3)", "flake8 (==3.7.8)"]
+
+[[package]]
+name = "cryptography"
+version = "37.0.4"
+description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+cffi = ">=1.12"
+
+[package.extras]
+docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
+docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
+pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
+sdist = ["setuptools_rust (>=0.11.4)"]
+ssh = ["bcrypt (>=3.1.5)"]
+test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
+
+[[package]]
+name = "deprecated"
+version = "1.2.13"
+description = "Python @deprecated decorator to deprecate old python classes, functions or methods."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[package.dependencies]
+wrapt = ">=1.10,<2"
+
+[package.extras]
+dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "importlib-resources (<4)", "configparser (<5)", "sphinxcontrib-websupport (<2)", "zipp (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"]
+
+[[package]]
+name = "docutils"
+version = "0.19"
+description = "Docutils -- Python Documentation Utilities"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "expression"
+version = "2.2.0"
+description = "Practical functional programming for Python 3.9+"
+category = "main"
+optional = false
+python-versions = ">=3.9,<3.11"
+
+[package.dependencies]
+typing-extensions = ">=4.1.1,<5.0.0"
+
+[[package]]
+name = "fastapi"
+version = "0.79.0"
+description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
+category = "main"
+optional = false
+python-versions = ">=3.6.1"
+
+[package.dependencies]
+pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0"
+starlette = "0.19.1"
+
+[package.extras]
+all = ["requests (>=2.24.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<3.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "orjson (>=3.2.1,<4.0.0)", "email_validator (>=1.1.1,<2.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"]
+dev = ["python-jose[cryptography] (>=3.3.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)", "pre-commit (>=2.17.0,<3.0.0)"]
+doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "typer (>=0.4.1,<0.5.0)", "pyyaml (>=5.3.1,<7.0.0)"]
+test = ["pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "mypy (==0.910)", "flake8 (>=3.8.3,<4.0.0)", "black (==22.3.0)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "orjson (>=3.2.1,<4.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "flask (>=1.1.2,<3.0.0)", "anyio[trio] (>=3.2.1,<4.0.0)", "types-ujson (==4.2.1)", "types-orjson (==3.6.2)", "types-dataclasses (==0.6.5)"]
+
+[[package]]
+name = "flake8"
+version = "5.0.4"
+description = "the modular source code checker: pep8 pyflakes and co"
+category = "dev"
+optional = false
+python-versions = ">=3.6.1"
+
+[package.dependencies]
+mccabe = ">=0.7.0,<0.8.0"
+pycodestyle = ">=2.9.0,<2.10.0"
+pyflakes = ">=2.5.0,<2.6.0"
+
+[[package]]
+name = "frozenlist"
+version = "1.3.1"
+description = "A list-like structure which implements collections.abc.MutableSequence"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "h11"
+version = "0.13.0"
+description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "httptools"
+version = "0.4.0"
+description = "A collection of framework independent HTTP protocol utils."
+category = "main"
+optional = false
+python-versions = ">=3.5.0"
+
+[package.extras]
+test = ["Cython (>=0.29.24,<0.30.0)"]
+
+[[package]]
+name = "idna"
+version = "3.3"
+description = "Internationalized Domain Names in Applications (IDNA)"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "importlib-metadata"
+version = "4.12.0"
+description = "Read metadata from Python packages"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+zipp = ">=0.5"
+
+[package.extras]
+docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
+perf = ["ipython"]
+testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"]
+
+[[package]]
+name = "iniconfig"
+version = "1.1.1"
+description = "iniconfig: brain-dead simple config-ini parsing"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "jeepney"
+version = "0.8.0"
+description = "Low-level, pure Python DBus protocol wrapper."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+trio = ["async-generator", "trio"]
+test = ["async-timeout", "trio", "testpath", "pytest-asyncio (>=0.17)", "pytest-trio", "pytest"]
+
+[[package]]
+name = "keyring"
+version = "23.8.1"
+description = "Store and access your passwords safely."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""}
+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\""}
+
+[package.extras]
+docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"]
+testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"]
+
+[[package]]
+name = "lazy"
+version = "1.4"
+description = "Lazy attributes for Python objects"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "loguru"
+version = "0.6.0"
+description = "Python logging made (stupidly) simple"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[package.dependencies]
+colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""}
+win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
+
+[package.extras]
+dev = ["colorama (>=0.3.4)", "docutils (==0.16)", "flake8 (>=3.7.7)", "tox (>=3.9.0)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "black (>=19.10b0)", "isort (>=5.1.1)", "Sphinx (>=4.1.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)"]
+
+[[package]]
+name = "mccabe"
+version = "0.7.0"
+description = "McCabe checker, plugin for flake8"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "motor"
+version = "3.0.0"
+description = "Non-blocking MongoDB driver for Tornado or asyncio"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+pymongo = ">=4.1,<5"
+
+[package.extras]
+aws = ["pymongo[aws] (>=4.1,<5)"]
+encryption = ["pymongo[encryption] (>=4.1,<5)"]
+gssapi = ["pymongo[gssapi] (>=4.1,<5)"]
+ocsp = ["pymongo[ocsp] (>=4.1,<5)"]
+snappy = ["pymongo[snappy] (>=4.1,<5)"]
+srv = ["pymongo[srv] (>=4.1,<5)"]
+zstd = ["pymongo[zstd] (>=4.1,<5)"]
+
+[[package]]
+name = "multidict"
+version = "6.0.2"
+description = "multidict implementation"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "nonebot-plugin-apscheduler"
+version = "0.1.4"
+description = "APScheduler Support for NoneBot2"
+category = "main"
+optional = false
+python-versions = ">=3.7.3,<4.0.0"
+
+[package.dependencies]
+apscheduler = ">=3.7.0,<4.0.0"
+nonebot2 = ">=2.0.0-alpha.8,<3.0.0"
+
+[[package]]
+name = "nonebot2"
+version = "2.0.0b5"
+description = "An asynchronous python bot framework."
+category = "main"
+optional = false
+python-versions = ">=3.7.3,<4.0.0"
+
+[package.dependencies]
+fastapi = ">=0.79.0,<0.80.0"
+loguru = ">=0.6.0,<0.7.0"
+pydantic = {version = ">=1.9.0,<1.10.0", extras = ["dotenv"]}
+pygtrie = ">=2.4.1,<3.0.0"
+tomlkit = ">=0.10.0,<1.0.0"
+typing-extensions = ">=3.10.0,<5.0.0"
+uvicorn = {version = ">=0.18.0,<0.19.0", extras = ["standard"]}
+yarl = ">=1.7.2,<2.0.0"
+
+[package.extras]
+quart = ["Quart (>=0.17.0,<0.18.0)"]
+all = ["Quart (>=0.17.0,<0.18.0)", "websockets (>=10.0,<11.0)", "aiohttp[speedups] (>=3.7.4,<4.0.0)", "httpx[http2] (>=0.20.0,<1.0.0)"]
+websockets = ["websockets (>=10.0,<11.0)"]
+aiohttp = ["aiohttp[speedups] (>=3.7.4,<4.0.0)"]
+httpx = ["httpx[http2] (>=0.20.0,<1.0.0)"]
+
+[[package]]
+name = "nonebug"
+version = "0.2.1"
+description = "nonebot2 test framework"
+category = "dev"
+optional = false
+python-versions = ">=3.7.3,<4.0.0"
+
+[package.dependencies]
+asgiref = ">=3.4.0,<4.0.0"
+async-asgi-testclient = ">=1.4.8,<2.0.0"
+nonebot2 = ">=2.0.0-beta.1,<3.0.0"
+pytest = ">=6.2.5,<7.0.0"
+pytest-asyncio = ">=0.16.0,<0.17.0"
+typing-extensions = ">=4.0.0,<5.0.0"
+
+[[package]]
+name = "numpy"
+version = "1.23.1"
+description = "NumPy is the fundamental package for array computing with Python."
+category = "main"
+optional = false
+python-versions = ">=3.8"
+
+[[package]]
+name = "packaging"
+version = "21.3"
+description = "Core utilities for Python packages"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
+
+[[package]]
+name = "pillow"
+version = "9.2.0"
+description = "Python Imaging Library (Fork)"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"]
+tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
+
+[[package]]
+name = "pixivpy-async"
+version = "1.2.14"
+description = "Pure Python 3 Async Pixiv API"
+category = "main"
+optional = false
+python-versions = ">=3.5.3"
+
+[package.dependencies]
+aiofiles = "*"
+aiohttp = "*"
+deprecated = "*"
+
+[[package]]
+name = "pkginfo"
+version = "1.8.3"
+description = "Query metadatdata from sdists / bdists / installed packages."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+
+[package.extras]
+testing = ["nose", "coverage"]
+
+[[package]]
+name = "pluggy"
+version = "1.0.0"
+description = "plugin and hook calling mechanisms for python"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+testing = ["pytest-benchmark", "pytest"]
+dev = ["tox", "pre-commit"]
+
+[[package]]
+name = "py"
+version = "1.11.0"
+description = "library with cross-python path, ini-parsing, io, code, log facilities"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "pycodestyle"
+version = "2.9.1"
+description = "Python style guide checker"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "pycparser"
+version = "2.21"
+description = "C parser in Python"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "pydantic"
+version = "1.9.1"
+description = "Data validation and settings management using python type hints"
+category = "main"
+optional = false
+python-versions = ">=3.6.1"
+
+[package.dependencies]
+python-dotenv = {version = ">=0.10.4", optional = true, markers = "extra == \"dotenv\""}
+typing-extensions = ">=3.7.4.3"
+
+[package.extras]
+dotenv = ["python-dotenv (>=0.10.4)"]
+email = ["email-validator (>=1.0.3)"]
+
+[[package]]
+name = "pyflakes"
+version = "2.5.0"
+description = "passive checker of Python programs"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "pygments"
+version = "2.12.0"
+description = "Pygments is a syntax highlighting package written in Python."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "pygtrie"
+version = "2.5.0"
+description = "A pure Python trie data structure implementation."
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pymongo"
+version = "4.2.0"
+description = "Python driver for MongoDB <http://www.mongodb.org>"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+aws = ["pymongo-auth-aws (<2.0.0)"]
+encryption = ["pymongocrypt (>=1.3.0,<2.0.0)"]
+gssapi = ["pykerberos"]
+ocsp = ["pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)", "certifi"]
+snappy = ["python-snappy"]
+srv = ["dnspython (>=1.16.0,<3.0.0)"]
+zstd = ["zstandard"]
+
+[[package]]
+name = "pyparsing"
+version = "3.0.9"
+description = "pyparsing module - Classes and methods to define and execute parsing grammars"
+category = "dev"
+optional = false
+python-versions = ">=3.6.8"
+
+[package.extras]
+diagrams = ["railroad-diagrams", "jinja2"]
+
+[[package]]
+name = "pytest"
+version = "6.2.5"
+description = "pytest: simple powerful testing with Python"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
+attrs = ">=19.2.0"
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+iniconfig = "*"
+packaging = "*"
+pluggy = ">=0.12,<2.0"
+py = ">=1.8.2"
+toml = "*"
+
+[package.extras]
+testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
+
+[[package]]
+name = "pytest-asyncio"
+version = "0.16.0"
+description = "Pytest support for asyncio."
+category = "dev"
+optional = false
+python-versions = ">= 3.6"
+
+[package.dependencies]
+pytest = ">=5.4.0"
+
+[package.extras]
+testing = ["coverage", "hypothesis (>=5.7.1)"]
+
+[[package]]
+name = "pytest-mock"
+version = "3.8.2"
+description = "Thin-wrapper around the mock package for easier use with pytest"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+pytest = ">=5.0"
+
+[package.extras]
+dev = ["pytest-asyncio", "tox", "pre-commit"]
+
+[[package]]
+name = "python-dotenv"
+version = "0.20.0"
+description = "Read key-value pairs from a .env file and set them as environment variables"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[package.extras]
+cli = ["click (>=5.0)"]
+
+[[package]]
+name = "python-socks"
+version = "2.0.3"
+description = "Core proxy (SOCKS4, SOCKS5, HTTP tunneling) functionality for Python"
+category = "main"
+optional = true
+python-versions = "*"
+
+[package.dependencies]
+async-timeout = {version = ">=3.0.1", optional = true, markers = "extra == \"asyncio\""}
+
+[package.extras]
+anyio = ["anyio (>=3.3.4)"]
+asyncio = ["async-timeout (>=3.0.1)"]
+curio = ["curio (>=1.4)"]
+trio = ["trio (>=0.16.0)"]
+
+[[package]]
+name = "pytz"
+version = "2022.1"
+description = "World timezone definitions, modern and historical"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pytz-deprecation-shim"
+version = "0.1.0.post0"
+description = "Shims to make deprecation of pytz easier"
+category = "main"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
+
+[package.dependencies]
+tzdata = {version = "*", markers = "python_version >= \"3.6\""}
+
+[[package]]
+name = "pywin32-ctypes"
+version = "0.2.0"
+description = ""
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pyyaml"
+version = "6.0"
+description = "YAML parser and emitter for Python"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "readme-renderer"
+version = "36.0"
+description = "readme_renderer is a library for rendering \"readme\" descriptions for Warehouse"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+bleach = ">=2.1.0"
+docutils = ">=0.13.1"
+Pygments = ">=2.5.1"
+
+[package.extras]
+md = ["cmarkgfm (>=0.8.0)"]
+
+[[package]]
+name = "requests"
+version = "2.28.1"
+description = "Python HTTP for Humans."
+category = "dev"
+optional = false
+python-versions = ">=3.7, <4"
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+charset-normalizer = ">=2,<3"
+idna = ">=2.5,<4"
+urllib3 = ">=1.21.1,<1.27"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7)"]
+use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"]
+
+[[package]]
+name = "requests-toolbelt"
+version = "0.9.1"
+description = "A utility belt for advanced users of python-requests"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+requests = ">=2.0.1,<3.0.0"
+
+[[package]]
+name = "rfc3986"
+version = "2.0.0"
+description = "Validating URI References per RFC 3986"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+idna2008 = ["idna"]
+
+[[package]]
+name = "rich"
+version = "12.5.1"
+description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
+category = "dev"
+optional = false
+python-versions = ">=3.6.3,<4.0.0"
+
+[package.dependencies]
+commonmark = ">=0.9.0,<0.10.0"
+pygments = ">=2.6.0,<3.0.0"
+
+[package.extras]
+jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"]
+
+[[package]]
+name = "secretstorage"
+version = "3.3.2"
+description = "Python bindings to FreeDesktop.org Secret Service API"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+cryptography = ">=2.0"
+jeepney = ">=0.6"
+
+[[package]]
+name = "six"
+version = "1.16.0"
+description = "Python 2 and 3 compatibility utilities"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
+name = "sniffio"
+version = "1.2.0"
+description = "Sniff out which async library your code is running under"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "starlette"
+version = "0.19.1"
+description = "The little ASGI library that shines."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+anyio = ">=3.4.0,<5"
+typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""}
+
+[package.extras]
+full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"]
+
+[[package]]
+name = "toml"
+version = "0.10.2"
+description = "Python Library for Tom's Obvious, Minimal Language"
+category = "dev"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
+name = "tomlkit"
+version = "0.11.2"
+description = "Style preserving TOML library"
+category = "main"
+optional = false
+python-versions = ">=3.6,<4.0"
+
+[[package]]
+name = "twine"
+version = "4.0.1"
+description = "Collection of utilities for publishing packages on PyPI"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+importlib-metadata = ">=3.6"
+keyring = ">=15.1"
+pkginfo = ">=1.8.1"
+readme-renderer = ">=35.0"
+requests = ">=2.20"
+requests-toolbelt = ">=0.8.0,<0.9.0 || >0.9.0"
+rfc3986 = ">=1.4.0"
+rich = ">=12.0.0"
+urllib3 = ">=1.26.0"
+
+[[package]]
+name = "typing-extensions"
+version = "4.3.0"
+description = "Backported and Experimental Type Hints for Python 3.7+"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "tzdata"
+version = "2022.1"
+description = "Provider of IANA time zone data"
+category = "main"
+optional = false
+python-versions = ">=2"
+
+[[package]]
+name = "tzlocal"
+version = "4.2"
+description = "tzinfo object for the local timezone"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+pytz-deprecation-shim = "*"
+tzdata = {version = "*", markers = "platform_system == \"Windows\""}
+
+[package.extras]
+devenv = ["black", "pyroma", "pytest-cov", "zest.releaser"]
+test = ["pytest-mock (>=3.3)", "pytest (>=4.3)"]
+
+[[package]]
+name = "urllib3"
+version = "1.26.11"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
+secure = ["ipaddress", "certifi", "idna (>=2.0.0)", "cryptography (>=1.3.4)", "pyOpenSSL (>=0.14)"]
+brotli = ["brotlipy (>=0.6.0)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
+
+[[package]]
+name = "uvicorn"
+version = "0.18.2"
+description = "The lightning-fast ASGI server."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+click = ">=7.0"
+colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""}
+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\""}
+uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""}
+watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
+websockets = {version = ">=10.0", optional = true, markers = "extra == \"standard\""}
+
+[package.extras]
+standard = ["websockets (>=10.0)", "httptools (>=0.4.0)", "watchfiles (>=0.13)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"]
+
+[[package]]
+name = "uvloop"
+version = "0.16.0"
+description = "Fast implementation of asyncio event loop on top of libuv"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+dev = ["Cython (>=0.29.24,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"]
+docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)"]
+test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"]
+
+[[package]]
+name = "watchfiles"
+version = "0.16.1"
+description = "Simple, modern and high performance file watching and code reload in python."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+anyio = ">=3.0.0,<4"
+
+[[package]]
+name = "webencodings"
+version = "0.5.1"
+description = "Character encoding aliases for legacy web content"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "websockets"
+version = "10.3"
+description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "win32-setctime"
+version = "1.1.0"
+description = "A small Python utility to set file creation time on Windows"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[package.extras]
+dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"]
+
+[[package]]
+name = "wrapt"
+version = "1.14.1"
+description = "Module for decorators, wrappers and monkey patching."
+category = "main"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+
+[[package]]
+name = "yarl"
+version = "1.8.1"
+description = "Yet another URL library"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+idna = ">=2.0"
+multidict = ">=4.0"
+
+[[package]]
+name = "zipp"
+version = "3.8.1"
+description = "Backport of pathlib-compatible object wrapper for zip files"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"]
+testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"]
+
+[extras]
+socks = ["aiohttp-socks"]
+
+[metadata]
+lock-version = "1.1"
+python-versions = ">=3.9,<3.11"
+content-hash = "76b40dbd768174f2560d34946a634498f0061e899b3adfd8b4fabae0fdeca464"
+
+[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"},
+]
+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"},
+]
+aiohttp-socks = [
+    {file = "aiohttp_socks-0.7.1-py3-none-any.whl", hash = "sha256:94bcff5ef73611c6c6231c2ffc1be4af1599abec90dbd2fdbbd63233ec2fb0ff"},
+    {file = "aiohttp_socks-0.7.1.tar.gz", hash = "sha256:2215cac4891ef3fa14b7d600ed343ed0f0a670c23b10e4142aa862b3db20341a"},
+]
+aioreactive = [
+    {file = "aioreactive-0.16.0-py3-none-any.whl", hash = "sha256:b98f055e902dcc585a22511cdf6c88ba313ebb38d7387a9e0350b65b14ccfe67"},
+    {file = "aioreactive-0.16.0.tar.gz", hash = "sha256:7010dab2d87f4b26cb38000880c91ee9cf0c0e7c60807066beda62c94f2a7f3a"},
+]
+aiosignal = [
+    {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"},
+    {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"},
+]
+apscheduler = [
+    {file = "APScheduler-3.9.1-py2.py3-none-any.whl", hash = "sha256:ddc25a0ddd899de44d7f451f4375fb971887e65af51e41e5dcf681f59b8b2c9a"},
+    {file = "APScheduler-3.9.1.tar.gz", hash = "sha256:65e6574b6395498d371d045f2a8a7e4f7d50c6ad21ef7313d15b1c7cf20df1e3"},
+]
+asgiref = [
+    {file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"},
+    {file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"},
+]
+async-asgi-testclient = [
+    {file = "async-asgi-testclient-1.4.11.tar.gz", hash = "sha256:4449ac85d512d661998ec61f91c9ae01851639611d748d81ae7f816736551792"},
+]
+async-timeout = [
+    {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
+    {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
+]
+atomicwrites = [
+    {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"},
+]
+attrs = [
+    {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"},
+    {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"},
+]
+bleach = [
+    {file = "bleach-5.0.1-py3-none-any.whl", hash = "sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a"},
+    {file = "bleach-5.0.1.tar.gz", hash = "sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c"},
+]
+certifi = [
+    {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
+    {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
+]
+cffi = [
+    {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
+    {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"},
+    {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"},
+    {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"},
+    {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"},
+    {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"},
+    {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"},
+    {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"},
+    {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"},
+    {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"},
+    {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"},
+    {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"},
+    {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"},
+    {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"},
+    {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"},
+    {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"},
+    {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"},
+    {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"},
+    {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"},
+    {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"},
+    {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"},
+    {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"},
+    {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"},
+    {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"},
+    {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"},
+    {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"},
+    {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"},
+    {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"},
+    {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"},
+    {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"},
+    {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"},
+    {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"},
+    {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"},
+    {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"},
+    {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"},
+    {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"},
+    {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"},
+    {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"},
+    {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"},
+    {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"},
+    {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"},
+    {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"},
+    {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"},
+    {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"},
+    {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"},
+    {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"},
+    {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"},
+    {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"},
+    {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"},
+    {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"},
+    {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"},
+    {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"},
+    {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"},
+    {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"},
+    {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"},
+    {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"},
+    {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"},
+    {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"},
+    {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"},
+    {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"},
+    {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"},
+    {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"},
+    {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"},
+    {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"},
+]
+charset-normalizer = [
+    {file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"},
+    {file = "charset_normalizer-2.1.0-py3-none-any.whl", hash = "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5"},
+]
+click = [
+    {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
+    {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
+]
+colorama = [
+    {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
+    {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
+]
+commonmark = [
+    {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"},
+    {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"},
+]
+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"},
+]
+deprecated = [
+    {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"},
+    {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"},
+]
+docutils = [
+    {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"},
+    {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"},
+]
+expression = [
+    {file = "Expression-2.2.0-py3-none-any.whl", hash = "sha256:b66ff9cf5e08b5ba99ed8013a30741f97bf13d62b3bcdf4fcf2368f71ab5f2fb"},
+    {file = "Expression-2.2.0.tar.gz", hash = "sha256:b703c2a3bf46fc7ff6fd585ccc3bc7b791b80b583a8e691f9049b651afd4a024"},
+]
+fastapi = [
+    {file = "fastapi-0.79.0-py3-none-any.whl", hash = "sha256:d337563424ceada23857f73d5abe8dae0c28e4cccb53b2af06e78b7bb4a1c7d7"},
+    {file = "fastapi-0.79.0.tar.gz", hash = "sha256:cf0ff6db25b91d321050c4112baab0908c90f19b40bf257f9591d2f9780d1f22"},
+]
+flake8 = [
+    {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"},
+    {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"},
+]
+frozenlist = [
+    {file = "frozenlist-1.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5f271c93f001748fc26ddea409241312a75e13466b06c94798d1a341cf0e6989"},
+    {file = "frozenlist-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9c6ef8014b842f01f5d2b55315f1af5cbfde284eb184075c189fd657c2fd8204"},
+    {file = "frozenlist-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:219a9676e2eae91cb5cc695a78b4cb43d8123e4160441d2b6ce8d2c70c60e2f3"},
+    {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b47d64cdd973aede3dd71a9364742c542587db214e63b7529fbb487ed67cddd9"},
+    {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2af6f7a4e93f5d08ee3f9152bce41a6015b5cf87546cb63872cc19b45476e98a"},
+    {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a718b427ff781c4f4e975525edb092ee2cdef6a9e7bc49e15063b088961806f8"},
+    {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c56c299602c70bc1bb5d1e75f7d8c007ca40c9d7aebaf6e4ba52925d88ef826d"},
+    {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:717470bfafbb9d9be624da7780c4296aa7935294bd43a075139c3d55659038ca"},
+    {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:31b44f1feb3630146cffe56344704b730c33e042ffc78d21f2125a6a91168131"},
+    {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c3b31180b82c519b8926e629bf9f19952c743e089c41380ddca5db556817b221"},
+    {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d82bed73544e91fb081ab93e3725e45dd8515c675c0e9926b4e1f420a93a6ab9"},
+    {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49459f193324fbd6413e8e03bd65789e5198a9fa3095e03f3620dee2f2dabff2"},
+    {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:94e680aeedc7fd3b892b6fa8395b7b7cc4b344046c065ed4e7a1e390084e8cb5"},
+    {file = "frozenlist-1.3.1-cp310-cp310-win32.whl", hash = "sha256:fabb953ab913dadc1ff9dcc3a7a7d3dc6a92efab3a0373989b8063347f8705be"},
+    {file = "frozenlist-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:eee0c5ecb58296580fc495ac99b003f64f82a74f9576a244d04978a7e97166db"},
+    {file = "frozenlist-1.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0bc75692fb3770cf2b5856a6c2c9de967ca744863c5e89595df64e252e4b3944"},
+    {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086ca1ac0a40e722d6833d4ce74f5bf1aba2c77cbfdc0cd83722ffea6da52a04"},
+    {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b51eb355e7f813bcda00276b0114c4172872dc5fb30e3fea059b9367c18fbcb"},
+    {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74140933d45271c1a1283f708c35187f94e1256079b3c43f0c2267f9db5845ff"},
+    {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee4c5120ddf7d4dd1eaf079af3af7102b56d919fa13ad55600a4e0ebe532779b"},
+    {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97d9e00f3ac7c18e685320601f91468ec06c58acc185d18bb8e511f196c8d4b2"},
+    {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6e19add867cebfb249b4e7beac382d33215d6d54476bb6be46b01f8cafb4878b"},
+    {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a027f8f723d07c3f21963caa7d585dcc9b089335565dabe9c814b5f70c52705a"},
+    {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:61d7857950a3139bce035ad0b0945f839532987dfb4c06cfe160254f4d19df03"},
+    {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:53b2b45052e7149ee8b96067793db8ecc1ae1111f2f96fe1f88ea5ad5fd92d10"},
+    {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bbb1a71b1784e68870800b1bc9f3313918edc63dbb8f29fbd2e767ce5821696c"},
+    {file = "frozenlist-1.3.1-cp37-cp37m-win32.whl", hash = "sha256:ab6fa8c7871877810e1b4e9392c187a60611fbf0226a9e0b11b7b92f5ac72792"},
+    {file = "frozenlist-1.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f89139662cc4e65a4813f4babb9ca9544e42bddb823d2ec434e18dad582543bc"},
+    {file = "frozenlist-1.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4c0c99e31491a1d92cde8648f2e7ccad0e9abb181f6ac3ddb9fc48b63301808e"},
+    {file = "frozenlist-1.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:61e8cb51fba9f1f33887e22488bad1e28dd8325b72425f04517a4d285a04c519"},
+    {file = "frozenlist-1.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc2f3e368ee5242a2cbe28323a866656006382872c40869b49b265add546703f"},
+    {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58fb94a01414cddcdc6839807db77ae8057d02ddafc94a42faee6004e46c9ba8"},
+    {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:022178b277cb9277d7d3b3f2762d294f15e85cd2534047e68a118c2bb0058f3e"},
+    {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:572ce381e9fe027ad5e055f143763637dcbac2542cfe27f1d688846baeef5170"},
+    {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19127f8dcbc157ccb14c30e6f00392f372ddb64a6ffa7106b26ff2196477ee9f"},
+    {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42719a8bd3792744c9b523674b752091a7962d0d2d117f0b417a3eba97d1164b"},
+    {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2743bb63095ef306041c8f8ea22bd6e4d91adabf41887b1ad7886c4c1eb43d5f"},
+    {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:fa47319a10e0a076709644a0efbcaab9e91902c8bd8ef74c6adb19d320f69b83"},
+    {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52137f0aea43e1993264a5180c467a08a3e372ca9d378244c2d86133f948b26b"},
+    {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:f5abc8b4d0c5b556ed8cd41490b606fe99293175a82b98e652c3f2711b452988"},
+    {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1e1cf7bc8cbbe6ce3881863671bac258b7d6bfc3706c600008925fb799a256e2"},
+    {file = "frozenlist-1.3.1-cp38-cp38-win32.whl", hash = "sha256:0dde791b9b97f189874d654c55c24bf7b6782343e14909c84beebd28b7217845"},
+    {file = "frozenlist-1.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:9494122bf39da6422b0972c4579e248867b6b1b50c9b05df7e04a3f30b9a413d"},
+    {file = "frozenlist-1.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:31bf9539284f39ff9398deabf5561c2b0da5bb475590b4e13dd8b268d7a3c5c1"},
+    {file = "frozenlist-1.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e0c8c803f2f8db7217898d11657cb6042b9b0553a997c4a0601f48a691480fab"},
+    {file = "frozenlist-1.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da5ba7b59d954f1f214d352308d1d86994d713b13edd4b24a556bcc43d2ddbc3"},
+    {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74e6b2b456f21fc93ce1aff2b9728049f1464428ee2c9752a4b4f61e98c4db96"},
+    {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526d5f20e954d103b1d47232e3839f3453c02077b74203e43407b962ab131e7b"},
+    {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b499c6abe62a7a8d023e2c4b2834fce78a6115856ae95522f2f974139814538c"},
+    {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab386503f53bbbc64d1ad4b6865bf001414930841a870fc97f1546d4d133f141"},
+    {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f63c308f82a7954bf8263a6e6de0adc67c48a8b484fab18ff87f349af356efd"},
+    {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:12607804084d2244a7bd4685c9d0dca5df17a6a926d4f1967aa7978b1028f89f"},
+    {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:da1cdfa96425cbe51f8afa43e392366ed0b36ce398f08b60de6b97e3ed4affef"},
+    {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f810e764617b0748b49a731ffaa525d9bb36ff38332411704c2400125af859a6"},
+    {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:35c3d79b81908579beb1fb4e7fcd802b7b4921f1b66055af2578ff7734711cfa"},
+    {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c92deb5d9acce226a501b77307b3b60b264ca21862bd7d3e0c1f3594022f01bc"},
+    {file = "frozenlist-1.3.1-cp39-cp39-win32.whl", hash = "sha256:5e77a8bd41e54b05e4fb2708dc6ce28ee70325f8c6f50f3df86a44ecb1d7a19b"},
+    {file = "frozenlist-1.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:625d8472c67f2d96f9a4302a947f92a7adbc1e20bedb6aff8dbc8ff039ca6189"},
+    {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"},
+]
+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"},
+]
+idna = [
+    {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
+    {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
+]
+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"},
+]
+iniconfig = [
+    {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
+    {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
+]
+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.1-py3-none-any.whl", hash = "sha256:73dec1c13b15a69979fc9952e01c5c69e38c8ba340cc664d0461a947fc90b93c"},
+    {file = "keyring-23.8.1.tar.gz", hash = "sha256:9715c8461f6d9e61066a5883b1fc8aec68f950e81db4ce9f8635cbaa4b9625bb"},
+]
+lazy = [
+    {file = "lazy-1.4-py2.py3-none-any.whl", hash = "sha256:9f293fd531546f3eda039da8f919b513b94f219e562764e3ceefc26fa503bf03"},
+    {file = "lazy-1.4.zip", hash = "sha256:2c6d27a5ab130fb85435320651a47403adcb37ecbcc501b0c6606391f65f5b43"},
+]
+loguru = [
+    {file = "loguru-0.6.0-py3-none-any.whl", hash = "sha256:4e2414d534a2ab57573365b3e6d0234dfb1d84b68b7f3b948e6fb743860a77c3"},
+    {file = "loguru-0.6.0.tar.gz", hash = "sha256:066bd06758d0a513e9836fd9c6b5a75bfb3fd36841f4b996bc60b547a309d41c"},
+]
+mccabe = [
+    {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
+    {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
+]
+motor = [
+    {file = "motor-3.0.0-py3-none-any.whl", hash = "sha256:b076de44970f518177f0eeeda8b183f52eafa557775bfe3294e93bda18867a71"},
+    {file = "motor-3.0.0.tar.gz", hash = "sha256:3e36d29406c151b61342e6a8fa5e90c00c4723b76e30f11276a4373ea2064b7d"},
+]
+multidict = [
+    {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"},
+    {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"},
+    {file = "multidict-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c"},
+    {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f"},
+    {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9"},
+    {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20"},
+    {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88"},
+    {file = "multidict-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7"},
+    {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee"},
+    {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672"},
+    {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9"},
+    {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87"},
+    {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389"},
+    {file = "multidict-6.0.2-cp310-cp310-win32.whl", hash = "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293"},
+    {file = "multidict-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658"},
+    {file = "multidict-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51"},
+    {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608"},
+    {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3"},
+    {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4"},
+    {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b"},
+    {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8"},
+    {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba"},
+    {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43"},
+    {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8"},
+    {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b"},
+    {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15"},
+    {file = "multidict-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc"},
+    {file = "multidict-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a"},
+    {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60"},
+    {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86"},
+    {file = "multidict-6.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d"},
+    {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0"},
+    {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d"},
+    {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376"},
+    {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693"},
+    {file = "multidict-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849"},
+    {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49"},
+    {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516"},
+    {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227"},
+    {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9"},
+    {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d"},
+    {file = "multidict-6.0.2-cp38-cp38-win32.whl", hash = "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57"},
+    {file = "multidict-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96"},
+    {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c"},
+    {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e"},
+    {file = "multidict-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071"},
+    {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032"},
+    {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2"},
+    {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c"},
+    {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9"},
+    {file = "multidict-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80"},
+    {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d"},
+    {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb"},
+    {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68"},
+    {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360"},
+    {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937"},
+    {file = "multidict-6.0.2-cp39-cp39-win32.whl", hash = "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a"},
+    {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"},
+    {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"},
+]
+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"},
+]
+nonebot2 = [
+    {file = "nonebot2-2.0.0b5-py3-none-any.whl", hash = "sha256:5cfbfcd62ffcf55f884f9fb750a71cd92c97e347c99e891ef617da4d445a1f22"},
+    {file = "nonebot2-2.0.0b5.tar.gz", hash = "sha256:fccb5af749cceb289d756901b7abc7a679e5952dbe88c8b6ad8b2d906715c3ee"},
+]
+nonebug = [
+    {file = "nonebug-0.2.1-py3-none-any.whl", hash = "sha256:f4d59effd50e400ee866df57902e4d749227a76857be26a0607fc2a5f6a05f7c"},
+    {file = "nonebug-0.2.1.tar.gz", hash = "sha256:2f363bd5d65081c802b7b19a72b07ada1ad8e61968cf313176f38a5cf97e84e2"},
+]
+numpy = [
+    {file = "numpy-1.23.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b15c3f1ed08df4980e02cc79ee058b788a3d0bef2fb3c9ca90bb8cbd5b8a3a04"},
+    {file = "numpy-1.23.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9ce242162015b7e88092dccd0e854548c0926b75c7924a3495e02c6067aba1f5"},
+    {file = "numpy-1.23.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0d7447679ae9a7124385ccf0ea990bb85bb869cef217e2ea6c844b6a6855073"},
+    {file = "numpy-1.23.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3119daed207e9410eaf57dcf9591fdc68045f60483d94956bee0bfdcba790953"},
+    {file = "numpy-1.23.1-cp310-cp310-win32.whl", hash = "sha256:3ab67966c8d45d55a2bdf40701536af6443763907086c0a6d1232688e27e5447"},
+    {file = "numpy-1.23.1-cp310-cp310-win_amd64.whl", hash = "sha256:1865fdf51446839ca3fffaab172461f2b781163f6f395f1aed256b1ddc253622"},
+    {file = "numpy-1.23.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:aeba539285dcf0a1ba755945865ec61240ede5432df41d6e29fab305f4384db2"},
+    {file = "numpy-1.23.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7e8229f3687cdadba2c4faef39204feb51ef7c1a9b669247d49a24f3e2e1617c"},
+    {file = "numpy-1.23.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68b69f52e6545af010b76516f5daaef6173e73353e3295c5cb9f96c35d755641"},
+    {file = "numpy-1.23.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1408c3527a74a0209c781ac82bde2182b0f0bf54dea6e6a363fe0cc4488a7ce7"},
+    {file = "numpy-1.23.1-cp38-cp38-win32.whl", hash = "sha256:47f10ab202fe4d8495ff484b5561c65dd59177949ca07975663f4494f7269e3e"},
+    {file = "numpy-1.23.1-cp38-cp38-win_amd64.whl", hash = "sha256:37e5ebebb0eb54c5b4a9b04e6f3018e16b8ef257d26c8945925ba8105008e645"},
+    {file = "numpy-1.23.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:173f28921b15d341afadf6c3898a34f20a0569e4ad5435297ba262ee8941e77b"},
+    {file = "numpy-1.23.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:876f60de09734fbcb4e27a97c9a286b51284df1326b1ac5f1bf0ad3678236b22"},
+    {file = "numpy-1.23.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35590b9c33c0f1c9732b3231bb6a72d1e4f77872390c47d50a615686ae7ed3fd"},
+    {file = "numpy-1.23.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a35c4e64dfca659fe4d0f1421fc0f05b8ed1ca8c46fb73d9e5a7f175f85696bb"},
+    {file = "numpy-1.23.1-cp39-cp39-win32.whl", hash = "sha256:c2f91f88230042a130ceb1b496932aa717dcbd665350beb821534c5c7e15881c"},
+    {file = "numpy-1.23.1-cp39-cp39-win_amd64.whl", hash = "sha256:37ece2bd095e9781a7156852e43d18044fd0d742934833335599c583618181b9"},
+    {file = "numpy-1.23.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8002574a6b46ac3b5739a003b5233376aeac5163e5dcd43dd7ad062f3e186129"},
+    {file = "numpy-1.23.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d732d17b8a9061540a10fda5bfeabca5785700ab5469a5e9b93aca5e2d3a5fb"},
+    {file = "numpy-1.23.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:55df0f7483b822855af67e38fb3a526e787adf189383b4934305565d71c4b148"},
+    {file = "numpy-1.23.1.tar.gz", hash = "sha256:d748ef349bfef2e1194b59da37ed5a29c19ea8d7e6342019921ba2ba4fd8b624"},
+]
+packaging = [
+    {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
+    {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
+]
+pillow = [
+    {file = "Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb"},
+    {file = "Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:510cef4a3f401c246cfd8227b300828715dd055463cdca6176c2e4036df8bd4f"},
+    {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7888310f6214f19ab2b6df90f3f06afa3df7ef7355fc025e78a3044737fab1f5"},
+    {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831e648102c82f152e14c1a0938689dbb22480c548c8d4b8b248b3e50967b88c"},
+    {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cc1d2451e8a3b4bfdb9caf745b58e6c7a77d2e469159b0d527a4554d73694d1"},
+    {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:136659638f61a251e8ed3b331fc6ccd124590eeff539de57c5f80ef3a9594e58"},
+    {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6e8c66f70fb539301e064f6478d7453e820d8a2c631da948a23384865cd95544"},
+    {file = "Pillow-9.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e"},
+    {file = "Pillow-9.2.0-cp310-cp310-win32.whl", hash = "sha256:c79698d4cd9318d9481d89a77e2d3fcaeff5486be641e60a4b49f3d2ecca4e28"},
+    {file = "Pillow-9.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:254164c57bab4b459f14c64e93df11eff5ded575192c294a0c49270f22c5d93d"},
+    {file = "Pillow-9.2.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:408673ed75594933714482501fe97e055a42996087eeca7e5d06e33218d05aa8"},
+    {file = "Pillow-9.2.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:727dd1389bc5cb9827cbd1f9d40d2c2a1a0c9b32dd2261db522d22a604a6eec9"},
+    {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50dff9cc21826d2977ef2d2a205504034e3a4563ca6f5db739b0d1026658e004"},
+    {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb6259196a589123d755380b65127ddc60f4c64b21fc3bb46ce3a6ea663659b0"},
+    {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b0554af24df2bf96618dac71ddada02420f946be943b181108cac55a7a2dcd4"},
+    {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:15928f824870535c85dbf949c09d6ae7d3d6ac2d6efec80f3227f73eefba741c"},
+    {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a"},
+    {file = "Pillow-9.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5b87da55a08acb586bad5c3aa3b86505f559b84f39035b233d5bf844b0834b1"},
+    {file = "Pillow-9.2.0-cp311-cp311-win32.whl", hash = "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf"},
+    {file = "Pillow-9.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6bf088c1ce160f50ea40764f825ec9b72ed9da25346216b91361eef8ad1b8f8c"},
+    {file = "Pillow-9.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069"},
+    {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eef7592281f7c174d3d6cbfbb7ee5984a671fcd77e3fc78e973d492e9bf0eb3f"},
+    {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcd7b9c7139dc8258d164b55696ecd16c04607f1cc33ba7af86613881ffe4ac8"},
+    {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a138441e95562b3c078746a22f8fca8ff1c22c014f856278bdbdd89ca36cff1b"},
+    {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:93689632949aff41199090eff5474f3990b6823404e45d66a5d44304e9cdc467"},
+    {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:f3fac744f9b540148fa7715a435d2283b71f68bfb6d4aae24482a890aed18b59"},
+    {file = "Pillow-9.2.0-cp37-cp37m-win32.whl", hash = "sha256:fa768eff5f9f958270b081bb33581b4b569faabf8774726b283edb06617101dc"},
+    {file = "Pillow-9.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:69bd1a15d7ba3694631e00df8de65a8cb031911ca11f44929c97fe05eb9b6c1d"},
+    {file = "Pillow-9.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:030e3460861488e249731c3e7ab59b07c7853838ff3b8e16aac9561bb345da14"},
+    {file = "Pillow-9.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:74a04183e6e64930b667d321524e3c5361094bb4af9083db5c301db64cd341f3"},
+    {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402"},
+    {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fd6f5e3c0e4697fa7eb45b6e93996299f3feee73a3175fa451f49a74d092b9f"},
+    {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8"},
+    {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:4134d3f1ba5f15027ff5c04296f13328fecd46921424084516bdb1b2548e66ff"},
+    {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1"},
+    {file = "Pillow-9.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1536ad017a9f789430fb6b8be8bf99d2f214c76502becc196c6f2d9a75b01b76"},
+    {file = "Pillow-9.2.0-cp38-cp38-win32.whl", hash = "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f"},
+    {file = "Pillow-9.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:ec52c351b35ca269cb1f8069d610fc45c5bd38c3e91f9ab4cbbf0aebc136d9c8"},
+    {file = "Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ed2c4ef2451de908c90436d6e8092e13a43992f1860275b4d8082667fbb2ffc"},
+    {file = "Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ad2f835e0ad81d1689f1b7e3fbac7b01bb8777d5a985c8962bedee0cc6d43da"},
+    {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea98f633d45f7e815db648fd7ff0f19e328302ac36427343e4432c84432e7ff4"},
+    {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7761afe0126d046974a01e030ae7529ed0ca6a196de3ec6937c11df0df1bc91c"},
+    {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a54614049a18a2d6fe156e68e188da02a046a4a93cf24f373bffd977e943421"},
+    {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:5aed7dde98403cd91d86a1115c78d8145c83078e864c1de1064f52e6feb61b20"},
+    {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:13b725463f32df1bfeacbf3dd197fb358ae8ebcd8c5548faa75126ea425ccb60"},
+    {file = "Pillow-9.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:808add66ea764ed97d44dda1ac4f2cfec4c1867d9efb16a33d158be79f32b8a4"},
+    {file = "Pillow-9.2.0-cp39-cp39-win32.whl", hash = "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885"},
+    {file = "Pillow-9.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:fac2d65901fb0fdf20363fbd345c01958a742f2dc62a8dd4495af66e3ff502a4"},
+    {file = "Pillow-9.2.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3"},
+    {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c7b502bc34f6e32ba022b4a209638f9e097d7a9098104ae420eb8186217ebbb"},
+    {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be"},
+    {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:dfe4c1fedfde4e2fbc009d5ad420647f7730d719786388b7de0999bf32c0d9fd"},
+    {file = "Pillow-9.2.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:f07f1f00e22b231dd3d9b9208692042e29792d6bd4f6639415d2f23158a80013"},
+    {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1802f34298f5ba11d55e5bb09c31997dc0c6aed919658dfdf0198a2fe75d5490"},
+    {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17d4cafe22f050b46d983b71c707162d63d796a1235cdf8b9d7a112e97b15bac"},
+    {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:96b5e6874431df16aee0c1ba237574cb6dff1dcb173798faa6a9d8b399a05d0e"},
+    {file = "Pillow-9.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927"},
+    {file = "Pillow-9.2.0.tar.gz", hash = "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04"},
+]
+pixivpy-async = [
+    {file = "PixivPy-Async-1.2.14.tar.gz", hash = "sha256:01f73b92c41219fcd19c18aa4b27b3b724e8da78574f0f2c7f62cc5e01c41c29"},
+    {file = "PixivPy_Async-1.2.14-py3-none-any.whl", hash = "sha256:44377cd929af53a66d36c2ea1abd6bf5f5398233b1af945d7802f802f112a7d7"},
+]
+pkginfo = [
+    {file = "pkginfo-1.8.3-py2.py3-none-any.whl", hash = "sha256:848865108ec99d4901b2f7e84058b6e7660aae8ae10164e015a6dcf5b242a594"},
+    {file = "pkginfo-1.8.3.tar.gz", hash = "sha256:a84da4318dd86f870a9447a8c98340aa06216bfc6f2b7bdc4b8766984ae1867c"},
+]
+pluggy = [
+    {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
+    {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
+]
+py = [
+    {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
+    {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
+]
+pycodestyle = [
+    {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"},
+    {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"},
+]
+pycparser = [
+    {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
+    {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
+]
+pydantic = [
+    {file = "pydantic-1.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8098a724c2784bf03e8070993f6d46aa2eeca031f8d8a048dff277703e6e193"},
+    {file = "pydantic-1.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c320c64dd876e45254bdd350f0179da737463eea41c43bacbee9d8c9d1021f11"},
+    {file = "pydantic-1.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18f3e912f9ad1bdec27fb06b8198a2ccc32f201e24174cec1b3424dda605a310"},
+    {file = "pydantic-1.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11951b404e08b01b151222a1cb1a9f0a860a8153ce8334149ab9199cd198131"},
+    {file = "pydantic-1.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8bc541a405423ce0e51c19f637050acdbdf8feca34150e0d17f675e72d119580"},
+    {file = "pydantic-1.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e565a785233c2d03724c4dc55464559639b1ba9ecf091288dd47ad9c629433bd"},
+    {file = "pydantic-1.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a4a88dcd6ff8fd47c18b3a3709a89adb39a6373f4482e04c1b765045c7e282fd"},
+    {file = "pydantic-1.9.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:447d5521575f18e18240906beadc58551e97ec98142266e521c34968c76c8761"},
+    {file = "pydantic-1.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:985ceb5d0a86fcaa61e45781e567a59baa0da292d5ed2e490d612d0de5796918"},
+    {file = "pydantic-1.9.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059b6c1795170809103a1538255883e1983e5b831faea6558ef873d4955b4a74"},
+    {file = "pydantic-1.9.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d12f96b5b64bec3f43c8e82b4aab7599d0157f11c798c9f9c528a72b9e0b339a"},
+    {file = "pydantic-1.9.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ae72f8098acb368d877b210ebe02ba12585e77bd0db78ac04a1ee9b9f5dd2166"},
+    {file = "pydantic-1.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:79b485767c13788ee314669008d01f9ef3bc05db9ea3298f6a50d3ef596a154b"},
+    {file = "pydantic-1.9.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:494f7c8537f0c02b740c229af4cb47c0d39840b829ecdcfc93d91dcbb0779892"},
+    {file = "pydantic-1.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0f047e11febe5c3198ed346b507e1d010330d56ad615a7e0a89fae604065a0e"},
+    {file = "pydantic-1.9.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:969dd06110cb780da01336b281f53e2e7eb3a482831df441fb65dd30403f4608"},
+    {file = "pydantic-1.9.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:177071dfc0df6248fd22b43036f936cfe2508077a72af0933d0c1fa269b18537"},
+    {file = "pydantic-1.9.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9bcf8b6e011be08fb729d110f3e22e654a50f8a826b0575c7196616780683380"},
+    {file = "pydantic-1.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a955260d47f03df08acf45689bd163ed9df82c0e0124beb4251b1290fa7ae728"},
+    {file = "pydantic-1.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9ce157d979f742a915b75f792dbd6aa63b8eccaf46a1005ba03aa8a986bde34a"},
+    {file = "pydantic-1.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0bf07cab5b279859c253d26a9194a8906e6f4a210063b84b433cf90a569de0c1"},
+    {file = "pydantic-1.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d93d4e95eacd313d2c765ebe40d49ca9dd2ed90e5b37d0d421c597af830c195"},
+    {file = "pydantic-1.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1542636a39c4892c4f4fa6270696902acb186a9aaeac6f6cf92ce6ae2e88564b"},
+    {file = "pydantic-1.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a9af62e9b5b9bc67b2a195ebc2c2662fdf498a822d62f902bf27cccb52dbbf49"},
+    {file = "pydantic-1.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fe4670cb32ea98ffbf5a1262f14c3e102cccd92b1869df3bb09538158ba90fe6"},
+    {file = "pydantic-1.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:9f659a5ee95c8baa2436d392267988fd0f43eb774e5eb8739252e5a7e9cf07e0"},
+    {file = "pydantic-1.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b83ba3825bc91dfa989d4eed76865e71aea3a6ca1388b59fc801ee04c4d8d0d6"},
+    {file = "pydantic-1.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1dd8fecbad028cd89d04a46688d2fcc14423e8a196d5b0a5c65105664901f810"},
+    {file = "pydantic-1.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02eefd7087268b711a3ff4db528e9916ac9aa18616da7bca69c1871d0b7a091f"},
+    {file = "pydantic-1.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7eb57ba90929bac0b6cc2af2373893d80ac559adda6933e562dcfb375029acee"},
+    {file = "pydantic-1.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4ce9ae9e91f46c344bec3b03d6ee9612802682c1551aaf627ad24045ce090761"},
+    {file = "pydantic-1.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:72ccb318bf0c9ab97fc04c10c37683d9eea952ed526707fabf9ac5ae59b701fd"},
+    {file = "pydantic-1.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:61b6760b08b7c395975d893e0b814a11cf011ebb24f7d869e7118f5a339a82e1"},
+    {file = "pydantic-1.9.1-py3-none-any.whl", hash = "sha256:4988c0f13c42bfa9ddd2fe2f569c9d54646ce84adc5de84228cfe83396f3bd58"},
+    {file = "pydantic-1.9.1.tar.gz", hash = "sha256:1ed987c3ff29fff7fd8c3ea3a3ea877ad310aae2ef9889a119e22d3f2db0691a"},
+]
+pyflakes = [
+    {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"},
+    {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"},
+]
+pygments = [
+    {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"},
+    {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"},
+]
+pygtrie = [
+    {file = "pygtrie-2.5.0.tar.gz", hash = "sha256:203514ad826eb403dab1d2e2ddd034e0d1534bbe4dbe0213bb0593f66beba4e2"},
+]
+pymongo = [
+    {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"},
+]
+pyparsing = [
+    {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
+    {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
+]
+pytest = [
+    {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"},
+    {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"},
+]
+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-mock = [
+    {file = "pytest-mock-3.8.2.tar.gz", hash = "sha256:77f03f4554392558700295e05aed0b1096a20d4a60a4f3ddcde58b0c31c8fca2"},
+    {file = "pytest_mock-3.8.2-py3-none-any.whl", hash = "sha256:8a9e226d6c0ef09fcf20c94eb3405c388af438a90f3e39687f84166da82d5948"},
+]
+python-dotenv = [
+    {file = "python-dotenv-0.20.0.tar.gz", hash = "sha256:b7e3b04a59693c42c36f9ab1cc2acc46fa5df8c78e178fc33a8d4cd05c8d498f"},
+    {file = "python_dotenv-0.20.0-py3-none-any.whl", hash = "sha256:d92a187be61fe482e4fd675b6d52200e7be63a12b724abbf931a40ce4fa92938"},
+]
+python-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.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"},
+    {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"},
+]
+pytz-deprecation-shim = [
+    {file = "pytz_deprecation_shim-0.1.0.post0-py2.py3-none-any.whl", hash = "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6"},
+    {file = "pytz_deprecation_shim-0.1.0.post0.tar.gz", hash = "sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"},
+]
+pywin32-ctypes = [
+    {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"},
+    {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"},
+]
+pyyaml = [
+    {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
+    {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
+    {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
+    {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
+    {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
+    {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
+    {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
+    {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
+    {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
+    {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
+    {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
+    {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
+    {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
+    {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
+    {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
+    {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
+    {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
+    {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
+    {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
+    {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
+    {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
+    {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
+    {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
+    {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
+    {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
+    {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
+    {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
+    {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
+    {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
+    {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
+    {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
+    {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
+    {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
+]
+readme-renderer = [
+    {file = "readme_renderer-36.0-py3-none-any.whl", hash = "sha256:2c37e472ca96755caba6cc58bcbf673a5574bc033385a2ac91d85dfef2799876"},
+    {file = "readme_renderer-36.0.tar.gz", hash = "sha256:f71aeef9a588fcbed1f4cc001ba611370e94a0cd27c75b1140537618ec78f0a2"},
+]
+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"},
+]
+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"},
+]
+secretstorage = [
+    {file = "SecretStorage-3.3.2-py3-none-any.whl", hash = "sha256:755dc845b6ad76dcbcbc07ea3da75ae54bb1ea529eb72d15f83d26499a5df319"},
+    {file = "SecretStorage-3.3.2.tar.gz", hash = "sha256:0a8eb9645b320881c222e827c26f4cfcf55363e8b374a021981ef886657a912f"},
+]
+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"},
+]
+starlette = [
+    {file = "starlette-0.19.1-py3-none-any.whl", hash = "sha256:5a60c5c2d051f3a8eb546136aa0c9399773a689595e099e0877704d5888279bf"},
+    {file = "starlette-0.19.1.tar.gz", hash = "sha256:c6d21096774ecb9639acad41b86b7706e52ba3bf1dc13ea4ed9ad593d47e24c7"},
+]
+toml = [
+    {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
+    {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
+]
+tomlkit = [
+    {file = "tomlkit-0.11.2-py3-none-any.whl", hash = "sha256:69e0675671a2eed1c08a53f342c955c4ead5d373a10f756219bf39f3d4f0018a"},
+    {file = "tomlkit-0.11.2.tar.gz", hash = "sha256:d1b49c3e460f5910b22d799b13513504acb4f5fcaee01660ee66f07bd45a271c"},
+]
+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"},
+]
+tzdata = [
+    {file = "tzdata-2022.1-py2.py3-none-any.whl", hash = "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9"},
+    {file = "tzdata-2022.1.tar.gz", hash = "sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3"},
+]
+tzlocal = [
+    {file = "tzlocal-4.2-py3-none-any.whl", hash = "sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745"},
+    {file = "tzlocal-4.2.tar.gz", hash = "sha256:ee5842fa3a795f023514ac2d801c4a81d1743bbe642e3940143326b3a00addd7"},
+]
+urllib3 = [
+    {file = "urllib3-1.26.11-py2.py3-none-any.whl", hash = "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc"},
+    {file = "urllib3-1.26.11.tar.gz", hash = "sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a"},
+]
+uvicorn = [
+    {file = "uvicorn-0.18.2-py3-none-any.whl", hash = "sha256:c19a057deb1c5bb060946e2e5c262fc01590c6529c0af2c3d9ce941e89bc30e0"},
+    {file = "uvicorn-0.18.2.tar.gz", hash = "sha256:cade07c403c397f9fe275492a48c1b869efd175d5d8a692df649e6e7e2ed8f4e"},
+]
+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"},
+]
+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"},
+]
+webencodings = [
+    {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},
+    {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"},
+]
+websockets = [
+    {file = "websockets-10.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:661f641b44ed315556a2fa630239adfd77bd1b11cb0b9d96ed8ad90b0b1e4978"},
+    {file = "websockets-10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b529fdfa881b69fe563dbd98acce84f3e5a67df13de415e143ef053ff006d500"},
+    {file = "websockets-10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f351c7d7d92f67c0609329ab2735eee0426a03022771b00102816a72715bb00b"},
+    {file = "websockets-10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379e03422178436af4f3abe0aa8f401aa77ae2487843738542a75faf44a31f0c"},
+    {file = "websockets-10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e904c0381c014b914136c492c8fa711ca4cced4e9b3d110e5e7d436d0fc289e8"},
+    {file = "websockets-10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e7e6f2d6fd48422071cc8a6f8542016f350b79cc782752de531577d35e9bd677"},
+    {file = "websockets-10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9c77f0d1436ea4b4dc089ed8335fa141e6a251a92f75f675056dac4ab47a71e"},
+    {file = "websockets-10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e6fa05a680e35d0fcc1470cb070b10e6fe247af54768f488ed93542e71339d6f"},
+    {file = "websockets-10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2f94fa3ae454a63ea3a19f73b95deeebc9f02ba2d5617ca16f0bbdae375cda47"},
+    {file = "websockets-10.3-cp310-cp310-win32.whl", hash = "sha256:6ed1d6f791eabfd9808afea1e068f5e59418e55721db8b7f3bfc39dc831c42ae"},
+    {file = "websockets-10.3-cp310-cp310-win_amd64.whl", hash = "sha256:347974105bbd4ea068106ec65e8e8ebd86f28c19e529d115d89bd8cc5cda3079"},
+    {file = "websockets-10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fab7c640815812ed5f10fbee7abbf58788d602046b7bb3af9b1ac753a6d5e916"},
+    {file = "websockets-10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:994cdb1942a7a4c2e10098d9162948c9e7b235df755de91ca33f6e0481366fdb"},
+    {file = "websockets-10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:aad5e300ab32036eb3fdc350ad30877210e2f51bceaca83fb7fef4d2b6c72b79"},
+    {file = "websockets-10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e49ea4c1a9543d2bd8a747ff24411509c29e4bdcde05b5b0895e2120cb1a761d"},
+    {file = "websockets-10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ea6b300a6bdd782e49922d690e11c3669828fe36fc2471408c58b93b5535a98"},
+    {file = "websockets-10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ef5ce841e102278c1c2e98f043db99d6755b1c58bde475516aef3a008ed7f28e"},
+    {file = "websockets-10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d1655a6fc7aecd333b079d00fb3c8132d18988e47f19740c69303bf02e9883c6"},
+    {file = "websockets-10.3-cp37-cp37m-win32.whl", hash = "sha256:83e5ca0d5b743cde3d29fda74ccab37bdd0911f25bd4cdf09ff8b51b7b4f2fa1"},
+    {file = "websockets-10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:da4377904a3379f0c1b75a965fff23b28315bcd516d27f99a803720dfebd94d4"},
+    {file = "websockets-10.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a1e15b230c3613e8ea82c9fc6941b2093e8eb939dd794c02754d33980ba81e36"},
+    {file = "websockets-10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:31564a67c3e4005f27815634343df688b25705cccb22bc1db621c781ddc64c69"},
+    {file = "websockets-10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c8d1d14aa0f600b5be363077b621b1b4d1eb3fbf90af83f9281cda668e6ff7fd"},
+    {file = "websockets-10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fbd7d77f8aba46d43245e86dd91a8970eac4fb74c473f8e30e9c07581f852b2"},
+    {file = "websockets-10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:210aad7fdd381c52e58777560860c7e6110b6174488ef1d4b681c08b68bf7f8c"},
+    {file = "websockets-10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6075fd24df23133c1b078e08a9b04a3bc40b31a8def4ee0b9f2c8865acce913e"},
+    {file = "websockets-10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7f6d96fdb0975044fdd7953b35d003b03f9e2bcf85f2d2cf86285ece53e9f991"},
+    {file = "websockets-10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c7250848ce69559756ad0086a37b82c986cd33c2d344ab87fea596c5ac6d9442"},
+    {file = "websockets-10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:28dd20b938a57c3124028680dc1600c197294da5db4292c76a0b48efb3ed7f76"},
+    {file = "websockets-10.3-cp38-cp38-win32.whl", hash = "sha256:54c000abeaff6d8771a4e2cef40900919908ea7b6b6a30eae72752607c6db559"},
+    {file = "websockets-10.3-cp38-cp38-win_amd64.whl", hash = "sha256:7ab36e17af592eec5747c68ef2722a74c1a4a70f3772bc661079baf4ae30e40d"},
+    {file = "websockets-10.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a141de3d5a92188234afa61653ed0bbd2dde46ad47b15c3042ffb89548e77094"},
+    {file = "websockets-10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:97bc9d41e69a7521a358f9b8e44871f6cdeb42af31815c17aed36372d4eec667"},
+    {file = "websockets-10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d6353ba89cfc657a3f5beabb3b69be226adbb5c6c7a66398e17809b0ce3c4731"},
+    {file = "websockets-10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec2b0ab7edc8cd4b0eb428b38ed89079bdc20c6bdb5f889d353011038caac2f9"},
+    {file = "websockets-10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:85506b3328a9e083cc0a0fb3ba27e33c8db78341b3eb12eb72e8afd166c36680"},
+    {file = "websockets-10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8af75085b4bc0b5c40c4a3c0e113fa95e84c60f4ed6786cbb675aeb1ee128247"},
+    {file = "websockets-10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:07cdc0a5b2549bcfbadb585ad8471ebdc7bdf91e32e34ae3889001c1c106a6af"},
+    {file = "websockets-10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5b936bf552e4f6357f5727579072ff1e1324717902127ffe60c92d29b67b7be3"},
+    {file = "websockets-10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e4e08305bfd76ba8edab08dcc6496f40674f44eb9d5e23153efa0a35750337e8"},
+    {file = "websockets-10.3-cp39-cp39-win32.whl", hash = "sha256:bb621ec2dbbbe8df78a27dbd9dd7919f9b7d32a73fafcb4d9252fc4637343582"},
+    {file = "websockets-10.3-cp39-cp39-win_amd64.whl", hash = "sha256:51695d3b199cd03098ae5b42833006a0f43dc5418d3102972addc593a783bc02"},
+    {file = "websockets-10.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:907e8247480f287aa9bbc9391bd6de23c906d48af54c8c421df84655eef66af7"},
+    {file = "websockets-10.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b1359aba0ff810d5830d5ab8e2c4a02bebf98a60aa0124fb29aa78cfdb8031f"},
+    {file = "websockets-10.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:93d5ea0b5da8d66d868b32c614d2b52d14304444e39e13a59566d4acb8d6e2e4"},
+    {file = "websockets-10.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7934e055fd5cd9dee60f11d16c8d79c4567315824bacb1246d0208a47eca9755"},
+    {file = "websockets-10.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:3eda1cb7e9da1b22588cefff09f0951771d6ee9fa8dbe66f5ae04cc5f26b2b55"},
+    {file = "websockets-10.3.tar.gz", hash = "sha256:fc06cc8073c8e87072138ba1e431300e2d408f054b27047d047b549455066ff4"},
+]
+win32-setctime = [
+    {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"},
+    {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"},
+]
+wrapt = [
+    {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"},
+    {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"},
+    {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"},
+    {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"},
+    {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"},
+    {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"},
+    {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"},
+    {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"},
+    {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"},
+    {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"},
+    {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"},
+    {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"},
+    {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"},
+    {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"},
+    {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"},
+    {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"},
+    {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"},
+    {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"},
+    {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"},
+    {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"},
+    {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"},
+    {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"},
+    {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"},
+    {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"},
+    {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"},
+    {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"},
+    {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"},
+    {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"},
+    {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"},
+    {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"},
+    {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"},
+    {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"},
+    {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"},
+    {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"},
+    {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"},
+    {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"},
+    {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"},
+    {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"},
+    {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"},
+    {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"},
+    {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"},
+    {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"},
+    {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"},
+    {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"},
+    {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"},
+    {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"},
+    {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"},
+    {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"},
+    {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"},
+    {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"},
+    {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"},
+    {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"},
+    {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"},
+    {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"},
+    {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"},
+    {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"},
+    {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"},
+    {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"},
+    {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"},
+    {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"},
+    {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"},
+    {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"},
+    {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"},
+    {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"},
+]
+yarl = [
+    {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:abc06b97407868ef38f3d172762f4069323de52f2b70d133d096a48d72215d28"},
+    {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:07b21e274de4c637f3e3b7104694e53260b5fc10d51fb3ec5fed1da8e0f754e3"},
+    {file = "yarl-1.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9de955d98e02fab288c7718662afb33aab64212ecb368c5dc866d9a57bf48880"},
+    {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ec362167e2c9fd178f82f252b6d97669d7245695dc057ee182118042026da40"},
+    {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:20df6ff4089bc86e4a66e3b1380460f864df3dd9dccaf88d6b3385d24405893b"},
+    {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5999c4662631cb798496535afbd837a102859568adc67d75d2045e31ec3ac497"},
+    {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed19b74e81b10b592084a5ad1e70f845f0aacb57577018d31de064e71ffa267a"},
+    {file = "yarl-1.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e4808f996ca39a6463f45182e2af2fae55e2560be586d447ce8016f389f626f"},
+    {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2d800b9c2eaf0684c08be5f50e52bfa2aa920e7163c2ea43f4f431e829b4f0fd"},
+    {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6628d750041550c5d9da50bb40b5cf28a2e63b9388bac10fedd4f19236ef4957"},
+    {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f5af52738e225fcc526ae64071b7e5342abe03f42e0e8918227b38c9aa711e28"},
+    {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:76577f13333b4fe345c3704811ac7509b31499132ff0181f25ee26619de2c843"},
+    {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0c03f456522d1ec815893d85fccb5def01ffaa74c1b16ff30f8aaa03eb21e453"},
+    {file = "yarl-1.8.1-cp310-cp310-win32.whl", hash = "sha256:ea30a42dc94d42f2ba4d0f7c0ffb4f4f9baa1b23045910c0c32df9c9902cb272"},
+    {file = "yarl-1.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:9130ddf1ae9978abe63808b6b60a897e41fccb834408cde79522feb37fb72fb0"},
+    {file = "yarl-1.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0ab5a138211c1c366404d912824bdcf5545ccba5b3ff52c42c4af4cbdc2c5035"},
+    {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0fb2cb4204ddb456a8e32381f9a90000429489a25f64e817e6ff94879d432fc"},
+    {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85cba594433915d5c9a0d14b24cfba0339f57a2fff203a5d4fd070e593307d0b"},
+    {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ca7e596c55bd675432b11320b4eacc62310c2145d6801a1f8e9ad160685a231"},
+    {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f77539733e0ec2475ddcd4e26777d08996f8cd55d2aef82ec4d3896687abda"},
+    {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29e256649f42771829974e742061c3501cc50cf16e63f91ed8d1bf98242e5507"},
+    {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7fce6cbc6c170ede0221cc8c91b285f7f3c8b9fe28283b51885ff621bbe0f8ee"},
+    {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:59ddd85a1214862ce7c7c66457f05543b6a275b70a65de366030d56159a979f0"},
+    {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:12768232751689c1a89b0376a96a32bc7633c08da45ad985d0c49ede691f5c0d"},
+    {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:b19255dde4b4f4c32e012038f2c169bb72e7f081552bea4641cab4d88bc409dd"},
+    {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6c8148e0b52bf9535c40c48faebb00cb294ee577ca069d21bd5c48d302a83780"},
+    {file = "yarl-1.8.1-cp37-cp37m-win32.whl", hash = "sha256:de839c3a1826a909fdbfe05f6fe2167c4ab033f1133757b5936efe2f84904c07"},
+    {file = "yarl-1.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:dd032e8422a52e5a4860e062eb84ac94ea08861d334a4bcaf142a63ce8ad4802"},
+    {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:19cd801d6f983918a3f3a39f3a45b553c015c5aac92ccd1fac619bd74beece4a"},
+    {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6347f1a58e658b97b0a0d1ff7658a03cb79bdbda0331603bed24dd7054a6dea1"},
+    {file = "yarl-1.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c0da7e44d0c9108d8b98469338705e07f4bb7dab96dbd8fa4e91b337db42548"},
+    {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5587bba41399854703212b87071c6d8638fa6e61656385875f8c6dff92b2e461"},
+    {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31a9a04ecccd6b03e2b0e12e82131f1488dea5555a13a4d32f064e22a6003cfe"},
+    {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:205904cffd69ae972a1707a1bd3ea7cded594b1d773a0ce66714edf17833cdae"},
+    {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea513a25976d21733bff523e0ca836ef1679630ef4ad22d46987d04b372d57fc"},
+    {file = "yarl-1.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0b51530877d3ad7a8d47b2fff0c8df3b8f3b8deddf057379ba50b13df2a5eae"},
+    {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2b8f245dad9e331540c350285910b20dd913dc86d4ee410c11d48523c4fd546"},
+    {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ab2a60d57ca88e1d4ca34a10e9fb4ab2ac5ad315543351de3a612bbb0560bead"},
+    {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:449c957ffc6bc2309e1fbe67ab7d2c1efca89d3f4912baeb8ead207bb3cc1cd4"},
+    {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a165442348c211b5dea67c0206fc61366212d7082ba8118c8c5c1c853ea4d82e"},
+    {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b3ded839a5c5608eec8b6f9ae9a62cb22cd037ea97c627f38ae0841a48f09eae"},
+    {file = "yarl-1.8.1-cp38-cp38-win32.whl", hash = "sha256:c1445a0c562ed561d06d8cbc5c8916c6008a31c60bc3655cdd2de1d3bf5174a0"},
+    {file = "yarl-1.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:56c11efb0a89700987d05597b08a1efcd78d74c52febe530126785e1b1a285f4"},
+    {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e80ed5a9939ceb6fda42811542f31c8602be336b1fb977bccb012e83da7e4936"},
+    {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6afb336e23a793cd3b6476c30f030a0d4c7539cd81649683b5e0c1b0ab0bf350"},
+    {file = "yarl-1.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4c322cbaa4ed78a8aac89b2174a6df398faf50e5fc12c4c191c40c59d5e28357"},
+    {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fae37373155f5ef9b403ab48af5136ae9851151f7aacd9926251ab26b953118b"},
+    {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5395da939ffa959974577eff2cbfc24b004a2fb6c346918f39966a5786874e54"},
+    {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:076eede537ab978b605f41db79a56cad2e7efeea2aa6e0fa8f05a26c24a034fb"},
+    {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1a50e461615747dd93c099f297c1994d472b0f4d2db8a64e55b1edf704ec1c"},
+    {file = "yarl-1.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7de89c8456525650ffa2bb56a3eee6af891e98f498babd43ae307bd42dca98f6"},
+    {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4a88510731cd8d4befaba5fbd734a7dd914de5ab8132a5b3dde0bbd6c9476c64"},
+    {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2d93a049d29df172f48bcb09acf9226318e712ce67374f893b460b42cc1380ae"},
+    {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:21ac44b763e0eec15746a3d440f5e09ad2ecc8b5f6dcd3ea8cb4773d6d4703e3"},
+    {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d0272228fabe78ce00a3365ffffd6f643f57a91043e119c289aaba202f4095b0"},
+    {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:99449cd5366fe4608e7226c6cae80873296dfa0cde45d9b498fefa1de315a09e"},
+    {file = "yarl-1.8.1-cp39-cp39-win32.whl", hash = "sha256:8b0af1cf36b93cee99a31a545fe91d08223e64390c5ecc5e94c39511832a4bb6"},
+    {file = "yarl-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:de49d77e968de6626ba7ef4472323f9d2e5a56c1d85b7c0e2a190b2173d3b9be"},
+    {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"},
+]
diff --git a/pyproject.toml b/pyproject.toml
index 5c18288..c710bb5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "nonebot-plugin-pixivbot"
-version = "1.1.2-post2"
+version = "1.2.0"
 description = "Nonebot Plugin PixivBot"
 authors = ["ssttkkl <huang.wen.long@hotmail.com>"]
 readme = "README.md"
@@ -24,8 +24,9 @@ aioreactive = "^0.16.0"
 
 [tool.poetry.dev-dependencies]
 flake8 = "^5.0.4"
-pytest = "^7.1.2"
 twine = "^4.0.1"
+nonebug = "^0.2.1"
+pytest-mock = "^3.8.2"
 
 [tool.poetry.extras]
 socks = ["aiohttp-socks"]
diff --git a/src/nonebot_plugin_pixivbot/config.py b/src/nonebot_plugin_pixivbot/config.py
index 2a62e25..611c3b9 100644
--- a/src/nonebot_plugin_pixivbot/config.py
+++ b/src/nonebot_plugin_pixivbot/config.py
@@ -42,7 +42,8 @@ class Config(BaseSettings):
     pixiv_compression_max_size: Optional[int]
     pixiv_compression_quantity: Optional[float]
 
-    @validator('pixiv_compression_max_size', 'pixiv_compression_quantity')
+    # 不加allow_reuse跑pytest会报错
+    @validator('pixiv_compression_max_size', 'pixiv_compression_quantity', allow_reuse=True)
     def compression_validator(cls, v, values, field: ModelField):
         if values['pixiv_compression_enabled'] and v is None:
             raise ValueError(
@@ -93,7 +94,7 @@ def compression_validator(cls, v, values, field: ModelField):
     pixiv_ranking_fetch_item = 150
     pixiv_ranking_max_item_per_query = 10
 
-    @validator('pixiv_ranking_default_range')
+    @validator('pixiv_ranking_default_range', allow_reuse=True)
     def ranking_default_range_validator(cls, v, field: ModelField):
         if len(v) < 2 or v[0] > v[1]:
             raise ValueError(f'illegal {field.name} value: {v}')
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 04410d6..01b55ad 100644
--- a/src/nonebot_plugin_pixivbot/data/pixiv_repo/abstract_repo.py
+++ b/src/nonebot_plugin_pixivbot/data/pixiv_repo/abstract_repo.py
@@ -1,51 +1,60 @@
 from abc import ABC, abstractmethod
-from typing import Tuple, AsyncGenerator, List
+from datetime import datetime
+from typing import Union, Optional, AsyncGenerator
+
+from pydantic import BaseModel
 
 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()
+    pages: Optional[int]
+    next_qs: Optional[dict]
+
+
 class AbstractPixivRepo(ABC):
     @abstractmethod
-    async def illust_detail(self, illust_id: int) -> Illust:
+    def illust_detail(self, illust_id: int) -> AsyncGenerator[Union[Illust, PixivRepoMetadata], None]:
         raise NotImplementedError()
 
     @abstractmethod
-    async def user_detail(self, user_id: int) -> User:
+    def user_detail(self, user_id: int) -> AsyncGenerator[Union[User, PixivRepoMetadata], None]:
         raise NotImplementedError()
 
     @abstractmethod
-    async def search_illust(self, word: str) -> AsyncGenerator[LazyIllust, None]:
+    def search_illust(self, word: str) -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
         raise NotImplementedError()
 
     @abstractmethod
-    async def search_user(self, word: str) -> AsyncGenerator[User, None]:
+    def search_user(self, word: str) -> AsyncGenerator[Union[User, PixivRepoMetadata], None]:
         raise NotImplementedError()
 
     @abstractmethod
-    async def user_illusts(self, user_id: int) -> AsyncGenerator[LazyIllust, None]:
+    def user_illusts(self, user_id: int) -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
         raise NotImplementedError()
 
     @abstractmethod
-    async def user_bookmarks(self, user_id: int = 0) -> AsyncGenerator[LazyIllust, None]:
+    def user_bookmarks(self, user_id: int = 0) -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
         raise NotImplementedError()
 
     @abstractmethod
-    async def recommended_illusts(self) -> AsyncGenerator[LazyIllust, None]:
+    def recommended_illusts(self) -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
         raise NotImplementedError()
 
     @abstractmethod
-    async def related_illusts(self, illust_id: int) -> AsyncGenerator[LazyIllust, None]:
+    def related_illusts(self, illust_id: int) -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
         raise NotImplementedError()
 
     @abstractmethod
-    async def illust_ranking(self, mode: RankingMode, range: Tuple[int, int]) -> List[LazyIllust]:
+    def illust_ranking(self, mode: RankingMode) -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
         raise NotImplementedError()
 
     @abstractmethod
-    async def image(self, illust: Illust) -> bytes:
+    def image(self, illust: Illust) -> AsyncGenerator[Union[bytes, PixivRepoMetadata], None]:
         raise NotImplementedError()
 
 
-__all__ = ("AbstractPixivRepo", )
+__all__ = ("AbstractPixivRepo", "PixivRepoMetadata",)
diff --git a/src/nonebot_plugin_pixivbot/data/pixiv_repo/enums.py b/src/nonebot_plugin_pixivbot/data/pixiv_repo/enums.py
new file mode 100644
index 0000000..fd8451e
--- /dev/null
+++ b/src/nonebot_plugin_pixivbot/data/pixiv_repo/enums.py
@@ -0,0 +1,10 @@
+SEARCH_ILLUST = 0
+SEARCH_USER = 1
+USER_ILLUSTS = 2
+USER_BOOKMARKS = 3
+RECOMMENDED_ILLUSTS = 4
+ILLUST_RANKING = 5
+ILLUST_DETAIL = 6
+IMAGE = 7
+RELATED_ILLUSTS = 8
+USER_DETAIL = 9
diff --git a/src/nonebot_plugin_pixivbot/data/pixiv_repo/lazy_illust.py b/src/nonebot_plugin_pixivbot/data/pixiv_repo/lazy_illust.py
index 2135c0e..c2b1ea3 100644
--- a/src/nonebot_plugin_pixivbot/data/pixiv_repo/lazy_illust.py
+++ b/src/nonebot_plugin_pixivbot/data/pixiv_repo/lazy_illust.py
@@ -23,7 +23,9 @@ def __init__(self, id: int, content: Optional[Illust] = None) -> None:
 
     async def get(self):
         if self.content is None:
-            self.content = await self.src.illust_detail(self.id)
+            async for x in self.src.illust_detail(self.id):
+                self.content = x
+                break
         return self.content
 
     @property
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 9dcfb98..2ecbcbc 100644
--- a/src/nonebot_plugin_pixivbot/data/pixiv_repo/local_repo.py
+++ b/src/nonebot_plugin_pixivbot/data/pixiv_repo/local_repo.py
@@ -1,5 +1,5 @@
 from datetime import datetime, timedelta
-from typing import Optional, List, Union, Tuple, AsyncGenerator, Sequence
+from typing import List, Union, Sequence, Any, Mapping, AsyncGenerator
 
 import bson
 from nonebot import logger
@@ -7,9 +7,10 @@
 
 from nonebot_plugin_pixivbot.enums import RankingMode
 from nonebot_plugin_pixivbot.model import Illust, User
-from .abstract_repo import AbstractPixivRepo
+from .abstract_repo import AbstractPixivRepo, PixivRepoMetadata
 from .lazy_illust import LazyIllust
 from .pkg_context import context
+from ..local_tag_repo import LocalTagRepo
 from ..source import MongoDataSource
 from ...config import Config
 
@@ -23,11 +24,20 @@ class NoSuchItemError(LocalPixivRepoError):
 
 
 class CacheExpiredError(LocalPixivRepoError):
-    pass
+    def __init__(self, metadata: PixivRepoMetadata):
+        self.metadata = metadata
+
+
+def handle_expires_in(doc: Mapping, expires_in: int):
+    if datetime.now() - doc["metadata"]["update_time"] >= timedelta(expires_in):
+        raise CacheExpiredError(PixivRepoMetadata.parse_obj(doc["metadata"]))
 
 
-def is_expired(update_time: datetime, expired_in: int):
-    return datetime.now() - update_time >= timedelta(expired_in)
+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 ''
 
 
 @context.inject
@@ -35,17 +45,24 @@ def is_expired(update_time: datetime, expired_in: int):
 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())
 
     async def _illusts_agen(self, collection_name: str,
                             query: dict,
-                            expired_in: int,
-                            *, skip: int = 0, limit: int = 0) -> AsyncGenerator[LazyIllust, None]:
-        update_time_result = await self.mongo.db[collection_name].find_one(query, {"update_time": 1})
-        if not update_time_result:
-            raise NoSuchItemError()
-        elif is_expired(update_time_result["update_time"], expired_in):
-            raise CacheExpiredError()
-
+                            *, offset: int = 0) -> AsyncGenerator[LazyIllust, None]:
         aggregation = [
             {
                 "$match": query
@@ -58,10 +75,8 @@ async def _illusts_agen(self, collection_name: str,
             },
         ]
 
-        if skip:
-            aggregation.append({"$skip": skip})
-        if limit:
-            aggregation.append({"$limit": limit})
+        if offset:
+            aggregation.append({"$offset": offset})
 
         aggregation.extend([
             {
@@ -101,9 +116,89 @@ 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]:
+        aggregation = [
+            {
+                "$match": query
+            },
+            {
+                "$replaceWith": {"user_id": "$user_id"}
+            },
+            {
+                "$unwind": "$user_id"
+            },
+        ]
+
+        if offset:
+            aggregation.append({"$offset": offset})
+
+        aggregation.extend([
+            {
+                "$lookup": {
+                    "from": "user_detail_cache",
+                    "localField": "user_id",
+                    "foreignField": "user.id",
+                    "as": "users"
+                }
+            },
+            {
+                "$replaceWith": {
+                    "$mergeObjects": [
+                        "$$ROOT",
+                        {"$arrayElemAt": ["$users", 0]}
+                    ]
+                }
+            },
+            {
+                "$project": {"_id": 0, "user": 1, "user_id": 1}
+            }
+        ])
+
+        result = self.mongo.db[collection_name].aggregate(aggregation)
+
+        async for x in result:
+            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,
+                           offset: int = 0) \
+            -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], Any]:
+        doc = await self.mongo.db[collection_name].find_one(query, {"metadata": 1})
+        if not doc:
+            raise NoSuchItemError()
+        handle_expires_in(doc, expired_in)
+
+        metadata = PixivRepoMetadata.parse_obj(doc["metadata"])
+        yield metadata.copy(update={"pages": 0})
+        async for x in self._illusts_agen(collection_name, query, offset=offset):
+            yield x
+        yield metadata
+
+    async def _get_users(self, collection_name: str,
+                         query: dict,
+                         *, expired_in: int,
+                         offset: int = 0) \
+            -> AsyncGenerator[Union[User, PixivRepoMetadata], Any]:
+        doc = await self.mongo.db[collection_name].find_one(query, {"metadata": 1})
+        if not doc:
+            raise NoSuchItemError()
+        handle_expires_in(doc, expired_in)
+
+        metadata = PixivRepoMetadata.parse_obj(doc["metadata"])
+        yield metadata.copy(update={"pages": 0})
+        async for x in self._users_agen(collection_name, query, offset=offset):
+            yield x
+        yield metadata
+
     async def _check_illusts_exists(self, collection_name: str,
                                     query: dict,
-                                    illust_id: Union[int, Sequence[int]]):
+                                    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
         else:
@@ -118,17 +213,34 @@ async def _check_illusts_exists(self, collection_name: str,
                 exists = x["count"]
                 return exists != 0
 
+    async def _check_users_exists(self, collection_name: str,
+                                  query: dict,
+                                  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
+        else:
+            agg = [
+                {'$match': 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 def _update_illusts(self, collection_name: str,
                               query: dict,
                               content: List[Union[Illust, LazyIllust]],
+                              metadata: PixivRepoMetadata,
                               append: bool = False):
-        now = datetime.now()
         if append:
             await self.mongo.db[collection_name].update_one(
                 query,
                 {
                     "$set": {
-                        "update_time": now
+                        "metadata": metadata.dict(exclude_none=True)
                     },
                     "$addToSet": {
                         "illust_id": {
@@ -144,7 +256,7 @@ async def _update_illusts(self, collection_name: str,
                 {
                     "$set": {
                         "illust_id": [illust.id for illust in content],
-                        "update_time": now
+                        "metadata": metadata.dict(exclude_none=True)
                     }
                 },
                 upsert=True
@@ -159,240 +271,313 @@ async def _update_illusts(self, collection_name: str,
                 opt.append(UpdateOne(
                     {"illust.id": illust.id},
                     {"$set": {
-                        "illust": illust.dict(),
-                        "update_time": now
+                        "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)
 
+        if self.conf.pixiv_tag_translation_enabled:
+            await self._add_to_local_tags(content)
+
+    async def _update_users(self, collection_name: str,
+                            query: dict,
+                            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]
+                        }
+                    }
+                },
+                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)
+                }},
+                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,
+                                        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)
+        return exists
+
+    async def _append_and_check_users(self, collection_name: str,
+                                      query: dict,
+                                      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)
+        return exists
+
     # ================ illust_detail ================
-    async def illust_detail(self, illust_id: int) -> Optional[Illust]:
+    async def illust_detail(self, illust_id: int) \
+            -> AsyncGenerator[Union[Illust, PixivRepoMetadata], None]:
         logger.debug(f"[local] illust_detail {illust_id}")
-        cache = await self.mongo.db.illust_detail_cache.find_one({"illust.id": illust_id})
-        if cache is not None:
-            if is_expired(cache["update_time"], self.conf.pixiv_illust_detail_cache_expires_in):
-                raise CacheExpiredError()
-            return Illust.parse_obj(cache["illust"])
+        doc = await self.mongo.db.illust_detail_cache.find_one({"illust.id": illust_id})
+        if doc is not None:
+            handle_expires_in(doc, self.conf.pixiv_illust_detail_cache_expires_in)
+
+            yield PixivRepoMetadata.parse_obj(doc["metadata"])
+            yield Illust.parse_obj(doc["illust"])
         else:
             raise NoSuchItemError()
 
-    async def update_illust_detail(self, illust: Illust):
-        logger.debug(f"[local] update illust_detail {illust.id}")
+    async def update_illust_detail(self, illust: Illust, metadata: PixivRepoMetadata = PixivRepoMetadata()):
+        logger.debug(f"[local] update illust_detail {illust.id} {metadata_to_text(metadata)}")
+
         await self.mongo.db.illust_detail_cache.update_one(
             {"illust.id": illust.id},
             {"$set": {
-                "illust": illust.dict(),
-                "update_time": datetime.now()
+                "illust": illust.dict(exclude_none=True),
+                "metadata": metadata.dict(exclude_none=True)
             }},
             upsert=True
         )
 
+        if self.conf.pixiv_tag_translation_enabled:
+            await self._add_to_local_tags([illust])
+
     # ================ user_detail ================
-    async def user_detail(self, user_id: int) -> Optional[User]:
+    async def user_detail(self, user_id: int) \
+            -> AsyncGenerator[Union[Illust, PixivRepoMetadata], None]:
         logger.debug(f"[local] user_detail {user_id}")
-        cache = await self.mongo.db.user_detail_cache.find_one({"user.id": user_id})
-        if cache is not None:
-            if is_expired(cache["update_time"], self.conf.pixiv_user_detail_cache_expires_in):
-                raise CacheExpiredError()
-            return User.parse_obj(cache["user"])
+        doc = await self.mongo.db.user_detail_cache.find_one({"user.id": user_id})
+        if doc is not None:
+            handle_expires_in(doc, self.conf.pixiv_user_detail_cache_expires_in)
+
+            yield PixivRepoMetadata.parse_obj(doc["metadata"])
+            yield User.parse_obj(doc["user"])
         else:
             raise NoSuchItemError()
 
-    async def update_user_detail(self, user: User):
-        logger.debug(f"[local] update user_detail {user.id}")
+    async def update_user_detail(self, user: User, metadata: PixivRepoMetadata = PixivRepoMetadata()):
+        logger.debug(f"[local] update user_detail {user.id} {metadata_to_text(metadata)}")
+
+        if not metadata:
+            metadata = PixivRepoMetadata(update_time=datetime.now())
+
         await self.mongo.db.user_detail_cache.update_one(
             {"user.id": user.id},
             {"$set": {
-                "user": user.dict(),
-                "update_time": datetime.now()
+                "user": user.dict(exclude_none=True),
+                "metadata": metadata.dict(exclude_none=True)
             }},
             upsert=True
         )
 
     # ================ search_illust ================
-    async def search_illust(self, word: str) -> AsyncGenerator[LazyIllust, None]:
+    def search_illust(self, word: str, *, offset: int = 0) \
+            -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
         logger.debug(f"[local] search_illust {word}")
-        async for x in self._illusts_agen("search_illust_cache", {"word": word},
-                                          self.conf.pixiv_search_illust_cache_expires_in):
-            yield x
-
-    async def update_search_illust(self, word: str, content: List[Union[Illust, LazyIllust]]):
-        logger.debug(f"[local] update search_illust {word} ({len(content)} illusts)")
-        await self._update_illusts("search_illust_cache", {"word": word}, content)
+        return self._get_illusts("search_illust_cache", {"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 append_search_illust(self, word: str, content: List[Union[Illust, LazyIllust]],
+                                   metadata: PixivRepoMetadata) -> bool:
+        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)
 
     # ================ search_user ================
-    async def search_user(self, word: str) -> AsyncGenerator[User, None]:
+    def search_user(self, word: str, *, offset: int = 0) \
+            -> AsyncGenerator[Union[User, PixivRepoMetadata], None]:
         logger.debug(f"[local] search_user {word}")
-
-        update_time_result = await self.mongo.db["search_user_cache"].find_one({"word": word}, {"update_time": 1})
-        if not update_time_result:
-            raise NoSuchItemError()
-        elif is_expired(update_time_result["update_time"], self.conf.pixiv_search_user_cache_expires_in):
-            raise CacheExpiredError()
-
-        aggregation = [
-            {
-                "$match": {"word": word}
-            },
-            {
-                "$replaceWith": {"user_id": "$user_id"}
-            },
-            {
-                "$unwind": "$user_id"
-            },
-        ]
-
-        aggregation.extend([
-            {
-                "$lookup": {
-                    "from": "user_detail_cache",
-                    "localField": "user_id",
-                    "foreignField": "user.id",
-                    "as": "users"
-                }
-            },
-            {
-                "$replaceWith": {
-                    "$mergeObjects": [
-                        "$$ROOT",
-                        {"$arrayElemAt": ["$users", 0]}
-                    ]
-                }
-            },
-            {
-                "$project": {"_id": 0, "user": 1, "user_id": 1}
-            }
-        ])
-
-        result = self.mongo.db.search_user_cache.aggregate(aggregation)
-
-        async for x in result:
-            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 update_search_user(self, word: str, content: List[User]):
-        logger.debug(f"[local] update search_user {word} ({len(content)} illusts)")
-
-        now = datetime.now()
-        await self.mongo.db.search_user_cache.update_one(
-            {"word": word},
-            {"$set": {
-                "user_id": [user.id for user in content],
-                "update_time": now
-            }},
-            upsert=True
-        )
-
-        opt = []
-        for user in content:
-            opt.append(UpdateOne(
-                {"user.id": user.id},
-                {"$set": {
-                    "user": user.dict(),
-                    "update_time": now
-                }},
-                upsert=True
-            ))
-        if len(opt) != 0:
-            await self.mongo.db.user_detail_cache.bulk_write(opt, ordered=False)
+        return self._get_users("search_user_cache", {"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 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_illust_cache", {"word": word}, content, metadata)
 
     # ================ user_illusts ================
-    async def user_illusts(self, user_id: int) -> AsyncGenerator[LazyIllust, None]:
+    def user_illusts(self, user_id: int, *, offset: int = 0) \
+            -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
         logger.debug(f"[local] user_illusts {user_id}")
-        async for x in self._illusts_agen("user_illusts_cache", {"user_id": user_id},
-                                          self.conf.pixiv_user_illusts_cache_expires_in):
-            yield x
+        return self._get_illusts("user_illusts_cache", {"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]]):
-        logger.debug(f"[local] update user_illusts {user_id} ({len(content)} illusts)")
-        await self._update_illusts("user_illusts_cache", {"user_id": user_id}, content)
+                                  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 append_user_illusts(self, user_id: int,
-                                  content: List[Union[Illust, LazyIllust]]) -> bool:
-        logger.debug(f"[local] append user_illusts {user_id} ({len(content)} illusts)")
-        exists = await self._check_illusts_exists("user_illusts_cache", {"user_id": user_id}, [x.id for x in content])
-        await self._update_illusts("user_illusts_cache", {"user_id": user_id}, content, True)
-        return exists
+                                  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)
 
     # ================ user_bookmarks ================
-    async def user_bookmarks(self, user_id: int = 0) -> AsyncGenerator[LazyIllust, None]:
+    def user_bookmarks(self, user_id: int = 0, *, offset: int = 0) \
+            -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
         logger.debug(f"[local] user_bookmarks {user_id}")
-        async for x in self._illusts_agen("user_bookmarks_cache", {"user_id": user_id},
-                                          self.conf.pixiv_user_bookmarks_cache_expires_in):
-            yield x
+        return self._get_illusts("user_bookmarks_cache", {"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]]):
-        logger.debug(f"[local] update user_bookmarks {user_id} ({len(content)} illusts)")
-        await self._update_illusts("user_bookmarks_cache", {"user_id": user_id}, content)
+                                    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 append_user_bookmarks(self, user_id: int,
-                                    content: List[Union[Illust, LazyIllust]]) -> bool:
-        logger.debug(f"[local] append user_bookmarks {user_id} ({len(content)} illusts)")
-        exists = await self._check_illusts_exists("user_bookmarks_cache", {"user_id": user_id}, [x.id for x in content])
-        await self._update_illusts("user_bookmarks_cache", {"user_id": user_id}, content, True)
-        return exists
+                                    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)
 
     # ================ recommended_illusts ================
-    async def recommended_illusts(self) -> AsyncGenerator[LazyIllust, None]:
+    def recommended_illusts(self, *, offset: int = 0) \
+            -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
         logger.debug(f"[local] recommended_illusts")
-        async for x in self._illusts_agen("other_cache", {"type": "recommended_illusts"},
-                                          self.conf.pixiv_other_cache_expires_in):
-            yield x
-
-    async def update_recommended_illusts(self, content: List[Union[Illust, LazyIllust]]):
-        logger.debug(f"[local] update recommended_illusts ({len(content)} illusts)")
-        await self._update_illusts("other_cache", {"type": "recommended_illusts"}, content)
+        return self._get_illusts("other_cache", {"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 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)
 
     # ================ related_illusts ================
-    async def related_illusts(self, illust_id: int) -> AsyncGenerator[LazyIllust, None]:
+    def related_illusts(self, illust_id: int, *, offset: int = 0) \
+            -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
         logger.debug(f"[local] related_illusts {illust_id}")
-        async for x in self._illusts_agen("related_illusts_cache", {"original_illust_id": illust_id},
-                                          self.conf.pixiv_related_illusts_cache_expires_in):
-            yield x
-
-    async def update_related_illusts(self, illust_id: int, content: List[Union[Illust, LazyIllust]]):
-        logger.debug(f"[local] update related_illusts {illust_id} ({len(content)} illusts)")
-        await self._update_illusts("related_illusts_cache", {"original_illust_id": illust_id}, content)
+        return self._get_illusts("related_illusts_cache", {"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 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)
 
     # ================ illust_ranking ================
-    async def illust_ranking(self, mode: RankingMode, range: Optional[Tuple[int, int]]) -> List[LazyIllust]:
-        logger.debug(f"[local] illust_ranking {mode} {range[0]}~{range[1]}")
-        gen = self._illusts_agen("other_cache", {"type": mode.name + "_ranking"},
-                                 self.conf.pixiv_illust_ranking_cache_expires_in,
-                                 skip=range[0] - 1,
-                                 limit=range[1] - range[0] + 1)
-        return [x async for x in gen]
-
-    async def update_illust_ranking(self, mode: RankingMode, content: List[Union[Illust, LazyIllust]]):
-        logger.debug(f"[local] update illust_ranking {mode} ({len(content)} illusts)")
-        await self._update_illusts("other_cache", {"type": mode.name + "_ranking"}, content)
+    def illust_ranking(self, mode: RankingMode, *, offset: int = 0) \
+            -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
+        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)
+
+    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 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)
 
     # ================ image ================
-    async def image(self, illust: Illust) -> Optional[bytes]:
+    async def image(self, illust: Illust) -> AsyncGenerator[Union[bytes, PixivRepoMetadata], None]:
         logger.debug(f"[local] image {illust.id}")
-        cache = await self.mongo.db.download_cache.find_one({"illust_id": illust.id})
-        if cache is not None:
-            if is_expired(cache["update_time"], self.conf.pixiv_download_cache_expires_in):
-                raise CacheExpiredError()
-            else:
-                return cache["content"]
+        doc = await self.mongo.db.download_cache.find_one({"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"]
         else:
             raise NoSuchItemError()
 
-    async def update_image(self, illust_id: int, content: bytes):
-        logger.debug(f"[local] update image {illust_id}")
-        now = datetime.now()
+    async def update_image(self, illust_id: int, content: bytes,
+                           metadata: PixivRepoMetadata = 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),
-                "update_time": now
+                "metadata": metadata.dict(exclude_none=True)
             }},
             upsert=True
         )
diff --git a/src/nonebot_plugin_pixivbot/data/pixiv_repo/mediator.py b/src/nonebot_plugin_pixivbot/data/pixiv_repo/mediator.py
new file mode 100644
index 0000000..0a04ba9
--- /dev/null
+++ b/src/nonebot_plugin_pixivbot/data/pixiv_repo/mediator.py
@@ -0,0 +1,182 @@
+from datetime import datetime
+from typing import List, AsyncGenerator, TypeVar, Union, Callable, Awaitable, Optional, Any
+
+from .abstract_repo import PixivRepoMetadata
+from .local_repo import NoSuchItemError, CacheExpiredError
+
+T = TypeVar("T")
+
+
+async def _load_local_and_remote_and_update_local(
+        cache_factory: Callable[[], AsyncGenerator[Union[T, PixivRepoMetadata], None]],
+        remote_factory: Callable[..., AsyncGenerator[Union[T, PixivRepoMetadata], None]],
+        cache_appender: Callable[[List[T], Optional[PixivRepoMetadata]], Awaitable[Any]],
+        max_item: int,
+        max_page: int):
+    loaded_items = 0
+    loaded_pages = 0
+
+    # first load from cache
+    metadata = None
+    async for x in cache_factory():
+        if isinstance(x, PixivRepoMetadata):
+            metadata = x
+            loaded_pages = metadata.pages
+        else:
+            loaded_items += 1
+
+        yield x
+
+        # check whether we approach limit
+        if loaded_items >= max_item or loaded_pages >= max_page:
+            return
+
+    # then check metadata["next_qs"] and load from remote
+    # finally update local cache
+    if metadata and metadata.next_qs:
+        next_qs = metadata.next_qs
+        content = []
+
+        try:
+            async for x in remote_factory(**next_qs):
+                if isinstance(x, PixivRepoMetadata):
+                    loaded_pages += 1
+                    metadata = x
+                    metadata.pages = loaded_pages
+                else:
+                    loaded_items += 1
+                    content.append(x)
+
+                yield x
+
+                # check whether we approach limit
+                if loaded_items >= max_item or loaded_pages >= max_page:
+                    return
+        finally:
+            await cache_appender(content, metadata)
+
+
+async def _load_remote_and_update_local(
+        remote_factory: Callable[..., AsyncGenerator[Union[T, PixivRepoMetadata], None]],
+        cache_updater: Callable[[T, Optional[PixivRepoMetadata]], Awaitable[Any]],
+        max_item: int,
+        max_page: int):
+    loaded_items = 0
+    loaded_pages = 0
+
+    # load from remote and update local cache
+    content = []
+    metadata = None
+
+    try:
+        async for x in remote_factory():
+            if isinstance(x, PixivRepoMetadata):
+                loaded_pages += 1
+                metadata = x
+                metadata.pages = loaded_pages
+            else:
+                loaded_items += 1
+                content.append(x)
+
+            yield x
+
+            # check whether we approach limit
+            if loaded_items >= max_item or loaded_pages >= max_page:
+                return
+    finally:
+        await cache_updater(content, metadata)
+
+
+async def mediate_single(cache_factory: Callable[[], AsyncGenerator[Union[T, PixivRepoMetadata], None]],
+                         remote_factory: Callable[..., AsyncGenerator[Union[T, PixivRepoMetadata], None]],
+                         cache_updater: Callable[[T, Optional[PixivRepoMetadata]], Awaitable[Any]]) \
+        -> AsyncGenerator[Union[T, PixivRepoMetadata], None]:
+    try:
+        async for x in cache_factory():
+            yield x
+    except (NoSuchItemError, CacheExpiredError):
+        content = None
+        metadata = None
+
+        async for x in remote_factory():
+            if isinstance(x, PixivRepoMetadata):
+                metadata = x
+            else:
+                content = x
+
+        try:
+            yield metadata
+            yield content
+        finally:
+            await cache_updater(content, metadata)
+
+
+async def mediate_many(cache_factory: Callable[[], AsyncGenerator[Union[T, PixivRepoMetadata], None]],
+                       remote_factory: Callable[..., AsyncGenerator[Union[T, PixivRepoMetadata], None]],
+                       cache_updater: Callable[[List[T], Optional[PixivRepoMetadata]], Awaitable[Any]],
+                       cache_appender: Callable[[List[T], Optional[PixivRepoMetadata]], Awaitable[Any]],
+                       max_item: int = 2 ** 31,
+                       max_page: int = 2 ** 31) \
+        -> AsyncGenerator[Union[T, PixivRepoMetadata], None]:
+    try:
+        async for x in _load_local_and_remote_and_update_local(cache_factory, remote_factory, cache_appender,
+                                                               max_item, max_page):
+            yield x
+    except (CacheExpiredError, NoSuchItemError):
+        async for x in _load_remote_and_update_local(remote_factory, cache_updater,
+                                                     max_item, max_page):
+            yield x
+
+
+async def mediate_append(cache_factory: Callable[[], AsyncGenerator[Union[T, PixivRepoMetadata], None]],
+                         remote_factory: Callable[..., AsyncGenerator[Union[T, PixivRepoMetadata], None]],
+                         cache_updater: Callable[[List[T], Optional[PixivRepoMetadata]], Awaitable[Any]],
+                         cache_appender: Callable[[List[T], Optional[PixivRepoMetadata]], Awaitable[bool]],
+                         max_item: int = 2 ** 31,
+                         max_page: int = 2 ** 31) \
+        -> AsyncGenerator[Union[T, PixivRepoMetadata], None]:
+    try:
+        async for x in _load_local_and_remote_and_update_local(cache_factory, remote_factory, cache_appender,
+                                                               max_item, max_page):
+            yield x
+    except NoSuchItemError:
+        async for x in _load_remote_and_update_local(remote_factory, cache_updater,
+                                                     max_item, max_page):
+            yield x
+    except CacheExpiredError as e:
+        loaded_items = 0
+        loaded_pages = 0
+
+        # peek new bookmarks from remote
+        # then append them to local cache until we found some items already exist
+        buffer = []
+        metadata = e.metadata
+
+        async for x in remote_factory():
+            if isinstance(x, PixivRepoMetadata):
+                loaded_pages += 1
+                # we don't use this metadata
+            else:
+                loaded_items += 1
+                buffer.append(x)
+
+            if len(buffer) >= 20:
+                metadata.update_time = datetime.now()
+                if await cache_appender(buffer, metadata):
+                    break
+                buffer = []
+
+            # check whether we approach limit
+            if loaded_items >= max_item or loaded_pages >= max_page:
+                return
+        else:
+            if len(buffer) > 0:
+                metadata.update_time = datetime.now()
+                await cache_appender(buffer, metadata)
+
+        async for x in _load_local_and_remote_and_update_local(cache_factory, remote_factory, cache_appender,
+                                                               max_item, max_page):
+            yield x
+
+
+__all__ = ("mediate_single", "mediate_many", "mediate_append")
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 d626683..7f82823 100644
--- a/src/nonebot_plugin_pixivbot/data/pixiv_repo/remote_repo.py
+++ b/src/nonebot_plugin_pixivbot/data/pixiv_repo/remote_repo.py
@@ -1,8 +1,7 @@
-import asyncio
 from asyncio import sleep, create_task, CancelledError, Semaphore, Task
 from functools import wraps
 from io import BytesIO
-from typing import TypeVar, Optional, Awaitable, List, Callable, Union, Tuple, AsyncGenerator
+from typing import TypeVar, Optional, Awaitable, List, Callable, Tuple, AsyncGenerator, Union
 
 from nonebot import logger
 from pixivpy_async import *
@@ -12,13 +11,15 @@
 from nonebot_plugin_pixivbot.enums import DownloadQuantity, RankingMode
 from nonebot_plugin_pixivbot.model import Illust, User
 from nonebot_plugin_pixivbot.utils.errors import QueryError
-from .abstract_repo import AbstractPixivRepo
+from .abstract_repo import AbstractPixivRepo, PixivRepoMetadata
 from .compressor import Compressor
 from .lazy_illust import LazyIllust
 from .pkg_context import context
 from ..local_tag_repo import LocalTagRepo
 from ...utils.lifecycler import on_startup, on_shutdown
 
+T = TypeVar("T")
+
 
 def auto_retry(func):
     @wraps(func)
@@ -39,7 +40,10 @@ async def wrapped(*args, **kwargs):
     return wrapped
 
 
-T = TypeVar("T")
+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
@@ -53,7 +57,7 @@ def __init__(self):
         self._sema: Semaphore = None
         self._pclient: PixivClient = None
         self._papi: AppPixivAPI = None
-        self._refresh_daemon_task: Task = None
+        self._refresh_daemon: Task = None
 
         self.user_id = 0
 
@@ -100,7 +104,7 @@ async def _refresh(self):
 
             return result
 
-    async def _refresh_daemon(self):
+    async def _refresh_daemon_worker(self):
         while True:
             try:
                 result = await self._refresh()
@@ -117,119 +121,94 @@ def start(self):
         self._pclient = PixivClient(proxy=self._conf.pixiv_proxy)
         self._papi = AppPixivAPI(client=self._pclient.start())
         self._papi.set_additional_headers({'Accept-Language': 'zh-CN'})
-        self._refresh_daemon_task = create_task(self._refresh_daemon())
+        self._refresh_daemon = create_task(self._refresh_daemon_worker())
         self._sema = Semaphore(self._conf.pixiv_simultaneous_query)
 
     async def shutdown(self):
         await self._pclient.close()
-        self._refresh_daemon_task.cancel()
-
-    @staticmethod
-    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"])
-
-    async def _load_page(self, papi_search_func: Callable[..., Awaitable[dict]],
-                         element_list_name: str,
-                         *, mapper: Optional[Callable[[dict], T]] = None,
-                         filter: Optional[Callable[[T], bool]] = None,
-                         skip: int = 0,
-                         limit: int = 0,
-                         limit_page: int = 0,
-                         **kwargs) -> AsyncGenerator[List[T], None]:
+        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]:
         """
-        一次加载多页
+        加载一页
         :param papi_search_func: PixivPy-Async的加载方法
         :param element_list_name: 返回JSON中元素所在列表名
         :param mapper: 将元素从JSON格式映射为特定格式
-        :param filter: 过滤不符合条件的元素（先映射再过滤）
-        :param abort_on: 加载完一页后判断是否要中断
-        :param skip: 跳过指定项数
-        :param limit: 最多加载多少项
-        :param limit_page: 最多加载多少页
+        :param filter_item: 过滤不符合条件的元素（先映射再过滤）
         :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)
+
+        pending = []
+        for x in raw_result[element_list_name]:
+            if mapper is not None:
+                x = mapper(x)
+
+            if not filter_item or filter_item(x):
+                pending.append(x)
+
+        metadata = PixivRepoMetadata(next_qs=AppPixivAPI.parse_qs(next_url=raw_result["next_url"]))
+
+        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]:
+        """
+        一次加载多页
+        :param papi_search_func: PixivPy-Async的加载方法
+        :param element_list_name: 返回JSON中元素所在列表名
+        :param mapper: 将元素从JSON格式映射为特定格式
+        :param filter_item: 过滤不符合条件的元素（先映射再过滤）
+        :param kwargs: 传给papi_search_func的参数
+        :return: 加载结果
+        """
         loaded_items = 0
         loaded_pages = 0
-
-        if skip:
-            raw_result = await papi_search_func(offset=skip, **kwargs)
-        else:
-            raw_result = await papi_search_func(**kwargs)
-        self._check_error_in_raw_result(raw_result)
+        next_qs = kwargs
 
         while True:
-            # 加载下一页
-            pending = []
-            for x in raw_result[element_list_name]:
-                element = x
-                if mapper is not None:
-                    element = mapper(x)
-                if filter is not None and not filter(element):
-                    break
-                pending.append(element)
-                if loaded_items + len(pending) == limit:
-                    break
+            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(pending)
+            loaded_items += len(page)
             logger.debug(f"[remote] {loaded_pages} pages loaded ({loaded_items} items in total)")
-            yield pending
 
-            # 判断是否中断
-            if (0 < limit <= loaded_items) or \
-                    (0 < limit_page <= loaded_pages):
-                break
-            else:
-                next_qs = AppPixivAPI.parse_qs(next_url=raw_result["next_url"])
-                if next_qs is None:
-                    break
+            yield page, metadata
 
+            next_qs = metadata.next_qs
+            if next_qs:
                 if 'viewed' in next_qs:
                     # 由于pixivpy-async的illust_recommended的bug，需要删掉这个参数
                     del next_qs['viewed']
-
-                raw_result = await papi_search_func(**next_qs)
-                self._check_error_in_raw_result(raw_result)
-
-    async def _add_to_local_tags(self, illusts: List[Union[LazyIllust, Illust]]):
-        try:
-            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())
-        except Exception as e:
-            logger.exception(e)
+            else:
+                break
 
     async def _get_illusts(self, papi_search_func: Callable[[], Awaitable[dict]],
                            *, block_tags: Optional[List[str]] = None,
                            min_bookmark: int = 0,
                            min_view: int = 0,
-                           skip: int = 0,
-                           limit: int = 0,
-                           limit_page: int = 0,
-                           **kwargs) -> AsyncGenerator[LazyIllust, None]:
+                           **kwargs) -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
         """
         加载插画
         :param papi_search_func: PixivPy-Async的加载方法
         :param block_tags: 要过滤的标签
         :param min_bookmark: 书签数下限
         :param min_view: 阅读数下限
-        :param abort_on: 加载完一页后判断是否要中断
-        :param skip: 跳过指定项数
-        :param limit: 最多加载多少项
-        :param limit_page: 最多加载多少页
         :param kwargs: 传给papi_search_func的参数
         :return:
         """
@@ -251,138 +230,116 @@ def illust_filter(illust: Illust) -> bool:
         total = 0
         broken = 0
 
-        await self._sema.acquire()
-        try:
-            async for page in self._load_page(papi_search_func, "illusts",
-                                              mapper=lambda x: Illust.parse_obj(x),
-                                              filter=illust_filter,
-                                              skip=skip,
-                                              limit=limit,
-                                              limit_page=limit_page,
-                                              **kwargs):
-                if self._conf.pixiv_tag_translation_enabled:
-                    create_task(self._add_to_local_tags(page))
-
-                for item in page:
-                    total += 1
-                    if "limit_unknown_360.png" in item.image_urls.large:
-                        broken += 1
-                        yield LazyIllust(item.id)
-                    else:
-                        yield LazyIllust(item.id, item)
-        finally:
-            self._sema.release()
-            logger.debug(f"[remote] {total} got, illust_detail of {broken} are missed")
+        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):
+            for item in page:
+                total += 1
+                if "limit_unknown_360.png" in item.image_urls.large:
+                    broken += 1
+                    yield LazyIllust(item.id)
+                else:
+                    yield LazyIllust(item.id, item)
+            yield metadata
 
     @auto_retry
-    async def illust_detail(self, illust_id: int) -> Illust:
+    async def _illust_detail(self, illust_id: int, **kwargs) -> Illust:
         logger.debug(f"[remote] illust_detail {illust_id}")
+        async with self._sema:
+            raw_result = await self._papi.illust_detail(illust_id, **kwargs)
+            check_error_in_raw_result(raw_result)
+            return Illust.parse_obj(raw_result["illust"])
 
-        await self._sema.acquire()
-        try:
-            raw_result = await self._papi.illust_detail(illust_id)
-            self._check_error_in_raw_result(raw_result)
-            illust = Illust.parse_obj(raw_result["illust"])
-
-            if self._conf.pixiv_tag_translation_enabled:
-                create_task(self._add_to_local_tags([illust]))
-
-            return illust
-        finally:
-            self._sema.release()
+    async def illust_detail(self, illust_id: int, **kwargs) -> AsyncGenerator[Illust, None]:
+        data = await self._illust_detail(illust_id, **kwargs)
+        yield PixivRepoMetadata()
+        yield data
 
     @auto_retry
-    async def user_detail(self, user_id: int) -> User:
+    async def _user_detail(self, user_id: int, **kwargs) -> User:
         logger.debug(f"[remote] user_detail {user_id}")
-
-        await self._sema.acquire()
-        try:
-            raw_result = await self._papi.user_detail(user_id)
-            self._check_error_in_raw_result(raw_result)
+        async with self._sema:
+            raw_result = await self._papi.user_detail(user_id, **kwargs)
+            check_error_in_raw_result(raw_result)
             return User.parse_obj(raw_result["user"])
-        finally:
-            self._sema.release()
 
-    async def search_illust(self, word: str) -> AsyncGenerator[LazyIllust, None]:
+    async def user_detail(self, user_id: int, **kwargs) -> AsyncGenerator[User, None]:
+        data = await self._user_detail(user_id, **kwargs)
+        yield PixivRepoMetadata()
+        yield data
+
+    def search_illust(self, word: str, **kwargs) \
+            -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
         logger.debug(f"[remote] search_illust {word}")
-        async for x in self._get_illusts(self._papi.search_illust,
-                                         block_tags=self._conf.pixiv_block_tags,
-                                         min_bookmark=self._conf.pixiv_random_illust_min_bookmark,
-                                         min_view=self._conf.pixiv_random_illust_min_view,
-                                         limit=self._conf.pixiv_random_illust_max_item,
-                                         limit_page=self._conf.pixiv_random_illust_max_page,
-                                         word=word):
-            yield x
-
-    async def search_user(self, word: str) -> AsyncGenerator[User, None]:
+        return self._get_illusts(self._papi.search_illust,
+                                 block_tags=self._conf.pixiv_block_tags,
+                                 min_bookmark=self._conf.pixiv_random_illust_min_bookmark,
+                                 min_view=self._conf.pixiv_random_illust_min_view,
+                                 word=word, **kwargs)
+
+    async def search_user(self, word: str, **kwargs) \
+            -> AsyncGenerator[Union[User, PixivRepoMetadata], None]:
         logger.debug(f"[remote] search_user {word}")
-        async for page in self._load_page(self._papi.search_user, "user_previews",
-                                          mapper=lambda x: User.parse_obj(x["user"]),
-                                          limit_page=1,
-                                          word=word):
+        async with self._sema:
+            page, metadata = await self.load_page(self._papi.search_user, "user_previews",
+                                                  mapper=lambda x: User.parse_obj(x["user"]),
+                                                  word=word, **kwargs)
             for item in page:
                 yield item
+            yield metadata
 
-    async def user_illusts(self, user_id: int) -> AsyncGenerator[LazyIllust, None]:
+    def user_illusts(self, user_id: int, **kwargs) \
+            -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
         logger.debug(f"[remote] user_illusts {user_id}")
-        async for x in self._get_illusts(self._papi.user_illusts,
-                                         block_tags=self._conf.pixiv_block_tags,
-                                         min_bookmark=self._conf.pixiv_random_user_illust_min_bookmark,
-                                         min_view=self._conf.pixiv_random_user_illust_min_view,
-                                         limit=self._conf.pixiv_random_user_illust_max_item,
-                                         limit_page=self._conf.pixiv_random_user_illust_max_page,
-                                         user_id=user_id):
-            yield x
-
-    async def user_bookmarks(self, user_id: int = 0) -> AsyncGenerator[LazyIllust, None]:
+        return self._get_illusts(self._papi.user_illusts,
+                                 block_tags=self._conf.pixiv_block_tags,
+                                 min_bookmark=self._conf.pixiv_random_user_illust_min_bookmark,
+                                 min_view=self._conf.pixiv_random_user_illust_min_view,
+                                 user_id=user_id, **kwargs)
+
+    def user_bookmarks(self, user_id: int = 0, **kwargs) \
+            -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
         if user_id == 0:
             user_id = self.user_id
 
         logger.debug(f"[remote] user_bookmarks {user_id}")
-        async for x in self._get_illusts(self._papi.user_bookmarks_illust,
-                                         block_tags=self._conf.pixiv_block_tags,
-                                         min_bookmark=self._conf.pixiv_random_bookmark_min_bookmark,
-                                         min_view=self._conf.pixiv_random_bookmark_min_view,
-                                         limit=self._conf.pixiv_random_bookmark_max_item,
-                                         limit_page=self._conf.pixiv_random_bookmark_max_page,
-                                         user_id=user_id):
-            yield x
-
-    async def recommended_illusts(self) -> AsyncGenerator[LazyIllust, None]:
+        return self._get_illusts(self._papi.user_bookmarks_illust,
+                                 block_tags=self._conf.pixiv_block_tags,
+                                 min_bookmark=self._conf.pixiv_random_bookmark_min_bookmark,
+                                 min_view=self._conf.pixiv_random_bookmark_min_view,
+                                 user_id=user_id, **kwargs)
+
+    def recommended_illusts(self, **kwargs) \
+            -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
         logger.debug(f"[remote] recommended_illusts")
-        async for x in self._get_illusts(self._papi.illust_recommended,
-                                         block_tags=self._conf.pixiv_block_tags,
-                                         min_bookmark=self._conf.pixiv_random_recommended_illust_min_bookmark,
-                                         min_view=self._conf.pixiv_random_recommended_illust_min_view,
-                                         limit=self._conf.pixiv_random_recommended_illust_max_item,
-                                         limit_page=self._conf.pixiv_random_recommended_illust_max_page):
-            yield x
-
-    async def related_illusts(self, illust_id: int) -> AsyncGenerator[LazyIllust, None]:
+        return self._get_illusts(self._papi.illust_recommended,
+                                 block_tags=self._conf.pixiv_block_tags,
+                                 min_bookmark=self._conf.pixiv_random_recommended_illust_min_bookmark,
+                                 min_view=self._conf.pixiv_random_recommended_illust_min_view,
+                                 **kwargs)
+
+    def related_illusts(self, illust_id: int, **kwargs) \
+            -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
         logger.debug(f"[remote] related_illusts {illust_id}")
-        async for x in self._get_illusts(self._papi.illust_related,
-                                         block_tags=self._conf.pixiv_block_tags,
-                                         min_bookmark=self._conf.pixiv_random_related_illust_min_bookmark,
-                                         min_view=self._conf.pixiv_random_related_illust_min_view,
-                                         limit=self._conf.pixiv_random_related_illust_max_item,
-                                         limit_page=self._conf.pixiv_random_related_illust_max_page,
-                                         illust_id=illust_id):
-            yield x
-
-    async def illust_ranking(self, mode: RankingMode, range: Tuple[int, int]) -> List[LazyIllust]:
-        logger.debug(f"[repo] illust_ranking {mode} {range[0]}~{range[1]}")
-        return [x async for x in self._get_illusts(self._papi.illust_ranking,
-                                                   block_tags=self._conf.pixiv_block_tags,
-                                                   skip=range[0] - 1,
-                                                   limit=range[1] - range[0] + 1,
-                                                   mode=mode.name)]
+        return self._get_illusts(self._papi.illust_related,
+                                 block_tags=self._conf.pixiv_block_tags,
+                                 min_bookmark=self._conf.pixiv_random_related_illust_min_bookmark,
+                                 min_view=self._conf.pixiv_random_related_illust_min_view,
+                                 illust_id=illust_id, **kwargs)
+
+    def illust_ranking(self, mode: RankingMode, **kwargs) \
+            -> AsyncGenerator[Union[LazyIllust, PixivRepoMetadata], None]:
+        logger.debug(f"[repo] 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) -> bytes:
+    async def _image(self, illust: Illust, **kwargs) -> bytes:
         logger.debug(f"[remote] image {illust.id}")
-
-        await self._sema.acquire()
-        try:
+        async with self._sema:
             download_quantity = self._conf.pixiv_download_quantity
             custom_domain = self._conf.pixiv_download_custom_domain
 
@@ -398,9 +355,16 @@ async def image(self, illust: Illust) -> bytes:
                 url = url.replace("i.pximg.net", custom_domain)
 
             with BytesIO() as bio:
-                await self._papi.download(url, fname=bio)
+                await self._papi.download(url, fname=bio, **kwargs)
                 content = bio.getvalue()
                 content = await self._compressor.compress(content)
                 return content
-        finally:
-            self._sema.release()
+
+    async def image(self, illust: Illust, **kwargs) \
+            -> AsyncGenerator[Union[bytes, PixivRepoMetadata], None]:
+        data = await self._image(illust, **kwargs)
+        yield PixivRepoMetadata()
+        yield data
+
+
+__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 5cf160d..3bcdc01 100644
--- a/src/nonebot_plugin_pixivbot/data/pixiv_repo/repo.py
+++ b/src/nonebot_plugin_pixivbot/data/pixiv_repo/repo.py
@@ -1,168 +1,148 @@
+from datetime import datetime, timedelta
 from functools import partial
-from inspect import isawaitable
-from typing import List, Tuple, AsyncGenerator, Optional, TypeVar, Callable, Union, Awaitable
+from typing import Any, AsyncGenerator, List, Tuple
 
 from nonebot import logger
 
 from nonebot_plugin_pixivbot.config import Config
 from nonebot_plugin_pixivbot.enums import RankingMode
 from nonebot_plugin_pixivbot.model import Illust, User
-from .abstract_mediator import AbstractMediator
+from . import LazyIllust
 from .abstract_repo import AbstractPixivRepo
-from .lazy_illust import LazyIllust
-from .local_repo import LocalPixivRepo, CacheExpiredError, NoSuchItemError
+from .abstract_repo import PixivRepoMetadata
+from .enums import *
+from .local_repo import LocalPixivRepo
+from .mediator import mediate_single, mediate_many, mediate_append
 from .pkg_context import context
 from .remote_repo import RemotePixivRepo
-
-T = TypeVar("T")
-
-SEARCH_ILLUST = 0
-SEARCH_USER = 1
-USER_ILLUSTS = 2
-USER_BOOKMARKS = 3
-RECOMMENDED_ILLUSTS = 4
-ILLUST_RANKING = 5
-ILLUST_DETAIL = 6
-IMAGE = 7
-RELATED_ILLUSTS = 8
-USER_DETAIL = 9
-
-
-async def mixin(cache_loader: Callable[[], Union[T, Awaitable[T]]],
-                remote_fetcher: Callable[[], Union[T, Awaitable[T]]],
-                hook_on_cache: Optional[Callable[[T], T]] = None,
-                hook_on_fetch: Optional[Callable[[T], T]] = None) \
-        -> AsyncGenerator[T, None]:
-    try:
-        cache = cache_loader()
-        if isawaitable(cache):
-            cache = await cache
-
-        if hook_on_cache:
-            cache = hook_on_cache(cache)
-        yield cache
-    except (CacheExpiredError, NoSuchItemError):
-        result = remote_fetcher()
-        if isawaitable(result):
-            result = await result
-
-        if hook_on_fetch:
-            result = hook_on_fetch(result)
-        yield result
-
-
-async def mixin_agen(cache_loader: Callable[[], AsyncGenerator[T, None]],
-                     remote_fetcher: Callable[[], AsyncGenerator[T, None]]) \
-        -> AsyncGenerator[T, None]:
-    try:
-        async for x in cache_loader():
-            yield x
-    except (CacheExpiredError, NoSuchItemError):
-        async for x in remote_fetcher():
-            yield x
-
-
-async def mixin_append_agen(cache_loader: Callable[[], AsyncGenerator[T, None]],
-                            remote_fetcher: Callable[[], AsyncGenerator[T, None]],
-                            cache_appender: Callable[[List[T]], Awaitable[bool]]) \
-        -> AsyncGenerator[T, None]:
-    try:
-        async for x in cache_loader():
-            yield x
-    except NoSuchItemError:
-        async for x in remote_fetcher():
-            yield x
-    except CacheExpiredError:
-        # if cache expired, pick new bookmarks from remote
-        buffer = []
-        async for illust in remote_fetcher():
-            buffer.append(illust)
-            if len(buffer) >= 20:
-                if await cache_appender(buffer):
-                    break
-                buffer = []
-        else:
-            if len(buffer) > 0:
-                await cache_appender(buffer)
-
-        async for x in cache_loader():
-            yield x
-
-
-T_ID = TypeVar("T_ID")
+from .shared_agen import SharedAsyncGeneratorManager
 
 
 @context.inject
 @context.register_singleton()
-class PixivMediator(AbstractMediator[T_ID]):
+class PixivSharedAsyncGeneratorManager(SharedAsyncGeneratorManager[Tuple[int, Any], Any]):
     conf: Config
     local: LocalPixivRepo
     remote: RemotePixivRepo
 
+    def calc_expires_time(self, identifier: Tuple[int, Any], update_time: datetime) -> datetime:
+        if identifier[0] == ILLUST_DETAIL:
+            return update_time + timedelta(seconds=self.conf.pixiv_illust_detail_cache_expires_in)
+        elif identifier[0] == USER_DETAIL:
+            return update_time + timedelta(seconds=self.conf.pixiv_user_detail_cache_expires_in)
+        elif identifier[0] == SEARCH_ILLUST:
+            return update_time + timedelta(seconds=self.conf.pixiv_search_illust_cache_expires_in)
+        elif identifier[0] == SEARCH_USER:
+            return update_time + timedelta(seconds=self.conf.pixiv_search_user_cache_expires_in)
+        elif identifier[0] == USER_ILLUSTS:
+            return update_time + timedelta(seconds=self.conf.pixiv_user_illusts_cache_expires_in)
+        elif identifier[0] == USER_BOOKMARKS:
+            return update_time + timedelta(seconds=self.conf.pixiv_user_bookmarks_cache_expires_in)
+        elif identifier[0] == RECOMMENDED_ILLUSTS:
+            return update_time + timedelta(seconds=self.conf.pixiv_other_cache_expires_in)
+        elif identifier[0] == RELATED_ILLUSTS:
+            return update_time + timedelta(seconds=self.conf.pixiv_related_illusts_cache_expires_in)
+        elif identifier[0] == ILLUST_RANKING:
+            return update_time + timedelta(seconds=self.conf.pixiv_illust_ranking_cache_expires_in)
+        elif identifier[0] == IMAGE:
+            return update_time + timedelta(seconds=self.conf.pixiv_download_cache_expires_in)
+        else:
+            raise ValueError("invalid identifier: " + str(identifier))
+
+    async def on_agen_next(self, identifier: Tuple[int, Any], item: Any):
+        if isinstance(item, PixivRepoMetadata) and not self.get_expires_time(identifier):
+            self.set_expires_time(identifier, self.calc_expires_time(identifier, item.update_time))
+
     def illust_detail_factory(self, illust_id: int) -> AsyncGenerator[Illust, None]:
-        return mixin(
-            cache_loader=partial(self.local.illust_detail, illust_id=illust_id),
-            remote_fetcher=partial(self.remote.illust_detail, illust_id=illust_id)
+        return mediate_single(
+            cache_factory=partial(self.local.illust_detail, illust_id=illust_id),
+            remote_factory=partial(self.remote.illust_detail, illust_id=illust_id),
+            cache_updater=lambda data, metadata: self.local.update_illust_detail(data, metadata)
         )
 
     def user_detail_factory(self, user_id: int) -> AsyncGenerator[User, None]:
-        return mixin(
-            cache_loader=partial(self.local.user_detail, user_id=user_id),
-            remote_fetcher=partial(self.remote.user_detail, user_id=user_id)
+        return mediate_single(
+            cache_factory=partial(self.local.user_detail, user_id=user_id),
+            remote_factory=partial(self.remote.user_detail, user_id=user_id),
+            cache_updater=lambda data, metadata: self.local.update_user_detail(data, metadata)
         )
 
     def search_illust_factory(self, word: str) -> AsyncGenerator[LazyIllust, None]:
-        return mixin_agen(
-            cache_loader=partial(self.local.search_illust, word=word),
-            remote_fetcher=partial(self.remote.search_illust, word=word),
+        return mediate_many(
+            cache_factory=partial(self.local.search_illust, word=word),
+            remote_factory=partial(self.remote.search_illust, word=word),
+            cache_updater=lambda data, metadata: self.local.update_search_illust(word, data, metadata),
+            cache_appender=lambda data, metadata: self.local.append_search_illust(word, data, metadata),
+            max_item=self.conf.pixiv_random_illust_max_item,
+            max_page=self.conf.pixiv_random_illust_max_page,
         )
 
     def search_user_factory(self, word: str) -> AsyncGenerator[User, None]:
-        return mixin_agen(
-            cache_loader=partial(self.local.search_user, word=word),
-            remote_fetcher=partial(self.remote.search_user, word=word),
+        return mediate_many(
+            cache_factory=partial(self.local.search_user, word=word),
+            remote_factory=partial(self.remote.search_user, word=word),
+            cache_updater=lambda data, metadata: self.local.update_search_user(word, data, metadata),
+            cache_appender=lambda data, metadata: self.local.append_search_user(word, data, metadata),
+            max_page=1,
         )
 
     def user_illusts_factory(self, user_id: int) -> AsyncGenerator[LazyIllust, None]:
-        return mixin_append_agen(
-            cache_loader=partial(self.local.user_illusts, user_id=user_id),
-            remote_fetcher=partial(self.remote.user_illusts, user_id=user_id),
-            cache_appender=partial(self.local.append_user_illusts, user_id=user_id),
+        return mediate_append(
+            cache_factory=partial(self.local.user_illusts, user_id=user_id),
+            remote_factory=partial(self.remote.user_illusts, user_id=user_id),
+            cache_updater=lambda data, metadata: self.local.update_user_illusts(user_id, data, metadata),
+            cache_appender=lambda data, metadata: self.local.append_user_illusts(user_id, data, metadata),
+            max_item=self.conf.pixiv_random_user_illust_max_item,
+            max_page=self.conf.pixiv_random_user_illust_max_page,
         )
 
     def user_bookmarks_factory(self, user_id: int) -> AsyncGenerator[LazyIllust, None]:
-        return mixin_append_agen(
-            cache_loader=partial(self.local.user_bookmarks, user_id=user_id),
-            remote_fetcher=partial(self.remote.user_bookmarks, user_id=user_id),
-            cache_appender=partial(self.local.append_user_bookmarks, user_id=user_id),
+        return mediate_append(
+            cache_factory=partial(self.local.user_bookmarks, user_id=user_id),
+            remote_factory=partial(self.remote.user_bookmarks, user_id=user_id),
+            cache_updater=lambda data, metadata: self.local.update_user_bookmarks(user_id, data, metadata),
+            cache_appender=lambda data, metadata: self.local.append_user_bookmarks(user_id, data, metadata),
+            max_item=self.conf.pixiv_random_bookmark_max_item,
+            max_page=self.conf.pixiv_random_bookmark_max_page,
         )
 
     def recommended_illusts_factory(self) -> AsyncGenerator[LazyIllust, None]:
-        return mixin_agen(
-            cache_loader=self.local.recommended_illusts,
-            remote_fetcher=self.remote.recommended_illusts
+        return mediate_many(
+            cache_factory=self.local.recommended_illusts,
+            remote_factory=self.remote.recommended_illusts,
+            cache_updater=lambda data, metadata: self.local.update_recommended_illusts(data, metadata),
+            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,
         )
 
     def related_illusts_factory(self, illust_id: int) -> AsyncGenerator[LazyIllust, None]:
-        return mixin_agen(
-            cache_loader=partial(self.local.related_illusts, illust_id=illust_id),
-            remote_fetcher=partial(self.remote.related_illusts, illust_id=illust_id),
+        return mediate_many(
+            cache_factory=partial(self.local.related_illusts, illust_id),
+            remote_factory=partial(self.remote.related_illusts, illust_id),
+            cache_updater=lambda data, metadata: self.local.update_related_illusts(illust_id, data, metadata),
+            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,
         )
 
     def illust_ranking_factory(self, mode: RankingMode) -> AsyncGenerator[List[LazyIllust], None]:
-        range = 1, self.conf.pixiv_ranking_fetch_item
-        return mixin(
-            cache_loader=partial(self.local.illust_ranking, mode=mode, range=range),
-            remote_fetcher=partial(self.remote.illust_ranking, mode=mode, range=range)
+        return mediate_many(
+            cache_factory=partial(self.local.illust_ranking, mode),
+            remote_factory=partial(self.remote.illust_ranking, mode),
+            cache_updater=lambda data, metadata: self.local.update_illust_ranking(mode, data, metadata),
+            cache_appender=lambda data, metadata: self.local.append_illust_ranking(mode, data, metadata),
+            max_item=self.conf.pixiv_ranking_fetch_item,
         )
 
     def image_factory(self, illust: Illust) -> AsyncGenerator[LazyIllust, None]:
-        return mixin(
-            cache_loader=partial(self.local.image, illust=illust),
-            remote_fetcher=partial(self.remote.image, illust=illust),
+        return mediate_single(
+            cache_factory=partial(self.local.image, illust),
+            remote_factory=partial(self.remote.image, illust),
+            cache_updater=lambda data, metadata: self.local.update_image(illust.id, data, metadata)
         )
 
-    def agen_factory(self, identifier: T_ID, *args, **kwargs) -> AsyncGenerator[T, None]:
+    def agen_factory(self, identifier: Tuple[int, Any], *args, **kwargs) -> AsyncGenerator[Any, None]:
         if identifier[0] == ILLUST_DETAIL:
             return self.illust_detail_factory(identifier[1])
         elif identifier[0] == USER_DETAIL:
@@ -186,101 +166,85 @@ def agen_factory(self, identifier: T_ID, *args, **kwargs) -> AsyncGenerator[T, N
         else:
             raise ValueError("invalid identifier: " + str(identifier))
 
-    async def on_agen_stop(self, identifier: T_ID, items: List[T]):
-        if identifier[0] == ILLUST_DETAIL:
-            return await self.local.update_illust_detail(items[0])
-        elif identifier[0] == USER_DETAIL:
-            return await self.local.update_user_detail(items[0])
-        elif identifier[0] == SEARCH_ILLUST:
-            return await self.local.update_search_illust(identifier[1], items)
-        elif identifier[0] == SEARCH_USER:
-            return await self.local.update_search_user(identifier[1], items)
-        elif identifier[0] == USER_ILLUSTS:
-            pass
-            # return await self.local.update_user_illusts(identifier[1], items)
-        elif identifier[0] == USER_BOOKMARKS:
-            pass
-            # return await self.local.update_user_bookmarks(identifier[1], items)
-        elif identifier[0] == RECOMMENDED_ILLUSTS:
-            return await self.local.update_recommended_illusts(items)
-        elif identifier[0] == RELATED_ILLUSTS:
-            return await self.local.update_related_illusts(identifier[1], items)
-        elif identifier[0] == ILLUST_RANKING:
-            return await self.local.update_illust_ranking(identifier[1], items[0])
-        elif identifier[0] == IMAGE:
-            return await self.local.update_image(identifier[1], items[0])
-        else:
-            raise ValueError("invalid identifier: " + str(identifier))
-
 
 @context.inject
 @context.root.register_singleton()
 class PixivRepo(AbstractPixivRepo):
-    _mediator: PixivMediator
+    _shared_agen_mgr: PixivSharedAsyncGeneratorManager
     _local: LocalPixivRepo
 
     async def invalidate_cache(self):
         await self._local.invalidate_cache()
 
-    async def illust_detail(self, illust_id: int) -> Illust:
+    async def illust_detail(self, illust_id: int) -> AsyncGenerator[Illust, None]:
         logger.info(f"[repo] illust_detail {illust_id}")
-        with self._mediator.get((ILLUST_DETAIL, illust_id)) as gen:
+        with self._shared_agen_mgr.get((ILLUST_DETAIL, illust_id)) as gen:
             async for x in gen:
-                return x
+                if not isinstance(x, PixivRepoMetadata):
+                    yield x
 
-    async def user_detail(self, user_id: int) -> User:
+    async def user_detail(self, user_id: int) -> AsyncGenerator[User, None]:
         logger.info(f"[repo] user_detail {user_id}")
-        with self._mediator.get((USER_DETAIL, user_id)) as gen:
+        with self._shared_agen_mgr.get((USER_DETAIL, user_id)) as gen:
             async for x in gen:
-                return x
+                if not isinstance(x, PixivRepoMetadata):
+                    yield x
 
     async def search_illust(self, word: str) -> AsyncGenerator[LazyIllust, None]:
         logger.info(f"[repo] search_illust {word}")
-        with self._mediator.get((SEARCH_ILLUST, word)) as gen:
+        with self._shared_agen_mgr.get((SEARCH_ILLUST, word)) as gen:
             async for x in gen:
-                yield x
+                if not isinstance(x, PixivRepoMetadata):
+                    yield x
 
     async def search_user(self, word: str) -> AsyncGenerator[User, None]:
         logger.info(f"[repo] search_user {word}")
-        with self._mediator.get((SEARCH_USER, word)) as gen:
+        with self._shared_agen_mgr.get((SEARCH_USER, word)) as gen:
             async for x in gen:
-                yield x
+                if not isinstance(x, PixivRepoMetadata):
+                    yield x
 
     async def user_illusts(self, user_id: int = 0) -> AsyncGenerator[LazyIllust, None]:
         logger.info(f"[repo] user_illusts {user_id}")
-        with self._mediator.get((USER_ILLUSTS, user_id)) as gen:
+        with self._shared_agen_mgr.get((USER_ILLUSTS, user_id)) as gen:
             async for x in gen:
-                yield x
+                if not isinstance(x, PixivRepoMetadata):
+                    yield x
 
     async def user_bookmarks(self, user_id: int = 0) -> AsyncGenerator[LazyIllust, None]:
         logger.info(f"[repo] user_bookmarks {user_id}")
-        with self._mediator.get((USER_BOOKMARKS, user_id)) as gen:
+        with self._shared_agen_mgr.get((USER_BOOKMARKS, user_id)) as gen:
             async for x in gen:
-                yield x
+                if not isinstance(x, PixivRepoMetadata):
+                    yield x
 
     async def recommended_illusts(self) -> AsyncGenerator[LazyIllust, None]:
         logger.info(f"[repo] recommended_illusts")
-        with self._mediator.get((RECOMMENDED_ILLUSTS,)) as gen:
+        with self._shared_agen_mgr.get((RECOMMENDED_ILLUSTS, None)) as gen:
             async for x in gen:
-                yield x
+                if not isinstance(x, PixivRepoMetadata):
+                    yield x
 
     async def related_illusts(self, illust_id: int) -> AsyncGenerator[LazyIllust, None]:
         logger.info(f"[repo] related_illusts {illust_id}")
-        with self._mediator.get((RELATED_ILLUSTS, illust_id)) as gen:
+        with self._shared_agen_mgr.get((RELATED_ILLUSTS, illust_id)) as gen:
             async for x in gen:
-                yield x
+                if not isinstance(x, PixivRepoMetadata):
+                    yield x
 
-    async def illust_ranking(self, mode: RankingMode, range: Tuple[int, int]) -> List[LazyIllust]:
-        logger.info(f"[repo] illust_ranking {mode} {range[0]}~{range[1]}")
-        with self._mediator.get((ILLUST_RANKING, mode)) as gen:
-            async for items in gen:
-                return items[range[0] - 1:range[1]]
+    async def illust_ranking(self, mode: RankingMode) -> AsyncGenerator[LazyIllust, None]:
+        logger.info(f"[repo] illust_ranking {mode}")
+        with self._shared_agen_mgr.get((ILLUST_RANKING, mode)) as gen:
+            async for x in gen:
+                if not isinstance(x, PixivRepoMetadata):
+                    yield x
 
-    async def image(self, illust: Illust) -> bytes:
+    async def image(self, illust: Illust) -> AsyncGenerator[bytes, None]:
         logger.info(f"[repo] image {illust.id}")
-        with self._mediator.get((IMAGE, illust.id), illust) as gen:
+        with self._shared_agen_mgr.get((IMAGE, illust.id), illust) as gen:
             async for x in gen:
-                return x
+                if not isinstance(x, PixivRepoMetadata):
+                    yield x
 
 
 __all__ = ('PixivRepo',)
diff --git a/src/nonebot_plugin_pixivbot/data/pixiv_repo/abstract_mediator.py b/src/nonebot_plugin_pixivbot/data/pixiv_repo/shared_agen.py
similarity index 58%
rename from src/nonebot_plugin_pixivbot/data/pixiv_repo/abstract_mediator.py
rename to src/nonebot_plugin_pixivbot/data/pixiv_repo/shared_agen.py
index ba76db7..7dd87b2 100644
--- a/src/nonebot_plugin_pixivbot/data/pixiv_repo/abstract_mediator.py
+++ b/src/nonebot_plugin_pixivbot/data/pixiv_repo/shared_agen.py
@@ -1,17 +1,22 @@
 from abc import ABC, abstractmethod
-from asyncio import Lock
+from asyncio import Lock, create_task
 from contextlib import AbstractContextManager
+from datetime import datetime
 from inspect import isawaitable
 from types import TracebackType
-from typing import Any, Awaitable, Generic, TypeVar, Callable, Union, AsyncGenerator, List, Type, Optional
+from typing import Any, Awaitable, Generic, TypeVar, Callable, Union, AsyncGenerator, List, Type, Optional, Dict
 
 from nonebot import logger
 
+from nonebot_plugin_pixivbot.utils.expires_lru_dict import ExpiresLruDict
+
+T_ID = TypeVar("T_ID")
 T_ITEM = TypeVar("T_ITEM")
 
 
 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]):
@@ -20,9 +25,10 @@ def __init__(self, origin: AsyncGenerator[T_ITEM, None],
         self._stopped = False  # whether origin has raised a StopIteration
         self._got_items = []  # items got from origin, used to replay
         self._got = 0  # count of items got from origin
-        self._mutex = Lock()  # for coroutine safety
+        self._mutex = Lock()  # to solve race
         self._consumers = 0  # count of consumer
-        self._on_stop = on_stop  # callback on origin stops iteration
+        self._on_each = on_each  # callback on origin yields
+        self._on_stop = on_stop  # callback on origin stops
         self._on_consumers_changed = on_consumers_changed  # callback on a consumer enters or exits
 
     @property
@@ -40,9 +46,13 @@ async def _generator_factory(self) -> AsyncGenerator[T_ITEM, None]:
                     break
 
                 async with self._mutex:
+                    if self._stopped:
+                        break
+
                     try:
                         if cur == self._got:
                             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]
@@ -64,46 +74,70 @@ def __exit__(self, exc_type: Optional[Type[BaseException]],
         self._consumers -= 1
         self._on_consumers_changed(self, self._consumers)
 
-    def close(self):
-        self._origin.athrow(GeneratorExit)
+    async def aclose(self):
+        return await self._origin.aclose()
 
-
-T_ID = TypeVar("T_ID")
+    def close(self):
+        create_task(self.aclose())
 
 
-class AbstractMediator(ABC, Generic[T_ID]):
+class SharedAsyncGeneratorManager(ABC, Generic[T_ID, T_ITEM]):
     def __init__(self):
         self._ctx_mgr = {}
-        self._stopped_ctx_mgr = {}  # TODO: Cache Control, with exact expires time
+        self._paused_ctx_mgr = ExpiresLruDict(1024, SharedAsyncGeneratorManager._cleanup)
+        self._expires_time: Dict[T_ID, datetime] = {}
 
     @abstractmethod
     def agen_factory(self, identifier: T_ID, *args, **kwargs) -> AsyncGenerator[T_ITEM, None]:
         raise NotImplementedError()
 
-    @abstractmethod
+    def on_agen_next(self, identifier: T_ID, item: T_ITEM) -> Union[None, Awaitable[None]]:
+        pass
+
     def on_agen_stop(self, identifier: T_ID, items: List[T_ITEM]) -> Union[None, Awaitable[None]]:
-        raise NotImplementedError()
+        pass
+
+    @staticmethod
+    def _cleanup(identifier: T_ID, ctx_mgr: SharedAsyncGeneratorContextManager[T_ITEM]):
+        ctx_mgr.close()
 
     def on_consumers_changed(self, identifier: T_ID,
                              ctx_mgr: SharedAsyncGeneratorContextManager[T_ITEM],
                              consumers: int):
-        if identifier in self._stopped_ctx_mgr and consumers > 0:
-            logger.debug(f"[mediator] {identifier} re-started")
-            del self._stopped_ctx_mgr[identifier]
+        if identifier in self._paused_ctx_mgr and consumers > 0:
+            logger.debug(f"[agen_manager] {identifier} re-started")
+            del self._paused_ctx_mgr[identifier]
             self._ctx_mgr[identifier] = ctx_mgr
         elif identifier in self._ctx_mgr and consumers == 0:
-            logger.debug(f"[mediator] {identifier} stopped")
             del self._ctx_mgr[identifier]
-            self._stopped_ctx_mgr[identifier] = ctx_mgr
-
-    def get(self, identifier: Any, *args, **kwargs) -> SharedAsyncGeneratorContextManager[T_ITEM]:
-        if identifier in self._stopped_ctx_mgr:
-            return self._stopped_ctx_mgr[identifier]
+            self._cleanup(identifier, ctx_mgr)
+            if identifier in self._expires_time:
+                logger.debug(f"[agen_manager] {identifier} paused")
+                self._paused_ctx_mgr.add(identifier, ctx_mgr, self._expires_time[identifier])
+            else:
+                logger.debug(f"[agen_manager] {identifier} stopped")
+
+    def get_expires_time(self, identifier: T_ID) -> Optional[datetime]:
+        return self._expires_time.get(identifier, None)
+
+    def set_expires_time(self, identifier: T_ID, expires_time: datetime):
+        if identifier not in self._expires_time:
+            self._expires_time[identifier] = expires_time
+            logger.debug(f"[agen_manager] {identifier} will expire at {expires_time}")
+        elif self._expires_time[identifier] != expires_time:
+            raise RuntimeError(f"{identifier} expires time already set")
+
+    def get(self, identifier: Any, *args, **kwargs) \
+            -> SharedAsyncGeneratorContextManager[T_ITEM]:
+        if identifier in self._paused_ctx_mgr:
+            return self._paused_ctx_mgr[identifier]
         elif identifier in self._ctx_mgr:
             return self._ctx_mgr[identifier]
         else:
+            origin = self.agen_factory(identifier, *args, **kwargs)
             self._ctx_mgr[identifier] = SharedAsyncGeneratorContextManager(
-                origin=self.agen_factory(identifier, *args, **kwargs),
+                origin=origin,
+                on_each=lambda item: self.on_agen_next(identifier, item),
                 on_stop=lambda items: self.on_agen_stop(identifier, items),
                 on_consumers_changed=lambda ctx_mgr, consumers: self.on_consumers_changed(
                     identifier, ctx_mgr, consumers)
diff --git a/src/nonebot_plugin_pixivbot/data/source/mongo/migration/__init__.py b/src/nonebot_plugin_pixivbot/data/source/mongo/migration/__init__.py
index a5c9367..7d57e7a 100644
--- a/src/nonebot_plugin_pixivbot/data/source/mongo/migration/__init__.py
+++ b/src/nonebot_plugin_pixivbot/data/source/mongo/migration/__init__.py
@@ -1,4 +1,5 @@
 from nonebot_plugin_pixivbot.data.source.mongo.migration.mongo_migration_manager import MongoMigrationManager
 from .mongo_v1_to_v2 import *
+from .mongo_v2_to_v3 import *
 
 __all__ = ("MongoMigrationManager",)
diff --git a/src/nonebot_plugin_pixivbot/data/source/mongo/migration/mongo_v2_to_v3.py b/src/nonebot_plugin_pixivbot/data/source/mongo/migration/mongo_v2_to_v3.py
new file mode 100644
index 0000000..88ba52d
--- /dev/null
+++ b/src/nonebot_plugin_pixivbot/data/source/mongo/migration/mongo_v2_to_v3.py
@@ -0,0 +1,22 @@
+from motor.motor_asyncio import AsyncIOMotorDatabase
+
+from nonebot_plugin_pixivbot import context
+from .mongo_migration import MongoMigration
+from .mongo_migration_manager import MongoMigrationManager
+
+
+@context.require(MongoMigrationManager).register
+class MongoV2ToV3(MongoMigration):
+    from_db_version = 2
+    to_db_version = 3
+
+    async def migrate(self, db: AsyncIOMotorDatabase):
+        await db.download_cache.delete_many({})
+        await db.illust_detail_cache.delete_many({})
+        await db.user_detail_cache.delete_many({})
+        await db.illust_ranking_cache.delete_many({})
+        await db.search_illust_cache.delete_many({})
+        await db.search_user_cache.delete_many({})
+        await db.user_illusts_cache.delete_many({})
+        await db.user_bookmarks_cache.delete_many({})
+        await db.other_cache.delete_many({})
diff --git a/src/nonebot_plugin_pixivbot/data/source/mongo/mongo.py b/src/nonebot_plugin_pixivbot/data/source/mongo/mongo.py
index 7d892b5..64fb3be 100644
--- a/src/nonebot_plugin_pixivbot/data/source/mongo/mongo.py
+++ b/src/nonebot_plugin_pixivbot/data/source/mongo/mongo.py
@@ -14,7 +14,7 @@
 class MongoDataSource:
     conf: Config
     mongo_migration_mgr: MongoMigrationManager
-    app_db_version = 2
+    app_db_version = 3
 
     def __init__(self):
         self._client = None
diff --git a/src/nonebot_plugin_pixivbot/handler/common/common.py b/src/nonebot_plugin_pixivbot/handler/common/common.py
index f2696f5..48d8828 100644
--- a/src/nonebot_plugin_pixivbot/handler/common/common.py
+++ b/src/nonebot_plugin_pixivbot/handler/common/common.py
@@ -7,6 +7,7 @@
 from .recorder import Recorder
 from ..entry_handler import EntryHandler
 from ..interceptor.cooldown_interceptor import CooldownInterceptor
+from ..interceptor.loading_interceptor import LoadingInterceptor
 from ..interceptor.timeout_interceptor import TimeoutInterceptor
 from ...model import Illust
 from ...model.message import IllustMessageModel, IllustMessagesModel
@@ -50,6 +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))
 
     async def post_illust(self, illust: Illust, *,
                           header: Optional[str] = None,
diff --git a/src/nonebot_plugin_pixivbot/handler/interceptor/default_error_interceptor.py b/src/nonebot_plugin_pixivbot/handler/interceptor/default_error_interceptor.py
index 1c91bfd..fa854f4 100644
--- a/src/nonebot_plugin_pixivbot/handler/interceptor/default_error_interceptor.py
+++ b/src/nonebot_plugin_pixivbot/handler/interceptor/default_error_interceptor.py
@@ -7,7 +7,6 @@
 from nonebot_plugin_pixivbot.protocol_dep.post_dest import PostDestination
 from nonebot_plugin_pixivbot.utils.errors import BadRequestError, QueryError
 from .interceptor import Interceptor
-from ...protocol_dep.postman import PostmanManager
 
 UID = TypeVar("UID")
 GID = TypeVar("GID")
@@ -16,11 +15,6 @@
 @context.inject
 @context.register_singleton()
 class DefaultErrorInterceptor(Interceptor):
-    postman_manager: PostmanManager
-
-    async def post_plain_text(self, message: str,
-                              post_dest: PostDestination):
-        await self.postman_manager.send_plain_text(message, post_dest=post_dest)
 
     async def intercept(self, wrapped_func: Callable, *,
                         post_dest: PostDestination[UID, GID],
diff --git a/src/nonebot_plugin_pixivbot/handler/interceptor/interceptor.py b/src/nonebot_plugin_pixivbot/handler/interceptor/interceptor.py
index f38aff8..c01eb69 100644
--- a/src/nonebot_plugin_pixivbot/handler/interceptor/interceptor.py
+++ b/src/nonebot_plugin_pixivbot/handler/interceptor/interceptor.py
@@ -2,21 +2,21 @@
 from functools import wraps
 from typing import Callable, TypeVar
 
+from nonebot_plugin_pixivbot import context
 from nonebot_plugin_pixivbot.protocol_dep.post_dest import PostDestination
+from nonebot_plugin_pixivbot.protocol_dep.postman import PostmanManager
 
 UID = TypeVar("UID")
 GID = TypeVar("GID")
 
 
+@context.inject
 class Interceptor(ABC):
-    def __call__(self, wrapped: Callable):
-        @wraps(wrapped)
-        async def wrapper(post_dest: PostDestination[UID, GID],
-                          silently: bool,
-                          **kwargs):
-            await self.intercept(wrapped, post_dest=post_dest, silently=silently, **kwargs)
+    postman_manager: PostmanManager
 
-        return wrapper
+    async def post_plain_text(self, message: str,
+                              post_dest: PostDestination):
+        await self.postman_manager.send_plain_text(message, post_dest=post_dest)
 
     @abstractmethod
     async def intercept(self, wrapped_func: Callable, *,
@@ -24,3 +24,12 @@ async def intercept(self, wrapped_func: Callable, *,
                         silently: bool,
                         **kwargs):
         raise NotImplementedError()
+
+    def __call__(self, wrapped: Callable):
+        @wraps(wrapped)
+        async def wrapper(post_dest: PostDestination[UID, GID],
+                          silently: bool,
+                          **kwargs):
+            await self.intercept(wrapped, post_dest=post_dest, silently=silently, **kwargs)
+
+        return wrapper
diff --git a/src/nonebot_plugin_pixivbot/handler/interceptor/loading_interceptor.py b/src/nonebot_plugin_pixivbot/handler/interceptor/loading_interceptor.py
new file mode 100644
index 0000000..cdaecd0
--- /dev/null
+++ b/src/nonebot_plugin_pixivbot/handler/interceptor/loading_interceptor.py
@@ -0,0 +1,30 @@
+from asyncio import create_task, sleep, shield, Task
+from typing import Callable, Optional
+
+from nonebot import logger
+
+from nonebot_plugin_pixivbot import context
+from nonebot_plugin_pixivbot.protocol_dep.post_dest import PostDestination
+from .interceptor import Interceptor, UID, GID
+
+
+@context.register_singleton()
+class LoadingInterceptor(Interceptor):
+    async def send_delayed_loading(self, post_dest: PostDestination[UID, GID]):
+        await sleep(3.0)
+        logger.debug(f"send delayed loading to {post_dest.identifier}")
+        await shield(self.post_plain_text("努力加载中", post_dest))
+
+    async def intercept(self, wrapped_func: Callable,
+                        *, post_dest: PostDestination[UID, GID],
+                        silently: bool,
+                        **kwargs):
+        task: Optional[Task] = None
+        if not silently:
+            task = create_task(self.send_delayed_loading(post_dest))
+
+        try:
+            await wrapped_func(post_dest=post_dest, silently=silently, **kwargs)
+        finally:
+            if task and not task.done():
+                task.cancel()
diff --git a/src/nonebot_plugin_pixivbot/handler/interceptor/permission_interceptor.py b/src/nonebot_plugin_pixivbot/handler/interceptor/permission_interceptor.py
index 41f2183..c517377 100644
--- a/src/nonebot_plugin_pixivbot/handler/interceptor/permission_interceptor.py
+++ b/src/nonebot_plugin_pixivbot/handler/interceptor/permission_interceptor.py
@@ -9,7 +9,6 @@
 from nonebot_plugin_pixivbot.protocol_dep.authenticator import AuthenticatorManager
 from nonebot_plugin_pixivbot.protocol_dep.post_dest import PostDestination
 from .interceptor import Interceptor
-from ...protocol_dep.postman import PostmanManager
 
 UID = TypeVar("UID")
 GID = TypeVar("GID")
@@ -17,12 +16,6 @@
 
 @context.inject
 class PermissionInterceptor(Interceptor, ABC):
-    postman_mgr: PostmanManager
-
-    async def post_plain_text(self, message: str,
-                              post_dest: PostDestination):
-        await self.postman_mgr.send_plain_text(message, post_dest=post_dest)
-
     @abstractmethod
     def has_permission(self, post_dest: PostDestination[UID, GID]) -> Union[bool, Awaitable[bool]]:
         raise NotImplementedError()
@@ -71,26 +64,6 @@ async def has_permission(self, post_dest: PostDestination[UID, GID]) -> bool:
         return False
 
 
-class AllPermissionInterceptor(PermissionInterceptor):
-    def __init__(self, *interceptors: PermissionInterceptor):
-        super().__init__()
-        self.interceptors = list(interceptors)
-
-    def append(self, interceptor: PermissionInterceptor):
-        self.interceptors.append(interceptor)
-
-    async def has_permission(self, post_dest: PostDestination[UID, GID]) -> bool:
-        for inter in self.interceptors:
-            p = inter.has_permission(post_dest)
-            if isawaitable(p):
-                p = await p
-
-            if not p:
-                return False
-
-        return True
-
-
 @context.register_singleton()
 class SuperuserInterceptor(PermissionInterceptor):
     def __init__(self):
diff --git a/src/nonebot_plugin_pixivbot/model/message/illust_message.py b/src/nonebot_plugin_pixivbot/model/message/illust_message.py
index f026441..021501b 100644
--- a/src/nonebot_plugin_pixivbot/model/message/illust_message.py
+++ b/src/nonebot_plugin_pixivbot/model/message/illust_message.py
@@ -12,6 +12,15 @@
 conf = context.require(Config)
 
 
+async def download_image(illust: Illust):
+    with BytesIO() as bio:
+        repo = context.require(PixivRepo)
+        async for x in repo.image(illust):
+            bio.write(x)
+            break
+        return bio.getvalue()
+
+
 class IllustMessageModel(BaseModel):
     id: int = 0
     title: str = ""
@@ -42,11 +51,7 @@ async def from_illust(illust: Illust, *,
             elif conf.pixiv_block_action == BlockAction.no_reply:
                 return None
         else:
-            with BytesIO() as bio:
-                repo = context.require(PixivRepo)
-                bio.write(await repo.image(illust))
-                model.image = bio.getvalue()
-
+            model.image = await download_image(illust)
         model.title = illust.title
         model.author = f"{illust.user.name} ({illust.user.id})"
         model.create_time = illust.create_date.strftime('%Y-%m-%d %H:%M:%S')
diff --git a/src/nonebot_plugin_pixivbot/model/subscription.py b/src/nonebot_plugin_pixivbot/model/subscription.py
index fbb71b0..2d77946 100644
--- a/src/nonebot_plugin_pixivbot/model/subscription.py
+++ b/src/nonebot_plugin_pixivbot/model/subscription.py
@@ -17,8 +17,8 @@ class Subscription(GenericModel, Generic[UID, GID]):
     kwargs: Dict[str, Any]
     schedule: Sequence[int]
 
-    @validator("user_id")
-    def validate(cls, user_id, values, **kwargs):
+    @validator("user_id", allow_reuse=True)
+    def validate(cls, user_id, values):
         group_id = None
         if "group_id" in values:
             group_id = values["group_id"]
diff --git a/src/nonebot_plugin_pixivbot/service/pixiv_service.py b/src/nonebot_plugin_pixivbot/service/pixiv_service.py
index d388c82..d90cf79 100644
--- a/src/nonebot_plugin_pixivbot/service/pixiv_service.py
+++ b/src/nonebot_plugin_pixivbot/service/pixiv_service.py
@@ -1,4 +1,3 @@
-import asyncio
 from typing import List, Union, Tuple
 
 from nonebot import logger
@@ -34,11 +33,11 @@ async def _choice_and_load(self, illusts: List[LazyIllust], random_method: Rando
         return [await x.get() for x in winners]
 
     async def illust_ranking(self, mode: RankingMode, range: Tuple[int, int]) -> List[Illust]:
-        illusts = await self.repo.illust_ranking(mode, range)
-        return [await x.get() for x in illusts]
+        return [await x.get() async for x in self.repo.illust_ranking(mode)[range[0] - 1:range[1]]]
 
     async def illust_detail(self, illust: int) -> Illust:
-        return await self.repo.illust_detail(illust)
+        async for x in self.repo.illust_detail(illust):
+            return x
 
     async def random_illust(self, word: str, *, count: int = 1) -> List[Illust]:
         if self.conf.pixiv_tag_translation_enabled:
@@ -59,7 +58,8 @@ async def get_user(self, user: Union[str, int]) -> User:
                 return x
             raise QueryError("未找到用户")
         else:
-            return await self.repo.user_detail(user)
+            async for x in self.repo.user_detail(user):
+                return x
 
     async def random_user_illust(self, user: Union[str, int], *, count: int = 1) -> Tuple[User, List[Illust]]:
         user = await self.get_user(user)
diff --git a/src/nonebot_plugin_pixivbot/utils/expires_lru_dict.py b/src/nonebot_plugin_pixivbot/utils/expires_lru_dict.py
new file mode 100644
index 0000000..886353b
--- /dev/null
+++ b/src/nonebot_plugin_pixivbot/utils/expires_lru_dict.py
@@ -0,0 +1,101 @@
+from collections import OrderedDict
+from collections.abc import MutableMapping
+from dataclasses import dataclass
+from datetime import datetime
+from functools import total_ordering
+from heapq import heappop, heapify, heappush
+from typing import TypeVar, Generic, Iterator, List, Dict, Optional, Callable
+
+_KT = TypeVar("_KT")
+_VT = TypeVar("_VT")
+
+
+@dataclass(frozen=True)
+@total_ordering
+class ExpiresNode(Generic[_KT]):
+    key: _KT
+    expires_time: datetime
+
+    def __le__(self, other: "ExpiresNode"):
+        return self.expires_time < other.expires_time
+
+
+class ExpiresLruDict(MutableMapping[_KT, _VT], Generic[_KT, _VT]):
+    def __init__(self, maxsize: int, on_cleanup: Optional[Callable[[_KT, _VT], None]] = None):
+        self.maxsize = maxsize
+        self.on_cleanup = on_cleanup
+
+        self._data = OrderedDict[_KT, _VT]()
+        self._expires_heap: List[ExpiresNode[_KT]] = []
+        self._in_expires_heap: Dict[_KT, bool] = {}
+
+    def _collate(self, ensure_size_for_put: bool):
+        now = datetime.now()
+        while len(self._expires_heap) > 0 and self._expires_heap[0].expires_time <= now:
+            k = self._expires_heap[0].key
+            if k in self._data:
+                v = self._data.pop(k)
+                self.on_cleanup and self.on_cleanup(k, v)
+
+            del self._in_expires_heap[k]
+            heappop(self._expires_heap)
+
+        if ensure_size_for_put and len(self._data) == self.maxsize:
+            k, v = self._data.popitem(last=False)
+            self.on_cleanup and self.on_cleanup(k, v)
+            # we will not remove items from expires_heap
+            # until its size reach the threshold (checked on add)
+
+    def _collate_expires_heap(self):
+        threshold = 2 * self.maxsize - 1
+
+        if len(self._expires_heap) > threshold:
+            # rebuild _expires_heap & _in_expires_heap
+            expires_time_mapping = {}
+            for node in self._expires_heap:
+                expires_time_mapping[node.key] = expires_time_mapping[node.expires_time]
+
+            self._expires_heap = []
+            self._in_expires_heap = []
+            for key in self._data:
+                self._expires_heap[key] = ExpiresNode(
+                    key=key,
+                    expires_time=expires_time_mapping[key]
+                )
+                self._in_expires_heap[key] = True
+            heapify(self._expires_heap)
+
+    def add(self, key: _KT, value: _VT, expires_time: datetime):
+        self._collate(True)
+
+        if not self._in_expires_heap.get(key, False):
+            heappush(self._expires_heap, ExpiresNode(key, expires_time))
+            self._in_expires_heap[key] = True
+        self._collate_expires_heap()
+
+        self._data.__setitem__(key, value)
+
+    def __setitem__(self, key: _KT, value: _VT) -> None:
+        self._collate(False)
+        if key not in self._data:
+            raise KeyError(key)
+        self._data.__setitem__(key, value)
+
+    def __delitem__(self, key: _KT) -> None:
+        self._collate(False)
+
+        # we will not remove items from expires_heap
+        # until its size reach the threshold (checked on add)
+        return self._data.__delitem__(key)
+
+    def __getitem__(self, key: _KT) -> _VT:
+        self._collate(False)
+        return self._data.__getitem__(key)
+
+    def __len__(self) -> int:
+        self._collate(False)
+        return self._data.__len__()
+
+    def __iter__(self) -> Iterator[_KT]:
+        self._collate(False)
+        return self._data.__iter__()
diff --git a/src/nonebot_plugin_pixivbot/utils/lifecycler.py b/src/nonebot_plugin_pixivbot/utils/lifecycler.py
index 0873193..e484dea 100644
--- a/src/nonebot_plugin_pixivbot/utils/lifecycler.py
+++ b/src/nonebot_plugin_pixivbot/utils/lifecycler.py
@@ -31,7 +31,7 @@
 
 def on_startup(func, replay: bool = False):
     if replay and _startup.is_set():
-        logger.debug("replaying on_startup")
+        logger.debug("[lifecycler] replaying on_startup")
         x = func()
         if isawaitable(x):
             asyncio.create_task(x)
@@ -42,7 +42,7 @@ def on_startup(func, replay: bool = False):
 
 def on_bot_connect(func, replay: bool = False):
     if replay:
-        logger.debug("replaying on_bot_connect")
+        logger.debug("[lifecycler] replaying on_bot_connect")
         for bot in _connected_bot:
             x = func(bot)
             if isawaitable(x):
@@ -64,6 +64,7 @@ def on_shutdown(func):
 async def _fire_startup():
     await _mutex.acquire()
     try:
+        logger.info("[lifecycler] on startup")
         cors = [f() for f in _on_startup_callback]
         cors = [x for x in cors if isawaitable(x)]
         if len(cors) > 0:
@@ -78,6 +79,7 @@ async def _fire_bot_connect(bot: Bot):
     await _startup.wait()
     await _mutex.acquire()
     try:
+        logger.info(f"[lifecycler] on bot {bot} connect")
         cors = [f(bot) for f in _on_bot_connect_callback]
         cors = [x for x in cors if isawaitable(x)]
         if len(cors) > 0:
@@ -93,6 +95,7 @@ async def _fire_bot_disconnect(bot: Bot):
     await _startup.wait()
     await _mutex.acquire()
     try:
+        logger.info(f"[lifecycler] on bot {bot} disconnect")
         cors = [f(bot) for f in _on_bot_disconnect_callback]
         cors = [x for x in cors if isawaitable(x)]
         if len(cors) > 0:
@@ -109,6 +112,7 @@ async def _fire_shutdown():
     await _no_bot_connect.wait()
     await _mutex.acquire()
     try:
+        logger.info(f"[lifecycler] on shutdown")
         cors = [f() for f in _on_shutdown_callback]
         cors = [x for x in cors if isawaitable(x)]
         if len(cors) > 0:


Test patch:
diff --git a/src/tests/__init__.py b/src/tests/__init__.py
new file mode 100644
index 0000000..fccbfab
--- /dev/null
+++ b/src/tests/__init__.py
@@ -0,0 +1,10 @@
+import pytest
+
+
+class MyTest:
+    @pytest.fixture(autouse=True)
+    def load_pixivbot(self, nonebug_init):
+        import nonebot  # 这里的导入必须在函数内
+
+        # 加载插件
+        return nonebot.load_plugins("nonebot_plugin_pixivbot")
diff --git a/src/tests/conftest.py b/src/tests/conftest.py
new file mode 100644
index 0000000..96e45e3
--- /dev/null
+++ b/src/tests/conftest.py
@@ -0,0 +1,8 @@
+import pytest
+
+
+@pytest.fixture(autouse=True)
+def conf_env(monkeypatch):
+    monkeypatch.setenv("PIXIV_MONGO_CONN_URL", "123")
+    monkeypatch.setenv("PIXIV_MONGO_DATABASE_NAME", "123")
+    monkeypatch.setenv("PIXIV_REFRESH_TOKEN", "123")
diff --git a/src/tests/data/__init__.py b/src/tests/data/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/tests/data/pixiv_repo/__init__.py b/src/tests/data/pixiv_repo/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/tests/data/pixiv_repo/test_mediator.py b/src/tests/data/pixiv_repo/test_mediator.py
new file mode 100644
index 0000000..85667d7
--- /dev/null
+++ b/src/tests/data/pixiv_repo/test_mediator.py
@@ -0,0 +1,351 @@
+from asyncio import sleep
+from datetime import datetime
+from unittest.mock import AsyncMock, call
+
+import pytest
+
+from tests import MyTest
+
+
+class TestMediateSingle(MyTest):
+    @pytest.fixture
+    def cache_factory_with_cache(self):
+        from nonebot_plugin_pixivbot.data.pixiv_repo.abstract_repo import PixivRepoMetadata
+
+        cache_metadata = PixivRepoMetadata(update_time=datetime.fromtimestamp(0))
+
+        async def agen():
+            await sleep(0.1)
+            yield cache_metadata
+            yield "cache"
+
+        return agen
+
+    @pytest.fixture
+    def cache_factory_with_expired_cache(self):
+        from nonebot_plugin_pixivbot.data.pixiv_repo.abstract_repo import PixivRepoMetadata
+        from nonebot_plugin_pixivbot.data.pixiv_repo.local_repo import CacheExpiredError
+
+        cache_metadata = PixivRepoMetadata(update_time=datetime.fromtimestamp(0))
+
+        async def agen():
+            await sleep(0.1)
+            raise CacheExpiredError(cache_metadata)
+            yield None  # to mark it as an AsyncGenerator
+
+        return agen
+
+    @pytest.fixture
+    def remote_factory(self):
+        from nonebot_plugin_pixivbot.data.pixiv_repo.abstract_repo import PixivRepoMetadata
+
+        async def remote_factory():
+            await sleep(0.2)
+            yield PixivRepoMetadata()
+            yield "remote"
+
+        return remote_factory
+
+    @pytest.mark.asyncio
+    async def test_with_cache(self, cache_factory_with_cache, remote_factory):
+        from nonebot_plugin_pixivbot.data.pixiv_repo.abstract_repo import PixivRepoMetadata
+        from nonebot_plugin_pixivbot.data.pixiv_repo.mediator import mediate_single
+
+        cache_updater = AsyncMock()
+
+        content = None
+        metadata = None
+        async for x in mediate_single(cache_factory_with_cache, remote_factory, cache_updater):
+            if isinstance(x, PixivRepoMetadata):
+                metadata = x
+            else:
+                content = x
+
+        assert content == "cache"
+        assert metadata.update_time == datetime.fromtimestamp(0)  # assert metadata comes from cache
+        cache_updater.assert_not_awaited()
+
+    @pytest.mark.asyncio
+    async def test_with_remote(self, cache_factory_with_expired_cache, remote_factory):
+        from nonebot_plugin_pixivbot.data.pixiv_repo.abstract_repo import PixivRepoMetadata
+        from nonebot_plugin_pixivbot.data.pixiv_repo.mediator import mediate_single
+
+        cache_updater = AsyncMock()
+
+        content = None
+        metadata = None
+        async for x in mediate_single(cache_factory_with_expired_cache, remote_factory, cache_updater):
+            if isinstance(x, PixivRepoMetadata):
+                metadata = x
+            else:
+                content = x
+
+        assert content == "remote"
+        assert metadata.update_time != datetime.fromtimestamp(0)  # assert metadata comes from remote
+        cache_updater.assert_awaited_once_with(content, metadata)
+
+
+class TestMediateCollection(MyTest):
+    @pytest.fixture
+    def cache_factory_with_cache(self):
+        from nonebot_plugin_pixivbot.data.pixiv_repo.abstract_repo import PixivRepoMetadata
+
+        cache_metadata = PixivRepoMetadata(update_time=datetime.fromtimestamp(0),
+                                           pages=5,
+                                           next_qs={"next": 30})
+
+        async def agen():
+            await sleep(0.1)
+            yield cache_metadata
+            for i in range(30):
+                yield i
+
+            # meta, 0, 1, ..., 29
+
+        return agen
+
+    @pytest.fixture
+    def cache_factory_with_cache_and_empty_next_qs(self):
+        from nonebot_plugin_pixivbot.data.pixiv_repo.abstract_repo import PixivRepoMetadata
+
+        cache_metadata = PixivRepoMetadata(update_time=datetime.fromtimestamp(0),
+                                           pages=5,
+                                           next_qs={})
+
+        async def agen():
+            await sleep(0.1)
+            yield cache_metadata
+            for i in range(30):
+                yield i
+
+            # meta, 0, 1, ..., 29
+
+        return agen
+
+    @pytest.fixture
+    def cache_factory_with_no_cache(self):
+        from nonebot_plugin_pixivbot.data.pixiv_repo.local_repo import NoSuchItemError
+
+        async def agen():
+            await sleep(0.1)
+            raise NoSuchItemError()
+            yield None  # to mark it as an AsyncGenerator
+
+        return agen
+
+    @pytest.fixture
+    def cache_factory_with_expired_cache(self):
+        from nonebot_plugin_pixivbot.data.pixiv_repo.abstract_repo import PixivRepoMetadata
+        from nonebot_plugin_pixivbot.data.pixiv_repo.local_repo import CacheExpiredError
+
+        cache_metadata = PixivRepoMetadata(update_time=datetime.fromtimestamp(0),
+                                           pages=5,
+                                           next_qs={"next": 30})
+
+        async def agen():
+            await sleep(0.1)
+            raise CacheExpiredError(cache_metadata)
+            yield None  # to mark it as an AsyncGenerator
+
+        return agen
+
+    @pytest.fixture
+    def remote_factory(self):
+        from nonebot_plugin_pixivbot.data.pixiv_repo.abstract_repo import PixivRepoMetadata
+
+        async def remote_factory(next=0):
+            await sleep(0.1)
+            yield PixivRepoMetadata(pages=0, next_qs={"next": next})
+
+            for i in range(1, 4):
+                for j in range(20):
+                    yield next
+                    next += 1
+                yield PixivRepoMetadata(pages=i, next_qs={"next": next})
+
+            # next=0: meta, 0, 1, ..., 19, meta, 20, 21, ..., 39, meta, 40, 41, ..., 59, meta
+            # next=30: meta, 30, 31, ..., 49, meta, 50, 51, ..., 69, meta, 70, 71, ..., 89, meta
+
+        return remote_factory
+
+
+class TestMediateMany(TestMediateCollection):
+    @pytest.mark.asyncio
+    async def test_with_cache_and_empty_next_qs(self, cache_factory_with_cache_and_empty_next_qs, 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_updater = AsyncMock()
+        cache_appender = AsyncMock()
+
+        items = []
+        metadata = None
+        async for x in mediate_many(cache_factory_with_cache_and_empty_next_qs, remote_factory,
+                                    cache_updater, cache_appender):
+            if not isinstance(x, PixivRepoMetadata):
+                items.append(x)
+            else:
+                metadata = x
+
+        assert items == [x for x in range(30)]
+        assert metadata.update_time == datetime.fromtimestamp(0)  # assert metadata comes from cache
+        cache_updater.assert_not_awaited()
+        cache_appender.assert_not_awaited()
+
+    @pytest.mark.asyncio
+    async def test_with_cache_and_remote(self, cache_factory_with_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_updater = AsyncMock()
+        cache_appender = AsyncMock()
+
+        items = []
+        metadata = None
+        async for x in mediate_many(cache_factory_with_cache, remote_factory,
+                                    cache_updater, cache_appender):
+            if not isinstance(x, PixivRepoMetadata):
+                items.append(x)
+            else:
+                metadata = x
+
+        assert items == [x for x in range(90)]
+        assert metadata.update_time != datetime.fromtimestamp(0)  # assert metadata comes from remote
+        cache_updater.assert_not_awaited()
+        cache_appender.assert_awaited_once_with([x for x in range(30, 90)], metadata)
+
+    @pytest.mark.asyncio
+    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_updater = AsyncMock()
+        cache_appender = AsyncMock()
+
+        items = []
+        metadata = None
+        async for x in mediate_many(cache_factory_with_no_cache, remote_factory,
+                                    cache_updater, 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_updater.assert_awaited_once_with([x for x in range(60)], metadata)
+        cache_appender.assert_not_awaited()
+
+
+class TestMediateAppend(TestMediateCollection):
+    @pytest.mark.asyncio
+    async def test_with_cache_and_empty_next_qs(self, cache_factory_with_cache_and_empty_next_qs, remote_factory):
+        from nonebot_plugin_pixivbot.data.pixiv_repo.mediator import mediate_append
+        from nonebot_plugin_pixivbot.data.pixiv_repo.abstract_repo import PixivRepoMetadata
+
+        cache_updater = AsyncMock()
+        cache_appender = AsyncMock()
+
+        items = []
+        metadata = None
+        async for x in mediate_append(cache_factory_with_cache_and_empty_next_qs, remote_factory,
+                                      cache_updater, cache_appender):
+            if not isinstance(x, PixivRepoMetadata):
+                items.append(x)
+            else:
+                metadata = x
+
+        assert items == [x for x in range(30)]
+        assert metadata.update_time == datetime.fromtimestamp(0)  # assert metadata comes from cache
+        cache_updater.assert_not_awaited()
+        cache_appender.assert_not_awaited()
+
+    @pytest.mark.asyncio
+    async def test_with_cache_and_remote(self, cache_factory_with_cache, remote_factory):
+        from nonebot_plugin_pixivbot.data.pixiv_repo.mediator import mediate_append
+        from nonebot_plugin_pixivbot.data.pixiv_repo.abstract_repo import PixivRepoMetadata
+
+        cache_updater = AsyncMock()
+        cache_appender = AsyncMock()
+
+        items = []
+        metadata = None
+        async for x in mediate_append(cache_factory_with_cache, remote_factory,
+                                      cache_updater, cache_appender):
+            if not isinstance(x, PixivRepoMetadata):
+                items.append(x)
+            else:
+                metadata = x
+
+        assert items == [x for x in range(90)]
+        assert metadata.update_time != datetime.fromtimestamp(0)  # assert metadata comes from remote
+        cache_updater.assert_not_awaited()
+        cache_appender.assert_awaited_once_with([x for x in range(30, 90)], metadata)
+
+    @pytest.mark.asyncio
+    async def test_with_remote(self, cache_factory_with_no_cache, remote_factory):
+        from nonebot_plugin_pixivbot.data.pixiv_repo.mediator import mediate_append
+        from nonebot_plugin_pixivbot.data.pixiv_repo.abstract_repo import PixivRepoMetadata
+
+        cache_updater = AsyncMock()
+        cache_appender = AsyncMock()
+
+        items = []
+        metadata = None
+        async for x in mediate_append(cache_factory_with_no_cache, remote_factory,
+                                      cache_updater, 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_updater.assert_awaited_once_with([x for x in range(60)], metadata)
+        cache_appender.assert_not_awaited()
+
+    @pytest.mark.asyncio
+    async def test_with_expired_cache(self, cache_factory_with_cache,
+                                      cache_factory_with_expired_cache,
+                                      remote_factory):
+        from nonebot_plugin_pixivbot.data.pixiv_repo.mediator import mediate_append
+        from nonebot_plugin_pixivbot.data.pixiv_repo.abstract_repo import PixivRepoMetadata
+
+        cache_updated = False
+
+        def cache_factory():
+            if not cache_updated:
+                return cache_factory_with_expired_cache()
+            else:
+                return cache_factory_with_cache()
+
+        async def cache_appender(items, metadata):
+            nonlocal cache_updated
+
+            await sleep(0.1)
+            cache_updated = True
+            return True
+
+        cache_updater = AsyncMock()
+        cache_appender = AsyncMock(side_effect=cache_appender)
+
+        items = []
+        metadata = []
+        async for x in mediate_append(cache_factory, remote_factory,
+                                      cache_updater, cache_appender):
+            if not isinstance(x, PixivRepoMetadata):
+                items.append(x)
+            else:
+                metadata.append(x)
+
+        assert items == [x for x in range(90)]
+        assert metadata[0].update_time == datetime.fromtimestamp(0)  # assert the first metadata comes from cache
+        assert metadata[-1].update_time != datetime.fromtimestamp(0)  # assert the last metadata comes from remote
+        cache_updater.assert_not_awaited()
+
+        # assert the first call for peek remote update with updated metadata
+        assert cache_appender.await_args_list[0].args[0] == [x for x in range(20)]
+        assert cache_appender.await_args_list[0].args[1].next_qs == metadata[0].next_qs
+
+        # assert the second call for append remote data
+        assert cache_appender.await_args_list[1] == call([x for x in range(30, 90)], metadata[-1])
diff --git a/src/tests/data/pixiv_repo/test_shared_agen.py b/src/tests/data/pixiv_repo/test_shared_agen.py
new file mode 100644
index 0000000..8271576
--- /dev/null
+++ b/src/tests/data/pixiv_repo/test_shared_agen.py
@@ -0,0 +1,164 @@
+from asyncio import sleep, create_task, gather
+from datetime import datetime, timedelta
+from typing import AsyncGenerator
+from unittest.mock import MagicMock, AsyncMock, call
+
+import pytest
+
+from tests import MyTest
+
+
+class TestSharedAsyncGeneratorContextManager(MyTest):
+    @pytest.fixture
+    def origin_agen(self):
+        cnt = 0
+
+        async def agen():
+            nonlocal cnt
+            for i in range(10):
+                await sleep(0.1)
+                yield cnt
+                cnt += 1
+
+        return agen()
+
+    @pytest.fixture
+    def ctx_mgr(self, origin_agen):
+        from nonebot_plugin_pixivbot.data.pixiv_repo.shared_agen import SharedAsyncGeneratorContextManager
+
+        on_each = AsyncMock()
+        on_stop = AsyncMock()
+        on_consumers_changed = MagicMock()
+
+        return SharedAsyncGeneratorContextManager(
+            origin_agen,
+            on_each,
+            on_stop,
+            on_consumers_changed
+        )
+
+    @pytest.mark.asyncio
+    async def test_on_each_and_on_stop(self, origin_agen):
+        from nonebot_plugin_pixivbot.data.pixiv_repo.shared_agen import SharedAsyncGeneratorContextManager
+
+        on_each = AsyncMock()
+        on_stop = AsyncMock()
+        on_consumers_changed = MagicMock()
+
+        ctx_mgr = SharedAsyncGeneratorContextManager(
+            origin_agen,
+            on_each,
+            on_stop,
+            on_consumers_changed
+        )
+
+        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)])
+
+    @pytest.mark.asyncio
+    async def test_single_consumer(self, ctx_mgr):
+        with ctx_mgr as iter:
+            items = [i async for i in iter]
+
+        assert items == [i for i in range(10)]
+
+        ctx_mgr._on_consumers_changed.assert_has_calls([call(ctx_mgr, 1), call(ctx_mgr, 0)])
+
+    @pytest.mark.asyncio
+    async def test_multi_consumer(self, ctx_mgr):
+        async def consume():
+            with ctx_mgr as iter:
+                items = [i async for i in iter]
+
+            assert items == [i for i in range(10)]
+
+        # consumer2 except to wait for all the 10 items together
+        consumer1 = create_task(consume())
+        await sleep(0.25)
+
+        # consumer2 except to get 2 generated items, then wait for the rest 8 items together
+        consumer2 = create_task(consume())
+
+        # consumer3 begin collecting after the agen stopped
+        await sleep(1.5)
+        consumer3 = create_task(consume())
+
+        await gather(consumer1, consumer2, consumer3)
+
+        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)])
+
+    @pytest.mark.asyncio
+    async def test_close(self, ctx_mgr):
+        with ctx_mgr as iter:
+            await iter.__anext__()
+            await iter.__anext__()
+
+        ctx_mgr.close()
+
+        with ctx_mgr as iter:
+            await iter.__anext__()
+            await iter.__anext__()
+            with pytest.raises(StopAsyncIteration):
+                await iter.__anext__()
+
+
+class TestSharedAsyncGeneratorManager(MyTest):
+    @pytest.fixture
+    def shared_agen_mgr(self):
+        from nonebot_plugin_pixivbot.data.pixiv_repo.shared_agen import SharedAsyncGeneratorManager
+
+        class SharedAsyncGeneratorManagerImpl(SharedAsyncGeneratorManager[int, int]):
+            def agen_factory(self, identifier: int, *args, **kwargs) -> AsyncGenerator[int, None]:
+                async def agen():
+                    cnt = identifier
+                    for i in range(10):
+                        await sleep(0.1)
+                        yield cnt
+                        cnt += 1
+
+                return agen()
+
+        impl = SharedAsyncGeneratorManagerImpl()
+        impl.on_agen_next = AsyncMock()
+        impl.on_agen_stop = AsyncMock()
+        return impl
+
+    @pytest.mark.asyncio
+    async def test_get(self, shared_agen_mgr):
+        # except same
+        inst = shared_agen_mgr.get(0)
+        with inst as iter:
+            inst2 = shared_agen_mgr.get(0)
+            with inst2 as iter2:
+                assert inst == inst2
+
+        # prev inst was except to be destroyed
+
+        inst3 = shared_agen_mgr.get(0)
+        assert inst != inst3
+
+    @pytest.mark.asyncio
+    async def test_set_expires_time(self, shared_agen_mgr):
+        inst = shared_agen_mgr.get(0)
+        with inst as iter:
+            await iter.__anext__()
+            await iter.__anext__()
+
+            shared_agen_mgr.set_expires_time(0, datetime.now() + timedelta(seconds=1))
+
+        # prev inst was except to be saved
+
+        inst2 = shared_agen_mgr.get(0)
+        assert inst == inst2
+
+        await sleep(1.5)
+
+        # prev inst was except to be removed
+
+        inst2 = shared_agen_mgr.get(0)
+        assert inst != inst2
diff --git a/src/tests/test_context.py b/src/tests/test_context.py
new file mode 100644
index 0000000..d55ecc7
--- /dev/null
+++ b/src/tests/test_context.py
@@ -0,0 +1,100 @@
+import pytest
+
+from tests import MyTest
+
+
+class A:
+    def __init__(self, data):
+        self.data = data
+
+    def hello(self):
+        return self.data
+
+
+class B(A):
+    def __init__(self, data):
+        super().__init__(data)
+
+    def hello(self):
+        return "Hello " + str(self.data)
+
+
+class TestContext(MyTest):
+    @pytest.fixture
+    def context(self):
+        from nonebot_plugin_pixivbot.context import Context
+
+        context = Context()
+        return context
+
+    def test_register_require_contains(self, context):
+        context.register(A, A(1))
+        assert context.require(A).hello() == 1
+        assert A in context
+
+    def test_register_lazy(self, context):
+        context.register_lazy(A, lambda: A(2))
+        assert A not in context._container
+        assert context.require(A).hello() == 2
+        assert A in context._container
+
+    def test_register_singleton(self, context):
+        context.register_singleton(3)(A)
+        assert A not in context._container
+        assert context.require(A).hello() == 3
+        assert A in context._container
+
+    def test_register_eager_singleton(self, context):
+        context.register_eager_singleton(4)(A)
+        assert A in context._container
+        assert context.require(A).hello() == 4
+
+    def test_bind_to(self, context):
+        context.register(B, B("world"))
+        context.bind_to(A, B)
+
+        assert context.require(A).hello() == "Hello world"
+
+    def test_bind_instance(self, context):
+        @context.bind_singleton_to(B, "world")
+        class C(B):
+            def __init__(self, data):
+                super().__init__(data)
+
+        assert context.require(C).hello() == "Hello world"
+
+    def test_parent(self, context):
+        from nonebot_plugin_pixivbot.context import Context
+        second = Context(context)
+        third = Context(second)
+
+        context.register(A, A(10))
+        assert third.require(A).hello() == 10
+        assert A in third
+
+    def test_inject(self, context):
+        context.register(A, A(5))
+
+        @context.inject
+        class X:
+            a: A
+
+        x = X()
+        assert x.a.hello() == 5
+
+    def test_inherited_inject(self, context):
+        context.register(A, A(6))
+        context.register(B, A(7))
+
+        @context.inject
+        class X:
+            a: A
+
+        @context.inject
+        class Y(X):
+            b: B
+
+        y = Y()
+
+        assert y.a.hello() == 6
+        assert y.b.hello() == 7
diff --git a/src/tests/utils/__init__.py b/src/tests/utils/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/tests/utils/test_expires_lru_dict.py b/src/tests/utils/test_expires_lru_dict.py
new file mode 100644
index 0000000..b77303d
--- /dev/null
+++ b/src/tests/utils/test_expires_lru_dict.py
@@ -0,0 +1,89 @@
+from asyncio import sleep
+from datetime import datetime, timedelta
+from unittest.mock import MagicMock
+
+import pytest
+
+from tests import MyTest
+
+
+class TestExpiresLruDict(MyTest):
+    @pytest.fixture
+    def lru(self):
+        from nonebot_plugin_pixivbot.utils.expires_lru_dict import ExpiresLruDict
+
+        return ExpiresLruDict(10)
+
+    @pytest.mark.asyncio
+    async def test_expires(self, lru):
+        lru.on_cleanup = MagicMock()
+
+        expires = datetime.now() + timedelta(seconds=1)
+        lru.add("hello", "world", expires)
+        assert lru["hello"] == "world"
+
+        await sleep(2)
+        assert "hello" not in lru
+        lru.on_cleanup.assert_called_once_with("hello", "world")
+
+        expires = datetime.now() + timedelta(seconds=30)
+        lru.add("hello", "test", expires)
+        assert lru["hello"] == "test"
+
+    def test_lru(self, lru):
+        lru.on_cleanup = MagicMock()
+
+        expires = datetime.now() + timedelta(seconds=3)
+        for i in range(11):
+            lru.add(i, i, expires)
+
+        assert 0 not in lru
+        lru.on_cleanup.assert_called_once_with(0, 0)
+        for i in range(1, 11):
+            assert lru[i] == i
+
+    @pytest.mark.asyncio
+    async def test_expires_lru(self, lru):
+        now = datetime.now()
+        for i in range(5):
+            lru.add(i, i, now + timedelta(seconds=2))
+        for i in range(5, 10):
+            lru.add(i, i, now + timedelta(seconds=20))
+        # except: 0 1 2 3 4 5 6 7 8 9
+
+        await sleep(3)
+        for i in range(5):
+            assert i not in lru
+        for i in range(5, 10):
+            assert lru[i] == i
+        # except: 5 6 7 8 9
+
+        now = datetime.now()
+        for i in range(10, 18):
+            lru.add(i, i, now + timedelta(seconds=20))
+        # except: 8 9 10 11 12 13 14 15 16 17
+
+        for i in range(5, 8):
+            assert i not in lru
+        for i in range(8, 18):
+            assert lru[i] == i
+
+    def test_key_error(self, lru):
+        with pytest.raises(KeyError):
+            lru["test"] = "py"
+
+    @pytest.mark.asyncio
+    async def test_collate_expires_heap(self, lru):
+        now = datetime.now()
+        for i in range(20):
+            lru.add(i, i, now + timedelta(milliseconds=100 * i))
+
+        await sleep(3)
+
+        now = datetime.now()
+        lru.add("hello", "world", now + timedelta(seconds=20))
+
+        for i in range(20):
+            assert i not in lru
+
+        assert lru["hello"] == "world"