├── .gitignore ├── README.md ├── poetry.lock ├── pyproject.toml └── webvpn ├── __init__.py ├── client.py ├── cmd.py ├── config.py ├── gateway ├── __init__.py ├── gateway.py ├── tcp.py └── webvpn.py ├── logger.py ├── login.py ├── server.py └── settings.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Ignore dynaconf secret files 3 | .secrets.* 4 | 5 | # Created by https://www.toptal.com/developers/gitignore/api/python,visualstudiocode 6 | # Edit at https://www.toptal.com/developers/gitignore?templates=python,visualstudiocode 7 | 8 | ### Python ### 9 | # Byte-compiled / optimized / DLL files 10 | __pycache__/ 11 | *.py[cod] 12 | *$py.class 13 | 14 | # C extensions 15 | *.so 16 | 17 | # Distribution / packaging 18 | .Python 19 | build/ 20 | develop-eggs/ 21 | dist/ 22 | downloads/ 23 | eggs/ 24 | .eggs/ 25 | lib/ 26 | lib64/ 27 | parts/ 28 | sdist/ 29 | var/ 30 | wheels/ 31 | share/python-wheels/ 32 | *.egg-info/ 33 | .installed.cfg 34 | *.egg 35 | MANIFEST 36 | 37 | # PyInstaller 38 | # Usually these files are written by a python script from a template 39 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 40 | *.manifest 41 | *.spec 42 | 43 | # Installer logs 44 | pip-log.txt 45 | pip-delete-this-directory.txt 46 | 47 | # Unit test / coverage reports 48 | htmlcov/ 49 | .tox/ 50 | .nox/ 51 | .coverage 52 | .coverage.* 53 | .cache 54 | nosetests.xml 55 | coverage.xml 56 | *.cover 57 | *.py,cover 58 | .hypothesis/ 59 | .pytest_cache/ 60 | cover/ 61 | 62 | # Translations 63 | *.mo 64 | *.pot 65 | 66 | # Django stuff: 67 | *.log 68 | local_settings.py 69 | db.sqlite3 70 | db.sqlite3-journal 71 | 72 | # Flask stuff: 73 | instance/ 74 | .webassets-cache 75 | 76 | # Scrapy stuff: 77 | .scrapy 78 | 79 | # Sphinx documentation 80 | docs/_build/ 81 | 82 | # PyBuilder 83 | .pybuilder/ 84 | target/ 85 | 86 | # Jupyter Notebook 87 | .ipynb_checkpoints 88 | 89 | # IPython 90 | profile_default/ 91 | ipython_config.py 92 | 93 | # pyenv 94 | # For a library or package, you might want to ignore these files since the code is 95 | # intended to run in multiple environments; otherwise, check them in: 96 | # .python-version 97 | 98 | # pipenv 99 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 100 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 101 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 102 | # install all needed dependencies. 103 | #Pipfile.lock 104 | 105 | # poetry 106 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 107 | # This is especially recommended for binary packages to ensure reproducibility, and is more 108 | # commonly ignored for libraries. 109 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 110 | #poetry.lock 111 | 112 | # pdm 113 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 114 | #pdm.lock 115 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 116 | # in version control. 117 | # https://pdm.fming.dev/#use-with-ide 118 | .pdm.toml 119 | 120 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 121 | __pypackages__/ 122 | 123 | # Celery stuff 124 | celerybeat-schedule 125 | celerybeat.pid 126 | 127 | # SageMath parsed files 128 | *.sage.py 129 | 130 | # Environments 131 | .env 132 | .venv 133 | env/ 134 | venv/ 135 | ENV/ 136 | env.bak/ 137 | venv.bak/ 138 | 139 | # Spyder project settings 140 | .spyderproject 141 | .spyproject 142 | 143 | # Rope project settings 144 | .ropeproject 145 | 146 | # mkdocs documentation 147 | /site 148 | 149 | # mypy 150 | .mypy_cache/ 151 | .dmypy.json 152 | dmypy.json 153 | 154 | # Pyre type checker 155 | .pyre/ 156 | 157 | # pytype static type analyzer 158 | .pytype/ 159 | 160 | # Cython debug symbols 161 | cython_debug/ 162 | 163 | # PyCharm 164 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 165 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 166 | # and can be added to the global gitignore or merged into this file. For a more nuclear 167 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 168 | #.idea/ 169 | 170 | ### VisualStudioCode ### 171 | .vscode/* 172 | !.vscode/settings.json 173 | !.vscode/tasks.json 174 | !.vscode/launch.json 175 | !.vscode/extensions.json 176 | !.vscode/*.code-snippets 177 | 178 | # Local History for Visual Studio Code 179 | .history/ 180 | 181 | # Built Visual Studio Code Extensions 182 | *.vsix 183 | 184 | ### VisualStudioCode Patch ### 185 | # Ignore all local history of files 186 | .history 187 | .ionide 188 | 189 | # Support for Project snippet scope 190 | .vscode/*.code-snippets 191 | 192 | # Ignore code-workspaces 193 | *.code-workspace 194 | 195 | # End of https://www.toptal.com/developers/gitignore/api/python,visualstudiocode 196 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebVPN-py 2 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "aiohttp" 3 | version = "3.8.1" 4 | description = "Async http client/server framework (asyncio)" 5 | category = "main" 6 | optional = false 7 | python-versions = ">=3.6" 8 | 9 | [package.dependencies] 10 | aiosignal = ">=1.1.2" 11 | async-timeout = ">=4.0.0a3,<5.0" 12 | attrs = ">=17.3.0" 13 | charset-normalizer = ">=2.0,<3.0" 14 | frozenlist = ">=1.1.1" 15 | multidict = ">=4.5,<7.0" 16 | yarl = ">=1.0,<2.0" 17 | 18 | [package.extras] 19 | speedups = ["aiodns", "brotli", "cchardet"] 20 | 21 | [[package]] 22 | name = "aiorun" 23 | version = "2022.4.1" 24 | description = "Boilerplate for asyncio applications" 25 | category = "main" 26 | optional = false 27 | python-versions = ">=3.5" 28 | 29 | [package.extras] 30 | dev = ["pytest", "pytest-cov"] 31 | 32 | [[package]] 33 | name = "aiosignal" 34 | version = "1.2.0" 35 | description = "aiosignal: a list of registered asynchronous callbacks" 36 | category = "main" 37 | optional = false 38 | python-versions = ">=3.6" 39 | 40 | [package.dependencies] 41 | frozenlist = ">=1.1.0" 42 | 43 | [[package]] 44 | name = "anyio" 45 | version = "3.6.1" 46 | description = "High level compatibility layer for multiple asynchronous event loop implementations" 47 | category = "dev" 48 | optional = false 49 | python-versions = ">=3.6.2" 50 | 51 | [package.dependencies] 52 | idna = ">=2.8" 53 | sniffio = ">=1.1" 54 | 55 | [package.extras] 56 | doc = ["packaging", "sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"] 57 | 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)"] 58 | trio = ["trio (>=0.16)"] 59 | 60 | [[package]] 61 | name = "async-timeout" 62 | version = "4.0.2" 63 | description = "Timeout context manager for asyncio programs" 64 | category = "main" 65 | optional = false 66 | python-versions = ">=3.6" 67 | 68 | [[package]] 69 | name = "attrs" 70 | version = "21.4.0" 71 | description = "Classes Without Boilerplate" 72 | category = "main" 73 | optional = false 74 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 75 | 76 | [package.extras] 77 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] 78 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] 79 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] 80 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] 81 | 82 | [[package]] 83 | name = "beautifulsoup4" 84 | version = "4.11.1" 85 | description = "Screen-scraping library" 86 | category = "main" 87 | optional = false 88 | python-versions = ">=3.6.0" 89 | 90 | [package.dependencies] 91 | soupsieve = ">1.2" 92 | 93 | [package.extras] 94 | html5lib = ["html5lib"] 95 | lxml = ["lxml"] 96 | 97 | [[package]] 98 | name = "certifi" 99 | version = "2022.5.18.1" 100 | description = "Python package for providing Mozilla's CA Bundle." 101 | category = "main" 102 | optional = false 103 | python-versions = ">=3.6" 104 | 105 | [[package]] 106 | name = "charset-normalizer" 107 | version = "2.0.12" 108 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 109 | category = "main" 110 | optional = false 111 | python-versions = ">=3.5.0" 112 | 113 | [package.extras] 114 | unicode_backport = ["unicodedata2"] 115 | 116 | [[package]] 117 | name = "click" 118 | version = "8.1.3" 119 | description = "Composable command line interface toolkit" 120 | category = "main" 121 | optional = false 122 | python-versions = ">=3.7" 123 | 124 | [package.dependencies] 125 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 126 | 127 | [[package]] 128 | name = "colorama" 129 | version = "0.4.4" 130 | description = "Cross-platform colored terminal text." 131 | category = "main" 132 | optional = false 133 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 134 | 135 | [[package]] 136 | name = "dynaconf" 137 | version = "3.1.8" 138 | description = "The dynamic configurator for your Python Project" 139 | category = "main" 140 | optional = false 141 | python-versions = ">=3.7" 142 | 143 | [package.extras] 144 | all = ["redis", "ruamel.yaml", "configobj", "hvac"] 145 | configobj = ["configobj"] 146 | ini = ["configobj"] 147 | redis = ["redis"] 148 | test = ["pytest", "pytest-cov", "pytest-xdist", "pytest-mock", "flake8", "pep8-naming", "flake8-debugger", "flake8-print", "flake8-todo", "radon", "flask (>=0.12)", "django", "python-dotenv", "toml", "codecov", "redis", "hvac", "configobj"] 149 | toml = ["toml"] 150 | vault = ["hvac"] 151 | yaml = ["ruamel.yaml"] 152 | 153 | [[package]] 154 | name = "fastapi" 155 | version = "0.78.0" 156 | description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" 157 | category = "dev" 158 | optional = false 159 | python-versions = ">=3.6.1" 160 | 161 | [package.dependencies] 162 | 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" 163 | starlette = "0.19.1" 164 | 165 | [package.extras] 166 | 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)"] 167 | 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)"] 168 | 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)"] 169 | 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)"] 170 | 171 | [[package]] 172 | name = "frozenlist" 173 | version = "1.3.0" 174 | description = "A list-like structure which implements collections.abc.MutableSequence" 175 | category = "main" 176 | optional = false 177 | python-versions = ">=3.7" 178 | 179 | [[package]] 180 | name = "idna" 181 | version = "3.3" 182 | description = "Internationalized Domain Names in Applications (IDNA)" 183 | category = "main" 184 | optional = false 185 | python-versions = ">=3.5" 186 | 187 | [[package]] 188 | name = "multidict" 189 | version = "6.0.2" 190 | description = "multidict implementation" 191 | category = "main" 192 | optional = false 193 | python-versions = ">=3.7" 194 | 195 | [[package]] 196 | name = "pydantic" 197 | version = "1.9.1" 198 | description = "Data validation and settings management using python type hints" 199 | category = "dev" 200 | optional = false 201 | python-versions = ">=3.6.1" 202 | 203 | [package.dependencies] 204 | typing-extensions = ">=3.7.4.3" 205 | 206 | [package.extras] 207 | dotenv = ["python-dotenv (>=0.10.4)"] 208 | email = ["email-validator (>=1.0.3)"] 209 | 210 | [[package]] 211 | name = "sentry-sdk" 212 | version = "1.5.12" 213 | description = "Python client for Sentry (https://sentry.io)" 214 | category = "main" 215 | optional = false 216 | python-versions = "*" 217 | 218 | [package.dependencies] 219 | certifi = "*" 220 | urllib3 = ">=1.10.0" 221 | 222 | [package.extras] 223 | aiohttp = ["aiohttp (>=3.5)"] 224 | beam = ["apache-beam (>=2.12)"] 225 | bottle = ["bottle (>=0.12.13)"] 226 | celery = ["celery (>=3)"] 227 | chalice = ["chalice (>=1.16.0)"] 228 | django = ["django (>=1.8)"] 229 | falcon = ["falcon (>=1.4)"] 230 | flask = ["flask (>=0.11)", "blinker (>=1.1)"] 231 | httpx = ["httpx (>=0.16.0)"] 232 | pure_eval = ["pure-eval", "executing", "asttokens"] 233 | pyspark = ["pyspark (>=2.4.4)"] 234 | quart = ["quart (>=0.16.1)", "blinker (>=1.1)"] 235 | rq = ["rq (>=0.6)"] 236 | sanic = ["sanic (>=0.8)"] 237 | sqlalchemy = ["sqlalchemy (>=1.2)"] 238 | tornado = ["tornado (>=5)"] 239 | 240 | [[package]] 241 | name = "shellingham" 242 | version = "1.4.0" 243 | description = "Tool to Detect Surrounding Shell" 244 | category = "main" 245 | optional = false 246 | python-versions = "!=3.0,!=3.1,!=3.2,!=3.3,>=2.6" 247 | 248 | [[package]] 249 | name = "sniffio" 250 | version = "1.2.0" 251 | description = "Sniff out which async library your code is running under" 252 | category = "dev" 253 | optional = false 254 | python-versions = ">=3.5" 255 | 256 | [[package]] 257 | name = "soupsieve" 258 | version = "2.3.2.post1" 259 | description = "A modern CSS selector implementation for Beautiful Soup." 260 | category = "main" 261 | optional = false 262 | python-versions = ">=3.6" 263 | 264 | [[package]] 265 | name = "starlette" 266 | version = "0.19.1" 267 | description = "The little ASGI library that shines." 268 | category = "dev" 269 | optional = false 270 | python-versions = ">=3.6" 271 | 272 | [package.dependencies] 273 | anyio = ">=3.4.0,<5" 274 | typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} 275 | 276 | [package.extras] 277 | full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"] 278 | 279 | [[package]] 280 | name = "typer" 281 | version = "0.4.1" 282 | description = "Typer, build great CLIs. Easy to code. Based on Python type hints." 283 | category = "main" 284 | optional = false 285 | python-versions = ">=3.6" 286 | 287 | [package.dependencies] 288 | click = ">=7.1.1,<9.0.0" 289 | 290 | [package.extras] 291 | all = ["colorama (>=0.4.3,<0.5.0)", "shellingham (>=1.3.0,<2.0.0)"] 292 | dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)"] 293 | doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "mdx-include (>=1.4.1,<2.0.0)"] 294 | test = ["shellingham (>=1.3.0,<2.0.0)", "pytest (>=4.4.0,<5.4.0)", "pytest-cov (>=2.10.0,<3.0.0)", "coverage (>=5.2,<6.0)", "pytest-xdist (>=1.32.0,<2.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "mypy (==0.910)", "black (>=22.3.0,<23.0.0)", "isort (>=5.0.6,<6.0.0)"] 295 | 296 | [[package]] 297 | name = "typing-extensions" 298 | version = "4.2.0" 299 | description = "Backported and Experimental Type Hints for Python 3.7+" 300 | category = "dev" 301 | optional = false 302 | python-versions = ">=3.7" 303 | 304 | [[package]] 305 | name = "urllib3" 306 | version = "1.26.9" 307 | description = "HTTP library with thread-safe connection pooling, file post, and more." 308 | category = "main" 309 | optional = false 310 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 311 | 312 | [package.extras] 313 | brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] 314 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] 315 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 316 | 317 | [[package]] 318 | name = "yarl" 319 | version = "1.7.2" 320 | description = "Yet another URL library" 321 | category = "main" 322 | optional = false 323 | python-versions = ">=3.6" 324 | 325 | [package.dependencies] 326 | idna = ">=2.0" 327 | multidict = ">=4.0" 328 | 329 | [metadata] 330 | lock-version = "1.1" 331 | python-versions = "^3.8" 332 | content-hash = "1a4b67e2b3361c9da1aba0be48daa727443cdf7412e1ebe31cfc560a8489bb42" 333 | 334 | [metadata.files] 335 | aiohttp = [ 336 | {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8"}, 337 | {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8"}, 338 | {file = "aiohttp-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a79004bb58748f31ae1cbe9fa891054baaa46fb106c2dc7af9f8e3304dc30316"}, 339 | {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12de6add4038df8f72fac606dff775791a60f113a725c960f2bab01d8b8e6b15"}, 340 | {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f0d5f33feb5f69ddd57a4a4bd3d56c719a141080b445cbf18f238973c5c9923"}, 341 | {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eaba923151d9deea315be1f3e2b31cc39a6d1d2f682f942905951f4e40200922"}, 342 | {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:099ebd2c37ac74cce10a3527d2b49af80243e2a4fa39e7bce41617fbc35fa3c1"}, 343 | {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"}, 344 | {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fa0ffcace9b3aa34d205d8130f7873fcfefcb6a4dd3dd705b0dab69af6712642"}, 345 | {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61bfc23df345d8c9716d03717c2ed5e27374e0fe6f659ea64edcd27b4b044cf7"}, 346 | {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:31560d268ff62143e92423ef183680b9829b1b482c011713ae941997921eebc8"}, 347 | {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:01d7bdb774a9acc838e6b8f1d114f45303841b89b95984cbb7d80ea41172a9e3"}, 348 | {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97ef77eb6b044134c0b3a96e16abcb05ecce892965a2124c566af0fd60f717e2"}, 349 | {file = "aiohttp-3.8.1-cp310-cp310-win32.whl", hash = "sha256:c2aef4703f1f2ddc6df17519885dbfa3514929149d3ff900b73f45998f2532fa"}, 350 | {file = "aiohttp-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:713ac174a629d39b7c6a3aa757b337599798da4c1157114a314e4e391cd28e32"}, 351 | {file = "aiohttp-3.8.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:473d93d4450880fe278696549f2e7aed8cd23708c3c1997981464475f32137db"}, 352 | {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b5eeae8e019e7aad8af8bb314fb908dd2e028b3cdaad87ec05095394cce632"}, 353 | {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af642b43ce56c24d063325dd2cf20ee012d2b9ba4c3c008755a301aaea720ad"}, 354 | {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3630c3ef435c0a7c549ba170a0633a56e92629aeed0e707fec832dee313fb7a"}, 355 | {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4a4a4e30bf1edcad13fb0804300557aedd07a92cabc74382fdd0ba6ca2661091"}, 356 | {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"}, 357 | {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a25fa703a527158aaf10dafd956f7d42ac6d30ec80e9a70846253dd13e2f067b"}, 358 | {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5bfde62d1d2641a1f5173b8c8c2d96ceb4854f54a44c23102e2ccc7e02f003ec"}, 359 | {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:51467000f3647d519272392f484126aa716f747859794ac9924a7aafa86cd411"}, 360 | {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:03a6d5349c9ee8f79ab3ff3694d6ce1cfc3ced1c9d36200cb8f08ba06bd3b782"}, 361 | {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:102e487eeb82afac440581e5d7f8f44560b36cf0bdd11abc51a46c1cd88914d4"}, 362 | {file = "aiohttp-3.8.1-cp36-cp36m-win32.whl", hash = "sha256:4aed991a28ea3ce320dc8ce655875e1e00a11bdd29fe9444dd4f88c30d558602"}, 363 | {file = "aiohttp-3.8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b0e20cddbd676ab8a64c774fefa0ad787cc506afd844de95da56060348021e96"}, 364 | {file = "aiohttp-3.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:37951ad2f4a6df6506750a23f7cbabad24c73c65f23f72e95897bb2cecbae676"}, 365 | {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c23b1ad869653bc818e972b7a3a79852d0e494e9ab7e1a701a3decc49c20d51"}, 366 | {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15b09b06dae900777833fe7fc4b4aa426556ce95847a3e8d7548e2d19e34edb8"}, 367 | {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:477c3ea0ba410b2b56b7efb072c36fa91b1e6fc331761798fa3f28bb224830dd"}, 368 | {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2f2f69dca064926e79997f45b2f34e202b320fd3782f17a91941f7eb85502ee2"}, 369 | {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"}, 370 | {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6d69f36d445c45cda7b3b26afef2fc34ef5ac0cdc75584a87ef307ee3c8c6d00"}, 371 | {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:55c3d1072704d27401c92339144d199d9de7b52627f724a949fc7d5fc56d8b93"}, 372 | {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b9d00268fcb9f66fbcc7cd9fe423741d90c75ee029a1d15c09b22d23253c0a44"}, 373 | {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:07b05cd3305e8a73112103c834e91cd27ce5b4bd07850c4b4dbd1877d3f45be7"}, 374 | {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c34dc4958b232ef6188c4318cb7b2c2d80521c9a56c52449f8f93ab7bc2a8a1c"}, 375 | {file = "aiohttp-3.8.1-cp37-cp37m-win32.whl", hash = "sha256:d2f9b69293c33aaa53d923032fe227feac867f81682f002ce33ffae978f0a9a9"}, 376 | {file = "aiohttp-3.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6ae828d3a003f03ae31915c31fa684b9890ea44c9c989056fea96e3d12a9fa17"}, 377 | {file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0c7ebbbde809ff4e970824b2b6cb7e4222be6b95a296e46c03cf050878fc1785"}, 378 | {file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b7ef7cbd4fec9a1e811a5de813311ed4f7ac7d93e0fda233c9b3e1428f7dd7b"}, 379 | {file = "aiohttp-3.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c3d6a4d0619e09dcd61021debf7059955c2004fa29f48788a3dfaf9c9901a7cd"}, 380 | {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:718626a174e7e467f0558954f94af117b7d4695d48eb980146016afa4b580b2e"}, 381 | {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:589c72667a5febd36f1315aa6e5f56dd4aa4862df295cb51c769d16142ddd7cd"}, 382 | {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ed076098b171573161eb146afcb9129b5ff63308960aeca4b676d9d3c35e700"}, 383 | {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:086f92daf51a032d062ec5f58af5ca6a44d082c35299c96376a41cbb33034675"}, 384 | {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"}, 385 | {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:31d1e1c0dbf19ebccbfd62eff461518dcb1e307b195e93bba60c965a4dcf1ba0"}, 386 | {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:11a67c0d562e07067c4e86bffc1553f2cf5b664d6111c894671b2b8712f3aba5"}, 387 | {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:bb01ba6b0d3f6c68b89fce7305080145d4877ad3acaed424bae4d4ee75faa950"}, 388 | {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:44db35a9e15d6fe5c40d74952e803b1d96e964f683b5a78c3cc64eb177878155"}, 389 | {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:844a9b460871ee0a0b0b68a64890dae9c415e513db0f4a7e3cab41a0f2fedf33"}, 390 | {file = "aiohttp-3.8.1-cp38-cp38-win32.whl", hash = "sha256:7d08744e9bae2ca9c382581f7dce1273fe3c9bae94ff572c3626e8da5b193c6a"}, 391 | {file = "aiohttp-3.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:04d48b8ce6ab3cf2097b1855e1505181bdd05586ca275f2505514a6e274e8e75"}, 392 | {file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5315a2eb0239185af1bddb1abf472d877fede3cc8d143c6cddad37678293237"}, 393 | {file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a996d01ca39b8dfe77440f3cd600825d05841088fd6bc0144cc6c2ec14cc5f74"}, 394 | {file = "aiohttp-3.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:13487abd2f761d4be7c8ff9080de2671e53fff69711d46de703c310c4c9317ca"}, 395 | {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea302f34477fda3f85560a06d9ebdc7fa41e82420e892fc50b577e35fc6a50b2"}, 396 | {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2f635ce61a89c5732537a7896b6319a8fcfa23ba09bec36e1b1ac0ab31270d2"}, 397 | {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e999f2d0e12eea01caeecb17b653f3713d758f6dcc770417cf29ef08d3931421"}, 398 | {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0770e2806a30e744b4e21c9d73b7bee18a1cfa3c47991ee2e5a65b887c49d5cf"}, 399 | {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"}, 400 | {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c7cefb4b0640703eb1069835c02486669312bf2f12b48a748e0a7756d0de33d"}, 401 | {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:71927042ed6365a09a98a6377501af5c9f0a4d38083652bcd2281a06a5976724"}, 402 | {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:28d490af82bc6b7ce53ff31337a18a10498303fe66f701ab65ef27e143c3b0ef"}, 403 | {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b6613280ccedf24354406caf785db748bebbddcf31408b20c0b48cb86af76866"}, 404 | {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81e3d8c34c623ca4e36c46524a3530e99c0bc95ed068fd6e9b55cb721d408fb2"}, 405 | {file = "aiohttp-3.8.1-cp39-cp39-win32.whl", hash = "sha256:7187a76598bdb895af0adbd2fb7474d7f6025d170bc0a1130242da817ce9e7d1"}, 406 | {file = "aiohttp-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac"}, 407 | {file = "aiohttp-3.8.1.tar.gz", hash = "sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578"}, 408 | ] 409 | aiorun = [ 410 | {file = "aiorun-2022.4.1-py3-none-any.whl", hash = "sha256:3a7925d66c394f6812a0150495fda8d359b4cdf24cf0c223f005b61ad02baaa7"}, 411 | {file = "aiorun-2022.4.1.tar.gz", hash = "sha256:6aafa65c4004c1697088d8f09a9a68e8d486a5403f2e35956f5f0173ce7501f5"}, 412 | ] 413 | aiosignal = [ 414 | {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, 415 | {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"}, 416 | ] 417 | anyio = [ 418 | {file = "anyio-3.6.1-py3-none-any.whl", hash = "sha256:cb29b9c70620506a9a8f87a309591713446953302d7d995344d0d7c6c0c9a7be"}, 419 | {file = "anyio-3.6.1.tar.gz", hash = "sha256:413adf95f93886e442aea925f3ee43baa5a765a64a0f52c6081894f9992fdd0b"}, 420 | ] 421 | async-timeout = [ 422 | {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, 423 | {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, 424 | ] 425 | attrs = [ 426 | {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, 427 | {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, 428 | ] 429 | beautifulsoup4 = [ 430 | {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, 431 | {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, 432 | ] 433 | certifi = [ 434 | {file = "certifi-2022.5.18.1-py3-none-any.whl", hash = "sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a"}, 435 | {file = "certifi-2022.5.18.1.tar.gz", hash = "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7"}, 436 | ] 437 | charset-normalizer = [ 438 | {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, 439 | {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, 440 | ] 441 | click = [ 442 | {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, 443 | {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, 444 | ] 445 | colorama = [ 446 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 447 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 448 | ] 449 | dynaconf = [ 450 | {file = "dynaconf-3.1.8-py2.py3-none-any.whl", hash = "sha256:dea41800cf4eef488f49d3b5d1ff6305b85c3c21538f4dcfc39ab34d29606d28"}, 451 | {file = "dynaconf-3.1.8.tar.gz", hash = "sha256:d141a6664fca3648d2d8e84440966af9f58c4f4201ca78353a3f595a67c19ab4"}, 452 | ] 453 | fastapi = [ 454 | {file = "fastapi-0.78.0-py3-none-any.whl", hash = "sha256:15fcabd5c78c266fa7ae7d8de9b384bfc2375ee0503463a6febbe3bab69d6f65"}, 455 | {file = "fastapi-0.78.0.tar.gz", hash = "sha256:3233d4a789ba018578658e2af1a4bb5e38bdd122ff722b313666a9b2c6786a83"}, 456 | ] 457 | frozenlist = [ 458 | {file = "frozenlist-1.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2257aaba9660f78c7b1d8fea963b68f3feffb1a9d5d05a18401ca9eb3e8d0a3"}, 459 | {file = "frozenlist-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4a44ebbf601d7bac77976d429e9bdb5a4614f9f4027777f9e54fd765196e9d3b"}, 460 | {file = "frozenlist-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:45334234ec30fc4ea677f43171b18a27505bfb2dba9aca4398a62692c0ea8868"}, 461 | {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47be22dc27ed933d55ee55845d34a3e4e9f6fee93039e7f8ebadb0c2f60d403f"}, 462 | {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03a7dd1bfce30216a3f51a84e6dd0e4a573d23ca50f0346634916ff105ba6e6b"}, 463 | {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:691ddf6dc50480ce49f68441f1d16a4c3325887453837036e0fb94736eae1e58"}, 464 | {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bde99812f237f79eaf3f04ebffd74f6718bbd216101b35ac7955c2d47c17da02"}, 465 | {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a202458d1298ced3768f5a7d44301e7c86defac162ace0ab7434c2e961166e8"}, 466 | {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9e3e9e365991f8cc5f5edc1fd65b58b41d0514a6a7ad95ef5c7f34eb49b3d3e"}, 467 | {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:04cb491c4b1c051734d41ea2552fde292f5f3a9c911363f74f39c23659c4af78"}, 468 | {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:436496321dad302b8b27ca955364a439ed1f0999311c393dccb243e451ff66aa"}, 469 | {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:754728d65f1acc61e0f4df784456106e35afb7bf39cfe37227ab00436fb38676"}, 470 | {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6eb275c6385dd72594758cbe96c07cdb9bd6becf84235f4a594bdf21e3596c9d"}, 471 | {file = "frozenlist-1.3.0-cp310-cp310-win32.whl", hash = "sha256:e30b2f9683812eb30cf3f0a8e9f79f8d590a7999f731cf39f9105a7c4a39489d"}, 472 | {file = "frozenlist-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f7353ba3367473d1d616ee727945f439e027f0bb16ac1a750219a8344d1d5d3c"}, 473 | {file = "frozenlist-1.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88aafd445a233dbbf8a65a62bc3249a0acd0d81ab18f6feb461cc5a938610d24"}, 474 | {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4406cfabef8f07b3b3af0f50f70938ec06d9f0fc26cbdeaab431cbc3ca3caeaa"}, 475 | {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8cf829bd2e2956066dd4de43fd8ec881d87842a06708c035b37ef632930505a2"}, 476 | {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:603b9091bd70fae7be28bdb8aa5c9990f4241aa33abb673390a7f7329296695f"}, 477 | {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25af28b560e0c76fa41f550eacb389905633e7ac02d6eb3c09017fa1c8cdfde1"}, 478 | {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c7a8a9fc9383b52c410a2ec952521906d355d18fccc927fca52ab575ee8b93"}, 479 | {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:65bc6e2fece04e2145ab6e3c47428d1bbc05aede61ae365b2c1bddd94906e478"}, 480 | {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3f7c935c7b58b0d78c0beea0c7358e165f95f1fd8a7e98baa40d22a05b4a8141"}, 481 | {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd89acd1b8bb4f31b47072615d72e7f53a948d302b7c1d1455e42622de180eae"}, 482 | {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:6983a31698490825171be44ffbafeaa930ddf590d3f051e397143a5045513b01"}, 483 | {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:adac9700675cf99e3615eb6a0eb5e9f5a4143c7d42c05cea2e7f71c27a3d0846"}, 484 | {file = "frozenlist-1.3.0-cp37-cp37m-win32.whl", hash = "sha256:0c36e78b9509e97042ef869c0e1e6ef6429e55817c12d78245eb915e1cca7468"}, 485 | {file = "frozenlist-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:57f4d3f03a18facacb2a6bcd21bccd011e3b75d463dc49f838fd699d074fabd1"}, 486 | {file = "frozenlist-1.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8c905a5186d77111f02144fab5b849ab524f1e876a1e75205cd1386a9be4b00a"}, 487 | {file = "frozenlist-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b5009062d78a8c6890d50b4e53b0ddda31841b3935c1937e2ed8c1bda1c7fb9d"}, 488 | {file = "frozenlist-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2fdc3cd845e5a1f71a0c3518528bfdbfe2efaf9886d6f49eacc5ee4fd9a10953"}, 489 | {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92e650bd09b5dda929523b9f8e7f99b24deac61240ecc1a32aeba487afcd970f"}, 490 | {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:40dff8962b8eba91fd3848d857203f0bd704b5f1fa2b3fc9af64901a190bba08"}, 491 | {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:768efd082074bb203c934e83a61654ed4931ef02412c2fbdecea0cff7ecd0274"}, 492 | {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:006d3595e7d4108a12025ddf415ae0f6c9e736e726a5db0183326fd191b14c5e"}, 493 | {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:871d42623ae15eb0b0e9df65baeee6976b2e161d0ba93155411d58ff27483ad8"}, 494 | {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aff388be97ef2677ae185e72dc500d19ecaf31b698986800d3fc4f399a5e30a5"}, 495 | {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9f892d6a94ec5c7b785e548e42722e6f3a52f5f32a8461e82ac3e67a3bd073f1"}, 496 | {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:e982878792c971cbd60ee510c4ee5bf089a8246226dea1f2138aa0bb67aff148"}, 497 | {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:c6c321dd013e8fc20735b92cb4892c115f5cdb82c817b1e5b07f6b95d952b2f0"}, 498 | {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:30530930410855c451bea83f7b272fb1c495ed9d5cc72895ac29e91279401db3"}, 499 | {file = "frozenlist-1.3.0-cp38-cp38-win32.whl", hash = "sha256:40ec383bc194accba825fbb7d0ef3dda5736ceab2375462f1d8672d9f6b68d07"}, 500 | {file = "frozenlist-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:f20baa05eaa2bcd5404c445ec51aed1c268d62600362dc6cfe04fae34a424bd9"}, 501 | {file = "frozenlist-1.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0437fe763fb5d4adad1756050cbf855bbb2bf0d9385c7bb13d7a10b0dd550486"}, 502 | {file = "frozenlist-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b684c68077b84522b5c7eafc1dc735bfa5b341fb011d5552ebe0968e22ed641c"}, 503 | {file = "frozenlist-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93641a51f89473837333b2f8100f3f89795295b858cd4c7d4a1f18e299dc0a4f"}, 504 | {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6d32ff213aef0fd0bcf803bffe15cfa2d4fde237d1d4838e62aec242a8362fa"}, 505 | {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31977f84828b5bb856ca1eb07bf7e3a34f33a5cddce981d880240ba06639b94d"}, 506 | {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c62964192a1c0c30b49f403495911298810bada64e4f03249ca35a33ca0417a"}, 507 | {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4eda49bea3602812518765810af732229b4291d2695ed24a0a20e098c45a707b"}, 508 | {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acb267b09a509c1df5a4ca04140da96016f40d2ed183cdc356d237286c971b51"}, 509 | {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e1e26ac0a253a2907d654a37e390904426d5ae5483150ce3adedb35c8c06614a"}, 510 | {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f96293d6f982c58ebebb428c50163d010c2f05de0cde99fd681bfdc18d4b2dc2"}, 511 | {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e84cb61b0ac40a0c3e0e8b79c575161c5300d1d89e13c0e02f76193982f066ed"}, 512 | {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:ff9310f05b9d9c5c4dd472983dc956901ee6cb2c3ec1ab116ecdde25f3ce4951"}, 513 | {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d26b650b71fdc88065b7a21f8ace70175bcf3b5bdba5ea22df4bfd893e795a3b"}, 514 | {file = "frozenlist-1.3.0-cp39-cp39-win32.whl", hash = "sha256:01a73627448b1f2145bddb6e6c2259988bb8aee0fb361776ff8604b99616cd08"}, 515 | {file = "frozenlist-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:772965f773757a6026dea111a15e6e2678fbd6216180f82a48a40b27de1ee2ab"}, 516 | {file = "frozenlist-1.3.0.tar.gz", hash = "sha256:ce6f2ba0edb7b0c1d8976565298ad2deba6f8064d2bebb6ffce2ca896eb35b0b"}, 517 | ] 518 | idna = [ 519 | {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, 520 | {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, 521 | ] 522 | multidict = [ 523 | {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"}, 524 | {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"}, 525 | {file = "multidict-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c"}, 526 | {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f"}, 527 | {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9"}, 528 | {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20"}, 529 | {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88"}, 530 | {file = "multidict-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7"}, 531 | {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee"}, 532 | {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672"}, 533 | {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9"}, 534 | {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87"}, 535 | {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389"}, 536 | {file = "multidict-6.0.2-cp310-cp310-win32.whl", hash = "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293"}, 537 | {file = "multidict-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658"}, 538 | {file = "multidict-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51"}, 539 | {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608"}, 540 | {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3"}, 541 | {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4"}, 542 | {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b"}, 543 | {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8"}, 544 | {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba"}, 545 | {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43"}, 546 | {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8"}, 547 | {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b"}, 548 | {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15"}, 549 | {file = "multidict-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc"}, 550 | {file = "multidict-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a"}, 551 | {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60"}, 552 | {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86"}, 553 | {file = "multidict-6.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d"}, 554 | {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0"}, 555 | {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d"}, 556 | {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376"}, 557 | {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693"}, 558 | {file = "multidict-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849"}, 559 | {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49"}, 560 | {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516"}, 561 | {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227"}, 562 | {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9"}, 563 | {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d"}, 564 | {file = "multidict-6.0.2-cp38-cp38-win32.whl", hash = "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57"}, 565 | {file = "multidict-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96"}, 566 | {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c"}, 567 | {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e"}, 568 | {file = "multidict-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071"}, 569 | {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032"}, 570 | {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2"}, 571 | {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c"}, 572 | {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9"}, 573 | {file = "multidict-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80"}, 574 | {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d"}, 575 | {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb"}, 576 | {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68"}, 577 | {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360"}, 578 | {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937"}, 579 | {file = "multidict-6.0.2-cp39-cp39-win32.whl", hash = "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a"}, 580 | {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, 581 | {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, 582 | ] 583 | pydantic = [ 584 | {file = "pydantic-1.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8098a724c2784bf03e8070993f6d46aa2eeca031f8d8a048dff277703e6e193"}, 585 | {file = "pydantic-1.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c320c64dd876e45254bdd350f0179da737463eea41c43bacbee9d8c9d1021f11"}, 586 | {file = "pydantic-1.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18f3e912f9ad1bdec27fb06b8198a2ccc32f201e24174cec1b3424dda605a310"}, 587 | {file = "pydantic-1.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11951b404e08b01b151222a1cb1a9f0a860a8153ce8334149ab9199cd198131"}, 588 | {file = "pydantic-1.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8bc541a405423ce0e51c19f637050acdbdf8feca34150e0d17f675e72d119580"}, 589 | {file = "pydantic-1.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e565a785233c2d03724c4dc55464559639b1ba9ecf091288dd47ad9c629433bd"}, 590 | {file = "pydantic-1.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a4a88dcd6ff8fd47c18b3a3709a89adb39a6373f4482e04c1b765045c7e282fd"}, 591 | {file = "pydantic-1.9.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:447d5521575f18e18240906beadc58551e97ec98142266e521c34968c76c8761"}, 592 | {file = "pydantic-1.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:985ceb5d0a86fcaa61e45781e567a59baa0da292d5ed2e490d612d0de5796918"}, 593 | {file = "pydantic-1.9.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059b6c1795170809103a1538255883e1983e5b831faea6558ef873d4955b4a74"}, 594 | {file = "pydantic-1.9.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d12f96b5b64bec3f43c8e82b4aab7599d0157f11c798c9f9c528a72b9e0b339a"}, 595 | {file = "pydantic-1.9.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ae72f8098acb368d877b210ebe02ba12585e77bd0db78ac04a1ee9b9f5dd2166"}, 596 | {file = "pydantic-1.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:79b485767c13788ee314669008d01f9ef3bc05db9ea3298f6a50d3ef596a154b"}, 597 | {file = "pydantic-1.9.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:494f7c8537f0c02b740c229af4cb47c0d39840b829ecdcfc93d91dcbb0779892"}, 598 | {file = "pydantic-1.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0f047e11febe5c3198ed346b507e1d010330d56ad615a7e0a89fae604065a0e"}, 599 | {file = "pydantic-1.9.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:969dd06110cb780da01336b281f53e2e7eb3a482831df441fb65dd30403f4608"}, 600 | {file = "pydantic-1.9.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:177071dfc0df6248fd22b43036f936cfe2508077a72af0933d0c1fa269b18537"}, 601 | {file = "pydantic-1.9.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9bcf8b6e011be08fb729d110f3e22e654a50f8a826b0575c7196616780683380"}, 602 | {file = "pydantic-1.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a955260d47f03df08acf45689bd163ed9df82c0e0124beb4251b1290fa7ae728"}, 603 | {file = "pydantic-1.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9ce157d979f742a915b75f792dbd6aa63b8eccaf46a1005ba03aa8a986bde34a"}, 604 | {file = "pydantic-1.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0bf07cab5b279859c253d26a9194a8906e6f4a210063b84b433cf90a569de0c1"}, 605 | {file = "pydantic-1.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d93d4e95eacd313d2c765ebe40d49ca9dd2ed90e5b37d0d421c597af830c195"}, 606 | {file = "pydantic-1.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1542636a39c4892c4f4fa6270696902acb186a9aaeac6f6cf92ce6ae2e88564b"}, 607 | {file = "pydantic-1.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a9af62e9b5b9bc67b2a195ebc2c2662fdf498a822d62f902bf27cccb52dbbf49"}, 608 | {file = "pydantic-1.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fe4670cb32ea98ffbf5a1262f14c3e102cccd92b1869df3bb09538158ba90fe6"}, 609 | {file = "pydantic-1.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:9f659a5ee95c8baa2436d392267988fd0f43eb774e5eb8739252e5a7e9cf07e0"}, 610 | {file = "pydantic-1.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b83ba3825bc91dfa989d4eed76865e71aea3a6ca1388b59fc801ee04c4d8d0d6"}, 611 | {file = "pydantic-1.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1dd8fecbad028cd89d04a46688d2fcc14423e8a196d5b0a5c65105664901f810"}, 612 | {file = "pydantic-1.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02eefd7087268b711a3ff4db528e9916ac9aa18616da7bca69c1871d0b7a091f"}, 613 | {file = "pydantic-1.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7eb57ba90929bac0b6cc2af2373893d80ac559adda6933e562dcfb375029acee"}, 614 | {file = "pydantic-1.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4ce9ae9e91f46c344bec3b03d6ee9612802682c1551aaf627ad24045ce090761"}, 615 | {file = "pydantic-1.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:72ccb318bf0c9ab97fc04c10c37683d9eea952ed526707fabf9ac5ae59b701fd"}, 616 | {file = "pydantic-1.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:61b6760b08b7c395975d893e0b814a11cf011ebb24f7d869e7118f5a339a82e1"}, 617 | {file = "pydantic-1.9.1-py3-none-any.whl", hash = "sha256:4988c0f13c42bfa9ddd2fe2f569c9d54646ce84adc5de84228cfe83396f3bd58"}, 618 | {file = "pydantic-1.9.1.tar.gz", hash = "sha256:1ed987c3ff29fff7fd8c3ea3a3ea877ad310aae2ef9889a119e22d3f2db0691a"}, 619 | ] 620 | sentry-sdk = [ 621 | {file = "sentry-sdk-1.5.12.tar.gz", hash = "sha256:259535ba66933eacf85ab46524188c84dcb4c39f40348455ce15e2c0aca68863"}, 622 | {file = "sentry_sdk-1.5.12-py2.py3-none-any.whl", hash = "sha256:778b53f0a6c83b1ee43d3b7886318ba86d975e686cb2c7906ccc35b334360be1"}, 623 | ] 624 | shellingham = [ 625 | {file = "shellingham-1.4.0-py2.py3-none-any.whl", hash = "sha256:536b67a0697f2e4af32ab176c00a50ac2899c5a05e0d8e2dadac8e58888283f9"}, 626 | {file = "shellingham-1.4.0.tar.gz", hash = "sha256:4855c2458d6904829bd34c299f11fdeed7cfefbf8a2c522e4caea6cd76b3171e"}, 627 | ] 628 | sniffio = [ 629 | {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"}, 630 | {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"}, 631 | ] 632 | soupsieve = [ 633 | {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, 634 | {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, 635 | ] 636 | starlette = [ 637 | {file = "starlette-0.19.1-py3-none-any.whl", hash = "sha256:5a60c5c2d051f3a8eb546136aa0c9399773a689595e099e0877704d5888279bf"}, 638 | {file = "starlette-0.19.1.tar.gz", hash = "sha256:c6d21096774ecb9639acad41b86b7706e52ba3bf1dc13ea4ed9ad593d47e24c7"}, 639 | ] 640 | typer = [ 641 | {file = "typer-0.4.1-py3-none-any.whl", hash = "sha256:e8467f0ebac0c81366c2168d6ad9f888efdfb6d4e1d3d5b4a004f46fa444b5c3"}, 642 | {file = "typer-0.4.1.tar.gz", hash = "sha256:5646aef0d936b2c761a10393f0384ee6b5c7fe0bb3e5cd710b17134ca1d99cff"}, 643 | ] 644 | typing-extensions = [ 645 | {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, 646 | {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, 647 | ] 648 | urllib3 = [ 649 | {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, 650 | {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, 651 | ] 652 | yarl = [ 653 | {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95"}, 654 | {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da6df107b9ccfe52d3a48165e48d72db0eca3e3029b5b8cb4fe6ee3cb870ba8b"}, 655 | {file = "yarl-1.7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1d0894f238763717bdcfea74558c94e3bc34aeacd3351d769460c1a586a8b05"}, 656 | {file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4b95b7e00c6635a72e2d00b478e8a28bfb122dc76349a06e20792eb53a523"}, 657 | {file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c145ab54702334c42237a6c6c4cc08703b6aa9b94e2f227ceb3d477d20c36c63"}, 658 | {file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ca56f002eaf7998b5fcf73b2421790da9d2586331805f38acd9997743114e98"}, 659 | {file = "yarl-1.7.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1d3d5ad8ea96bd6d643d80c7b8d5977b4e2fb1bab6c9da7322616fd26203d125"}, 660 | {file = "yarl-1.7.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:167ab7f64e409e9bdd99333fe8c67b5574a1f0495dcfd905bc7454e766729b9e"}, 661 | {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:95a1873b6c0dd1c437fb3bb4a4aaa699a48c218ac7ca1e74b0bee0ab16c7d60d"}, 662 | {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6152224d0a1eb254f97df3997d79dadd8bb2c1a02ef283dbb34b97d4f8492d23"}, 663 | {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:5bb7d54b8f61ba6eee541fba4b83d22b8a046b4ef4d8eb7f15a7e35db2e1e245"}, 664 | {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:9c1f083e7e71b2dd01f7cd7434a5f88c15213194df38bc29b388ccdf1492b739"}, 665 | {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f44477ae29025d8ea87ec308539f95963ffdc31a82f42ca9deecf2d505242e72"}, 666 | {file = "yarl-1.7.2-cp310-cp310-win32.whl", hash = "sha256:cff3ba513db55cc6a35076f32c4cdc27032bd075c9faef31fec749e64b45d26c"}, 667 | {file = "yarl-1.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:c9c6d927e098c2d360695f2e9d38870b2e92e0919be07dbe339aefa32a090265"}, 668 | {file = "yarl-1.7.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9b4c77d92d56a4c5027572752aa35082e40c561eec776048330d2907aead891d"}, 669 | {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c01a89a44bb672c38f42b49cdb0ad667b116d731b3f4c896f72302ff77d71656"}, 670 | {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c19324a1c5399b602f3b6e7db9478e5b1adf5cf58901996fc973fe4fccd73eed"}, 671 | {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3abddf0b8e41445426d29f955b24aeecc83fa1072be1be4e0d194134a7d9baee"}, 672 | {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6a1a9fe17621af43e9b9fcea8bd088ba682c8192d744b386ee3c47b56eaabb2c"}, 673 | {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8b0915ee85150963a9504c10de4e4729ae700af11df0dc5550e6587ed7891e92"}, 674 | {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:29e0656d5497733dcddc21797da5a2ab990c0cb9719f1f969e58a4abac66234d"}, 675 | {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:bf19725fec28452474d9887a128e98dd67eee7b7d52e932e6949c532d820dc3b"}, 676 | {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:d6f3d62e16c10e88d2168ba2d065aa374e3c538998ed04996cd373ff2036d64c"}, 677 | {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ac10bbac36cd89eac19f4e51c032ba6b412b3892b685076f4acd2de18ca990aa"}, 678 | {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aa32aaa97d8b2ed4e54dc65d241a0da1c627454950f7d7b1f95b13985afd6c5d"}, 679 | {file = "yarl-1.7.2-cp36-cp36m-win32.whl", hash = "sha256:87f6e082bce21464857ba58b569370e7b547d239ca22248be68ea5d6b51464a1"}, 680 | {file = "yarl-1.7.2-cp36-cp36m-win_amd64.whl", hash = "sha256:ac35ccde589ab6a1870a484ed136d49a26bcd06b6a1c6397b1967ca13ceb3913"}, 681 | {file = "yarl-1.7.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a467a431a0817a292121c13cbe637348b546e6ef47ca14a790aa2fa8cc93df63"}, 682 | {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ab0c3274d0a846840bf6c27d2c60ba771a12e4d7586bf550eefc2df0b56b3b4"}, 683 | {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d260d4dc495c05d6600264a197d9d6f7fc9347f21d2594926202fd08cf89a8ba"}, 684 | {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc4dd8b01a8112809e6b636b00f487846956402834a7fd59d46d4f4267181c41"}, 685 | {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c1164a2eac148d85bbdd23e07dfcc930f2e633220f3eb3c3e2a25f6148c2819e"}, 686 | {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:67e94028817defe5e705079b10a8438b8cb56e7115fa01640e9c0bb3edf67332"}, 687 | {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:89ccbf58e6a0ab89d487c92a490cb5660d06c3a47ca08872859672f9c511fc52"}, 688 | {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8cce6f9fa3df25f55521fbb5c7e4a736683148bcc0c75b21863789e5185f9185"}, 689 | {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:211fcd65c58bf250fb994b53bc45a442ddc9f441f6fec53e65de8cba48ded986"}, 690 | {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c10ea1e80a697cf7d80d1ed414b5cb8f1eec07d618f54637067ae3c0334133c4"}, 691 | {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:52690eb521d690ab041c3919666bea13ab9fbff80d615ec16fa81a297131276b"}, 692 | {file = "yarl-1.7.2-cp37-cp37m-win32.whl", hash = "sha256:695ba021a9e04418507fa930d5f0704edbce47076bdcfeeaba1c83683e5649d1"}, 693 | {file = "yarl-1.7.2-cp37-cp37m-win_amd64.whl", hash = "sha256:c17965ff3706beedafd458c452bf15bac693ecd146a60a06a214614dc097a271"}, 694 | {file = "yarl-1.7.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fce78593346c014d0d986b7ebc80d782b7f5e19843ca798ed62f8e3ba8728576"}, 695 | {file = "yarl-1.7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c2a1ac41a6aa980db03d098a5531f13985edcb451bcd9d00670b03129922cd0d"}, 696 | {file = "yarl-1.7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:39d5493c5ecd75c8093fa7700a2fb5c94fe28c839c8e40144b7ab7ccba6938c8"}, 697 | {file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1eb6480ef366d75b54c68164094a6a560c247370a68c02dddb11f20c4c6d3c9d"}, 698 | {file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ba63585a89c9885f18331a55d25fe81dc2d82b71311ff8bd378fc8004202ff6"}, 699 | {file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e39378894ee6ae9f555ae2de332d513a5763276a9265f8e7cbaeb1b1ee74623a"}, 700 | {file = "yarl-1.7.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c0910c6b6c31359d2f6184828888c983d54d09d581a4a23547a35f1d0b9484b1"}, 701 | {file = "yarl-1.7.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6feca8b6bfb9eef6ee057628e71e1734caf520a907b6ec0d62839e8293e945c0"}, 702 | {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8300401dc88cad23f5b4e4c1226f44a5aa696436a4026e456fe0e5d2f7f486e6"}, 703 | {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:788713c2896f426a4e166b11f4ec538b5736294ebf7d5f654ae445fd44270832"}, 704 | {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fd547ec596d90c8676e369dd8a581a21227fe9b4ad37d0dc7feb4ccf544c2d59"}, 705 | {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:737e401cd0c493f7e3dd4db72aca11cfe069531c9761b8ea474926936b3c57c8"}, 706 | {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baf81561f2972fb895e7844882898bda1eef4b07b5b385bcd308d2098f1a767b"}, 707 | {file = "yarl-1.7.2-cp38-cp38-win32.whl", hash = "sha256:ede3b46cdb719c794427dcce9d8beb4abe8b9aa1e97526cc20de9bd6583ad1ef"}, 708 | {file = "yarl-1.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:cc8b7a7254c0fc3187d43d6cb54b5032d2365efd1df0cd1749c0c4df5f0ad45f"}, 709 | {file = "yarl-1.7.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:580c1f15500e137a8c37053e4cbf6058944d4c114701fa59944607505c2fe3a0"}, 710 | {file = "yarl-1.7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ec1d9a0d7780416e657f1e405ba35ec1ba453a4f1511eb8b9fbab81cb8b3ce1"}, 711 | {file = "yarl-1.7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3bf8cfe8856708ede6a73907bf0501f2dc4e104085e070a41f5d88e7faf237f3"}, 712 | {file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1be4bbb3d27a4e9aa5f3df2ab61e3701ce8fcbd3e9846dbce7c033a7e8136746"}, 713 | {file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:534b047277a9a19d858cde163aba93f3e1677d5acd92f7d10ace419d478540de"}, 714 | {file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6ddcd80d79c96eb19c354d9dca95291589c5954099836b7c8d29278a7ec0bda"}, 715 | {file = "yarl-1.7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9bfcd43c65fbb339dc7086b5315750efa42a34eefad0256ba114cd8ad3896f4b"}, 716 | {file = "yarl-1.7.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f64394bd7ceef1237cc604b5a89bf748c95982a84bcd3c4bbeb40f685c810794"}, 717 | {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044daf3012e43d4b3538562da94a88fb12a6490652dbc29fb19adfa02cf72eac"}, 718 | {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:368bcf400247318382cc150aaa632582d0780b28ee6053cd80268c7e72796dec"}, 719 | {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:bab827163113177aee910adb1f48ff7af31ee0289f434f7e22d10baf624a6dfe"}, 720 | {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0cba38120db72123db7c58322fa69e3c0efa933040ffb586c3a87c063ec7cae8"}, 721 | {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:59218fef177296451b23214c91ea3aba7858b4ae3306dde120224cfe0f7a6ee8"}, 722 | {file = "yarl-1.7.2-cp39-cp39-win32.whl", hash = "sha256:1edc172dcca3f11b38a9d5c7505c83c1913c0addc99cd28e993efeaafdfaa18d"}, 723 | {file = "yarl-1.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:797c2c412b04403d2da075fb93c123df35239cd7b4cc4e0cd9e5839b73f52c58"}, 724 | {file = "yarl-1.7.2.tar.gz", hash = "sha256:45399b46d60c253327a460e99856752009fcee5f5d3c80b2f7c0cae1c38d56dd"}, 725 | ] 726 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "webvpn" 3 | version = "0.1.4" 4 | description = "TCP over WebVPN" 5 | authors = ["Ming Yang "] 6 | license = "MIT" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.8" 10 | beautifulsoup4 = "^4.11.1" 11 | aiohttp = "^3.8.1" 12 | typer = "^0.4.1" 13 | shellingham = "^1.4.0" 14 | sentry-sdk = "^1.5.12" 15 | aiorun = "^2022.4.1" 16 | dynaconf = "^3.1.8" 17 | 18 | [tool.poetry.dev-dependencies] 19 | fastapi = "^0.78.0" 20 | 21 | [tool.poetry.scripts] 22 | webvpn = "webvpn.cmd:app" 23 | 24 | [build-system] 25 | requires = ["poetry-core>=1.0.0"] 26 | build-backend = "poetry.core.masonry.api" 27 | -------------------------------------------------------------------------------- /webvpn/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivym/webvpn-py/6ba1052197108289f7e0b60c40f044cdac770ae7/webvpn/__init__.py -------------------------------------------------------------------------------- /webvpn/client.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from dataclasses import dataclass 3 | import asyncio 4 | import logging 5 | 6 | from .gateway import WebVPNGateway 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | @dataclass 12 | class Connection: 13 | reader: asyncio.StreamReader 14 | writer: asyncio.StreamWriter 15 | token: str 16 | closed: bool = False 17 | 18 | 19 | class Client: 20 | def __init__( 21 | self, 22 | *, 23 | host: str = "localhost", 24 | port: int = 23388, 25 | rhost: str = "10.251.0.23", 26 | rport: int = 22, 27 | username: str, 28 | password: str, 29 | gateway: WebVPNGateway, 30 | ): 31 | self.host = host 32 | self.port = port 33 | self.rhost = rhost 34 | self.rport = rport 35 | 36 | self.username = username 37 | self.password = password 38 | 39 | self.gateway = gateway 40 | self.tcp_server: Optional[asyncio.Server] = None 41 | 42 | async def pull(self, conn: Connection): 43 | writer, token = conn.writer, conn.token 44 | 45 | while not conn.closed: 46 | data = await self.gateway.pull(token, 1000 * 1024) 47 | if data: 48 | writer.write(data) 49 | elif data is None: 50 | conn.closed = True 51 | 52 | await writer.drain() 53 | 54 | writer.close() 55 | await writer.wait_closed() 56 | 57 | logger.debug("pull done") 58 | 59 | async def push(self, conn: Connection): 60 | reader, token = conn.reader, conn.token 61 | 62 | while not conn.closed: 63 | data = await reader.read(1000 * 1024) 64 | if not data: 65 | conn.closed = True 66 | break 67 | conn.closed = not await self.gateway.push(token, data) 68 | 69 | logger.debug("push done") 70 | 71 | async def conn_handler(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter): 72 | addr = writer.get_extra_info("peername") 73 | logger.info(f"Connection established: {addr!r}") 74 | 75 | try: 76 | token = await self.gateway.open_connection( 77 | self.rhost, self.rport, self.username, self.password 78 | ) 79 | except Exception as e: 80 | logger.error(f"Failed to open connection: {e}") 81 | writer.close() 82 | await writer.wait_closed() 83 | return 84 | 85 | conn = Connection(reader, writer, token) 86 | 87 | await asyncio.gather( 88 | self.pull(conn), 89 | self.push(conn), 90 | ) 91 | 92 | await self.gateway.close(token) 93 | 94 | logger.info(f"Connection closed: {addr!r}") 95 | 96 | async def run(self): 97 | self.tcp_server = await asyncio.start_server( 98 | self.conn_handler, self.host, self.port, 99 | ) 100 | logger.info(f"Listening on {self.host}:{self.port} -> {self.rhost}:{self.rport}") 101 | 102 | async with self.tcp_server: 103 | await self.tcp_server.serve_forever() 104 | -------------------------------------------------------------------------------- /webvpn/cmd.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import logging 3 | from pathlib import Path 4 | 5 | import typer 6 | import sentry_sdk 7 | from aiorun import run 8 | 9 | from .client import Client 10 | from .config import settings, save_settings 11 | from .gateway import WebVPNGateway 12 | from .login import buaa_webvpn_login 13 | from .logger import setup_logger 14 | 15 | app = typer.Typer() 16 | 17 | 18 | @app.command() 19 | def login( 20 | username: str, 21 | password: str = typer.Option(..., prompt=True, hide_input=True) 22 | ): 23 | settings.USERNAME = username 24 | settings.PASSWORD = password 25 | save_settings() 26 | 27 | asyncio.run(buaa_webvpn_login(username, password)) 28 | 29 | 30 | @app.command() 31 | def forward( 32 | host: str = settings.HOST, 33 | port: int = settings.PORT, 34 | rhost: str = settings.RHOST, 35 | rport: int = settings.RPORT, 36 | ): 37 | if not settings.USERNAME or not settings.PASSWORD: 38 | typer.echo( 39 | "Please login first by `webvpn login`" 40 | ) 41 | return 42 | 43 | client = Client( 44 | host=host, 45 | port=port, 46 | rhost=rhost, 47 | rport=rport, 48 | username=settings.USERNAME, 49 | password=settings.PASSWORD, 50 | gateway=WebVPNGateway() 51 | ) 52 | run(client.run(), stop_on_unhandled_errors=True) 53 | 54 | 55 | @app.command() 56 | def serve(): 57 | logger = logging.getLogger(__name__) 58 | logger.info("Not implemented yet.") 59 | 60 | 61 | @app.callback() 62 | def main(verbose: bool = False): 63 | sentry_sdk.init( 64 | "https://6e41d074f65e4d5c85ac42611a08fe94@o246548.ingest.sentry.io/6464187", 65 | traces_sample_rate=0.5 66 | ) 67 | setup_logger(verbose) 68 | 69 | 70 | if __name__ == "__main__": 71 | app() 72 | -------------------------------------------------------------------------------- /webvpn/config.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from dynaconf import Dynaconf, loaders 4 | from dynaconf.utils.boxing import DynaBox 5 | 6 | 7 | def get_setting_path(): 8 | config_dir = Path.home() / ".config" / "webvpn-py" 9 | if not config_dir.exists(): 10 | config_dir.mkdir(parents=True) 11 | return config_dir / "settings.yaml" 12 | 13 | 14 | settings = Dynaconf( 15 | envvar_prefix="WEBVPN", 16 | settings_files=[ 17 | str(Path(__file__).parent / "settings.yaml"), 18 | str(get_setting_path()) 19 | ], 20 | ) 21 | 22 | 23 | def save_settings(): 24 | config_dir = Path.home() / ".config" / "webvpn-py" 25 | if not config_dir.exists(): 26 | config_dir.mkdir(parents=True) 27 | 28 | data = settings.as_dict() 29 | loaders.write(str(get_setting_path()), DynaBox(data).to_dict()) 30 | -------------------------------------------------------------------------------- /webvpn/gateway/__init__.py: -------------------------------------------------------------------------------- 1 | from .gateway import Gateway, InvalidToken, ConnectionClosedError 2 | from .tcp import TCPGateway 3 | from .webvpn import WebVPNGateway 4 | -------------------------------------------------------------------------------- /webvpn/gateway/gateway.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | from typing import Optional, Dict 3 | import uuid 4 | import time 5 | import asyncio 6 | import logging 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | class InvalidToken(Exception): 12 | ... 13 | 14 | 15 | class ConnectionClosedError(Exception): 16 | ... 17 | 18 | 19 | class Connection(metaclass=ABCMeta): 20 | def __init__(self, closed: bool = False): 21 | self.closed = closed 22 | self.created_at = time.time() 23 | self.updated_at = time.time() 24 | 25 | @abstractmethod 26 | async def push(self, data: bytes): 27 | ... 28 | 29 | @abstractmethod 30 | async def pull(self, n: int) -> bytes: 31 | ... 32 | 33 | def update(self): 34 | self.updated_at = time.time() 35 | 36 | async def keep_alive(self) -> bool: 37 | self.update() 38 | return not self.closed 39 | 40 | @abstractmethod 41 | async def close(self): 42 | ... 43 | 44 | 45 | class Gateway(metaclass=ABCMeta): 46 | def __init__(self, expire_time: float = -1): 47 | self.expire_time = expire_time 48 | 49 | self.connections: Dict[str, Connection] = {} 50 | 51 | @abstractmethod 52 | def open_connection(self, conn) -> Optional[str]: 53 | token = str(uuid.uuid4()) 54 | self.connections[token] = conn 55 | return token 56 | 57 | async def push(self, token: str, data: bytes) -> bool: 58 | if token not in self.connections: 59 | raise InvalidToken() 60 | 61 | conn = self.connections[token] 62 | try: 63 | await conn.push(data) 64 | except ConnectionClosedError: 65 | await self.close(token) 66 | return False 67 | 68 | return True 69 | 70 | async def pull(self, token: str, n: int = 1024) -> Optional[bytes]: 71 | if token not in self.connections: 72 | raise InvalidToken() 73 | 74 | conn = self.connections[token] 75 | try: 76 | return await conn.pull(n) 77 | except ConnectionClosedError: 78 | await self.close(token) 79 | return None 80 | 81 | async def keep_alive(self, token: str): 82 | if token not in self.connections: 83 | raise InvalidToken() 84 | 85 | conn = self.connections[token] 86 | if not await conn.keep_alive(): 87 | await self.close(token) 88 | return False 89 | else: 90 | return True 91 | 92 | async def close(self, token: str): 93 | if token in self.connections: 94 | await self.connections[token].close() 95 | del self.connections[token] 96 | 97 | async def clean(self): 98 | if self.expire_time <= 0: 99 | return 100 | 101 | while True: 102 | await asyncio.sleep(self.expire_time) 103 | 104 | try: 105 | now = time.time() 106 | logger.info("Clean expired connections.") 107 | for token, conn in list(self.connections.items()): 108 | if now - conn.updated_at > self.expire_time: 109 | logger.info(f"Connection {token} expired") 110 | 111 | await self.close(token) 112 | except Exception as e: 113 | print("Clean Error:", e) 114 | -------------------------------------------------------------------------------- /webvpn/gateway/tcp.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | import asyncio 3 | import time 4 | 5 | from .gateway import Gateway, Connection, ConnectionClosedError 6 | 7 | 8 | class TCPConnection(Connection): 9 | def __init__( 10 | self, 11 | reader: asyncio.StreamReader, 12 | writer: asyncio.StreamWriter, 13 | username: str, 14 | ): 15 | super().__init__(closed=False) 16 | 17 | self.reader = reader 18 | self.writer = writer 19 | self.username = username 20 | 21 | async def push(self, data: bytes): 22 | self.update() 23 | if self.closed: 24 | raise ConnectionClosedError() 25 | self.writer.write(data) 26 | 27 | async def pull(self, n: int) -> bytes: 28 | self.update() 29 | if self.closed: 30 | raise ConnectionClosedError() 31 | 32 | data = await self.reader.read(n) 33 | if not data: 34 | self.closed = True 35 | return data 36 | 37 | async def close(self): 38 | self.closed = True 39 | if not self.writer.is_closing(): 40 | await self.writer.drain() 41 | self.writer.close() 42 | await self.writer.wait_closed() 43 | 44 | 45 | class TCPGateway(Gateway): 46 | def __init__(self): 47 | super().__init__(expire_time=20) 48 | 49 | async def open_connection(self, host: str, port: int, username: str) -> Optional[str]: 50 | try: 51 | future = asyncio.open_connection(host, port) 52 | reader, writer = await asyncio.wait_for(future, timeout=5) 53 | except ConnectionRefusedError: 54 | return None 55 | except asyncio.TimeoutError: 56 | return None 57 | 58 | conn = TCPConnection(reader, writer, username) 59 | return super().open_connection(conn) 60 | 61 | def get_username(self, token: str) -> Optional[str]: 62 | if token not in self.connections: 63 | return None 64 | return self.connections[token].username 65 | -------------------------------------------------------------------------------- /webvpn/gateway/webvpn.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | import asyncio 3 | import logging 4 | 5 | import aiohttp 6 | 7 | from webvpn.login import buaa_webvpn_login, get_cookie_jar_path 8 | from .gateway import Gateway, Connection, ConnectionClosedError 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | TEST_MODE = False 13 | 14 | if TEST_MODE: 15 | TOKEN_URL = "http://127.0.0.1:8000/token" 16 | PUSH_URL = "http://127.0.0.1:8000/push" 17 | PULL_URL = "http://127.0.0.1:8000/pull" 18 | KEEP_ALIVE_URL = "http://127.0.0.1:8000/keep-alive" 19 | else: 20 | BASE_URL = "https://d.buaa.edu.cn" 21 | TOKEN_URL = BASE_URL + "/http-23381/77726476706e69737468656265737421a1a70fce72612600305add/token" 22 | PUSH_URL = BASE_URL + "/http-23381/77726476706e69737468656265737421a1a70fce72612600305add/push" 23 | PULL_URL = BASE_URL + "/http-23381/77726476706e69737468656265737421a1a70fce72612600305add/pull" 24 | KEEP_ALIVE_URL = BASE_URL + "/http-23381/77726476706e69737468656265737421a1a70fce72612600305add/keep-alive" 25 | 26 | RETRY_DELAY = [10, 20, 30, 100, 200, 1000, 2000, 3000, 4000, 5000] # ms 27 | 28 | 29 | class WebVPNConnection(Connection): 30 | def __init__(self, username: str, password: str, host: str, port: str): 31 | super().__init__(closed=False) 32 | 33 | self.username = username 34 | self.password = password 35 | self.host = host 36 | self.port = port 37 | 38 | timeout = aiohttp.ClientTimeout(total=0) 39 | self.cookie_jar = aiohttp.CookieJar() 40 | self.cookie_jar.load(get_cookie_jar_path()) 41 | self.session = aiohttp.ClientSession(timeout=timeout, cookie_jar=self.cookie_jar) 42 | self.timeout = aiohttp.ClientTimeout(total=5) 43 | 44 | self.token: Optional[str] = None 45 | 46 | async def login(self) -> bool: 47 | if not await buaa_webvpn_login(self.username, self.password): 48 | return False 49 | 50 | self.cookie_jar.load(get_cookie_jar_path()) 51 | logger.debug("Cookie loaded.") 52 | 53 | return True 54 | 55 | async def get_token(self): 56 | params = { 57 | "username": self.username, 58 | "host": self.host, 59 | "port": self.port, 60 | } 61 | 62 | retry_count = 0 63 | while retry_count < 5: 64 | try: 65 | async with self.session.get( 66 | TOKEN_URL, params=params, timeout=self.timeout, allow_redirects=False 67 | ) as rsp: 68 | if rsp.status == 302: # redirect 69 | await self.login() 70 | elif rsp.status == 200: 71 | data = await rsp.json() 72 | if data["code"] == 0: 73 | self.token = data["data"]["token"] 74 | logger.info(f"Token: {self.token}") 75 | return 76 | else: 77 | logger.error(data["message"]) 78 | break 79 | except asyncio.TimeoutError: 80 | ... 81 | 82 | await asyncio.sleep(1) 83 | retry_count += 1 84 | 85 | raise RuntimeError("Failed to get token") 86 | 87 | async def push(self, data: bytes): 88 | self.update() 89 | 90 | if self.closed: 91 | raise ConnectionClosedError() 92 | 93 | params = {"token": self.token} 94 | 95 | retry_count = 0 96 | done = False 97 | while retry_count < 10: 98 | try: 99 | async with self.session.post( 100 | PUSH_URL, params=params, data=data, 101 | timeout=self.timeout, allow_redirects=False 102 | ) as rsp: 103 | if rsp.status == 302: # redirect 104 | logger.info("Redirect to login.") 105 | await self.login() 106 | elif rsp.status == 200: 107 | data = await rsp.json() 108 | if data["code"] == 0: 109 | logger.debug(f"Push successfully. {len(data)} bytes.") 110 | done = True 111 | break 112 | except asyncio.TimeoutError: 113 | ... 114 | 115 | await asyncio.sleep(RETRY_DELAY[retry_count] / 1000) 116 | retry_count += 1 117 | 118 | if not done: 119 | self.closed = True 120 | logger.info("Connection closed. (push)") 121 | raise ConnectionClosedError() 122 | 123 | async def pull(self, n: int) -> bytes: 124 | self.update() 125 | 126 | if self.closed: 127 | raise ConnectionClosedError() 128 | 129 | params = {"token": self.token, "n": n} 130 | 131 | retry_count = 0 132 | done = False 133 | while retry_count < 10: 134 | try: 135 | async with self.session.get( 136 | PULL_URL, params=params, timeout=self.timeout, allow_redirects=False 137 | ) as rsp: 138 | if rsp.status == 302: # redirect 139 | logger.info("Redirect to login.") 140 | await self.login() 141 | elif rsp.status == 200: 142 | data = await rsp.read() 143 | done = True 144 | logger.debug(f"Pull successfully. {len(data)} bytes.") 145 | break 146 | elif rsp.status == 400: 147 | logger.error("invalid token.") 148 | break 149 | elif rsp.status == 503: 150 | logger.error("Connection closed. (503)") 151 | break 152 | except asyncio.TimeoutError: 153 | ... 154 | 155 | await asyncio.sleep(RETRY_DELAY[retry_count] / 1000) 156 | retry_count += 1 157 | 158 | if not done: 159 | self.closed = True 160 | logger.info("Connection closed. (pull)") 161 | raise ConnectionClosedError() 162 | 163 | return data 164 | 165 | async def keep_alive(self) -> bool: 166 | if self.closed: 167 | return False 168 | 169 | params = {"token": self.token} 170 | 171 | retry_count = 0 172 | while retry_count < 5: 173 | try: 174 | async with self.session.get( 175 | KEEP_ALIVE_URL, params=params, timeout=self.timeout, allow_redirects=False 176 | ) as rsp: 177 | if rsp.status == 302: # redirect 178 | logger.info("Redirect to login.") 179 | await self.login() 180 | elif rsp.status == 200: 181 | data = await rsp.json() 182 | logger.debug("Keep alive successfully.") 183 | if data["code"] != 0: 184 | logger.info("Failed to keep alive.") 185 | self.closed = True 186 | break 187 | except asyncio.TimeoutError: 188 | ... 189 | 190 | await asyncio.sleep(1) 191 | retry_count += 1 192 | 193 | return super().keep_alive() 194 | 195 | async def close(self): 196 | self.closed = True 197 | await self.session.close() 198 | 199 | 200 | class WebVPNGateway(Gateway): 201 | async def open_connection( 202 | self, host: str, port: int, username: str, password: str 203 | ) -> Optional[str]: 204 | conn = WebVPNConnection(username, password, host, port) 205 | 206 | if not TEST_MODE: 207 | if not await conn.login(): 208 | await conn.close() 209 | raise RuntimeError("Failed to login") 210 | await conn.get_token() 211 | 212 | return super().open_connection(conn) 213 | -------------------------------------------------------------------------------- /webvpn/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from pathlib import Path 3 | 4 | 5 | def setup_logger(verbose: bool = False, is_server: bool = False): 6 | logger = logging.getLogger("webvpn") 7 | logger.setLevel(logging.DEBUG if verbose else logging.INFO) 8 | 9 | ch = logging.StreamHandler() 10 | formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") 11 | ch.setFormatter(formatter) 12 | logger.addHandler(ch) 13 | 14 | config_dir = Path.home() / ".config" / "webvpn-py" 15 | if not config_dir.exists(): 16 | config_dir.mkdir(parents=True) 17 | log_filename = "server.log" if is_server else "client.log" 18 | ch = logging.FileHandler(config_dir / log_filename) 19 | ch.setFormatter(formatter) 20 | logger.addHandler(ch) 21 | -------------------------------------------------------------------------------- /webvpn/login.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from pathlib import Path 3 | 4 | import aiohttp 5 | from bs4 import BeautifulSoup 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | LOGIN_URL_1 = r"https://sso.buaa.edu.cn/login?service=https%3A%2F%2Fd.buaa.edu.cn%2Flogin%3Fcas_login%3Dtrue" 10 | LOGIN_URL_2 = r"https://sso.buaa.edu.cn/login" 11 | 12 | 13 | def get_cookie_jar_path() -> Path: 14 | config_dir = Path.home() / ".config" / "webvpn-py" 15 | if not config_dir.exists(): 16 | config_dir.mkdir(parents=True) 17 | return config_dir / "buaa-webvpn.cookie" 18 | 19 | 20 | async def buaa_webvpn_login(username: str, password: str) -> bool: 21 | timeout = aiohttp.ClientTimeout(total=5) 22 | cookie_jar = aiohttp.CookieJar() 23 | 24 | async with aiohttp.ClientSession(timeout=timeout, cookie_jar=cookie_jar) as session: 25 | # Get `execution` 26 | async with session.get(LOGIN_URL_1) as rsp: 27 | soup = BeautifulSoup(await rsp.text(), "html.parser") 28 | tag = soup.find("input", {"name": "execution"}) 29 | assert tag is not None 30 | execution = tag["value"] 31 | 32 | data = { 33 | "username": username, 34 | "password": password, 35 | "submit": "登录", 36 | "type": "username_password", 37 | "execution": execution, 38 | "_eventId": "submit", 39 | } 40 | async with session.post(LOGIN_URL_2, data=data) as rsp: 41 | ok = rsp.status == 200 42 | 43 | if ok: 44 | cookie_jar.save(get_cookie_jar_path()) 45 | logger.info(f"Successfully logged in: {username}") 46 | else: 47 | logger.error(f"Failed to login: {username}") 48 | 49 | return ok 50 | -------------------------------------------------------------------------------- /webvpn/server.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | import asyncio 3 | import logging 4 | 5 | from fastapi import FastAPI, Response, Depends, Request 6 | import sentry_sdk 7 | 8 | from .gateway import TCPGateway, InvalidToken 9 | from .logger import setup_logger 10 | 11 | setup_logger(is_server=True) 12 | logger = logging.getLogger(__name__) 13 | 14 | sentry_sdk.init( 15 | "https://e8a30444dfe04196bc925f1c36fffce0@o246548.ingest.sentry.io/6464341", 16 | traces_sample_rate=0.5, 17 | ) 18 | 19 | app = FastAPI() 20 | gateway = TCPGateway() 21 | 22 | asyncio.create_task(gateway.clean()) 23 | 24 | 25 | @app.get("/token") 26 | async def get_token(username: str, host: str, port: int): 27 | token = await gateway.open_connection(host, port, username) 28 | logger.info(f"new connection: {username}:{token} -> {host}:{port}") 29 | 30 | if token: 31 | return { 32 | "code": 0, 33 | "data": {"token": token}, 34 | } 35 | else: 36 | return { 37 | "code": 1000, 38 | "message": "Connection refused (open_connection)", 39 | } 40 | 41 | 42 | @app.get("/keep-alive") 43 | async def keep_alive(token: str): 44 | try: 45 | if await gateway.keep_alive(token): 46 | logger.info(f"keep-alive: {gateway.get_username(token)}:{token}") 47 | return {"code": 0} 48 | else: 49 | return {"code": 2000, "message": "Connection closed (keep_alive)"} 50 | except InvalidToken: 51 | return Response(status_code=400) 52 | 53 | 54 | @app.get( 55 | "/pull", 56 | response_class=Response, 57 | responses={ 58 | 200: {"content": {"application/octet-stream": {}}} 59 | }, 60 | ) 61 | async def pull(token: str, n: int = 1024): 62 | future = gateway.pull(token, n) 63 | 64 | data = b"" 65 | try: 66 | data = await asyncio.wait_for(future, timeout=2) 67 | except asyncio.TimeoutError: 68 | ... 69 | except InvalidToken: 70 | return Response(status_code=400) 71 | 72 | if data is None: 73 | return Response(status_code=503) # Connection closed 74 | 75 | if len(data) > 0: 76 | logger.info(f"pull {len(data)} bytes: {gateway.get_username(token)}:{token}") 77 | return Response(data, media_type="application/octet-stream") 78 | 79 | 80 | async def parse_body(request: Request): 81 | return await request.body() 82 | 83 | 84 | @app.post("/push") 85 | async def push(token: str, data: bytes = Depends(parse_body)): 86 | try: 87 | if await gateway.push(token, data): 88 | logger.info(f"push {len(data)} bytes: {gateway.get_username(token)}:{token}") 89 | return {"code": 0} 90 | else: 91 | return {"code": 2000, "message": "Connection closed (push)"} 92 | except InvalidToken: 93 | return Response(status_code=400) 94 | -------------------------------------------------------------------------------- /webvpn/settings.yaml: -------------------------------------------------------------------------------- 1 | HOST: localhost 2 | PORT: 23388 3 | 4 | RHOST: 10.251.0.23 5 | RPORT: 22 6 | 7 | USERNAME: "" 8 | PASSWORD: "" 9 | 10 | VERBOSE: False 11 | --------------------------------------------------------------------------------