mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-23 19:44:20 +00:00
More progress
This commit is contained in:
parent
db39874408
commit
48c746ea7d
35 changed files with 1618 additions and 125 deletions
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
|
@ -6,5 +6,5 @@
|
|||
<component name="MacroExpansionManager">
|
||||
<option name="directoryName" value="cqweyjJr" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8" project-jdk-type="Python SDK" />
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8 (royalnet-1MWM6-kd-py3.8)" project-jdk-type="Python SDK" />
|
||||
</project>
|
|
@ -5,7 +5,7 @@
|
|||
<sourceFolder url="file://$MODULE_DIR$/royalnet" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.8" jdkType="Python SDK" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.8 (royalnet-1MWM6-kd-py3.8)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="TestRunnerService">
|
||||
|
|
755
poetry.lock
generated
Normal file
755
poetry.lock
generated
Normal file
|
@ -0,0 +1,755 @@
|
|||
[[package]]
|
||||
category = "main"
|
||||
description = "Async http client/server framework (asyncio)"
|
||||
name = "aiohttp"
|
||||
optional = true
|
||||
python-versions = ">=3.5.3"
|
||||
version = "3.5.4"
|
||||
|
||||
[package.dependencies]
|
||||
async-timeout = ">=3.0,<4.0"
|
||||
attrs = ">=17.3.0"
|
||||
chardet = ">=2.0,<4.0"
|
||||
multidict = ">=4.0,<5.0"
|
||||
yarl = ">=1.0,<2.0"
|
||||
|
||||
[package.extras]
|
||||
speedups = ["aiodns", "brotlipy", "cchardet"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Timeout context manager for asyncio programs"
|
||||
name = "async-timeout"
|
||||
optional = true
|
||||
python-versions = ">=3.5.3"
|
||||
version = "3.0.1"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Classes Without Boilerplate"
|
||||
name = "attrs"
|
||||
optional = true
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "19.3.0"
|
||||
|
||||
[package.extras]
|
||||
azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"]
|
||||
dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"]
|
||||
docs = ["sphinx", "zope.interface"]
|
||||
tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
name = "certifi"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
version = "2019.9.11"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Foreign Function Interface for Python calling C code."
|
||||
name = "cffi"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
version = "1.13.2"
|
||||
|
||||
[package.dependencies]
|
||||
pycparser = "*"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Universal encoding detector for Python 2 and 3"
|
||||
name = "chardet"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
version = "3.0.4"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Composable command line interface toolkit"
|
||||
name = "click"
|
||||
optional = true
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "7.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||
name = "cryptography"
|
||||
optional = true
|
||||
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
|
||||
version = "2.8"
|
||||
|
||||
[package.dependencies]
|
||||
cffi = ">=1.8,<1.11.3 || >1.11.3"
|
||||
six = ">=1.4.1"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=1.6.5,<1.8.0 || >1.8.0)", "sphinx-rtd-theme"]
|
||||
docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
|
||||
idna = ["idna (>=2.1)"]
|
||||
pep8test = ["flake8", "flake8-import-order", "pep8-naming"]
|
||||
test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,<3.79.2 || >3.79.2)"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Date parsing library designed to parse dates from HTML pages"
|
||||
name = "dateparser"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
version = "0.7.2"
|
||||
|
||||
[package.dependencies]
|
||||
python-dateutil = "*"
|
||||
pytz = "*"
|
||||
regex = "*"
|
||||
tzlocal = "*"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "A python wrapper for the Discord API"
|
||||
name = "discord.py"
|
||||
optional = true
|
||||
python-versions = ">=3.5.3"
|
||||
version = "1.3.0a2122+g09a08f9"
|
||||
|
||||
[package.dependencies]
|
||||
aiohttp = ">=3.3.0,<3.6.0"
|
||||
websockets = ">=8.0"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (1.8.5)", "sphinxcontrib_trio (1.1.0)", "sphinxcontrib-websupport"]
|
||||
voice = ["PyNaCl (1.3.0)"]
|
||||
|
||||
[package.source]
|
||||
reference = "09a08f9a9f126aa1f55c2444eb70508d1d52f8d9"
|
||||
type = "git"
|
||||
url = "https://github.com/Steffo99/discord.py"
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Python bindings for FFmpeg - with complex filtering support"
|
||||
name = "ffmpeg-python"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
version = "0.2.0"
|
||||
|
||||
[package.dependencies]
|
||||
future = "*"
|
||||
|
||||
[package.extras]
|
||||
dev = ["future (0.17.1)", "numpy (1.16.4)", "pytest-mock (1.10.4)", "pytest (4.6.1)", "Sphinx (2.1.0)", "tox (3.12.1)"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Clean single-source support for Python 3 and 2"
|
||||
name = "future"
|
||||
optional = true
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
version = "0.18.2"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||
name = "h11"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
version = "0.8.1"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "A collection of framework independent HTTP protocol utils."
|
||||
marker = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"pypy\""
|
||||
name = "httptools"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
version = "0.0.13"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
name = "idna"
|
||||
optional = true
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "2.8"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "multidict implementation"
|
||||
name = "multidict"
|
||||
optional = true
|
||||
python-versions = ">=3.4.1"
|
||||
version = "4.5.2"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "psycopg2 - Python-PostgreSQL Database Adapter"
|
||||
name = "psycopg2"
|
||||
optional = true
|
||||
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
|
||||
version = "2.8.4"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "psycopg2 - Python-PostgreSQL Database Adapter"
|
||||
name = "psycopg2-binary"
|
||||
optional = true
|
||||
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
|
||||
version = "2.8.4"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "C parser in Python"
|
||||
name = "pycparser"
|
||||
optional = true
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "2.19"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Python binding to the Networking and Cryptography (NaCl) library"
|
||||
name = "pynacl"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
version = "1.3.0"
|
||||
|
||||
[package.dependencies]
|
||||
cffi = ">=1.4.1"
|
||||
six = "*"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"]
|
||||
tests = ["pytest (>=3.2.1,<3.3.0 || >3.3.0)", "hypothesis (>=3.27.0)"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Extensions to the standard Python datetime module"
|
||||
name = "python-dateutil"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||
version = "2.8.1"
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.5"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "We have made you a wrapper you can't refuse"
|
||||
name = "python-telegram-bot"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
version = "12.2.0"
|
||||
|
||||
[package.dependencies]
|
||||
certifi = "*"
|
||||
cryptography = "*"
|
||||
future = ">=0.16.0"
|
||||
tornado = ">=5.1"
|
||||
|
||||
[package.extras]
|
||||
json = ["ujson"]
|
||||
socks = ["pysocks"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "World timezone definitions, modern and historical"
|
||||
name = "pytz"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "2019.3"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Alternative regular expression module, to replace re."
|
||||
name = "regex"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "2019.11.1"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Python client for Sentry (https://getsentry.com)"
|
||||
name = "sentry-sdk"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
version = "0.13.2"
|
||||
|
||||
[package.dependencies]
|
||||
certifi = "*"
|
||||
urllib3 = ">=1.10.0"
|
||||
|
||||
[package.extras]
|
||||
bottle = ["bottle (>=0.12.13)"]
|
||||
falcon = ["falcon (>=1.4)"]
|
||||
flask = ["flask (>=0.8)", "blinker (>=1.1)"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
name = "six"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*"
|
||||
version = "1.13.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Database Abstraction Library"
|
||||
name = "sqlalchemy"
|
||||
optional = true
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "1.3.11"
|
||||
|
||||
[package.extras]
|
||||
mssql = ["pyodbc"]
|
||||
mssql_pymssql = ["pymssql"]
|
||||
mssql_pyodbc = ["pyodbc"]
|
||||
mysql = ["mysqlclient"]
|
||||
oracle = ["cx-oracle"]
|
||||
postgresql = ["psycopg2"]
|
||||
postgresql_pg8000 = ["pg8000"]
|
||||
postgresql_psycopg2binary = ["psycopg2-binary"]
|
||||
postgresql_psycopg2cffi = ["psycopg2cffi"]
|
||||
pymysql = ["pymysql"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "The little ASGI library that shines."
|
||||
name = "starlette"
|
||||
optional = true
|
||||
python-versions = ">=3.6"
|
||||
version = "0.12.13"
|
||||
|
||||
[package.extras]
|
||||
full = ["aiofiles", "graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests", "ujson"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed."
|
||||
name = "tornado"
|
||||
optional = true
|
||||
python-versions = ">= 3.5"
|
||||
version = "6.0.3"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "tzinfo object for the local timezone"
|
||||
name = "tzlocal"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "2.0.0"
|
||||
|
||||
[package.dependencies]
|
||||
pytz = "*"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
name = "urllib3"
|
||||
optional = true
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4"
|
||||
version = "1.25.7"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotlipy (>=0.6.0)"]
|
||||
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
|
||||
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "The lightning-fast ASGI server."
|
||||
name = "uvicorn"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
version = "0.10.8"
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=7.0.0,<8.0.0"
|
||||
h11 = ">=0.8.0,<0.9.0"
|
||||
httptools = "0.0.13"
|
||||
uvloop = ">=0.14.0"
|
||||
websockets = ">=8.0.0,<9.0.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Fast implementation of asyncio event loop on top of libuv"
|
||||
marker = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"pypy\""
|
||||
name = "uvloop"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
version = "0.14.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
|
||||
name = "websockets"
|
||||
optional = true
|
||||
python-versions = ">=3.6.1"
|
||||
version = "8.1"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Yet another URL library"
|
||||
name = "yarl"
|
||||
optional = true
|
||||
python-versions = ">=3.5.3"
|
||||
version = "1.3.0"
|
||||
|
||||
[package.dependencies]
|
||||
idna = ">=2.0"
|
||||
multidict = ">=4.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "YouTube video downloader"
|
||||
name = "youtube-dl"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
version = "2019.11.5"
|
||||
|
||||
[extras]
|
||||
alchemy_easy = ["sqlalchemy", "psycopg2_binary"]
|
||||
alchemy_hard = ["sqlalchemy", "psycopg2"]
|
||||
bard = ["ffmpeg_python", "youtube_dl"]
|
||||
constellation = ["starlette", "uvicorn"]
|
||||
discord = ["discord.py", "pynacl"]
|
||||
sentry = ["sentry_sdk"]
|
||||
telegram = ["python_telegram_bot"]
|
||||
|
||||
[metadata]
|
||||
content-hash = "d101c51ae28aea2b4692767328e42026950bd3f920fdf6f6afca76bac40e41df"
|
||||
python-versions = "^3.8"
|
||||
|
||||
[metadata.files]
|
||||
aiohttp = [
|
||||
{file = "aiohttp-3.5.4-cp35-cp35m-macosx_10_10_x86_64.whl", hash = "sha256:199f1d106e2b44b6dacdf6f9245493c7d716b01d0b7fbe1959318ba4dc64d1f5"},
|
||||
{file = "aiohttp-3.5.4-cp35-cp35m-macosx_10_11_x86_64.whl", hash = "sha256:0155af66de8c21b8dba4992aaeeabf55503caefae00067a3b1139f86d0ec50ed"},
|
||||
{file = "aiohttp-3.5.4-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:cc619d974c8c11fe84527e4b5e1c07238799a8c29ea1c1285149170524ba9303"},
|
||||
{file = "aiohttp-3.5.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:09654a9eca62d1bd6d64aa44db2498f60a5c1e0ac4750953fdd79d5c88955e10"},
|
||||
{file = "aiohttp-3.5.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:629102a193162e37102c50713e2e31dc9a2fe7ac5e481da83e5bb3c0cee700aa"},
|
||||
{file = "aiohttp-3.5.4-cp35-cp35m-win32.whl", hash = "sha256:acc89b29b5f4e2332d65cd1b7d10c609a75b88ef8925d487a611ca788432dfa4"},
|
||||
{file = "aiohttp-3.5.4-cp35-cp35m-win_amd64.whl", hash = "sha256:a25237abf327530d9561ef751eef9511ab56fd9431023ca6f4803f1994104d72"},
|
||||
{file = "aiohttp-3.5.4-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:87331d1d6810214085a50749160196391a712a13336cd02ce1c3ea3d05bcf8d5"},
|
||||
{file = "aiohttp-3.5.4-cp36-cp36m-macosx_10_11_x86_64.whl", hash = "sha256:a5cbd7157b0e383738b8e29d6e556fde8726823dae0e348952a61742b21aeb12"},
|
||||
{file = "aiohttp-3.5.4-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:9cddaff94c0135ee627213ac6ca6d05724bfe6e7a356e5e09ec57bd3249510f6"},
|
||||
{file = "aiohttp-3.5.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:d4392defd4648badaa42b3e101080ae3313e8f4787cb517efd3f5b8157eaefd6"},
|
||||
{file = "aiohttp-3.5.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c2bec436a2b5dafe5eaeb297c03711074d46b6eb236d002c13c42f25c4a8ce9d"},
|
||||
{file = "aiohttp-3.5.4-cp36-cp36m-win32.whl", hash = "sha256:296f30dedc9f4b9e7a301e5cc963012264112d78a1d3094cd83ef148fdf33ca1"},
|
||||
{file = "aiohttp-3.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:9a02a04bbe581c8605ac423ba3a74999ec9d8bce7ae37977a3d38680f5780b6d"},
|
||||
{file = "aiohttp-3.5.4-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:b05bd85cc99b06740aad3629c2585bda7b83bd86e080b44ba47faf905fdf1300"},
|
||||
{file = "aiohttp-3.5.4-cp37-cp37m-macosx_10_11_x86_64.whl", hash = "sha256:40d7ea570b88db017c51392349cf99b7aefaaddd19d2c78368aeb0bddde9d390"},
|
||||
{file = "aiohttp-3.5.4-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:a97a516e02b726e089cffcde2eea0d3258450389bbac48cbe89e0f0b6e7b0366"},
|
||||
{file = "aiohttp-3.5.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e1c3c582ee11af7f63a34a46f0448fca58e59889396ffdae1f482085061a2889"},
|
||||
{file = "aiohttp-3.5.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:00d198585474299c9c3b4f1d5de1a576cc230d562abc5e4a0e81d71a20a6ca55"},
|
||||
{file = "aiohttp-3.5.4-cp37-cp37m-win32.whl", hash = "sha256:6d5ec9b8948c3d957e75ea14d41e9330e1ac3fed24ec53766c780f82805140dc"},
|
||||
{file = "aiohttp-3.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:368ed312550bd663ce84dc4b032a962fcb3c7cae099dbbd48663afc305e3b939"},
|
||||
{file = "aiohttp-3.5.4.tar.gz", hash = "sha256:9c4c83f4fa1938377da32bc2d59379025ceeee8e24b89f72fcbccd8ca22dc9bf"},
|
||||
]
|
||||
async-timeout = [
|
||||
{file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"},
|
||||
{file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"},
|
||||
]
|
||||
attrs = [
|
||||
{file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
|
||||
{file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"},
|
||||
]
|
||||
certifi = [
|
||||
{file = "certifi-2019.9.11-py2.py3-none-any.whl", hash = "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef"},
|
||||
{file = "certifi-2019.9.11.tar.gz", hash = "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50"},
|
||||
]
|
||||
cffi = [
|
||||
{file = "cffi-1.13.2-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43"},
|
||||
{file = "cffi-1.13.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396"},
|
||||
{file = "cffi-1.13.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54"},
|
||||
{file = "cffi-1.13.2-cp27-cp27m-win32.whl", hash = "sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159"},
|
||||
{file = "cffi-1.13.2-cp27-cp27m-win_amd64.whl", hash = "sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97"},
|
||||
{file = "cffi-1.13.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579"},
|
||||
{file = "cffi-1.13.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc"},
|
||||
{file = "cffi-1.13.2-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f"},
|
||||
{file = "cffi-1.13.2-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858"},
|
||||
{file = "cffi-1.13.2-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42"},
|
||||
{file = "cffi-1.13.2-cp34-cp34m-win32.whl", hash = "sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b"},
|
||||
{file = "cffi-1.13.2-cp34-cp34m-win_amd64.whl", hash = "sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20"},
|
||||
{file = "cffi-1.13.2-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3"},
|
||||
{file = "cffi-1.13.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25"},
|
||||
{file = "cffi-1.13.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5"},
|
||||
{file = "cffi-1.13.2-cp35-cp35m-win32.whl", hash = "sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c"},
|
||||
{file = "cffi-1.13.2-cp35-cp35m-win_amd64.whl", hash = "sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b"},
|
||||
{file = "cffi-1.13.2-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04"},
|
||||
{file = "cffi-1.13.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652"},
|
||||
{file = "cffi-1.13.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57"},
|
||||
{file = "cffi-1.13.2-cp36-cp36m-win32.whl", hash = "sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e"},
|
||||
{file = "cffi-1.13.2-cp36-cp36m-win_amd64.whl", hash = "sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d"},
|
||||
{file = "cffi-1.13.2-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410"},
|
||||
{file = "cffi-1.13.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a"},
|
||||
{file = "cffi-1.13.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12"},
|
||||
{file = "cffi-1.13.2-cp37-cp37m-win32.whl", hash = "sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e"},
|
||||
{file = "cffi-1.13.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a"},
|
||||
{file = "cffi-1.13.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d"},
|
||||
{file = "cffi-1.13.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3"},
|
||||
{file = "cffi-1.13.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db"},
|
||||
{file = "cffi-1.13.2-cp38-cp38-win32.whl", hash = "sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506"},
|
||||
{file = "cffi-1.13.2-cp38-cp38-win_amd64.whl", hash = "sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba"},
|
||||
{file = "cffi-1.13.2.tar.gz", hash = "sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346"},
|
||||
]
|
||||
chardet = [
|
||||
{file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
|
||||
{file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
|
||||
]
|
||||
click = [
|
||||
{file = "Click-7.0-py2.py3-none-any.whl", hash = "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13"},
|
||||
{file = "Click-7.0.tar.gz", hash = "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"},
|
||||
]
|
||||
cryptography = [
|
||||
{file = "cryptography-2.8-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8"},
|
||||
{file = "cryptography-2.8-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2"},
|
||||
{file = "cryptography-2.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad"},
|
||||
{file = "cryptography-2.8-cp27-cp27m-win32.whl", hash = "sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2"},
|
||||
{file = "cryptography-2.8-cp27-cp27m-win_amd64.whl", hash = "sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912"},
|
||||
{file = "cryptography-2.8-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d"},
|
||||
{file = "cryptography-2.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42"},
|
||||
{file = "cryptography-2.8-cp34-abi3-macosx_10_6_intel.whl", hash = "sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879"},
|
||||
{file = "cryptography-2.8-cp34-abi3-manylinux1_x86_64.whl", hash = "sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d"},
|
||||
{file = "cryptography-2.8-cp34-abi3-manylinux2010_x86_64.whl", hash = "sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9"},
|
||||
{file = "cryptography-2.8-cp34-cp34m-win32.whl", hash = "sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c"},
|
||||
{file = "cryptography-2.8-cp34-cp34m-win_amd64.whl", hash = "sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0"},
|
||||
{file = "cryptography-2.8-cp35-cp35m-win32.whl", hash = "sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf"},
|
||||
{file = "cryptography-2.8-cp35-cp35m-win_amd64.whl", hash = "sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793"},
|
||||
{file = "cryptography-2.8-cp36-cp36m-win32.whl", hash = "sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595"},
|
||||
{file = "cryptography-2.8-cp36-cp36m-win_amd64.whl", hash = "sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7"},
|
||||
{file = "cryptography-2.8-cp37-cp37m-win32.whl", hash = "sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff"},
|
||||
{file = "cryptography-2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f"},
|
||||
{file = "cryptography-2.8-cp38-cp38-win32.whl", hash = "sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e"},
|
||||
{file = "cryptography-2.8-cp38-cp38-win_amd64.whl", hash = "sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13"},
|
||||
{file = "cryptography-2.8.tar.gz", hash = "sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651"},
|
||||
]
|
||||
dateparser = [
|
||||
{file = "dateparser-0.7.2-py2.py3-none-any.whl", hash = "sha256:983d84b5e3861cb0aa240cad07f12899bb10b62328aae188b9007e04ce37d665"},
|
||||
{file = "dateparser-0.7.2.tar.gz", hash = "sha256:e1eac8ef28de69a554d5fcdb60b172d526d61924b1a40afbbb08df459a36006b"},
|
||||
]
|
||||
"discord.py" = []
|
||||
ffmpeg-python = [
|
||||
{file = "ffmpeg-python-0.2.0.tar.gz", hash = "sha256:65225db34627c578ef0e11c8b1eb528bb35e024752f6f10b78c011f6f64c4127"},
|
||||
{file = "ffmpeg_python-0.2.0-py3-none-any.whl", hash = "sha256:ac441a0404e053f8b6a1113a77c0f452f1cfc62f6344a769475ffdc0f56c23c5"},
|
||||
]
|
||||
future = [
|
||||
{file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
|
||||
]
|
||||
h11 = [
|
||||
{file = "h11-0.8.1-py2.py3-none-any.whl", hash = "sha256:f2b1ca39bfed357d1f19ac732913d5f9faa54a5062eca7d2ec3a916cfb7ae4c7"},
|
||||
{file = "h11-0.8.1.tar.gz", hash = "sha256:acca6a44cb52a32ab442b1779adf0875c443c689e9e028f8d831a3769f9c5208"},
|
||||
]
|
||||
httptools = [
|
||||
{file = "httptools-0.0.13.tar.gz", hash = "sha256:e00cbd7ba01ff748e494248183abc6e153f49181169d8a3d41bb49132ca01dfc"},
|
||||
]
|
||||
idna = [
|
||||
{file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"},
|
||||
{file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"},
|
||||
]
|
||||
multidict = [
|
||||
{file = "multidict-4.5.2-cp34-cp34m-macosx_10_12_intel.macosx_10_12_x86_64.macosx_10_13_intel.macosx_10_13_x86_64.whl", hash = "sha256:068167c2d7bbeebd359665ac4fff756be5ffac9cda02375b5c5a7c4777038e73"},
|
||||
{file = "multidict-4.5.2-cp34-cp34m-macosx_10_6_intel.macosx_10_6_x86_64.macosx_10_7_intel.macosx_10_7_x86_64.macosx_10_8_intel.macosx_10_8_x86_64.whl", hash = "sha256:7c1b7eab7a49aa96f3db1f716f0113a8a2e93c7375dd3d5d21c4941f1405c9c5"},
|
||||
{file = "multidict-4.5.2-cp34-cp34m-macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.macosx_10_11_intel.macosx_10_11_x86_64.whl", hash = "sha256:8ccd1c5fff1aa1427100ce188557fc31f1e0a383ad8ec42c559aabd4ff08802d"},
|
||||
{file = "multidict-4.5.2-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:6a3a9b0f45fd75dc05d8e93dc21b18fc1670135ec9544d1ad4acbcf6b86781d0"},
|
||||
{file = "multidict-4.5.2-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:31dfa2fc323097f8ad7acd41aa38d7c614dd1960ac6681745b6da124093dc351"},
|
||||
{file = "multidict-4.5.2-cp34-cp34m-win32.whl", hash = "sha256:8e08dd76de80539d613654915a2f5196dbccc67448df291e69a88712ea21e24a"},
|
||||
{file = "multidict-4.5.2-cp34-cp34m-win_amd64.whl", hash = "sha256:d1071414dd06ca2eafa90c85a079169bfeb0e5f57fd0b45d44c092546fcd6fd9"},
|
||||
{file = "multidict-4.5.2-cp35-cp35m-macosx_10_12_intel.macosx_10_12_x86_64.macosx_10_13_intel.macosx_10_13_x86_64.whl", hash = "sha256:1d1c77013a259971a72ddaa83b9f42c80a93ff12df6a4723be99d858fa30bee3"},
|
||||
{file = "multidict-4.5.2-cp35-cp35m-macosx_10_6_intel.macosx_10_6_x86_64.macosx_10_7_intel.macosx_10_7_x86_64.macosx_10_8_intel.macosx_10_8_x86_64.whl", hash = "sha256:3d5dd8e5998fb4ace04789d1d008e2bb532de501218519d70bb672c4c5a2fc5d"},
|
||||
{file = "multidict-4.5.2-cp35-cp35m-macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.macosx_10_11_intel.macosx_10_11_x86_64.whl", hash = "sha256:7fc0eee3046041387cbace9314926aa48b681202f8897f8bff3809967a049036"},
|
||||
{file = "multidict-4.5.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:041e9442b11409be5e4fc8b6a97e4bcead758ab1e11768d1e69160bdde18acc3"},
|
||||
{file = "multidict-4.5.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c49db89d602c24928e68c0d510f4fcf8989d77defd01c973d6cbe27e684833b1"},
|
||||
{file = "multidict-4.5.2-cp35-cp35m-win32.whl", hash = "sha256:34f82db7f80c49f38b032c5abb605c458bac997a6c3142e0d6c130be6fb2b941"},
|
||||
{file = "multidict-4.5.2-cp35-cp35m-win_amd64.whl", hash = "sha256:5de53a28f40ef3c4fd57aeab6b590c2c663de87a5af76136ced519923d3efbb3"},
|
||||
{file = "multidict-4.5.2-cp36-cp36m-macosx_10_12_intel.macosx_10_12_x86_64.macosx_10_13_intel.macosx_10_13_x86_64.whl", hash = "sha256:db603a1c235d110c860d5f39988ebc8218ee028f07a7cbc056ba6424372ca31b"},
|
||||
{file = "multidict-4.5.2-cp36-cp36m-macosx_10_6_intel.macosx_10_6_x86_64.macosx_10_7_intel.macosx_10_7_x86_64.macosx_10_8_intel.macosx_10_8_x86_64.whl", hash = "sha256:ce20044d0317649ddbb4e54dab3c1bcc7483c78c27d3f58ab3d0c7e6bc60d26a"},
|
||||
{file = "multidict-4.5.2-cp36-cp36m-macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.macosx_10_11_intel.macosx_10_11_x86_64.whl", hash = "sha256:4b843f8e1dd6a3195679d9838eb4670222e8b8d01bc36c9894d6c3538316fa0a"},
|
||||
{file = "multidict-4.5.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:047c0a04e382ef8bd74b0de01407e8d8632d7d1b4db6f2561106af812a68741b"},
|
||||
{file = "multidict-4.5.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:148ff60e0fffa2f5fad2eb25aae7bef23d8f3b8bdaf947a65cdbe84a978092bc"},
|
||||
{file = "multidict-4.5.2-cp36-cp36m-win32.whl", hash = "sha256:4b02a3b2a2f01d0490dd39321c74273fed0568568ea0e7ea23e02bd1fb10a10b"},
|
||||
{file = "multidict-4.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d3be11ac43ab1a3e979dac80843b42226d5d3cccd3986f2e03152720a4297cd7"},
|
||||
{file = "multidict-4.5.2-cp37-cp37m-macosx_10_12_intel.macosx_10_12_x86_64.macosx_10_13_intel.macosx_10_13_x86_64.whl", hash = "sha256:1d48bc124a6b7a55006d97917f695effa9725d05abe8ee78fd60d6588b8344cd"},
|
||||
{file = "multidict-4.5.2-cp37-cp37m-macosx_10_6_intel.macosx_10_6_x86_64.macosx_10_7_intel.macosx_10_7_x86_64.macosx_10_8_intel.macosx_10_8_x86_64.whl", hash = "sha256:61b2b33ede821b94fa99ce0b09c9ece049c7067a33b279f343adfe35108a4ea7"},
|
||||
{file = "multidict-4.5.2-cp37-cp37m-macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.macosx_10_11_intel.macosx_10_11_x86_64.whl", hash = "sha256:76ad8e4c69dadbb31bad17c16baee61c0d1a4a73bed2590b741b2e1a46d3edd0"},
|
||||
{file = "multidict-4.5.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:7ba19b777dc00194d1b473180d4ca89a054dd18de27d0ee2e42a103ec9b7d014"},
|
||||
{file = "multidict-4.5.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c18498c50c59263841862ea0501da9f2b3659c00db54abfbf823a80787fde8ce"},
|
||||
{file = "multidict-4.5.2-cp37-cp37m-win32.whl", hash = "sha256:045b4dd0e5f6121e6f314d81759abd2c257db4634260abcfe0d3f7083c4908ef"},
|
||||
{file = "multidict-4.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:4a6ae52bd3ee41ee0f3acf4c60ceb3f44e0e3bc52ab7da1c2b2aa6703363a3d1"},
|
||||
{file = "multidict-4.5.2.tar.gz", hash = "sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f"},
|
||||
]
|
||||
psycopg2 = [
|
||||
{file = "psycopg2-2.8.4-cp27-cp27m-win32.whl", hash = "sha256:72772181d9bad1fa349792a1e7384dde56742c14af2b9986013eb94a240f005b"},
|
||||
{file = "psycopg2-2.8.4-cp27-cp27m-win_amd64.whl", hash = "sha256:893c11064b347b24ecdd277a094413e1954f8a4e8cdaf7ffbe7ca3db87c103f0"},
|
||||
{file = "psycopg2-2.8.4-cp34-cp34m-win32.whl", hash = "sha256:9ab75e0b2820880ae24b7136c4d230383e07db014456a476d096591172569c38"},
|
||||
{file = "psycopg2-2.8.4-cp34-cp34m-win_amd64.whl", hash = "sha256:b0845e3bdd4aa18dc2f9b6fb78fbd3d9d371ad167fd6d1b7ad01c0a6cdad4fc6"},
|
||||
{file = "psycopg2-2.8.4-cp35-cp35m-win32.whl", hash = "sha256:ef6df7e14698e79c59c7ee7cf94cd62e5b869db369ed4b1b8f7b729ea825712a"},
|
||||
{file = "psycopg2-2.8.4-cp35-cp35m-win_amd64.whl", hash = "sha256:965c4c93e33e6984d8031f74e51227bd755376a9df6993774fd5b6fb3288b1f4"},
|
||||
{file = "psycopg2-2.8.4-cp36-cp36m-win32.whl", hash = "sha256:ed686e5926929887e2c7ae0a700e32c6129abb798b4ad2b846e933de21508151"},
|
||||
{file = "psycopg2-2.8.4-cp36-cp36m-win_amd64.whl", hash = "sha256:dca2d7203f0dfce8ea4b3efd668f8ea65cd2b35112638e488a4c12594015f67b"},
|
||||
{file = "psycopg2-2.8.4-cp37-cp37m-win32.whl", hash = "sha256:8396be6e5ff844282d4d49b81631772f80dabae5658d432202faf101f5283b7c"},
|
||||
{file = "psycopg2-2.8.4-cp37-cp37m-win_amd64.whl", hash = "sha256:47fc642bf6f427805daf52d6e52619fe0637648fe27017062d898f3bf891419d"},
|
||||
{file = "psycopg2-2.8.4-cp38-cp38-win32.whl", hash = "sha256:4212ca404c4445dc5746c0d68db27d2cbfb87b523fe233dc84ecd24062e35677"},
|
||||
{file = "psycopg2-2.8.4-cp38-cp38-win_amd64.whl", hash = "sha256:92a07dfd4d7c325dd177548c4134052d4842222833576c8391aab6f74038fc3f"},
|
||||
{file = "psycopg2-2.8.4.tar.gz", hash = "sha256:f898e5cc0a662a9e12bde6f931263a1bbd350cfb18e1d5336a12927851825bb6"},
|
||||
]
|
||||
psycopg2-binary = [
|
||||
{file = "psycopg2-binary-2.8.4.tar.gz", hash = "sha256:3a2522b1d9178575acee4adf8fd9f979f9c0449b00b4164bb63c3475ea6528ed"},
|
||||
{file = "psycopg2_binary-2.8.4-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:890167d5091279a27e2505ff0e1fb273f8c48c41d35c5b92adbf4af80e6b2ed6"},
|
||||
{file = "psycopg2_binary-2.8.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:dbc5cd56fff1a6152ca59445178652756f4e509f672e49ccdf3d79c1043113a4"},
|
||||
{file = "psycopg2_binary-2.8.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:7f42a8490c4fe854325504ce7a6e4796b207960dabb2cbafe3c3959cb00d1d7e"},
|
||||
{file = "psycopg2_binary-2.8.4-cp27-cp27m-win32.whl", hash = "sha256:8578d6b8192e4c805e85f187bc530d0f52ba86c39172e61cd51f68fddd648103"},
|
||||
{file = "psycopg2_binary-2.8.4-cp27-cp27m-win_amd64.whl", hash = "sha256:5dd90c5438b4f935c9d01fcbad3620253da89d19c1f5fca9158646407ed7df35"},
|
||||
{file = "psycopg2_binary-2.8.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:9aadff9032e967865f9778485571e93908d27dab21d0fdfdec0ca779bb6f8ad9"},
|
||||
{file = "psycopg2_binary-2.8.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:659c815b5b8e2a55193ede2795c1e2349b8011497310bb936da7d4745652823b"},
|
||||
{file = "psycopg2_binary-2.8.4-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:2166e770cb98f02ed5ee2b0b569d40db26788e0bf2ec3ae1a0d864ea6f1d8309"},
|
||||
{file = "psycopg2_binary-2.8.4-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:7e6e3c52e6732c219c07bd97fff6c088f8df4dae3b79752ee3a817e6f32e177e"},
|
||||
{file = "psycopg2_binary-2.8.4-cp34-cp34m-win32.whl", hash = "sha256:040234f8a4a8dfd692662a8308d78f63f31a97e1c42d2480e5e6810c48966a29"},
|
||||
{file = "psycopg2_binary-2.8.4-cp34-cp34m-win_amd64.whl", hash = "sha256:69b13fdf12878b10dc6003acc8d0abf3ad93e79813fd5f3812497c1c9fb9be49"},
|
||||
{file = "psycopg2_binary-2.8.4-cp35-cp35m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:19dc39616850342a2a6db70559af55b22955f86667b5f652f40c0e99253d9881"},
|
||||
{file = "psycopg2_binary-2.8.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:9f24f383a298a0c0f9b3113b982e21751a8ecde6615494a3f1470eb4a9d70e9e"},
|
||||
{file = "psycopg2_binary-2.8.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:eaed1c65f461a959284649e37b5051224f4db6ebdc84e40b5e65f2986f101a08"},
|
||||
{file = "psycopg2_binary-2.8.4-cp35-cp35m-win32.whl", hash = "sha256:4c6717962247445b4f9e21c962ea61d2e884fc17df5ddf5e35863b016f8a1f03"},
|
||||
{file = "psycopg2_binary-2.8.4-cp35-cp35m-win_amd64.whl", hash = "sha256:84156313f258eafff716b2961644a4483a9be44a5d43551d554844d15d4d224e"},
|
||||
{file = "psycopg2_binary-2.8.4-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:3b5deaa3ee7180585a296af33e14c9b18c218d148e735c7accf78130765a47e3"},
|
||||
{file = "psycopg2_binary-2.8.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5057669b6a66aa9ca118a2a860159f0ee3acf837eda937bdd2a64f3431361a2d"},
|
||||
{file = "psycopg2_binary-2.8.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:afd96845e12638d2c44d213d4810a08f4dc4a563f9a98204b7428e567014b1cd"},
|
||||
{file = "psycopg2_binary-2.8.4-cp36-cp36m-win32.whl", hash = "sha256:a73021b44813b5c84eda4a3af5826dd72356a900bac9bd9dd1f0f81ee1c22c2f"},
|
||||
{file = "psycopg2_binary-2.8.4-cp36-cp36m-win_amd64.whl", hash = "sha256:407af6d7e46593415f216c7f56ba087a9a42bd6dc2ecb86028760aa45b802bd7"},
|
||||
{file = "psycopg2_binary-2.8.4-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:3aa773580f85a28ffdf6f862e59cb5a3cc7ef6885121f2de3fca8d6ada4dbf3b"},
|
||||
{file = "psycopg2_binary-2.8.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:eac8a3499754790187bb00574ab980df13e754777d346f85e0ff6df929bcd964"},
|
||||
{file = "psycopg2_binary-2.8.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:7a1cb80e35e1ccea3e11a48afe65d38744a0e0bde88795cc56a4d05b6e4f9d70"},
|
||||
{file = "psycopg2_binary-2.8.4-cp37-cp37m-win32.whl", hash = "sha256:086f7e89ec85a6704db51f68f0dcae432eff9300809723a6e8782c41c2f48e03"},
|
||||
{file = "psycopg2_binary-2.8.4-cp37-cp37m-win_amd64.whl", hash = "sha256:b73ddf033d8cd4cc9dfed6324b1ad2a89ba52c410ef6877998422fcb9c23e3a8"},
|
||||
{file = "psycopg2_binary-2.8.4-cp38-cp38-macosx_10_9_x86_64.macosx_10_9_intel.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:4c3c09fb674401f630626310bcaf6cd6285daf0d5e4c26d6e55ca26a2734e39b"},
|
||||
{file = "psycopg2_binary-2.8.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:18ca813fdb17bc1db73fe61b196b05dd1ca2165b884dd5ec5568877cabf9b039"},
|
||||
{file = "psycopg2_binary-2.8.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:50446fae5681fc99f87e505d4e77c9407e683ab60c555ec302f9ac9bffa61103"},
|
||||
{file = "psycopg2_binary-2.8.4-cp38-cp38-win32.whl", hash = "sha256:98e10634792ac0e9e7a92a76b4991b44c2325d3e7798270a808407355e7bb0a1"},
|
||||
{file = "psycopg2_binary-2.8.4-cp38-cp38-win_amd64.whl", hash = "sha256:b8f490f5fad1767a1331df1259763b3bad7d7af12a75b950c2843ba319b2415f"},
|
||||
]
|
||||
pycparser = [
|
||||
{file = "pycparser-2.19.tar.gz", hash = "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"},
|
||||
]
|
||||
pynacl = [
|
||||
{file = "PyNaCl-1.3.0-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:2424c8b9f41aa65bbdbd7a64e73a7450ebb4aa9ddedc6a081e7afcc4c97f7621"},
|
||||
{file = "PyNaCl-1.3.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:30f36a9c70450c7878053fa1344aca0145fd47d845270b43a7ee9192a051bf39"},
|
||||
{file = "PyNaCl-1.3.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:05c26f93964373fc0abe332676cb6735f0ecad27711035b9472751faa8521255"},
|
||||
{file = "PyNaCl-1.3.0-cp27-cp27m-win32.whl", hash = "sha256:a14e499c0f5955dcc3991f785f3f8e2130ed504fa3a7f44009ff458ad6bdd17f"},
|
||||
{file = "PyNaCl-1.3.0-cp27-cp27m-win_amd64.whl", hash = "sha256:f67814c38162f4deb31f68d590771a29d5ae3b1bd64b75cf232308e5c74777e0"},
|
||||
{file = "PyNaCl-1.3.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:e2da3c13307eac601f3de04887624939aca8ee3c9488a0bb0eca4fb9401fc6b1"},
|
||||
{file = "PyNaCl-1.3.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0d0a8171a68edf51add1e73d2159c4bc19fc0718e79dec51166e940856c2f28e"},
|
||||
{file = "PyNaCl-1.3.0-cp34-abi3-macosx_10_6_intel.whl", hash = "sha256:4943decfc5b905748f0756fdd99d4f9498d7064815c4cf3643820c9028b711d1"},
|
||||
{file = "PyNaCl-1.3.0-cp34-abi3-manylinux1_i686.whl", hash = "sha256:5bd61e9b44c543016ce1f6aef48606280e45f892a928ca7068fba30021e9b786"},
|
||||
{file = "PyNaCl-1.3.0-cp34-abi3-manylinux1_x86_64.whl", hash = "sha256:aabb0c5232910a20eec8563503c153a8e78bbf5459490c49ab31f6adf3f3a415"},
|
||||
{file = "PyNaCl-1.3.0-cp34-cp34m-win32.whl", hash = "sha256:7d3ce02c0784b7cbcc771a2da6ea51f87e8716004512493a2b69016326301c3b"},
|
||||
{file = "PyNaCl-1.3.0-cp34-cp34m-win_amd64.whl", hash = "sha256:1c780712b206317a746ace34c209b8c29dbfd841dfbc02aa27f2084dd3db77ae"},
|
||||
{file = "PyNaCl-1.3.0-cp35-cp35m-win32.whl", hash = "sha256:37aa336a317209f1bb099ad177fef0da45be36a2aa664507c5d72015f956c310"},
|
||||
{file = "PyNaCl-1.3.0-cp35-cp35m-win_amd64.whl", hash = "sha256:57ef38a65056e7800859e5ba9e6091053cd06e1038983016effaffe0efcd594a"},
|
||||
{file = "PyNaCl-1.3.0-cp36-cp36m-win32.whl", hash = "sha256:a39f54ccbcd2757d1d63b0ec00a00980c0b382c62865b61a505163943624ab20"},
|
||||
{file = "PyNaCl-1.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6482d3017a0c0327a49dddc8bd1074cc730d45db2ccb09c3bac1f8f32d1eb61b"},
|
||||
{file = "PyNaCl-1.3.0-cp37-cp37m-win32.whl", hash = "sha256:2d23c04e8d709444220557ae48ed01f3f1086439f12dbf11976e849a4926db56"},
|
||||
{file = "PyNaCl-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bd4ecb473a96ad0f90c20acba4f0bf0df91a4e03a1f4dd6a4bdc9ca75aa3a715"},
|
||||
{file = "PyNaCl-1.3.0-cp38-cp38-win32.whl", hash = "sha256:53126cd91356342dcae7e209f840212a58dcf1177ad52c1d938d428eebc9fee5"},
|
||||
{file = "PyNaCl-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:bf459128feb543cfca16a95f8da31e2e65e4c5257d2f3dfa8c0c1031139c9c92"},
|
||||
{file = "PyNaCl-1.3.0.tar.gz", hash = "sha256:0c6100edd16fefd1557da078c7a31e7b7d7a52ce39fdca2bec29d4f7b6e7600c"},
|
||||
]
|
||||
python-dateutil = [
|
||||
{file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"},
|
||||
{file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"},
|
||||
]
|
||||
python-telegram-bot = [
|
||||
{file = "python-telegram-bot-12.2.0.tar.gz", hash = "sha256:346d42771c2b23384c59f5f41e05bd7e801a0ce118d8dcb95209bb73d5f694c5"},
|
||||
{file = "python_telegram_bot-12.2.0-py2.py3-none-any.whl", hash = "sha256:3beee89cba3bc3217566c96199f04776dd25f541ac8992da27fd247b2d208a14"},
|
||||
]
|
||||
pytz = [
|
||||
{file = "pytz-2019.3-py2.py3-none-any.whl", hash = "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d"},
|
||||
{file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"},
|
||||
]
|
||||
regex = [
|
||||
{file = "regex-2019.11.1-cp27-none-win32.whl", hash = "sha256:604dc563a02a74d70ae1f55208ddc9bfb6d9f470f6d1a5054c4bd5ae58744ab1"},
|
||||
{file = "regex-2019.11.1-cp27-none-win_amd64.whl", hash = "sha256:5e00f65cc507d13ab4dfa92c1232d004fa202c1d43a32a13940ab8a5afe2fb96"},
|
||||
{file = "regex-2019.11.1-cp35-none-win32.whl", hash = "sha256:15454b37c5a278f46f7aa2d9339bda450c300617ca2fca6558d05d870245edc7"},
|
||||
{file = "regex-2019.11.1-cp35-none-win_amd64.whl", hash = "sha256:d2b302f8cdd82c8f48e9de749d1d17f85ce9a0f082880b9a4859f66b07037dc6"},
|
||||
{file = "regex-2019.11.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b4e0406d822aa4993ac45072a584d57aa4931cf8288b5455bbf30c1d59dbad59"},
|
||||
{file = "regex-2019.11.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:7faf534c1841c09d8fefa60ccde7b9903c9b528853ecf41628689793290ca143"},
|
||||
{file = "regex-2019.11.1-cp36-none-win32.whl", hash = "sha256:7caf47e4a9ac6ef08cabd3442cc4ca3386db141fb3c8b2a7e202d0470028e910"},
|
||||
{file = "regex-2019.11.1-cp36-none-win_amd64.whl", hash = "sha256:e3d8dd0ec0ea280cf89026b0898971f5750a7bd92cb62c51af5a52abd020054a"},
|
||||
{file = "regex-2019.11.1-cp37-none-win32.whl", hash = "sha256:c31eaf28c6fe75ea329add0022efeed249e37861c19681960f99bbc7db981fb2"},
|
||||
{file = "regex-2019.11.1-cp37-none-win_amd64.whl", hash = "sha256:1ad40708c255943a227e778b022c6497c129ad614bb7a2a2f916e12e8a359ee7"},
|
||||
{file = "regex-2019.11.1-cp38-none-win32.whl", hash = "sha256:ec032cbfed59bd5a4b8eab943c310acfaaa81394e14f44454ad5c9eba4f24a74"},
|
||||
{file = "regex-2019.11.1-cp38-none-win_amd64.whl", hash = "sha256:c7393597191fc2043c744db021643549061e12abe0b3ff5c429d806de7b93b66"},
|
||||
{file = "regex-2019.11.1.tar.gz", hash = "sha256:720e34a539a76a1fedcebe4397290604cc2bdf6f81eca44adb9fb2ea071c0c69"},
|
||||
]
|
||||
sentry-sdk = [
|
||||
{file = "sentry-sdk-0.13.2.tar.gz", hash = "sha256:ff1fa7fb85703ae9414c8b427ee73f8363232767c9cd19158f08f6e4f0b58fc7"},
|
||||
{file = "sentry_sdk-0.13.2-py2.py3-none-any.whl", hash = "sha256:09e1e8f00f22ea580348f83bbbd880adf40b29f1dec494a8e4b33e22f77184fb"},
|
||||
]
|
||||
six = [
|
||||
{file = "six-1.13.0-py2.py3-none-any.whl", hash = "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd"},
|
||||
{file = "six-1.13.0.tar.gz", hash = "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"},
|
||||
]
|
||||
sqlalchemy = [
|
||||
{file = "SQLAlchemy-1.3.11.tar.gz", hash = "sha256:afa5541e9dea8ad0014251bc9d56171ca3d8b130c9627c6cb3681cff30be3f8a"},
|
||||
]
|
||||
starlette = [
|
||||
{file = "starlette-0.12.13.tar.gz", hash = "sha256:9597bc28e3c4659107c1c4a45ec32dc45e947d78fe56230222be673b2c36454a"},
|
||||
]
|
||||
tornado = [
|
||||
{file = "tornado-6.0.3-cp35-cp35m-win32.whl", hash = "sha256:c9399267c926a4e7c418baa5cbe91c7d1cf362d505a1ef898fde44a07c9dd8a5"},
|
||||
{file = "tornado-6.0.3-cp35-cp35m-win_amd64.whl", hash = "sha256:398e0d35e086ba38a0427c3b37f4337327231942e731edaa6e9fd1865bbd6f60"},
|
||||
{file = "tornado-6.0.3-cp36-cp36m-win32.whl", hash = "sha256:4e73ef678b1a859f0cb29e1d895526a20ea64b5ffd510a2307b5998c7df24281"},
|
||||
{file = "tornado-6.0.3-cp36-cp36m-win_amd64.whl", hash = "sha256:349884248c36801afa19e342a77cc4458caca694b0eda633f5878e458a44cb2c"},
|
||||
{file = "tornado-6.0.3-cp37-cp37m-win32.whl", hash = "sha256:559bce3d31484b665259f50cd94c5c28b961b09315ccd838f284687245f416e5"},
|
||||
{file = "tornado-6.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:abbe53a39734ef4aba061fca54e30c6b4639d3e1f59653f0da37a0003de148c7"},
|
||||
{file = "tornado-6.0.3.tar.gz", hash = "sha256:c845db36ba616912074c5b1ee897f8e0124df269468f25e4fe21fe72f6edd7a9"},
|
||||
]
|
||||
tzlocal = [
|
||||
{file = "tzlocal-2.0.0-py2.py3-none-any.whl", hash = "sha256:11c9f16e0a633b4b60e1eede97d8a46340d042e67b670b290ca526576e039048"},
|
||||
{file = "tzlocal-2.0.0.tar.gz", hash = "sha256:949b9dd5ba4be17190a80c0268167d7e6c92c62b30026cf9764caf3e308e5590"},
|
||||
]
|
||||
urllib3 = [
|
||||
{file = "urllib3-1.25.7-py2.py3-none-any.whl", hash = "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293"},
|
||||
{file = "urllib3-1.25.7.tar.gz", hash = "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"},
|
||||
]
|
||||
uvicorn = [
|
||||
{file = "uvicorn-0.10.8.tar.gz", hash = "sha256:f4c34642618449f55e2bab8c6b22ff7615b520d2e7e23275be2ca894254327a3"},
|
||||
]
|
||||
uvloop = [
|
||||
{file = "uvloop-0.14.0-cp35-cp35m-macosx_10_11_x86_64.whl", hash = "sha256:08b109f0213af392150e2fe6f81d33261bb5ce968a288eb698aad4f46eb711bd"},
|
||||
{file = "uvloop-0.14.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:4544dcf77d74f3a84f03dd6278174575c44c67d7165d4c42c71db3fdc3860726"},
|
||||
{file = "uvloop-0.14.0-cp36-cp36m-macosx_10_11_x86_64.whl", hash = "sha256:b4f591aa4b3fa7f32fb51e2ee9fea1b495eb75b0b3c8d0ca52514ad675ae63f7"},
|
||||
{file = "uvloop-0.14.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f07909cd9fc08c52d294b1570bba92186181ca01fe3dc9ffba68955273dd7362"},
|
||||
{file = "uvloop-0.14.0-cp37-cp37m-macosx_10_11_x86_64.whl", hash = "sha256:afd5513c0ae414ec71d24f6f123614a80f3d27ca655a4fcf6cabe50994cc1891"},
|
||||
{file = "uvloop-0.14.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:e7514d7a48c063226b7d06617cbb12a14278d4323a065a8d46a7962686ce2e95"},
|
||||
{file = "uvloop-0.14.0-cp38-cp38-macosx_10_11_x86_64.whl", hash = "sha256:bcac356d62edd330080aed082e78d4b580ff260a677508718f88016333e2c9c5"},
|
||||
{file = "uvloop-0.14.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4315d2ec3ca393dd5bc0b0089d23101276778c304d42faff5dc4579cb6caef09"},
|
||||
{file = "uvloop-0.14.0.tar.gz", hash = "sha256:123ac9c0c7dd71464f58f1b4ee0bbd81285d96cdda8bc3519281b8973e3a461e"},
|
||||
]
|
||||
websockets = [
|
||||
{file = "websockets-8.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c"},
|
||||
{file = "websockets-8.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170"},
|
||||
{file = "websockets-8.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8"},
|
||||
{file = "websockets-8.1-cp36-cp36m-win32.whl", hash = "sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a"},
|
||||
{file = "websockets-8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5"},
|
||||
{file = "websockets-8.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989"},
|
||||
{file = "websockets-8.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d"},
|
||||
{file = "websockets-8.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779"},
|
||||
{file = "websockets-8.1-cp37-cp37m-win32.whl", hash = "sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc"},
|
||||
{file = "websockets-8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308"},
|
||||
{file = "websockets-8.1.tar.gz", hash = "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f"},
|
||||
]
|
||||
yarl = [
|
||||
{file = "yarl-1.3.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:3e2724eb9af5dc41648e5bb304fcf4891adc33258c6e14e2a7414ea32541e320"},
|
||||
{file = "yarl-1.3.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:3890ab952d508523ef4881457c4099056546593fa05e93da84c7250516e632eb"},
|
||||
{file = "yarl-1.3.0-cp35-cp35m-win32.whl", hash = "sha256:7ab825726f2940c16d92aaec7d204cfc34ac26c0040da727cf8ba87255a33829"},
|
||||
{file = "yarl-1.3.0-cp35-cp35m-win_amd64.whl", hash = "sha256:b25de84a8c20540531526dfbb0e2d2b648c13fd5dd126728c496d7c3fea33310"},
|
||||
{file = "yarl-1.3.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:2f3010703295fbe1aec51023740871e64bb9664c789cba5a6bdf404e93f7568f"},
|
||||
{file = "yarl-1.3.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5badb97dd0abf26623a9982cd448ff12cb39b8e4c94032ccdedf22ce01a64842"},
|
||||
{file = "yarl-1.3.0-cp36-cp36m-win32.whl", hash = "sha256:c9bb7c249c4432cd47e75af3864bc02d26c9594f49c82e2a28624417f0ae63b8"},
|
||||
{file = "yarl-1.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c6e341f5a6562af74ba55205dbd56d248daf1b5748ec48a0200ba227bb9e33f4"},
|
||||
{file = "yarl-1.3.0-cp37-cp37m-win32.whl", hash = "sha256:e060906c0c585565c718d1c3841747b61c5439af2211e185f6739a9412dfbde1"},
|
||||
{file = "yarl-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:73f447d11b530d860ca1e6b582f947688286ad16ca42256413083d13f260b7a0"},
|
||||
{file = "yarl-1.3.0.tar.gz", hash = "sha256:024ecdc12bc02b321bc66b41327f930d1c2c543fa9a561b39861da9388ba7aa9"},
|
||||
]
|
||||
youtube-dl = [
|
||||
{file = "youtube_dl-2019.11.5-py2.py3-none-any.whl", hash = "sha256:1314de17f0d41c0f1062c4942406b8e0558d14d44b32f9fce00272760a06455b"},
|
||||
{file = "youtube_dl-2019.11.5.tar.gz", hash = "sha256:25324aab78df9a09b2ee34f642f116933134bc66ea629a778c1fffe05b66f733"},
|
||||
]
|
|
@ -1,3 +1,5 @@
|
|||
# Remember to run `poetry update` after you edit this file!
|
||||
|
||||
[tool.poetry]
|
||||
name = "royalnet"
|
||||
version = "5.1a1"
|
||||
|
@ -18,30 +20,41 @@
|
|||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
dateparser = "^0.7.2"
|
||||
# telegram
|
||||
python_telegram_bot = {version="^12.2.0", optional=true}
|
||||
discord_py = {git = "https://github.com/Rapptz/discord.py", optional=true} # discord.py 1.2.4 is missing Go Live related methods
|
||||
# discord
|
||||
"discord.py" = {git = "https://github.com/Steffo99/discord.py", optional=true} # discord.py 1.2.4 is missing Go Live related methods
|
||||
pynacl = {version="^1.3.0", optional=true} # This requires libffi-dev and python3.*-dev to be installed on Linux systems
|
||||
# bard
|
||||
ffmpeg_python = {version="~0.2.0", optional=true}
|
||||
youtube_dl = {version="*", optional=true}
|
||||
# alchemy
|
||||
sqlalchemy = {version="^1.3.10", optional=true}
|
||||
psycopg2 = {version="^2.8.4", optional=true} # Requires quite a bit of stuff http://initd.org/psycopg/docs/install.html#install-from-source
|
||||
psycopg2_binary = {version="^2.8.4", optional=true} # Prebuilt alternative to psycopg2, not recommended
|
||||
# constellation
|
||||
starlette = {version="^0.12.13", optional=true}
|
||||
uvicorn = {version="^0.10.7", optional=true}
|
||||
# sentry
|
||||
sentry_sdk = {version="~0.13.2", optional=true}
|
||||
# herald
|
||||
websockets = {version="^8.1", optional=true}
|
||||
|
||||
# Development dependencies
|
||||
[tool.poetry.dev-dependencies]
|
||||
pytest = "^5.2.1"
|
||||
|
||||
# Optional dependencies
|
||||
[tool.poetry.extras]
|
||||
telegram = ["python_telegram_bot"]
|
||||
discord = ["discord_py", "pynacl"]
|
||||
discord = ["discord.py", "pynacl"]
|
||||
alchemy_easy = ["sqlalchemy", "psycopg2_binary"]
|
||||
alchemy_hard = ["sqlalchemy", "psycopg2"]
|
||||
bard = ["ffmpeg_python", "youtube_dl"]
|
||||
constellation = ["starlette"]
|
||||
constellation = ["starlette", "uvicorn"]
|
||||
sentry = ["sentry_sdk"]
|
||||
herald = ["websockets"]
|
||||
|
||||
# Development dependencies
|
||||
[tool.poetry.dev-dependencies]
|
||||
# There are none
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry>=0.12"]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Set, Dict, Union, Optional
|
||||
from typing import Set, Dict, Union, Type
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.engine import Engine
|
||||
from sqlalchemy.schema import Table
|
||||
|
@ -28,15 +28,17 @@ class Alchemy:
|
|||
self._engine: Engine = create_engine(database_uri)
|
||||
self._Base: DeclarativeMeta = declarative_base(bind=self._engine)
|
||||
self._Session: sessionmaker = sessionmaker(bind=self._engine)
|
||||
self._tables: Dict[str, Table] = {}
|
||||
self._tables: Dict[str, Type[Table]] = {}
|
||||
for table in tables:
|
||||
name = table.__name__
|
||||
assert self._tables.get(name) is None
|
||||
assert isinstance(name, str)
|
||||
self._tables[name] = type(name, (self._Base, table), {})
|
||||
# noinspection PyTypeChecker
|
||||
bound_table: Type[Table] = type(name, (self._Base, table), {})
|
||||
self._tables[name] = bound_table
|
||||
self._Base.metadata.create_all()
|
||||
|
||||
def get(self, table: Union[str, type]) -> Optional[Table]:
|
||||
def get(self, table: Union[str, type]) -> Type[Table]:
|
||||
"""Get the table with a specified name or class.
|
||||
|
||||
Args:
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
class AlchemyException(Exception):
|
||||
"""Base class for Alchemy exceptions."""
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from typing import Optional
|
||||
from typing import Type
|
||||
from sqlalchemy.inspection import inspect
|
||||
from sqlalchemy.schema import Table
|
||||
|
||||
|
||||
def table_dfs(starting_table: Table, ending_table: Table) -> tuple:
|
||||
def table_dfs(starting_table: Type[Table], ending_table: Type[Table]) -> tuple:
|
||||
"""Depth-first-search for the path from the starting table to the ending table.
|
||||
|
||||
Returns:
|
||||
|
|
|
@ -2,7 +2,7 @@ from .commandinterface import CommandInterface
|
|||
from .command import Command
|
||||
from .commanddata import CommandData
|
||||
from .commandargs import CommandArgs
|
||||
from .commanderrors import CommandError, InvalidInputError, UnsupportedError, KeyboardExpiredError, ConfigurationError
|
||||
from .errors import CommandError, InvalidInputError, UnsupportedError, KeyboardExpiredError, ConfigurationError
|
||||
|
||||
__all__ = [
|
||||
"CommandInterface",
|
||||
|
|
|
@ -1,16 +1,33 @@
|
|||
import re
|
||||
from typing import Pattern, AnyStr, Optional, Sequence, Union
|
||||
from .commanderrors import InvalidInputError
|
||||
from .errors import InvalidInputError
|
||||
|
||||
|
||||
class CommandArgs(list):
|
||||
"""An interface to access the arguments of a command with ease."""
|
||||
"""An interface to easily access the arguments of a command.
|
||||
|
||||
Inherits from :class:`list`."""
|
||||
|
||||
def __getitem__(self, item):
|
||||
"""Arguments can be accessed with an array notation, such as ``args[0]``.
|
||||
"""Access arguments as if they were a :class:`list`.
|
||||
|
||||
Raises:
|
||||
royalnet.error.InvalidInputError: if the requested argument does not exist."""
|
||||
royalnet.error.InvalidInputError: if the requested argument does not exist.
|
||||
|
||||
Examples:
|
||||
::
|
||||
|
||||
# /pasta spaghetti aldente
|
||||
>>> self[0]
|
||||
"spaghetti"
|
||||
>>> self[1]
|
||||
"aldente"
|
||||
>>> self[2]
|
||||
# InvalidInputError: Missing argument #3.
|
||||
>>> self[0:2]
|
||||
["spaghetti", "aldente"]
|
||||
|
||||
"""
|
||||
if isinstance(item, int):
|
||||
try:
|
||||
return super().__getitem__(item)
|
||||
|
@ -33,7 +50,18 @@ class CommandArgs(list):
|
|||
royalnet.error.InvalidInputError: if there are less than ``require_at_least`` arguments.
|
||||
|
||||
Returns:
|
||||
The space-joined string."""
|
||||
The space-joined string.
|
||||
|
||||
Examples:
|
||||
::
|
||||
|
||||
# /pasta spaghetti aldente
|
||||
>>> self.joined()
|
||||
"spaghetti aldente"
|
||||
>>> self.joined(require_at_least=3)
|
||||
# InvalidInputError: Not enough arguments specified (minimum is 3).
|
||||
|
||||
"""
|
||||
if len(self) < require_at_least:
|
||||
raise InvalidInputError(f"Not enough arguments specified (minimum is {require_at_least}).")
|
||||
return " ".join(self)
|
||||
|
@ -63,7 +91,20 @@ class CommandArgs(list):
|
|||
default: The value returned if the argument is missing.
|
||||
|
||||
Returns:
|
||||
Either the argument or the ``default`` value, defaulting to ``None``."""
|
||||
Either the argument or the ``default`` value, defaulting to ``None``.
|
||||
|
||||
Examples:
|
||||
::
|
||||
|
||||
# /pasta spaghetti aldente
|
||||
>>> self.optional(0)
|
||||
"spaghetti"
|
||||
>>> self.optional(2)
|
||||
None
|
||||
>>> self.optional(2, default="carbonara")
|
||||
"carbonara"
|
||||
|
||||
"""
|
||||
try:
|
||||
return self[index]
|
||||
except InvalidInputError:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from typing import Dict, Callable
|
||||
import warnings
|
||||
from .commanderrors import UnsupportedError
|
||||
from .errors import UnsupportedError
|
||||
from .commandinterface import CommandInterface
|
||||
from ..utils import asyncify
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import typing
|
||||
import asyncio
|
||||
from .commanderrors import UnsupportedError
|
||||
from .errors import UnsupportedError
|
||||
if typing.TYPE_CHECKING:
|
||||
from .command import Command
|
||||
from ..alchemy import Alchemy
|
||||
from ..interfaces import GenericBot
|
||||
from ..serf import GenericBot
|
||||
|
||||
|
||||
class CommandInterface:
|
||||
|
|
26
royalnet/herald/__init__.py
Normal file
26
royalnet/herald/__init__.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
from .config import Config
|
||||
from .errors import HeraldError, ConnectionClosedError, LinkError, InvalidServerResponseError, ServerError
|
||||
from .link import Link
|
||||
from .package import Package
|
||||
from .request import Request
|
||||
from .response import Response, ResponseSuccess, ResponseFailure
|
||||
from .server import Server
|
||||
from .broadcast import Broadcast
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Config",
|
||||
"HeraldError",
|
||||
"ConnectionClosedError",
|
||||
"LinkError",
|
||||
"InvalidServerResponseError",
|
||||
"ServerError",
|
||||
"Link",
|
||||
"Package",
|
||||
"Request",
|
||||
"Response",
|
||||
"ResponseSuccess",
|
||||
"ResponseFailure",
|
||||
"Server",
|
||||
"Broadcast",
|
||||
]
|
26
royalnet/herald/broadcast.py
Normal file
26
royalnet/herald/broadcast.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
import typing
|
||||
|
||||
|
||||
class Broadcast:
|
||||
def __init__(self, handler: str, data: dict, msg_type: typing.Optional[str] = None):
|
||||
super().__init__()
|
||||
if msg_type is not None:
|
||||
assert msg_type == self.__class__.__name__
|
||||
self.msg_type = self.__class__.__name__
|
||||
self.handler: str = handler
|
||||
self.data: dict = data
|
||||
|
||||
def to_dict(self):
|
||||
return self.__dict__
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d: dict):
|
||||
return cls(**d)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, self.__class__):
|
||||
return self.handler == other.handler and self.data == other.data
|
||||
return False
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__qualname__}(handler={self.handler}, data={self.data})"
|
34
royalnet/herald/config.py
Normal file
34
royalnet/herald/config.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
class Config:
|
||||
def __init__(self,
|
||||
name: str,
|
||||
address: str,
|
||||
port: int,
|
||||
secret: str,
|
||||
secure: bool = False,
|
||||
path: str = "/"):
|
||||
if ":" in name:
|
||||
raise ValueError("Herald names cannot contain colons (:)")
|
||||
self.name = name
|
||||
|
||||
self.address = address
|
||||
|
||||
if port < 0 or port > 65535:
|
||||
raise ValueError("No such port")
|
||||
self.port = port
|
||||
|
||||
self.secure = secure
|
||||
|
||||
if ":" in secret:
|
||||
raise ValueError("Herald secrets cannot contain colons (:)")
|
||||
self.secret = secret
|
||||
|
||||
if not path.startswith("/"):
|
||||
raise ValueError("Herald paths must start with a slash (/)")
|
||||
self.path = path
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return f"ws{'s' if self.secure else ''}://{self.address}:{self.port}{self.path}"
|
||||
|
||||
def __repr__(self):
|
||||
return f"<HeraldConfig for {self.url}>"
|
18
royalnet/herald/errors.py
Normal file
18
royalnet/herald/errors.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
class HeraldError(Exception):
|
||||
"""A generic :py:mod:`royalherald` error."""
|
||||
|
||||
|
||||
class LinkError(HeraldError):
|
||||
"""An error for something that happened in a :py:class:`Link`."""
|
||||
|
||||
|
||||
class ServerError(HeraldError):
|
||||
"""An error for something that happened in a :py:class:`Server`."""
|
||||
|
||||
|
||||
class ConnectionClosedError(LinkError):
|
||||
"""The :py:class:`Link`'s connection was closed unexpectedly. The link can't be used anymore."""
|
||||
|
||||
|
||||
class InvalidServerResponseError(LinkError):
|
||||
"""The :py:class:`Server` sent invalid data to the :py:class:`Link`."""
|
179
royalnet/herald/link.py
Normal file
179
royalnet/herald/link.py
Normal file
|
@ -0,0 +1,179 @@
|
|||
import asyncio
|
||||
import websockets
|
||||
import uuid
|
||||
import functools
|
||||
import logging as _logging
|
||||
import typing
|
||||
from .package import Package
|
||||
from .request import Request
|
||||
from .response import Response, ResponseSuccess, ResponseFailure
|
||||
from .broadcast import Broadcast
|
||||
from .errors import ConnectionClosedError, InvalidServerResponseError
|
||||
from .config import Config
|
||||
|
||||
|
||||
log = _logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PendingRequest:
|
||||
def __init__(self, *, loop: asyncio.AbstractEventLoop = None):
|
||||
if loop is None:
|
||||
self.loop = asyncio.get_event_loop()
|
||||
else:
|
||||
self.loop = loop
|
||||
self.event: asyncio.Event = asyncio.Event(loop=loop)
|
||||
self.data: typing.Optional[dict] = None
|
||||
|
||||
def __repr__(self):
|
||||
if self.event.is_set():
|
||||
return f"<{self.__class__.__qualname__}: {self.data.__class__.__name__}>"
|
||||
return f"<{self.__class__.__qualname__}>"
|
||||
|
||||
def set(self, data):
|
||||
self.data = data
|
||||
self.event.set()
|
||||
|
||||
|
||||
def requires_connection(func):
|
||||
@functools.wraps(func)
|
||||
async def new_func(self, *args, **kwargs):
|
||||
await self.connect_event.wait()
|
||||
return await func(self, *args, **kwargs)
|
||||
return new_func
|
||||
|
||||
|
||||
def requires_identification(func):
|
||||
@functools.wraps(func)
|
||||
async def new_func(self, *args, **kwargs):
|
||||
await self.identify_event.wait()
|
||||
return await func(self, *args, **kwargs)
|
||||
return new_func
|
||||
|
||||
|
||||
class Link:
|
||||
def __init__(self, config: Config, request_handler, *,
|
||||
loop: asyncio.AbstractEventLoop = None):
|
||||
self.config: Config = config
|
||||
self.nid: str = str(uuid.uuid4())
|
||||
self.websocket: typing.Optional[websockets.WebSocketClientProtocol] = None
|
||||
self.request_handler: typing.Callable[[typing.Union[Request, Broadcast]],
|
||||
typing.Awaitable[Response]] = request_handler
|
||||
self._pending_requests: typing.Dict[str, PendingRequest] = {}
|
||||
if loop is None:
|
||||
self._loop = asyncio.get_event_loop()
|
||||
else:
|
||||
self._loop = loop
|
||||
self.error_event: asyncio.Event = asyncio.Event(loop=self._loop)
|
||||
self.connect_event: asyncio.Event = asyncio.Event(loop=self._loop)
|
||||
self.identify_event: asyncio.Event = asyncio.Event(loop=self._loop)
|
||||
|
||||
def __repr__(self):
|
||||
if self.identify_event.is_set():
|
||||
return f"<{self.__class__.__qualname__} (identified)>"
|
||||
elif self.connect_event.is_set():
|
||||
return f"<{self.__class__.__qualname__} (connected)>"
|
||||
elif self.error_event.is_set():
|
||||
return f"<{self.__class__.__qualname__} (error)>"
|
||||
else:
|
||||
return f"<{self.__class__.__qualname__} (disconnected)>"
|
||||
|
||||
async def connect(self):
|
||||
"""Connect to the :py:class:`royalnet.network.NetworkServer` at ``self.master_uri``."""
|
||||
log.info(f"Connecting to {self.config.url}...")
|
||||
self.websocket = await websockets.connect(self.config.url, loop=self._loop)
|
||||
self.connect_event.set()
|
||||
log.info(f"Connected!")
|
||||
|
||||
@requires_connection
|
||||
async def receive(self) -> Package:
|
||||
"""Recieve a :py:class:`Package` from the :py:class:`Server`.
|
||||
|
||||
Raises:
|
||||
:py:exc:`royalnet.network.royalnetlink.ConnectionClosedError` if the connection closes."""
|
||||
try:
|
||||
jbytes: bytes = await self.websocket.recv()
|
||||
package: Package = Package.from_json_bytes(jbytes)
|
||||
except websockets.ConnectionClosed:
|
||||
self.error_event.set()
|
||||
self.connect_event.clear()
|
||||
self.identify_event.clear()
|
||||
log.info(f"Connection to {self.config.url} was closed.")
|
||||
# What to do now? Let's just reraise.
|
||||
raise ConnectionClosedError()
|
||||
if self.identify_event.is_set() and package.destination != self.nid:
|
||||
raise InvalidServerResponseError("Package is not addressed to this NetworkLink.")
|
||||
log.debug(f"Received package: {package}")
|
||||
return package
|
||||
|
||||
@requires_connection
|
||||
async def identify(self) -> None:
|
||||
log.info(f"Identifying...")
|
||||
await self.websocket.send(f"Identify {self.nid}:{self.config.name}:{self.config.secret}")
|
||||
response: Package = await self.receive()
|
||||
if not response.source == "<server>":
|
||||
raise InvalidServerResponseError("Received a non-service package before identification.")
|
||||
if "type" not in response.data:
|
||||
raise InvalidServerResponseError("Missing 'type' in response data")
|
||||
if response.data["type"] == "error":
|
||||
raise ConnectionClosedError(f"Identification error: {response.data['type']}")
|
||||
assert response.data["type"] == "success"
|
||||
self.identify_event.set()
|
||||
log.info(f"Identified successfully!")
|
||||
|
||||
@requires_identification
|
||||
async def send(self, package: Package):
|
||||
await self.websocket.send(package.to_json_bytes())
|
||||
log.debug(f"Sent package: {package}")
|
||||
|
||||
@requires_identification
|
||||
async def broadcast(self, destination: str, broadcast: Broadcast) -> None:
|
||||
package = Package(broadcast.to_dict(), source=self.nid, destination=destination)
|
||||
await self.send(package)
|
||||
log.debug(f"Sent broadcast: {broadcast}")
|
||||
|
||||
@requires_identification
|
||||
async def request(self, destination: str, request: Request) -> Response:
|
||||
if destination.startswith("*"):
|
||||
raise ValueError("requests cannot have multiple destinations")
|
||||
package = Package(request.to_dict(), source=self.nid, destination=destination)
|
||||
request = PendingRequest(loop=self._loop)
|
||||
self._pending_requests[package.source_conv_id] = request
|
||||
await self.send(package)
|
||||
log.debug(f"Sent request to {destination}: {request}")
|
||||
await request.event.wait()
|
||||
if request.data["type"] == "ResponseSuccess":
|
||||
response: Response = ResponseSuccess.from_dict(request.data)
|
||||
elif request.data["type"] == "ResponseFailure":
|
||||
response: Response = ResponseFailure.from_dict(request.data)
|
||||
else:
|
||||
raise TypeError("Unknown response type")
|
||||
log.debug(f"Received from {destination}: {request} -> {response}")
|
||||
return response
|
||||
|
||||
async def run(self):
|
||||
"""Blockingly run the Link."""
|
||||
log.debug(f"Running main client loop for {self.nid}.")
|
||||
if self.error_event.is_set():
|
||||
raise ConnectionClosedError("RoyalnetLinks can't be rerun after an error.")
|
||||
while True:
|
||||
if not self.connect_event.is_set():
|
||||
await self.connect()
|
||||
if not self.identify_event.is_set():
|
||||
await self.identify()
|
||||
package: Package = await self.receive()
|
||||
# Package is a response
|
||||
if package.destination_conv_id in self._pending_requests:
|
||||
request = self._pending_requests[package.destination_conv_id]
|
||||
request.set(package.data)
|
||||
continue
|
||||
# Package is a request
|
||||
elif package.data["msg_type"] == "Request":
|
||||
log.debug(f"Received request {package.source_conv_id}: {package}")
|
||||
response: Response = await self.request_handler(Request.from_dict(package.data))
|
||||
response_package: Package = package.reply(response.to_dict())
|
||||
await self.send(response_package)
|
||||
log.debug(f"Replied to request {response_package.source_conv_id}: {response_package}")
|
||||
# Package is a broadcast
|
||||
elif package.data["msg_type"] == "Broadcast":
|
||||
log.debug(f"Received broadcast {package.source_conv_id}: {package}")
|
||||
await self.request_handler(Broadcast.from_dict(package.data))
|
113
royalnet/herald/package.py
Normal file
113
royalnet/herald/package.py
Normal file
|
@ -0,0 +1,113 @@
|
|||
import json
|
||||
import uuid
|
||||
import typing
|
||||
|
||||
|
||||
class Package:
|
||||
"""A ``royalherald`` package, the data type with which a :py:class:`Link` communicates with a :py:class:`Server` or
|
||||
another Link.
|
||||
|
||||
Contains info about the source and the destination."""
|
||||
|
||||
def __init__(self,
|
||||
data: dict,
|
||||
*,
|
||||
source: str,
|
||||
destination: str,
|
||||
source_conv_id: typing.Optional[str] = None,
|
||||
destination_conv_id: typing.Optional[str] = None):
|
||||
"""Create a Package.
|
||||
|
||||
Parameters:
|
||||
data: The data that should be sent.
|
||||
source: The ``nid`` of the node that created this Package.
|
||||
destination: The ``link_type`` of the destination node, or alternatively, the ``nid`` of the node. Can also be the ``NULL`` value to send the message to nobody.
|
||||
source_conv_id: The conversation id of the node that created this package. Akin to the sequence number on IP packets.
|
||||
destination_conv_id: The conversation id of the node that this Package is a reply to."""
|
||||
# TODO: something is not right in these type hints. Check them.
|
||||
self.data: dict = data
|
||||
self.source: str = source
|
||||
self.source_conv_id: str = source_conv_id or str(uuid.uuid4())
|
||||
self.destination: str = destination
|
||||
self.destination_conv_id: typing.Optional[str] = destination_conv_id
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__qualname__} {self.source} ({self.source_conv_id}) to {self.destination} ({self.destination_conv_id}>"
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Package):
|
||||
return (self.data == other.data) and \
|
||||
(self.source == other.source) and \
|
||||
(self.destination == other.destination) and \
|
||||
(self.source_conv_id == other.source_conv_id) and \
|
||||
(self.destination_conv_id == other.destination_conv_id)
|
||||
return False
|
||||
|
||||
def reply(self, data) -> "Package":
|
||||
"""Reply to this Package with another Package.
|
||||
|
||||
Parameters:
|
||||
data: The data that should be sent. Usually a :py:class:`royalnet.network.Message`.
|
||||
|
||||
Returns:
|
||||
The reply Package."""
|
||||
return Package(data,
|
||||
source=self.destination,
|
||||
destination=self.source,
|
||||
source_conv_id=self.destination_conv_id or str(uuid.uuid4()),
|
||||
destination_conv_id=self.source_conv_id)
|
||||
|
||||
@staticmethod
|
||||
def from_dict(d) -> "Package":
|
||||
"""Create a Package from a dictionary."""
|
||||
if "source" not in d:
|
||||
raise ValueError("Missing source field")
|
||||
if "nid" not in d["source"]:
|
||||
raise ValueError("Missing source.nid field")
|
||||
if "conv_id" not in d["source"]:
|
||||
raise ValueError("Missing source.conv_id field")
|
||||
if "destination" not in d:
|
||||
raise ValueError("Missing destination field")
|
||||
if "nid" not in d["destination"]:
|
||||
raise ValueError("Missing destination.nid field")
|
||||
if "conv_id" not in d["destination"]:
|
||||
raise ValueError("Missing destination.conv_id field")
|
||||
if "data" not in d:
|
||||
raise ValueError("Missing data field")
|
||||
return Package(d["data"],
|
||||
source=d["source"]["nid"],
|
||||
destination=d["destination"]["nid"],
|
||||
source_conv_id=d["source"]["conv_id"],
|
||||
destination_conv_id=d["destination"]["conv_id"])
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Convert the Package into a dictionary."""
|
||||
return {
|
||||
"source": {
|
||||
"nid": self.source,
|
||||
"conv_id": self.source_conv_id
|
||||
},
|
||||
"destination": {
|
||||
"nid": self.destination,
|
||||
"conv_id": self.destination_conv_id
|
||||
},
|
||||
"data": self.data
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def from_json_string(string: str) -> "Package":
|
||||
"""Create a Package from a JSON string."""
|
||||
return Package.from_dict(json.loads(string))
|
||||
|
||||
def to_json_string(self) -> str:
|
||||
"""Convert the Package into a JSON string."""
|
||||
return json.dumps(self.to_dict())
|
||||
|
||||
@staticmethod
|
||||
def from_json_bytes(b: bytes) -> "Package":
|
||||
"""Create a Package from UTF8-encoded JSON bytes."""
|
||||
return Package.from_json_string(str(b, encoding="utf8"))
|
||||
|
||||
def to_json_bytes(self) -> bytes:
|
||||
"""Convert the Package into UTF8-encoded JSON bytes."""
|
||||
return bytes(self.to_json_string(), encoding="utf8")
|
30
royalnet/herald/request.py
Normal file
30
royalnet/herald/request.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
import typing
|
||||
|
||||
|
||||
class Request:
|
||||
"""A request sent from a :py:class:`Link` to another.
|
||||
|
||||
It contains the name of the requested handler, in addition to the data."""
|
||||
|
||||
def __init__(self, handler: str, data: dict, msg_type: typing.Optional[str] = None):
|
||||
super().__init__()
|
||||
if msg_type is not None:
|
||||
assert msg_type == self.__class__.__name__
|
||||
self.msg_type = self.__class__.__name__
|
||||
self.handler: str = handler
|
||||
self.data: dict = data
|
||||
|
||||
def to_dict(self):
|
||||
return self.__dict__
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d: dict):
|
||||
return cls(**d)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, self.__class__):
|
||||
return self.handler == other.handler and self.data == other.data
|
||||
return False
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__qualname__}(handler={self.handler}, data={self.data})"
|
50
royalnet/herald/response.py
Normal file
50
royalnet/herald/response.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
import typing
|
||||
|
||||
|
||||
class Response:
|
||||
"""A base class to be inherited by all other response types."""
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Prepare the Response to be sent by converting it to a JSONable :py:class:`dict`."""
|
||||
return {
|
||||
"type": self.__class__.__name__,
|
||||
**self.__dict__
|
||||
}
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Response):
|
||||
return self.to_dict() == other.to_dict()
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d: dict) -> "Response":
|
||||
"""Recreate the response from a received :py:class:`dict`."""
|
||||
# Ignore type in dict
|
||||
del d["type"]
|
||||
# noinspection PyArgumentList
|
||||
return cls(**d)
|
||||
|
||||
|
||||
class ResponseSuccess(Response):
|
||||
"""A response to a successful :py:class:`Request`."""
|
||||
|
||||
def __init__(self, data: typing.Optional[dict] = None):
|
||||
if data is None:
|
||||
self.data = {}
|
||||
else:
|
||||
self.data = data
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__qualname__}(data={self.data})"
|
||||
|
||||
|
||||
class ResponseFailure(Response):
|
||||
"""A response to a invalid :py:class:`Request`."""
|
||||
|
||||
def __init__(self, name: str, description: str, extra_info: typing.Optional[dict] = None):
|
||||
self.name: str = name
|
||||
self.description: str = description
|
||||
self.extra_info: typing.Optional[dict] = extra_info
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__qualname__}(name={self.name}, description={self.description}, extra_info={self.extra_info})"
|
153
royalnet/herald/server.py
Normal file
153
royalnet/herald/server.py
Normal file
|
@ -0,0 +1,153 @@
|
|||
import typing
|
||||
import websockets
|
||||
import re
|
||||
import datetime
|
||||
import uuid
|
||||
import asyncio
|
||||
import logging as _logging
|
||||
from .package import Package
|
||||
from .config import Config
|
||||
|
||||
|
||||
log = _logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ConnectedClient:
|
||||
"""The :py:class:`Server`-side representation of a connected :py:class:`Link`."""
|
||||
def __init__(self, socket: websockets.WebSocketServerProtocol):
|
||||
self.socket: websockets.WebSocketServerProtocol = socket
|
||||
self.nid: typing.Optional[str] = None
|
||||
self.link_type: typing.Optional[str] = None
|
||||
self.connection_datetime: datetime.datetime = datetime.datetime.now()
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__qualname__} {self.nid}>"
|
||||
|
||||
@property
|
||||
def is_identified(self) -> bool:
|
||||
"""Has the client sent a valid identification package?"""
|
||||
return bool(self.nid)
|
||||
|
||||
async def send_service(self, msg_type: str, message: str):
|
||||
await self.send(Package({"type": msg_type, "service": message},
|
||||
source="<server>",
|
||||
destination=self.nid))
|
||||
|
||||
async def send(self, package: Package):
|
||||
"""Send a :py:class:`Package` to the :py:class:`Link`."""
|
||||
await self.socket.send(package.to_json_bytes())
|
||||
|
||||
|
||||
class Server:
|
||||
def __init__(self, config: Config, *, loop: asyncio.AbstractEventLoop = None):
|
||||
self.config: Config = config
|
||||
self.identified_clients: typing.List[ConnectedClient] = []
|
||||
self.loop = loop
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__qualname__}>"
|
||||
|
||||
def find_client(self, *, nid: str = None, link_type: str = None) -> typing.List[ConnectedClient]:
|
||||
assert not (nid and link_type)
|
||||
if nid:
|
||||
matching = [client for client in self.identified_clients if client.nid == nid]
|
||||
assert len(matching) <= 1
|
||||
return matching
|
||||
if link_type:
|
||||
matching = [client for client in self.identified_clients if client.link_type == link_type]
|
||||
return matching or []
|
||||
|
||||
async def listener(self, websocket: websockets.server.WebSocketServerProtocol, path):
|
||||
log.info(f"{websocket.remote_address} connected to the server.")
|
||||
connected_client = ConnectedClient(websocket)
|
||||
# Wait for identification
|
||||
identify_msg = await websocket.recv()
|
||||
log.debug(f"{websocket.remote_address} identified itself with: {identify_msg}.")
|
||||
if not isinstance(identify_msg, str):
|
||||
await connected_client.send_service("error", "Invalid identification message (not a str)")
|
||||
return
|
||||
identification = re.match(r"Identify ([^:\s]+):([^:\s]+):([^:\s]+)", identify_msg)
|
||||
if identification is None:
|
||||
await connected_client.send_service("error", "Invalid identification message (regex failed)")
|
||||
return
|
||||
secret = identification.group(3)
|
||||
if secret != self.config.secret:
|
||||
await connected_client.send_service("error", "Invalid secret")
|
||||
return
|
||||
# Identification successful
|
||||
connected_client.nid = identification.group(1)
|
||||
connected_client.link_type = identification.group(2)
|
||||
self.identified_clients.append(connected_client)
|
||||
log.debug(f"{websocket.remote_address} identified successfully as {connected_client.nid}"
|
||||
f" ({connected_client.link_type}).")
|
||||
await connected_client.send_service("success", "Identification successful!")
|
||||
log.debug(f"{connected_client.nid}'s identification confirmed.")
|
||||
# Main loop
|
||||
while True:
|
||||
# Receive packages
|
||||
raw_bytes = await websocket.recv()
|
||||
package: Package = Package.from_json_bytes(raw_bytes)
|
||||
log.debug(f"Received package: {package}")
|
||||
# Check if the package destination is the server itself.
|
||||
if package.destination == "<server>":
|
||||
# TODO: do stuff
|
||||
pass
|
||||
# Otherwise, route the package to its destination
|
||||
# noinspection PyAsyncCall
|
||||
self.loop.create_task(self.route_package(package))
|
||||
|
||||
def find_destination(self, package: Package) -> typing.List[ConnectedClient]:
|
||||
"""Find a list of destinations for the package.
|
||||
|
||||
Parameters:
|
||||
package: The package to find the destination of.
|
||||
|
||||
Returns:
|
||||
A :py:class:`list` of :py:class:`ConnectedClient` to send the package to."""
|
||||
# Parse destination
|
||||
# Is it nothing?
|
||||
if package.destination == "<none>":
|
||||
return []
|
||||
# Is it all possible destinations?
|
||||
if package.destination == "*":
|
||||
return self.identified_clients
|
||||
# Is it a valid nid?
|
||||
try:
|
||||
destination = str(uuid.UUID(package.destination))
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
return self.find_client(nid=destination)
|
||||
# Is it a link_type?
|
||||
return self.find_client(link_type=package.destination)
|
||||
|
||||
async def route_package(self, package: Package) -> None:
|
||||
"""Executed every time a package is received and must be routed somewhere."""
|
||||
destinations = self.find_destination(package)
|
||||
log.debug(f"Routing package: {package} -> {destinations}")
|
||||
for destination in destinations:
|
||||
# This may have some consequences
|
||||
specific_package = Package(package.data,
|
||||
source=package.source,
|
||||
destination=destination.nid,
|
||||
source_conv_id=package.source_conv_id,
|
||||
destination_conv_id=package.destination_conv_id)
|
||||
await destination.send(specific_package)
|
||||
|
||||
def serve(self):
|
||||
if self.config.secure:
|
||||
raise Exception("Secure servers aren't supported yet")
|
||||
log.debug(f"Serving on {self.config.url}")
|
||||
self.loop.run_until_complete(self.run())
|
||||
self.loop.run_forever()
|
||||
|
||||
async def run(self):
|
||||
await websockets.serve(self.listener,
|
||||
host=self.config.address,
|
||||
port=self.config.port,
|
||||
loop=self.loop)
|
||||
|
||||
def run_blocking(self):
|
||||
if self.loop is None:
|
||||
self.loop = asyncio.get_event_loop()
|
||||
self.serve()
|
|
@ -1,6 +1,6 @@
|
|||
"""Various bot interfaces, and a common class to create new ones."""
|
||||
|
||||
from .interface import GenericBot
|
||||
from .serf import GenericBot
|
||||
from .telegram import TelegramBot
|
||||
from .discord import DiscordBot
|
||||
|
12
royalnet/serf/alchemyconfig.py
Normal file
12
royalnet/serf/alchemyconfig.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
from typing import Type
|
||||
from sqlalchemy.schema import Table
|
||||
|
||||
|
||||
class AlchemyConfig:
|
||||
"""A helper class to configure :class:`Alchemy` in a :class:`Serf`."""
|
||||
def __init__(self,
|
||||
master_table: Type[Table],
|
||||
identity_table: Type[Table],
|
||||
):
|
||||
self.master_table: Type[Table] = master_table
|
||||
self.identity_table: Type[Table] = identity_table
|
|
@ -31,7 +31,7 @@ class DiscordBot(GenericBot):
|
|||
|
||||
def _interface_factory(self) -> typing.Type[CommandInterface]:
|
||||
# noinspection PyPep8Naming
|
||||
GenericInterface = super()._interface_factory()
|
||||
GenericInterface = super().interface_factory()
|
||||
|
||||
# noinspection PyMethodParameters,PyAbstractClass
|
||||
class DiscordInterface(GenericInterface):
|
|
@ -1,86 +1,101 @@
|
|||
import sys
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Type, Optional, Awaitable, Dict, List, Any, Callable, Union
|
||||
import sentry_sdk
|
||||
import keyring
|
||||
import royalnet.version
|
||||
import royalherald as rh
|
||||
from royalnet.herald import Response, ResponseSuccess, Broadcast, ResponseFailure, Request, Link
|
||||
from royalnet.herald import Config as HeraldConfig
|
||||
from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
|
||||
from sentry_sdk.integrations.aiohttp import AioHttpIntegration
|
||||
from sentry_sdk.integrations.logging import LoggingIntegration
|
||||
from ..utils import *
|
||||
from ..alchemy import *
|
||||
from ..commands import *
|
||||
from ..error import *
|
||||
from royalnet.commands import Command, CommandInterface, CommandData, CommandError, UnsupportedError
|
||||
from royalnet.alchemy import Alchemy
|
||||
from .alchemyconfig import AlchemyConfig
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Interface:
|
||||
"""A common bot class, to be used as base for the other more specific classes, such as
|
||||
:py:class:`royalnet.bots.TelegramBot` and :py:class:`royalnet.bots.DiscordBot`. """
|
||||
class Serf:
|
||||
"""An abstract class, to be used as base to implement Royalnet bots on multiple interfaces (such as Telegram or
|
||||
Discord)."""
|
||||
interface_name = NotImplemented
|
||||
|
||||
def _init_commands(self) -> None:
|
||||
"""Generate the ``packs`` dictionary required to handle incoming messages, and the ``network_handlers``
|
||||
dictionary required to handle incoming requests. """
|
||||
log.info(f"Registering packs...")
|
||||
self._Interface = self._interface_factory()
|
||||
self._Data = self._data_factory()
|
||||
self.commands = {}
|
||||
self.network_handlers: typing.Dict[str, typing.Callable[["Interface", typing.Any],
|
||||
typing.Awaitable[typing.Optional[typing.Dict]]]] = {}
|
||||
for SelectedCommand in self.uninitialized_commands:
|
||||
interface = self._Interface()
|
||||
try:
|
||||
command = SelectedCommand(interface)
|
||||
except Exception as e:
|
||||
log.error(f"{e.__class__.__qualname__} during the registration of {SelectedCommand.__qualname__}")
|
||||
sentry_sdk.capture_exception(e)
|
||||
continue
|
||||
# Linking the command to the interface
|
||||
interface.command = command
|
||||
# Override the main command name, but warn if it's overriding something
|
||||
if f"{interface.prefix}{SelectedCommand.name}" in self.commands:
|
||||
log.warning(f"Overriding (already defined): {SelectedCommand.__qualname__} -> {interface.prefix}{SelectedCommand.name}")
|
||||
else:
|
||||
log.debug(f"Registering: {SelectedCommand.__qualname__} -> {interface.prefix}{SelectedCommand.name}")
|
||||
self.commands[f"{interface.prefix}{SelectedCommand.name}"] = command
|
||||
# Register aliases, but don't override anything
|
||||
for alias in SelectedCommand.aliases:
|
||||
if f"{interface.prefix}{alias}" not in self.commands:
|
||||
log.debug(f"Aliasing: {SelectedCommand.__qualname__} -> {interface.prefix}{alias}")
|
||||
self.commands[f"{interface.prefix}{alias}"] = self.commands[f"{interface.prefix}{SelectedCommand.name}"]
|
||||
else:
|
||||
log.info(f"Ignoring (already defined): {SelectedCommand.__qualname__} -> {interface.prefix}{alias}")
|
||||
def __init__(self, *,
|
||||
alchemy_config: Optional[AlchemyConfig] = None,
|
||||
commands: List[Type[Command]] = None,
|
||||
network_config: Optional[HeraldConfig] = None,
|
||||
sentry_dsn: Optional[str] = None,
|
||||
loop: asyncio.AbstractEventLoop = None,
|
||||
secrets_name: str = "__default__"):
|
||||
self.secrets_name = secrets_name
|
||||
|
||||
def _interface_factory(self) -> typing.Type[CommandInterface]:
|
||||
# noinspection PyAbstractClass,PyMethodParameters
|
||||
if alchemy_config is not None:
|
||||
self.init_alchemy(alchemy_config)
|
||||
|
||||
self.Interface: Type[CommandInterface] = self.interface_factory()
|
||||
"""The :class:`CommandInterface` class of this Serf."""
|
||||
|
||||
self.Data: Type[CommandData] = self.data_factory()
|
||||
"""The :class:`CommandData` class of this Serf."""
|
||||
|
||||
self.commands: Dict[str, Command] = {}
|
||||
"""The :class:`dict` connecting each command name to its :class:`Command` object."""
|
||||
|
||||
if commands is None:
|
||||
commands = []
|
||||
self.register_commands(commands)
|
||||
|
||||
self.herald_handlers: Dict[str, Callable[["Serf", Any], Awaitable[Optional[dict]]]] = {}
|
||||
"""A :class:`dict` linking :class:`Request` event names to coroutines returning a :class:`dict` that will be
|
||||
sent as :class:`Response` to the event."""
|
||||
|
||||
self.herald: Optional[Link] = None
|
||||
"""The :class:`Link` object connecting the Serf to the rest of the herald network."""
|
||||
|
||||
self.herald_task: Optional[asyncio.Task] = None
|
||||
"""A reference to the :class:`asyncio.Task` that runs the :class:`Link`."""
|
||||
|
||||
if network_config is not None:
|
||||
self.init_network(network_config)
|
||||
|
||||
def interface_factory(self) -> Type[CommandInterface]:
|
||||
"""Create the :class:`CommandInterface` class for the Serf."""
|
||||
# noinspection PyMethodParameters
|
||||
class GenericInterface(CommandInterface):
|
||||
alchemy = self.alchemy
|
||||
bot = self
|
||||
loop = self.loop
|
||||
alchemy: Alchemy = self.alchemy
|
||||
bot: "Serf" = self
|
||||
loop: asyncio.AbstractEventLoop = self.loop
|
||||
|
||||
def register_herald_action(ci,
|
||||
event_name: str,
|
||||
coroutine: typing.Callable[[typing.Any], typing.Awaitable[typing.Dict]]) -> None:
|
||||
self.network_handlers[event_name] = coroutine
|
||||
coroutine: Callable[[Any], Awaitable[Dict]]) -> None:
|
||||
"""Allow a coroutine to be called when a :class:`royalherald.Request` is received."""
|
||||
if self.herald is None:
|
||||
raise UnsupportedError("`royalherald` is not enabled on this bot.")
|
||||
self.herald_handlers[event_name] = coroutine
|
||||
|
||||
def unregister_herald_action(ci, event_name: str):
|
||||
del self.network_handlers[event_name]
|
||||
"""Disable a previously registered coroutine from being called on reception of a
|
||||
:class:`royalherald.Request`."""
|
||||
if self.herald is None:
|
||||
raise UnsupportedError("`royalherald` is not enabled on this bot.")
|
||||
del self.herald_handlers[event_name]
|
||||
|
||||
async def call_herald_action(ci, destination: str, event_name: str, args: typing.Dict) -> typing.Dict:
|
||||
if self.network is None:
|
||||
raise UnsupportedError("Herald is not enabled on this bot")
|
||||
request: rh.Request = rh.Request(handler=event_name, data=args)
|
||||
response: rh.Response = await self.network.request(destination=destination, request=request)
|
||||
if isinstance(response, rh.ResponseFailure):
|
||||
async def call_herald_action(ci, destination: str, event_name: str, args: Dict) -> Dict:
|
||||
"""Send a :class:`royalherald.Request` to a specific destination, and wait for a
|
||||
:class:`royalherald.Response`."""
|
||||
if self.herald is None:
|
||||
raise UnsupportedError("`royalherald` is not enabled on this bot.")
|
||||
request: Request = Request(handler=event_name, data=args)
|
||||
response: Response = await self.herald.request(destination=destination, request=request)
|
||||
if isinstance(response, ResponseFailure):
|
||||
if response.extra_info["type"] == "CommandError":
|
||||
raise CommandError(response.extra_info["message"])
|
||||
raise CommandError(f"Herald action call failed:\n"
|
||||
# TODO: change exception type
|
||||
raise Exception(f"Herald action call failed:\n"
|
||||
f"[p]{response}[/p]")
|
||||
elif isinstance(response, rh.ResponseSuccess):
|
||||
elif isinstance(response, ResponseSuccess):
|
||||
return response.data
|
||||
else:
|
||||
raise TypeError(f"Other Herald Link returned unknown response:\n"
|
||||
|
@ -88,41 +103,78 @@ class Interface:
|
|||
|
||||
return GenericInterface
|
||||
|
||||
def _data_factory(self) -> typing.Type[CommandData]:
|
||||
def data_factory(self) -> Type[CommandData]:
|
||||
"""Create the :class:`CommandData` for the Serf."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _init_network(self):
|
||||
"""Create a :py:class:`royalherald.Link`, and run it as a :py:class:`asyncio.Task`."""
|
||||
if self.uninitialized_network_config is not None:
|
||||
self.network: rh.Link = rh.Link(self.uninitialized_network_config.master_uri,
|
||||
self.uninitialized_network_config.master_secret,
|
||||
self.interface_name,
|
||||
self._network_handler)
|
||||
log.debug(f"Running NetworkLink {self.network}")
|
||||
self.loop.create_task(self.network.run())
|
||||
def register_commands(self, commands: List[Type[Command]]) -> None:
|
||||
"""Initialize and register all commands passed as argument.
|
||||
|
||||
async def _network_handler(self, message: typing.Union[rh.Request, rh.Broadcast]) -> rh.Response:
|
||||
If called again during the execution of the bot, all current commands will be replaced with the new ones.
|
||||
|
||||
Warning:
|
||||
Hot-replacing commands was never tested and probably doesn't work."""
|
||||
log.info(f"Registering {len(commands)} commands...")
|
||||
# Instantiate the Commands
|
||||
for SelectedCommand in commands:
|
||||
# Warn if the command would be overriding something
|
||||
if f"{self.Interface.prefix}{SelectedCommand.name}" in self.commands:
|
||||
log.warning(f"Overriding (already defined): "
|
||||
f"{SelectedCommand.__qualname__} -> {self.Interface.prefix}{SelectedCommand.name}")
|
||||
else:
|
||||
log.debug(f"Registering: "
|
||||
f"{SelectedCommand.__qualname__} -> {self.Interface.prefix}{SelectedCommand.name}")
|
||||
# Create a new interface
|
||||
interface = self.Interface()
|
||||
# Try to instantiate the command
|
||||
try:
|
||||
network_handler = self.network_handlers[message.handler]
|
||||
command = SelectedCommand(interface)
|
||||
except Exception as e:
|
||||
log.error(f"Skipping: "
|
||||
f"{SelectedCommand.__qualname__} - {e.__class__.__qualname__} in the initialization.")
|
||||
sentry_sdk.capture_exception(e)
|
||||
continue
|
||||
# Link the interface to the command
|
||||
interface.command = command
|
||||
# Register the command in the commands dict
|
||||
self.commands[f"{interface.prefix}{SelectedCommand.name}"] = command
|
||||
# Register aliases, but don't override anything
|
||||
for alias in SelectedCommand.aliases:
|
||||
if f"{interface.prefix}{alias}" not in self.commands:
|
||||
log.debug(f"Aliasing: {SelectedCommand.__qualname__} -> {interface.prefix}{alias}")
|
||||
self.commands[f"{interface.prefix}{alias}"] = \
|
||||
self.commands[f"{interface.prefix}{SelectedCommand.name}"]
|
||||
else:
|
||||
log.info(f"Ignoring (already defined): {SelectedCommand.__qualname__} -> {interface.prefix}{alias}")
|
||||
|
||||
def init_network(self, config: HeraldConfig):
|
||||
"""Create a :py:class:`Link`, and run it as a :py:class:`asyncio.Task`."""
|
||||
log.debug(f"Initializing herald...")
|
||||
self.herald: Link = Link(config, self._network_handler)
|
||||
self.herald_task = self.loop.create_task(self.herald.run())
|
||||
|
||||
async def _network_handler(self, message: Union[Request, Broadcast]) -> Response:
|
||||
try:
|
||||
network_handler = self.herald_handlers[message.handler]
|
||||
except KeyError:
|
||||
log.warning(f"Missing network_handler for {message.handler}")
|
||||
return rh.ResponseFailure("no_handler", f"This bot is missing a network handler for {message.handler}.")
|
||||
return ResponseFailure("no_handler", f"This bot is missing a network handler for {message.handler}.")
|
||||
else:
|
||||
log.debug(f"Using {network_handler} as handler for {message.handler}")
|
||||
if isinstance(message, rh.Request):
|
||||
if isinstance(message, Request):
|
||||
try:
|
||||
response_data = await network_handler(self, **message.data)
|
||||
return rh.ResponseSuccess(data=response_data)
|
||||
return ResponseSuccess(data=response_data)
|
||||
except Exception as e:
|
||||
sentry_sdk.capture_exception(e)
|
||||
log.error(f"Exception {e} in {network_handler}")
|
||||
return rh.ResponseFailure("exception_in_handler",
|
||||
return ResponseFailure("exception_in_handler",
|
||||
f"An exception was raised in {network_handler} for {message.handler}.",
|
||||
extra_info={
|
||||
"type": e.__class__.__name__,
|
||||
"message": str(e)
|
||||
})
|
||||
elif isinstance(message, rh.Broadcast):
|
||||
elif isinstance(message, Broadcast):
|
||||
await network_handler(self, **message.data)
|
||||
|
||||
def _init_database(self):
|
||||
|
@ -173,21 +225,6 @@ class Interface:
|
|||
else:
|
||||
self.loop = self.uninitialized_loop
|
||||
|
||||
def __init__(self, *,
|
||||
network_config: typing.Optional[rh.Config] = None,
|
||||
database_config: typing.Optional[DatabaseConfig] = None,
|
||||
commands: typing.List[typing.Type[Command]] = None,
|
||||
sentry_dsn: typing.Optional[str] = None,
|
||||
loop: asyncio.AbstractEventLoop = None,
|
||||
secrets_name: str = "__default__"):
|
||||
self.initialized = False
|
||||
self.uninitialized_network_config = network_config
|
||||
self.uninitialized_database_config = database_config
|
||||
self.uninitialized_commands = commands
|
||||
self.uninitialized_sentry_dsn = sentry_dsn
|
||||
self.uninitialized_loop = loop
|
||||
self.secrets_name = secrets_name
|
||||
|
||||
def get_secret(self, username: str):
|
||||
return keyring.get_password(f"Royalnet/{self.secrets_name}", username)
|
||||
|
||||
|
@ -200,7 +237,7 @@ class Interface:
|
|||
self._init_loop()
|
||||
self._init_database()
|
||||
self._init_commands()
|
||||
self._init_network()
|
||||
self.init_network()
|
||||
self.initialized = True
|
||||
|
||||
def run(self):
|
|
@ -29,7 +29,7 @@ class TelegramBot(GenericBot):
|
|||
|
||||
def _interface_factory(self) -> typing.Type[CommandInterface]:
|
||||
# noinspection PyPep8Naming
|
||||
GenericInterface = super()._interface_factory()
|
||||
GenericInterface = super().interface_factory()
|
||||
|
||||
# noinspection PyMethodParameters,PyAbstractClass
|
||||
class TelegramInterface(GenericInterface):
|
Loading…
Reference in a new issue