From e8fc086e73d9a37c37a19948db3885be1dcdee28 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Fri, 11 Nov 2022 13:26:41 -0500 Subject: [PATCH 01/51] Consolidating db configs to database_url Removing databasetype enum --- harambot/database/databasetype.py | 6 ------ harambot/database/models.py | 14 ++------------ 2 files changed, 2 insertions(+), 18 deletions(-) delete mode 100644 harambot/database/databasetype.py diff --git a/harambot/database/databasetype.py b/harambot/database/databasetype.py deleted file mode 100644 index ce55f9c..0000000 --- a/harambot/database/databasetype.py +++ /dev/null @@ -1,6 +0,0 @@ -from enum import Enum - -class DatabaseType(Enum): - SQLITE = 'sqlite' - MYSQL = 'mysql' - POSTGRES = 'postgres' \ No newline at end of file diff --git a/harambot/database/models.py b/harambot/database/models.py index 7f8b03f..a59d450 100644 --- a/harambot/database/models.py +++ b/harambot/database/models.py @@ -1,21 +1,11 @@ from peewee import * from playhouse.db_url import connect from config import settings -from database.databasetype import DatabaseType - -if settings.guilds_datastore_type == DatabaseType.POSTGRES.value: - database = PostgresqlDatabase(settings.guild_db,user=settings.guild_db_user, password=settings.guild_db_pass, - host=settings.guild_db_host, port=settings.guild_db_port) -elif settings.guilds_datastore_type == DatabaseType.MYSQL.value: - database = MySQLDatabase(settings.guild_db,user=settings.guild_db_user, password=settings.guild_db_pass, - host=settings.guild_db_host, port=settings.guild_db_port) -elif settings.guilds_datastore_type == DatabaseType.SQLITE.value: - database = SqliteDatabase(settings.guilds_datastore_loc) -else: - database = SqliteDatabase(':memory:') if settings.database_url: database = connect(settings.database_url) +else: + database = SqliteDatabase(':memory:') class BaseModel(Model): class Meta: From a090b19b9be16f3e772427ea770f56c8e343f0d4 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Fri, 11 Nov 2022 13:36:19 -0500 Subject: [PATCH 02/51] Removing unused vars from toml files --- config/example.secrets.toml | 5 ----- config/settings.toml | 6 +----- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/config/example.secrets.toml b/config/example.secrets.toml index 8ffd017..218f5b7 100644 --- a/config/example.secrets.toml +++ b/config/example.secrets.toml @@ -2,9 +2,4 @@ DISCORD_TOKEN = 'Discord API Token' YAHOO_KEY = 'Yahoo Client ID' YAHOO_SECRET = 'Yahoo Client Secret' -GUILD_DB = 'harambot' -GUILD_DB_USER = 'harambot' -GUILD_DB_PASS = 'harambe' -GUILD_DB_HOST = 'localhost' -GUILD_DB_PORT = 3308 DATABASE_URL = 'sqlite:///harambot.db' \ No newline at end of file diff --git a/config/settings.toml b/config/settings.toml index 7ad2de6..7e7f8e3 100644 --- a/config/settings.toml +++ b/config/settings.toml @@ -1,6 +1,2 @@ [default] -LOGLEVEL = "DEBUG" -# Supported databases are sqlite, mysql, and postgres -GUILDS_DATASTORE_TYPE = "sqlite" -# if using a sqlite database specify the path to the db file -GUILDS_DATASTORE_LOC = "harambot.db" \ No newline at end of file +LOGLEVEL = "DEBUG" \ No newline at end of file From c62a90192bf1d0c0fb3e17c76df69da75e950ee4 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Fri, 11 Nov 2022 13:53:53 -0500 Subject: [PATCH 03/51] Refactoring dockerfile --- Dockerfile | 3 --- 1 file changed, 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5d9108d..dd0cdbc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,6 @@ WORKDIR /app/harambot ADD ./Makefile /app/harambot ADD ./requirements.txt /app/harambot/ ADD ./harambot /app/harambot/harambot -ADD ./config /app/harambot/config RUN apt-get update RUN apt-get upgrade -y @@ -19,6 +18,4 @@ RUN pip install -U pip RUN pip install -r requirements.txt -RUN ls -ltr /app/harambot - CMD ["python", "./harambot/bot.py"] From 8e82313f3fa91c3e60fc380ec917e53b1ee9f58d Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Fri, 11 Nov 2022 13:54:12 -0500 Subject: [PATCH 04/51] Fixing if statements for checking for variables --- harambot/bot.py | 5 ++++- harambot/database/models.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/harambot/bot.py b/harambot/bot.py index 813084b..20a7ba9 100644 --- a/harambot/bot.py +++ b/harambot/bot.py @@ -16,7 +16,10 @@ #logging.basicConfig(level=logging.INFO) logger = logging.getLogger('harambot.py') -logger.setLevel(settings.loglevel) +if 'LOGLEVEL' in settings: + logger.setLevel(settings.loglevel) +else: + logger.setLevel('INFO') intents = discord.Intents.default() intents.members = True diff --git a/harambot/database/models.py b/harambot/database/models.py index a59d450..b23360e 100644 --- a/harambot/database/models.py +++ b/harambot/database/models.py @@ -2,7 +2,7 @@ from playhouse.db_url import connect from config import settings -if settings.database_url: +if 'DATABASE_URL' in settings: database = connect(settings.database_url) else: database = SqliteDatabase(':memory:') From f12a7aa17001544c0ead8a4ac0c4c4bfbbbccc50 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Tue, 15 Nov 2022 13:50:18 -0500 Subject: [PATCH 05/51] Migrating project to poetry from pip --- poetry.lock | 853 +++++++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 48 +++ 2 files changed, 901 insertions(+) create mode 100644 poetry.lock create mode 100644 pyproject.toml diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..74acce5 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,853 @@ +[[package]] +name = "aiohttp" +version = "3.8.3" +description = "Async http client/server framework (asyncio)" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = ">=4.0.0a3,<5.0" +attrs = ">=17.3.0" +charset-normalizer = ">=2.0,<3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns", "cchardet"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "async-timeout" +version = "4.0.2" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "attrs" +version = "22.1.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] +docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] +tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] +tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] + +[[package]] +name = "cachetools" +version = "5.2.0" +description = "Extensible memoizing collections and decorators" +category = "main" +optional = false +python-versions = "~=3.7" + +[[package]] +name = "certifi" +version = "2022.9.24" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "charset-normalizer" +version = "2.1.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.6.0" + +[package.extras] +unicode-backport = ["unicodedata2"] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" + +[[package]] +name = "discord" +version = "2.1.0" +description = "A mirror package for discord.py. Please install that instead." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +"discord.py" = ">=2.1.0" + +[[package]] +name = "discord-py" +version = "2.1.0" +description = "A Python wrapper for the Discord API" +category = "main" +optional = false +python-versions = ">=3.8.0" + +[package.dependencies] +aiohttp = ">=3.7.4,<4" + +[package.extras] +docs = ["sphinx (==4.4.0)", "sphinxcontrib-trio (==1.1.2)", "sphinxcontrib-websupport", "typing-extensions (>=4.3,<5)"] +speed = ["Brotli", "aiodns (>=1.1)", "cchardet (==2.1.7)", "orjson (>=3.5.4)"] +test = ["coverage[toml]", "pytest", "pytest-asyncio", "pytest-cov", "pytest-mock", "typing-extensions (>=4.3,<5)"] +voice = ["PyNaCl (>=1.3.0,<1.6)"] + +[[package]] +name = "docopt" +version = "0.6.2" +description = "Pythonic argument parser, that will make you smile" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "dynaconf" +version = "3.1.11" +description = "The dynamic configurator for your Python Project" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +all = ["configobj", "hvac", "redis", "ruamel.yaml"] +configobj = ["configobj"] +ini = ["configobj"] +redis = ["redis"] +test = ["codecov", "configobj", "django", "flake8", "flake8-debugger", "flake8-print", "flake8-todo", "flask (>=0.12)", "hvac", "pep8-naming", "pytest", "pytest-cov", "pytest-mock", "pytest-xdist", "python-dotenv", "radon", "redis", "toml"] +toml = ["toml"] +vault = ["hvac"] +yaml = ["ruamel.yaml"] + +[[package]] +name = "exceptiongroup" +version = "1.0.4" +description = "Backport of PEP 654 (exception groups)" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "frozenlist" +version = "1.3.3" +description = "A list-like structure which implements collections.abc.MutableSequence" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "multidict" +version = "6.0.2" +description = "multidict implementation" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "mysqlclient" +version = "2.1.1" +description = "Python interface to MySQL" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "objectpath" +version = "0.6.1" +description = "The agile query language for semi-structured data. #JSON" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "peewee" +version = "3.15.4" +description = "a little orm" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "psycopg2" +version = "2.9.5" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "pyaml" +version = "21.10.1" +description = "PyYAML-based module to produce pretty and readable YAML-serialized data" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +PyYAML = "*" + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "dev" +optional = false +python-versions = ">=3.6.8" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pytest" +version = "7.2.0" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "pytz" +version = "2022.6" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "rauth" +version = "0.7.3" +description = "A Python library for OAuth 1.0/a, 2.0, and Ofly." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +requests = ">=1.2.3" + +[[package]] +name = "requests" +version = "2.28.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=3.7, <4" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<3" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "urllib3" +version = "1.26.12" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "yahoo-fantasy-api" +version = "2.6.0" +description = "Python bindings to access the Yahoo! Fantasy APIs" +category = "main" +optional = false +python-versions = ">=3" + +[package.dependencies] +docopt = "*" +objectpath = "*" +pytz = "*" +yahoo_oauth = "*" + +[[package]] +name = "yahoo-oauth" +version = "2.0" +description = "Python Yahoo OAuth Library. Supports OAuth1 and OAuth2" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pyaml = "*" +rauth = "*" + +[[package]] +name = "yarl" +version = "1.8.1" +description = "Yet another URL library" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[metadata] +lock-version = "1.1" +python-versions = "^3.8" +content-hash = "d61130143a91a5c0caa6f1b001f945056b3f0aeec6a13be14e12baf1fab5bc93" + +[metadata.files] +aiohttp = [ + {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ba71c9b4dcbb16212f334126cc3d8beb6af377f6703d9dc2d9fb3874fd667ee9"}, + {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d24b8bb40d5c61ef2d9b6a8f4528c2f17f1c5d2d31fed62ec860f6006142e83e"}, + {file = "aiohttp-3.8.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f88df3a83cf9df566f171adba39d5bd52814ac0b94778d2448652fc77f9eb491"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97decbb3372d4b69e4d4c8117f44632551c692bb1361b356a02b97b69e18a62"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:309aa21c1d54b8ef0723181d430347d7452daaff93e8e2363db8e75c72c2fb2d"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad5383a67514e8e76906a06741febd9126fc7c7ff0f599d6fcce3e82b80d026f"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20acae4f268317bb975671e375493dbdbc67cddb5f6c71eebdb85b34444ac46b"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05a3c31c6d7cd08c149e50dc7aa2568317f5844acd745621983380597f027a18"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d6f76310355e9fae637c3162936e9504b4767d5c52ca268331e2756e54fd4ca5"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:256deb4b29fe5e47893fa32e1de2d73c3afe7407738bd3c63829874661d4822d"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:5c59fcd80b9049b49acd29bd3598cada4afc8d8d69bd4160cd613246912535d7"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:059a91e88f2c00fe40aed9031b3606c3f311414f86a90d696dd982e7aec48142"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2feebbb6074cdbd1ac276dbd737b40e890a1361b3cc30b74ac2f5e24aab41f7b"}, + {file = "aiohttp-3.8.3-cp310-cp310-win32.whl", hash = "sha256:5bf651afd22d5f0c4be16cf39d0482ea494f5c88f03e75e5fef3a85177fecdeb"}, + {file = "aiohttp-3.8.3-cp310-cp310-win_amd64.whl", hash = "sha256:653acc3880459f82a65e27bd6526e47ddf19e643457d36a2250b85b41a564715"}, + {file = "aiohttp-3.8.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:86fc24e58ecb32aee09f864cb11bb91bc4c1086615001647dbfc4dc8c32f4008"}, + {file = "aiohttp-3.8.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75e14eac916f024305db517e00a9252714fce0abcb10ad327fb6dcdc0d060f1d"}, + {file = "aiohttp-3.8.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d1fde0f44029e02d02d3993ad55ce93ead9bb9b15c6b7ccd580f90bd7e3de476"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ab94426ddb1ecc6a0b601d832d5d9d421820989b8caa929114811369673235c"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89d2e02167fa95172c017732ed7725bc8523c598757f08d13c5acca308e1a061"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02f9a2c72fc95d59b881cf38a4b2be9381b9527f9d328771e90f72ac76f31ad8"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c7149272fb5834fc186328e2c1fa01dda3e1fa940ce18fded6d412e8f2cf76d"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:512bd5ab136b8dc0ffe3fdf2dfb0c4b4f49c8577f6cae55dca862cd37a4564e2"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7018ecc5fe97027214556afbc7c502fbd718d0740e87eb1217b17efd05b3d276"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:88c70ed9da9963d5496d38320160e8eb7e5f1886f9290475a881db12f351ab5d"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:da22885266bbfb3f78218dc40205fed2671909fbd0720aedba39b4515c038091"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:e65bc19919c910127c06759a63747ebe14f386cda573d95bcc62b427ca1afc73"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:08c78317e950e0762c2983f4dd58dc5e6c9ff75c8a0efeae299d363d439c8e34"}, + {file = "aiohttp-3.8.3-cp311-cp311-win32.whl", hash = "sha256:45d88b016c849d74ebc6f2b6e8bc17cabf26e7e40c0661ddd8fae4c00f015697"}, + {file = "aiohttp-3.8.3-cp311-cp311-win_amd64.whl", hash = "sha256:96372fc29471646b9b106ee918c8eeb4cca423fcbf9a34daa1b93767a88a2290"}, + {file = "aiohttp-3.8.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c971bf3786b5fad82ce5ad570dc6ee420f5b12527157929e830f51c55dc8af77"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff25f48fc8e623d95eca0670b8cc1469a83783c924a602e0fbd47363bb54aaca"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e381581b37db1db7597b62a2e6b8b57c3deec95d93b6d6407c5b61ddc98aca6d"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db19d60d846283ee275d0416e2a23493f4e6b6028825b51290ac05afc87a6f97"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25892c92bee6d9449ffac82c2fe257f3a6f297792cdb18ad784737d61e7a9a85"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:398701865e7a9565d49189f6c90868efaca21be65c725fc87fc305906be915da"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4a4fbc769ea9b6bd97f4ad0b430a6807f92f0e5eb020f1e42ece59f3ecfc4585"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:b29bfd650ed8e148f9c515474a6ef0ba1090b7a8faeee26b74a8ff3b33617502"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:1e56b9cafcd6531bab5d9b2e890bb4937f4165109fe98e2b98ef0dcfcb06ee9d"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ec40170327d4a404b0d91855d41bfe1fe4b699222b2b93e3d833a27330a87a6d"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2df5f139233060578d8c2c975128fb231a89ca0a462b35d4b5fcf7c501ebdbe1"}, + {file = "aiohttp-3.8.3-cp36-cp36m-win32.whl", hash = "sha256:f973157ffeab5459eefe7b97a804987876dd0a55570b8fa56b4e1954bf11329b"}, + {file = "aiohttp-3.8.3-cp36-cp36m-win_amd64.whl", hash = "sha256:437399385f2abcd634865705bdc180c8314124b98299d54fe1d4c8990f2f9494"}, + {file = "aiohttp-3.8.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:09e28f572b21642128ef31f4e8372adb6888846f32fecb288c8b0457597ba61a"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f3553510abdbec67c043ca85727396ceed1272eef029b050677046d3387be8d"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e168a7560b7c61342ae0412997b069753f27ac4862ec7867eff74f0fe4ea2ad9"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db4c979b0b3e0fa7e9e69ecd11b2b3174c6963cebadeecfb7ad24532ffcdd11a"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e164e0a98e92d06da343d17d4e9c4da4654f4a4588a20d6c73548a29f176abe2"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8a78079d9a39ca9ca99a8b0ac2fdc0c4d25fc80c8a8a82e5c8211509c523363"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:21b30885a63c3f4ff5b77a5d6caf008b037cb521a5f33eab445dc566f6d092cc"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4b0f30372cef3fdc262f33d06e7b411cd59058ce9174ef159ad938c4a34a89da"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:8135fa153a20d82ffb64f70a1b5c2738684afa197839b34cc3e3c72fa88d302c"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:ad61a9639792fd790523ba072c0555cd6be5a0baf03a49a5dd8cfcf20d56df48"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:978b046ca728073070e9abc074b6299ebf3501e8dee5e26efacb13cec2b2dea0"}, + {file = "aiohttp-3.8.3-cp37-cp37m-win32.whl", hash = "sha256:0d2c6d8c6872df4a6ec37d2ede71eff62395b9e337b4e18efd2177de883a5033"}, + {file = "aiohttp-3.8.3-cp37-cp37m-win_amd64.whl", hash = "sha256:21d69797eb951f155026651f7e9362877334508d39c2fc37bd04ff55b2007091"}, + {file = "aiohttp-3.8.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ca9af5f8f5812d475c5259393f52d712f6d5f0d7fdad9acdb1107dd9e3cb7eb"}, + {file = "aiohttp-3.8.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d90043c1882067f1bd26196d5d2db9aa6d268def3293ed5fb317e13c9413ea4"}, + {file = "aiohttp-3.8.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d737fc67b9a970f3234754974531dc9afeea11c70791dcb7db53b0cf81b79784"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebf909ea0a3fc9596e40d55d8000702a85e27fd578ff41a5500f68f20fd32e6c"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5835f258ca9f7c455493a57ee707b76d2d9634d84d5d7f62e77be984ea80b849"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da37dcfbf4b7f45d80ee386a5f81122501ec75672f475da34784196690762f4b"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87f44875f2804bc0511a69ce44a9595d5944837a62caecc8490bbdb0e18b1342"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:527b3b87b24844ea7865284aabfab08eb0faf599b385b03c2aa91fc6edd6e4b6"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d5ba88df9aa5e2f806650fcbeedbe4f6e8736e92fc0e73b0400538fd25a4dd96"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e7b8813be97cab8cb52b1375f41f8e6804f6507fe4660152e8ca5c48f0436017"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:2dea10edfa1a54098703cb7acaa665c07b4e7568472a47f4e64e6319d3821ccf"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:713d22cd9643ba9025d33c4af43943c7a1eb8547729228de18d3e02e278472b6"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2d252771fc85e0cf8da0b823157962d70639e63cb9b578b1dec9868dd1f4f937"}, + {file = "aiohttp-3.8.3-cp38-cp38-win32.whl", hash = "sha256:66bd5f950344fb2b3dbdd421aaa4e84f4411a1a13fca3aeb2bcbe667f80c9f76"}, + {file = "aiohttp-3.8.3-cp38-cp38-win_amd64.whl", hash = "sha256:84b14f36e85295fe69c6b9789b51a0903b774046d5f7df538176516c3e422446"}, + {file = "aiohttp-3.8.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16c121ba0b1ec2b44b73e3a8a171c4f999b33929cd2397124a8c7fcfc8cd9e06"}, + {file = "aiohttp-3.8.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8d6aaa4e7155afaf994d7924eb290abbe81a6905b303d8cb61310a2aba1c68ba"}, + {file = "aiohttp-3.8.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43046a319664a04b146f81b40e1545d4c8ac7b7dd04c47e40bf09f65f2437346"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599418aaaf88a6d02a8c515e656f6faf3d10618d3dd95866eb4436520096c84b"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92a2964319d359f494f16011e23434f6f8ef0434acd3cf154a6b7bec511e2fb7"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73a4131962e6d91109bca6536416aa067cf6c4efb871975df734f8d2fd821b37"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598adde339d2cf7d67beaccda3f2ce7c57b3b412702f29c946708f69cf8222aa"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75880ed07be39beff1881d81e4a907cafb802f306efd6d2d15f2b3c69935f6fb"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0239da9fbafd9ff82fd67c16704a7d1bccf0d107a300e790587ad05547681c8"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4e3a23ec214e95c9fe85a58470b660efe6534b83e6cbe38b3ed52b053d7cb6ad"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:47841407cc89a4b80b0c52276f3cc8138bbbfba4b179ee3acbd7d77ae33f7ac4"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:54d107c89a3ebcd13228278d68f1436d3f33f2dd2af5415e3feaeb1156e1a62c"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c37c5cce780349d4d51739ae682dec63573847a2a8dcb44381b174c3d9c8d403"}, + {file = "aiohttp-3.8.3-cp39-cp39-win32.whl", hash = "sha256:f178d2aadf0166be4df834c4953da2d7eef24719e8aec9a65289483eeea9d618"}, + {file = "aiohttp-3.8.3-cp39-cp39-win_amd64.whl", hash = "sha256:88e5be56c231981428f4f506c68b6a46fa25c4123a2e86d156c58a8369d31ab7"}, + {file = "aiohttp-3.8.3.tar.gz", hash = "sha256:3828fb41b7203176b82fe5d699e0d845435f2374750a44b480ea6b930f6be269"}, +] +aiosignal = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] +async-timeout = [ + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, +] +attrs = [ + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, +] +cachetools = [ + {file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"}, + {file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"}, +] +certifi = [ + {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, + {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, +] +colorama = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] +discord = [ + {file = "discord-2.1.0-py3-none-any.whl", hash = "sha256:44515e6d02e61528b2a81e511e03b3f60ce0b3737ba9efc69c2defce2f1ebc1d"}, + {file = "discord-2.1.0.tar.gz", hash = "sha256:3f479fcab569c621528c0190092d6383b4da13ec9631711ce3c14f33aedff4ff"}, +] +discord-py = [ + {file = "discord.py-2.1.0-py3-none-any.whl", hash = "sha256:a2cfa9f09e3013aaaa43600cc8dfaf67c532dd34afcb71e550f5a0dc9133a5e0"}, + {file = "discord.py-2.1.0.tar.gz", hash = "sha256:027ccdd22b5bb66a9e19cbd8daa1bc74b49271a16a074d57e52f288fcfa208e8"}, +] +docopt = [ + {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, +] +dynaconf = [ + {file = "dynaconf-3.1.11-py2.py3-none-any.whl", hash = "sha256:87e0b3b12b5db9e8fb465e1f8c7fdb926cd2ec5b6d88aa7f821f316df93fb165"}, + {file = "dynaconf-3.1.11.tar.gz", hash = "sha256:d9cfb50fd4a71a543fd23845d4f585b620b6ff6d9d3cc1825c614f7b2097cb39"}, +] +exceptiongroup = [ + {file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"}, + {file = "exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"}, +] +frozenlist = [ + {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff8bf625fe85e119553b5383ba0fb6aa3d0ec2ae980295aaefa552374926b3f4"}, + {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dfbac4c2dfcc082fcf8d942d1e49b6aa0766c19d3358bd86e2000bf0fa4a9cf0"}, + {file = "frozenlist-1.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b1c63e8d377d039ac769cd0926558bb7068a1f7abb0f003e3717ee003ad85530"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fdfc24dcfce5b48109867c13b4cb15e4660e7bd7661741a391f821f23dfdca7"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c926450857408e42f0bbc295e84395722ce74bae69a3b2aa2a65fe22cb14b99"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1841e200fdafc3d51f974d9d377c079a0694a8f06de2e67b48150328d66d5483"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f470c92737afa7d4c3aacc001e335062d582053d4dbe73cda126f2d7031068dd"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:783263a4eaad7c49983fe4b2e7b53fa9770c136c270d2d4bbb6d2192bf4d9caf"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:924620eef691990dfb56dc4709f280f40baee568c794b5c1885800c3ecc69816"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae4dc05c465a08a866b7a1baf360747078b362e6a6dbeb0c57f234db0ef88ae0"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bed331fe18f58d844d39ceb398b77d6ac0b010d571cba8267c2e7165806b00ce"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:02c9ac843e3390826a265e331105efeab489ffaf4dd86384595ee8ce6d35ae7f"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9545a33965d0d377b0bc823dcabf26980e77f1b6a7caa368a365a9497fb09420"}, + {file = "frozenlist-1.3.3-cp310-cp310-win32.whl", hash = "sha256:d5cd3ab21acbdb414bb6c31958d7b06b85eeb40f66463c264a9b343a4e238642"}, + {file = "frozenlist-1.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:b756072364347cb6aa5b60f9bc18e94b2f79632de3b0190253ad770c5df17db1"}, + {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b4395e2f8d83fbe0c627b2b696acce67868793d7d9750e90e39592b3626691b7"}, + {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14143ae966a6229350021384870458e4777d1eae4c28d1a7aa47f24d030e6678"}, + {file = "frozenlist-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d8860749e813a6f65bad8285a0520607c9500caa23fea6ee407e63debcdbef6"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23d16d9f477bb55b6154654e0e74557040575d9d19fe78a161bd33d7d76808e8"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb82dbba47a8318e75f679690190c10a5e1f447fbf9df41cbc4c3afd726d88cb"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9309869032abb23d196cb4e4db574232abe8b8be1339026f489eeb34a4acfd91"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a97b4fe50b5890d36300820abd305694cb865ddb7885049587a5678215782a6b"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c188512b43542b1e91cadc3c6c915a82a5eb95929134faf7fd109f14f9892ce4"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:303e04d422e9b911a09ad499b0368dc551e8c3cd15293c99160c7f1f07b59a48"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0771aed7f596c7d73444c847a1c16288937ef988dc04fb9f7be4b2aa91db609d"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:66080ec69883597e4d026f2f71a231a1ee9887835902dbe6b6467d5a89216cf6"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:41fe21dc74ad3a779c3d73a2786bdf622ea81234bdd4faf90b8b03cad0c2c0b4"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f20380df709d91525e4bee04746ba612a4df0972c1b8f8e1e8af997e678c7b81"}, + {file = "frozenlist-1.3.3-cp311-cp311-win32.whl", hash = "sha256:f30f1928162e189091cf4d9da2eac617bfe78ef907a761614ff577ef4edfb3c8"}, + {file = "frozenlist-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:a6394d7dadd3cfe3f4b3b186e54d5d8504d44f2d58dcc89d693698e8b7132b32"}, + {file = "frozenlist-1.3.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8df3de3a9ab8325f94f646609a66cbeeede263910c5c0de0101079ad541af332"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0693c609e9742c66ba4870bcee1ad5ff35462d5ffec18710b4ac89337ff16e27"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd4210baef299717db0a600d7a3cac81d46ef0e007f88c9335db79f8979c0d3d"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:394c9c242113bfb4b9aa36e2b80a05ffa163a30691c7b5a29eba82e937895d5e"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6327eb8e419f7d9c38f333cde41b9ae348bec26d840927332f17e887a8dcb70d"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e24900aa13212e75e5b366cb9065e78bbf3893d4baab6052d1aca10d46d944c"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3843f84a6c465a36559161e6c59dce2f2ac10943040c2fd021cfb70d58c4ad56"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:84610c1502b2461255b4c9b7d5e9c48052601a8957cd0aea6ec7a7a1e1fb9420"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:c21b9aa40e08e4f63a2f92ff3748e6b6c84d717d033c7b3438dd3123ee18f70e"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:efce6ae830831ab6a22b9b4091d411698145cb9b8fc869e1397ccf4b4b6455cb"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:40de71985e9042ca00b7953c4f41eabc3dc514a2d1ff534027f091bc74416401"}, + {file = "frozenlist-1.3.3-cp37-cp37m-win32.whl", hash = "sha256:180c00c66bde6146a860cbb81b54ee0df350d2daf13ca85b275123bbf85de18a"}, + {file = "frozenlist-1.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9bbbcedd75acdfecf2159663b87f1bb5cfc80e7cd99f7ddd9d66eb98b14a8411"}, + {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:034a5c08d36649591be1cbb10e09da9f531034acfe29275fc5454a3b101ce41a"}, + {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba64dc2b3b7b158c6660d49cdb1d872d1d0bf4e42043ad8d5006099479a194e5"}, + {file = "frozenlist-1.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:47df36a9fe24054b950bbc2db630d508cca3aa27ed0566c0baf661225e52c18e"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:008a054b75d77c995ea26629ab3a0c0d7281341f2fa7e1e85fa6153ae29ae99c"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:841ea19b43d438a80b4de62ac6ab21cfe6827bb8a9dc62b896acc88eaf9cecba"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e235688f42b36be2b6b06fc37ac2126a73b75fb8d6bc66dd632aa35286238703"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca713d4af15bae6e5d79b15c10c8522859a9a89d3b361a50b817c98c2fb402a2"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ac5995f2b408017b0be26d4a1d7c61bce106ff3d9e3324374d66b5964325448"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4ae8135b11652b08a8baf07631d3ebfe65a4c87909dbef5fa0cdde440444ee4"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ea42116ceb6bb16dbb7d526e242cb6747b08b7710d9782aa3d6732bd8d27649"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:810860bb4bdce7557bc0febb84bbd88198b9dbc2022d8eebe5b3590b2ad6c842"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ee78feb9d293c323b59a6f2dd441b63339a30edf35abcb51187d2fc26e696d13"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0af2e7c87d35b38732e810befb9d797a99279cbb85374d42ea61c1e9d23094b3"}, + {file = "frozenlist-1.3.3-cp38-cp38-win32.whl", hash = "sha256:899c5e1928eec13fd6f6d8dc51be23f0d09c5281e40d9cf4273d188d9feeaf9b"}, + {file = "frozenlist-1.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:7f44e24fa70f6fbc74aeec3e971f60a14dde85da364aa87f15d1be94ae75aeef"}, + {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2b07ae0c1edaa0a36339ec6cce700f51b14a3fc6545fdd32930d2c83917332cf"}, + {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ebb86518203e12e96af765ee89034a1dbb0c3c65052d1b0c19bbbd6af8a145e1"}, + {file = "frozenlist-1.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5cf820485f1b4c91e0417ea0afd41ce5cf5965011b3c22c400f6d144296ccbc0"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c11e43016b9024240212d2a65043b70ed8dfd3b52678a1271972702d990ac6d"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8fa3c6e3305aa1146b59a09b32b2e04074945ffcfb2f0931836d103a2c38f936"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:352bd4c8c72d508778cf05ab491f6ef36149f4d0cb3c56b1b4302852255d05d5"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65a5e4d3aa679610ac6e3569e865425b23b372277f89b5ef06cf2cdaf1ebf22b"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e2c1185858d7e10ff045c496bbf90ae752c28b365fef2c09cf0fa309291669"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f163d2fd041c630fed01bc48d28c3ed4a3b003c00acd396900e11ee5316b56bb"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:05cdb16d09a0832eedf770cb7bd1fe57d8cf4eaf5aced29c4e41e3f20b30a784"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:8bae29d60768bfa8fb92244b74502b18fae55a80eac13c88eb0b496d4268fd2d"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eedab4c310c0299961ac285591acd53dc6723a1ebd90a57207c71f6e0c2153ab"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3bbdf44855ed8f0fbcd102ef05ec3012d6a4fd7c7562403f76ce6a52aeffb2b1"}, + {file = "frozenlist-1.3.3-cp39-cp39-win32.whl", hash = "sha256:efa568b885bca461f7c7b9e032655c0c143d305bf01c30caf6db2854a4532b38"}, + {file = "frozenlist-1.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfe33efc9cb900a4c46f91a5ceba26d6df370ffddd9ca386eb1d4f0ad97b9ea9"}, + {file = "frozenlist-1.3.3.tar.gz", hash = "sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a"}, +] +idna = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +multidict = [ + {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389"}, + {file = "multidict-6.0.2-cp310-cp310-win32.whl", hash = "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293"}, + {file = "multidict-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658"}, + {file = "multidict-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15"}, + {file = "multidict-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc"}, + {file = "multidict-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d"}, + {file = "multidict-6.0.2-cp38-cp38-win32.whl", hash = "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57"}, + {file = "multidict-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937"}, + {file = "multidict-6.0.2-cp39-cp39-win32.whl", hash = "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a"}, + {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, + {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, +] +mysqlclient = [ + {file = "mysqlclient-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:c1ed71bd6244993b526113cca3df66428609f90e4652f37eb51c33496d478b37"}, + {file = "mysqlclient-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:c812b67e90082a840efb82a8978369e6e69fc62ce1bda4ca8f3084a9d862308b"}, + {file = "mysqlclient-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:0d1cd3a5a4d28c222fa199002810e8146cffd821410b67851af4cc80aeccd97c"}, + {file = "mysqlclient-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b355c8b5a7d58f2e909acdbb050858390ee1b0e13672ae759e5e784110022994"}, + {file = "mysqlclient-2.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:996924f3483fd36a34a5812210c69e71dea5a3d5978d01199b78b7f6d485c855"}, + {file = "mysqlclient-2.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:dea88c8d3f5a5d9293dfe7f087c16dd350ceb175f2f6631c9cf4caf3e19b7a96"}, + {file = "mysqlclient-2.1.1.tar.gz", hash = "sha256:828757e419fb11dd6c5ed2576ec92c3efaa93a0f7c39e263586d1ee779c3d782"}, +] +objectpath = [ + {file = "objectpath-0.6.1-py2.py3-none-any.whl", hash = "sha256:7ac2c507e7e5bfbf245701044453edc8e4073ded01d53349237d918124d7ca87"}, + {file = "objectpath-0.6.1.tar.gz", hash = "sha256:461263136c79292e42431fbb85cdcaac4c6a256f6b1aa5b3ae9316e4965ad819"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +peewee = [ + {file = "peewee-3.15.4.tar.gz", hash = "sha256:2581520c8dfbacd9d580c2719ae259f0637a9e46eda47dfc0ce01864c6366205"}, +] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] +psycopg2 = [ + {file = "psycopg2-2.9.5-cp310-cp310-win32.whl", hash = "sha256:d3ef67e630b0de0779c42912fe2cbae3805ebaba30cda27fea2a3de650a9414f"}, + {file = "psycopg2-2.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:4cb9936316d88bfab614666eb9e32995e794ed0f8f6b3b718666c22819c1d7ee"}, + {file = "psycopg2-2.9.5-cp311-cp311-win32.whl", hash = "sha256:093e3894d2d3c592ab0945d9eba9d139c139664dcf83a1c440b8a7aa9bb21955"}, + {file = "psycopg2-2.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:920bf418000dd17669d2904472efeab2b20546efd0548139618f8fa305d1d7ad"}, + {file = "psycopg2-2.9.5-cp36-cp36m-win32.whl", hash = "sha256:b9ac1b0d8ecc49e05e4e182694f418d27f3aedcfca854ebd6c05bb1cffa10d6d"}, + {file = "psycopg2-2.9.5-cp36-cp36m-win_amd64.whl", hash = "sha256:fc04dd5189b90d825509caa510f20d1d504761e78b8dfb95a0ede180f71d50e5"}, + {file = "psycopg2-2.9.5-cp37-cp37m-win32.whl", hash = "sha256:922cc5f0b98a5f2b1ff481f5551b95cd04580fd6f0c72d9b22e6c0145a4840e0"}, + {file = "psycopg2-2.9.5-cp37-cp37m-win_amd64.whl", hash = "sha256:1e5a38aa85bd660c53947bd28aeaafb6a97d70423606f1ccb044a03a1203fe4a"}, + {file = "psycopg2-2.9.5-cp38-cp38-win32.whl", hash = "sha256:f5b6320dbc3cf6cfb9f25308286f9f7ab464e65cfb105b64cc9c52831748ced2"}, + {file = "psycopg2-2.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:1a5c7d7d577e0eabfcf15eb87d1e19314c8c4f0e722a301f98e0e3a65e238b4e"}, + {file = "psycopg2-2.9.5-cp39-cp39-win32.whl", hash = "sha256:322fd5fca0b1113677089d4ebd5222c964b1760e361f151cbb2706c4912112c5"}, + {file = "psycopg2-2.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:190d51e8c1b25a47484e52a79638a8182451d6f6dff99f26ad9bd81e5359a0fa"}, + {file = "psycopg2-2.9.5.tar.gz", hash = "sha256:a5246d2e683a972e2187a8714b5c2cf8156c064629f9a9b1a873c1730d9e245a"}, +] +pyaml = [ + {file = "pyaml-21.10.1-py2.py3-none-any.whl", hash = "sha256:19985ed303c3a985de4cf8fd329b6d0a5a5b5c9035ea240eccc709ebacbaf4a0"}, + {file = "pyaml-21.10.1.tar.gz", hash = "sha256:c6519fee13bf06e3bb3f20cacdea8eba9140385a7c2546df5dbae4887f768383"}, +] +pyparsing = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] +pytest = [ + {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, + {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, +] +pytz = [ + {file = "pytz-2022.6-py2.py3-none-any.whl", hash = "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427"}, + {file = "pytz-2022.6.tar.gz", hash = "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2"}, +] +pyyaml = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] +rauth = [ + {file = "rauth-0.7.3-py2-none-any.whl", hash = "sha256:b18590fbd77bc3d871936bbdb851377d1b0c08e337b219c303f8fc2b5a42ef2d"}, + {file = "rauth-0.7.3.tar.gz", hash = "sha256:524cdbc1c28560eacfc9a9d40c59525eb8d00fdf07fbad86107ea24411477b0a"}, +] +requests = [ + {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, + {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, +] +tomli = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +urllib3 = [ + {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, + {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, +] +yahoo-fantasy-api = [ + {file = "yahoo_fantasy_api-2.6.0.tar.gz", hash = "sha256:8dbc247c0d3128b03b132044b559e4aa47a57de88dff7a841c2147ce5d3e0075"}, +] +yahoo-oauth = [ + {file = "yahoo_oauth-2.0.tar.gz", hash = "sha256:912921bf8724ced6e5d7924308b2b15c2b51e14ed7d25c9c42cb8b52e3e75158"}, +] +yarl = [ + {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:abc06b97407868ef38f3d172762f4069323de52f2b70d133d096a48d72215d28"}, + {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:07b21e274de4c637f3e3b7104694e53260b5fc10d51fb3ec5fed1da8e0f754e3"}, + {file = "yarl-1.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9de955d98e02fab288c7718662afb33aab64212ecb368c5dc866d9a57bf48880"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ec362167e2c9fd178f82f252b6d97669d7245695dc057ee182118042026da40"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:20df6ff4089bc86e4a66e3b1380460f864df3dd9dccaf88d6b3385d24405893b"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5999c4662631cb798496535afbd837a102859568adc67d75d2045e31ec3ac497"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed19b74e81b10b592084a5ad1e70f845f0aacb57577018d31de064e71ffa267a"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e4808f996ca39a6463f45182e2af2fae55e2560be586d447ce8016f389f626f"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2d800b9c2eaf0684c08be5f50e52bfa2aa920e7163c2ea43f4f431e829b4f0fd"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6628d750041550c5d9da50bb40b5cf28a2e63b9388bac10fedd4f19236ef4957"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f5af52738e225fcc526ae64071b7e5342abe03f42e0e8918227b38c9aa711e28"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:76577f13333b4fe345c3704811ac7509b31499132ff0181f25ee26619de2c843"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0c03f456522d1ec815893d85fccb5def01ffaa74c1b16ff30f8aaa03eb21e453"}, + {file = "yarl-1.8.1-cp310-cp310-win32.whl", hash = "sha256:ea30a42dc94d42f2ba4d0f7c0ffb4f4f9baa1b23045910c0c32df9c9902cb272"}, + {file = "yarl-1.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:9130ddf1ae9978abe63808b6b60a897e41fccb834408cde79522feb37fb72fb0"}, + {file = "yarl-1.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0ab5a138211c1c366404d912824bdcf5545ccba5b3ff52c42c4af4cbdc2c5035"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0fb2cb4204ddb456a8e32381f9a90000429489a25f64e817e6ff94879d432fc"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85cba594433915d5c9a0d14b24cfba0339f57a2fff203a5d4fd070e593307d0b"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ca7e596c55bd675432b11320b4eacc62310c2145d6801a1f8e9ad160685a231"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f77539733e0ec2475ddcd4e26777d08996f8cd55d2aef82ec4d3896687abda"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29e256649f42771829974e742061c3501cc50cf16e63f91ed8d1bf98242e5507"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7fce6cbc6c170ede0221cc8c91b285f7f3c8b9fe28283b51885ff621bbe0f8ee"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:59ddd85a1214862ce7c7c66457f05543b6a275b70a65de366030d56159a979f0"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:12768232751689c1a89b0376a96a32bc7633c08da45ad985d0c49ede691f5c0d"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:b19255dde4b4f4c32e012038f2c169bb72e7f081552bea4641cab4d88bc409dd"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6c8148e0b52bf9535c40c48faebb00cb294ee577ca069d21bd5c48d302a83780"}, + {file = "yarl-1.8.1-cp37-cp37m-win32.whl", hash = "sha256:de839c3a1826a909fdbfe05f6fe2167c4ab033f1133757b5936efe2f84904c07"}, + {file = "yarl-1.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:dd032e8422a52e5a4860e062eb84ac94ea08861d334a4bcaf142a63ce8ad4802"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:19cd801d6f983918a3f3a39f3a45b553c015c5aac92ccd1fac619bd74beece4a"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6347f1a58e658b97b0a0d1ff7658a03cb79bdbda0331603bed24dd7054a6dea1"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c0da7e44d0c9108d8b98469338705e07f4bb7dab96dbd8fa4e91b337db42548"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5587bba41399854703212b87071c6d8638fa6e61656385875f8c6dff92b2e461"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31a9a04ecccd6b03e2b0e12e82131f1488dea5555a13a4d32f064e22a6003cfe"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:205904cffd69ae972a1707a1bd3ea7cded594b1d773a0ce66714edf17833cdae"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea513a25976d21733bff523e0ca836ef1679630ef4ad22d46987d04b372d57fc"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0b51530877d3ad7a8d47b2fff0c8df3b8f3b8deddf057379ba50b13df2a5eae"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2b8f245dad9e331540c350285910b20dd913dc86d4ee410c11d48523c4fd546"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ab2a60d57ca88e1d4ca34a10e9fb4ab2ac5ad315543351de3a612bbb0560bead"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:449c957ffc6bc2309e1fbe67ab7d2c1efca89d3f4912baeb8ead207bb3cc1cd4"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a165442348c211b5dea67c0206fc61366212d7082ba8118c8c5c1c853ea4d82e"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b3ded839a5c5608eec8b6f9ae9a62cb22cd037ea97c627f38ae0841a48f09eae"}, + {file = "yarl-1.8.1-cp38-cp38-win32.whl", hash = "sha256:c1445a0c562ed561d06d8cbc5c8916c6008a31c60bc3655cdd2de1d3bf5174a0"}, + {file = "yarl-1.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:56c11efb0a89700987d05597b08a1efcd78d74c52febe530126785e1b1a285f4"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e80ed5a9939ceb6fda42811542f31c8602be336b1fb977bccb012e83da7e4936"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6afb336e23a793cd3b6476c30f030a0d4c7539cd81649683b5e0c1b0ab0bf350"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4c322cbaa4ed78a8aac89b2174a6df398faf50e5fc12c4c191c40c59d5e28357"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fae37373155f5ef9b403ab48af5136ae9851151f7aacd9926251ab26b953118b"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5395da939ffa959974577eff2cbfc24b004a2fb6c346918f39966a5786874e54"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:076eede537ab978b605f41db79a56cad2e7efeea2aa6e0fa8f05a26c24a034fb"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1a50e461615747dd93c099f297c1994d472b0f4d2db8a64e55b1edf704ec1c"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7de89c8456525650ffa2bb56a3eee6af891e98f498babd43ae307bd42dca98f6"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4a88510731cd8d4befaba5fbd734a7dd914de5ab8132a5b3dde0bbd6c9476c64"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2d93a049d29df172f48bcb09acf9226318e712ce67374f893b460b42cc1380ae"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:21ac44b763e0eec15746a3d440f5e09ad2ecc8b5f6dcd3ea8cb4773d6d4703e3"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d0272228fabe78ce00a3365ffffd6f643f57a91043e119c289aaba202f4095b0"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:99449cd5366fe4608e7226c6cae80873296dfa0cde45d9b498fefa1de315a09e"}, + {file = "yarl-1.8.1-cp39-cp39-win32.whl", hash = "sha256:8b0af1cf36b93cee99a31a545fe91d08223e64390c5ecc5e94c39511832a4bb6"}, + {file = "yarl-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:de49d77e968de6626ba7ef4472323f9d2e5a56c1d85b7c0e2a190b2173d3b9be"}, + {file = "yarl-1.8.1.tar.gz", hash = "sha256:af887845b8c2e060eb5605ff72b6f2dd2aab7a761379373fd89d314f4752abbf"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1e0f841 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,48 @@ +[tool.poetry] +name = "harambot" +version = "0.3.0-Beta" +description = "A Yahoo Fantasy Sports bot for Discord" +authors = ["DMcP89 "] +license = "MIT" +readme = "README.md" +homepage = "https://github.com/DMcP89/harambot" +repository = "https://github.com/DMcP89/harambot" +keywords = [ + "yahoo", + "yahoo fantasy sports", + "fantasy football", + "fantasy basketball", + "fantasy baseball", + "fantasy hockey", + "discord", + "bot" +] + +include = [ + "LICENSE.md", +] + +packages = [ + {include = "harambot"}, +] + +[tool.poetry.dependencies] +python = "^3.8" +discord = "^2.1.0" +yahoo-fantasy-api = "^2.6.0" +dynaconf = "^3.1.11" +peewee = "^3.15.4" +cachetools = "^5.2.0" +psycopg2 = "^2.9.5" +mysqlclient = "^2.1.1" + + +[tool.poetry.group.dev.dependencies] +pytest = "^7.2.0" + +[tool.poetry.scripts] +harambot = "harambot.bot:run" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" From 3846b00a5267e79ed60095e40c1268172dc7b4bd Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Tue, 15 Nov 2022 13:50:58 -0500 Subject: [PATCH 06/51] Updating relative imports to be explicit --- harambot/bot.py | 19 +++++++++++-------- harambot/cogs/meta.py | 2 +- harambot/cogs/misc.py | 2 +- harambot/cogs/yahoo.py | 15 +++++++-------- harambot/database/models.py | 2 +- harambot/utils.py | 4 ++-- 6 files changed, 23 insertions(+), 21 deletions(-) diff --git a/harambot/bot.py b/harambot/bot.py index 20a7ba9..73a1100 100644 --- a/harambot/bot.py +++ b/harambot/bot.py @@ -7,12 +7,12 @@ from discord.ext import commands -from cogs.meta import Meta -from cogs.misc import Misc -from cogs.yahoo import Yahoo -from config import settings -from database.models import Guild -from utils import configure_guild +from harambot.cogs.meta import Meta +from harambot.cogs.misc import Misc +from harambot.cogs.yahoo import Yahoo +from harambot.config import settings +from harambot.database.models import Guild +from harambot.utils import configure_guild #logging.basicConfig(level=logging.INFO) logger = logging.getLogger('harambot.py') @@ -49,5 +49,8 @@ async def on_guild_join(guild): if not Guild.select().where(Guild.guild_id == str(guild.id)).exists(): await configure_guild(bot,guild.owner, guild.id) logger.info("Guild not configured!") - -bot.run(settings.discord_token, reconnect=True) # Where 'TOKEN' is your bot token \ No newline at end of file + +def run(): + bot.run(settings.discord_token, reconnect=True) # Where 'TOKEN' is your bot token + +run() \ No newline at end of file diff --git a/harambot/cogs/meta.py b/harambot/cogs/meta.py index 2582722..0477dfd 100644 --- a/harambot/cogs/meta.py +++ b/harambot/cogs/meta.py @@ -3,7 +3,7 @@ import discord import logging -from utils import configure_guild +from harambot.utils import configure_guild logger = logging.getLogger(__file__) logger.setLevel(logging.INFO) diff --git a/harambot/cogs/misc.py b/harambot/cogs/misc.py index 83a8424..2a11c18 100644 --- a/harambot/cogs/misc.py +++ b/harambot/cogs/misc.py @@ -4,7 +4,7 @@ import discord import logging -from database.models import Guild +from harambot.database.models import Guild logger = logging.getLogger(__file__) logger.setLevel(logging.INFO) diff --git a/harambot/cogs/yahoo.py b/harambot/cogs/yahoo.py index b792dfe..fc1c9ec 100644 --- a/harambot/cogs/yahoo.py +++ b/harambot/cogs/yahoo.py @@ -1,15 +1,14 @@ +import discord +import logging +import urllib3 + from discord import embeds from discord.ext import commands from yahoo_oauth import OAuth2 from playhouse.shortcuts import model_to_dict - -import discord -import logging -import urllib3 -import yahoo_api - -from database.models import Guild +from harambot.yahoo_api import Yahoo +from harambot.database.models import Guild logger = logging.getLogger(__file__) @@ -28,7 +27,7 @@ def __init__(self, bot, KEY, SECRET): async def cog_before_invoke(self, ctx): guild = Guild.get(Guild.guild_id == str(ctx.guild.id)) - self.yahoo_api = yahoo_api.Yahoo(OAuth2(self.KEY, self.SECRET,store_file=False, **model_to_dict(guild)), guild.league_id, guild.league_type) + self.yahoo_api = Yahoo(OAuth2(self.KEY, self.SECRET,store_file=False, **model_to_dict(guild)), guild.league_id, guild.league_type) return @commands.command("standings") diff --git a/harambot/database/models.py b/harambot/database/models.py index b23360e..7e0ddeb 100644 --- a/harambot/database/models.py +++ b/harambot/database/models.py @@ -1,6 +1,6 @@ from peewee import * from playhouse.db_url import connect -from config import settings +from harambot.config import settings if 'DATABASE_URL' in settings: database = connect(settings.database_url) diff --git a/harambot/utils.py b/harambot/utils.py index 0dc9c6e..be5a355 100644 --- a/harambot/utils.py +++ b/harambot/utils.py @@ -2,8 +2,8 @@ import requests import time -from config import settings -from database.models import Guild +from harambot.config import settings +from harambot.database.models import Guild async def configure_guild(bot,owner, id): From 013503b194dbfecacc8dccf19157c40b7a24a92b Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Thu, 17 Nov 2022 11:28:29 -0500 Subject: [PATCH 07/51] Setting up additional tooling Pre-commit Flake8 Black --- .dockerignore | 2 +- .github/ISSUE_TEMPLATE/question.md | 2 - .github/workflows/pytest.yml | 2 +- .gitignore | 5 +- .pre-commit-config.yaml | 20 ++ Dockerfile | 2 +- Makefile | 2 +- README.md | 10 +- app.json | 2 +- config/example.secrets.toml | 2 +- config/settings.toml | 2 +- harambot/bot.py | 32 +-- harambot/cogs/meta.py | 49 +++- harambot/cogs/misc.py | 10 +- harambot/cogs/yahoo.py | 102 +++++--- harambot/config.py | 5 +- harambot/database/models.py | 11 +- harambot/utils.py | 52 ++-- harambot/yahoo_api.py | 268 ++++++++++++++------ poetry.lock | 392 ++++++++++++++++++++++++++++- pyproject.toml | 31 ++- pytest.ini | 2 +- setup.cfg | 2 + tests/conftest.py | 61 +++-- tests/test-matchups.json | 2 +- tests/test-player-details.json | 2 +- tests/test-roster.json | 2 +- tests/test-standings.json | 2 +- tests/test-teams.json | 2 +- tests/test_yahoo_api.py | 39 +-- 30 files changed, 884 insertions(+), 233 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 setup.cfg diff --git a/.dockerignore b/.dockerignore index 5574008..852de25 100644 --- a/.dockerignore +++ b/.dockerignore @@ -19,4 +19,4 @@ coverage.xml .vscode .gitignore secrets.json -.secrets.toml \ No newline at end of file +.secrets.toml diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 27877ff..eb71818 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -6,5 +6,3 @@ labels: '' assignees: '' --- - - diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index a5cc418..2b2095a 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -22,4 +22,4 @@ jobs: if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Test with pytest run: | - python -m pytest -v \ No newline at end of file + python -m pytest -v diff --git a/.gitignore b/.gitignore index 9403da1..d09e44f 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,7 @@ guilds.json answers.txt *.pem .python-version -*.db \ No newline at end of file +*.db + +# Pytest +.coverage diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..272898d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,20 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + +- repo: https://gitlab.com/pycqa/flake8 + rev: 3.8.3 + hooks: + - id: flake8 + +- repo: https://github.com/psf/black + rev: 22.10.0 + hooks: + - id: black diff --git a/Dockerfile b/Dockerfile index dd0cdbc..635b192 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ ADD ./requirements.txt /app/harambot/ ADD ./harambot /app/harambot/harambot RUN apt-get update -RUN apt-get upgrade -y +RUN apt-get upgrade -y RUN apt-get install -y gcc libc-dev make git libffi-dev python3-dev libxml2-dev libxslt-dev RUN apt-get install -y default-libmysqlclient-dev diff --git a/Makefile b/Makefile index 8064fc2..94749d3 100644 --- a/Makefile +++ b/Makefile @@ -29,4 +29,4 @@ run-docker: @echo "${BLUE}Running docker image.." @echo "name: ${MODULE}" @echo "tag: ${MODULE}:${TAG}${NC}\n" - @docker run --name ${MODULE} -d ${MODULE}:${TAG} + @docker run --name ${MODULE} -d ${MODULE}:${TAG} diff --git a/README.md b/README.md index 516427c..c21d478 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ In order to properly configure your bot you will need the following: 2. Fill out the form as shown below, you can provide your own values for Application Name, Description, and Homepage URL. Once complete click the "Create App" button ![yahoo-app-details](/assests/yahoo-app-details.png) 3. Copy the Client ID and Client Secret values - ![yahoo-app-secrets](/assests/yahoo-app-secrets.png) + ![yahoo-app-secrets](/assests/yahoo-app-secrets.png) ### Yahoo League ID @@ -54,7 +54,7 @@ You can find your league's ID under the settings page of your league Harambot now supports heroku deployments! -Click the button at the top and fill out the form with your discord token and yahoo api client key and and secret. +Click the button at the top and fill out the form with your discord token and yahoo api client key and and secret. ![heroku-deployment](/assests/heroku-deployment.png) @@ -63,12 +63,12 @@ Once the deployment is complete enable the dyno ![heroku-dyno](/assests/heroku-dyno.png) ### Local deployment -1. Clone this repository +1. Clone this repository git clone git@github.com:DMcP89/harambot.git cd harambot -2. Run the bot. +2. Run the bot. ### On local machine make run @@ -146,4 +146,4 @@ In order for the bot to work properly it requires the following intents: ### $RIP "My Season" -![rip](/assests/rip.PNG) \ No newline at end of file +![rip](/assests/rip.PNG) diff --git a/app.json b/app.json index f29c9ab..4f15cc3 100644 --- a/app.json +++ b/app.json @@ -16,7 +16,7 @@ "description": "Yahoo Consumer Key", "value": "", "required": "true" - }, + }, "YAHOO_SECRET" : { "description": "Yahoo Consumer Secret", "value": "", diff --git a/config/example.secrets.toml b/config/example.secrets.toml index 218f5b7..5a19999 100644 --- a/config/example.secrets.toml +++ b/config/example.secrets.toml @@ -2,4 +2,4 @@ DISCORD_TOKEN = 'Discord API Token' YAHOO_KEY = 'Yahoo Client ID' YAHOO_SECRET = 'Yahoo Client Secret' -DATABASE_URL = 'sqlite:///harambot.db' \ No newline at end of file +DATABASE_URL = 'sqlite:///harambot.db' diff --git a/config/settings.toml b/config/settings.toml index 7e7f8e3..e3ba27b 100644 --- a/config/settings.toml +++ b/config/settings.toml @@ -1,2 +1,2 @@ [default] -LOGLEVEL = "DEBUG" \ No newline at end of file +LOGLEVEL = "DEBUG" diff --git a/harambot/bot.py b/harambot/bot.py index 73a1100..d3a7be9 100644 --- a/harambot/bot.py +++ b/harambot/bot.py @@ -1,25 +1,21 @@ -import os - import logging import discord -from yahoo_fantasy_api import game -from yahoo_oauth import OAuth2 from discord.ext import commands from harambot.cogs.meta import Meta from harambot.cogs.misc import Misc -from harambot.cogs.yahoo import Yahoo +from harambot.cogs.yahoo import YahooCog from harambot.config import settings from harambot.database.models import Guild from harambot.utils import configure_guild -#logging.basicConfig(level=logging.INFO) -logger = logging.getLogger('harambot.py') -if 'LOGLEVEL' in settings: +# logging.basicConfig(level=logging.INFO) +logger = logging.getLogger("harambot.py") +if "LOGLEVEL" in settings: logger.setLevel(settings.loglevel) else: - logger.setLevel('INFO') + logger.setLevel("INFO") intents = discord.Intents.default() intents.members = True @@ -27,14 +23,13 @@ intents.message_content = True bot = commands.Bot(command_prefix="$", description="", intents=intents) -bot.remove_command('help') - +bot.remove_command("help") @bot.event async def on_ready(): await bot.add_cog(Meta(bot)) - await bot.add_cog(Yahoo(bot, settings.yahoo_key, settings.yahoo_secret)) + await bot.add_cog(YahooCog(bot, settings.yahoo_key, settings.yahoo_secret)) await bot.add_cog(Misc(bot)) if not Guild.table_exists(): Guild.create_table() @@ -43,14 +38,19 @@ async def on_ready(): await bot.tree.sync(guild=guild) logger.info("Everything's all ready to go~") + @bot.event async def on_guild_join(guild): logger.info("Joined {}".format(guild.name)) if not Guild.select().where(Guild.guild_id == str(guild.id)).exists(): - await configure_guild(bot,guild.owner, guild.id) + await configure_guild(bot, guild.owner, guild.id) logger.info("Guild not configured!") -def run(): - bot.run(settings.discord_token, reconnect=True) # Where 'TOKEN' is your bot token -run() \ No newline at end of file +def run(): + bot.run( + settings.discord_token, reconnect=True + ) # Where 'TOKEN' is your bot token + + +run() diff --git a/harambot/cogs/meta.py b/harambot/cogs/meta.py index 0477dfd..bb13f5c 100644 --- a/harambot/cogs/meta.py +++ b/harambot/cogs/meta.py @@ -8,21 +8,49 @@ logger = logging.getLogger(__file__) logger.setLevel(logging.INFO) + class Meta(commands.Cog): - def __init__(self, bot): self.bot = bot @commands.command("help") async def help(self, ctx): - embed = discord.Embed(title="Harambot", description="Yahoo Fantasy Sports Bot for Discord", color=0xeee657) - embed.add_field(name="$ping", value="Gives the latency of harambot", inline=False) - embed.add_field(name="$RIP", value="Pay respects to Harambe", inline=False) - embed.add_field(name="$standings", value="Returns the current standings of your league", inline=False) - embed.add_field(name="$roster team_name", value="Returns the roster of the given team", inline=False) - embed.add_field(name="$stats player_name", value="Returns the details of the given player", inline=False) - embed.add_field(name="$trade", value="Create poll for latest trade for league approval", inline=False) - embed.add_field(name="$matchups", value="Returns the current weeks matchups", inline=False) + embed = discord.Embed( + title="Harambot", + description="Yahoo Fantasy Sports Bot for Discord", + color=0xEEE657, + ) + embed.add_field( + name="$ping", value="Gives the latency of harambot", inline=False + ) + embed.add_field( + name="$RIP", value="Pay respects to Harambe", inline=False + ) + embed.add_field( + name="$standings", + value="Returns the current standings of your league", + inline=False, + ) + embed.add_field( + name="$roster team_name", + value="Returns the roster of the given team", + inline=False, + ) + embed.add_field( + name="$stats player_name", + value="Returns the details of the given player", + inline=False, + ) + embed.add_field( + name="$trade", + value="Create poll for latest trade for league approval", + inline=False, + ) + embed.add_field( + name="$matchups", + value="Returns the current weeks matchups", + inline=False, + ) await ctx.send(embed=embed) @commands.command("ping") @@ -32,8 +60,7 @@ async def ping(self, ctx): await ctx.send(latency) @commands.hybrid_command() - async def configure(self, ctx): await ctx.send("Configuring guild...") await configure_guild(self.bot, ctx.guild.owner, ctx.guild.id) - await ctx.send("Guild configured successfully") \ No newline at end of file + await ctx.send("Guild configured successfully") diff --git a/harambot/cogs/misc.py b/harambot/cogs/misc.py index 2a11c18..cb515af 100644 --- a/harambot/cogs/misc.py +++ b/harambot/cogs/misc.py @@ -9,8 +9,8 @@ logger = logging.getLogger(__file__) logger.setLevel(logging.INFO) + class Misc(commands.Cog): - def __init__(self, bot): self.bot = bot @@ -19,8 +19,8 @@ async def RIP(self, ctx, *args): logger.info("RIP called") guild = Guild.get(Guild.guild_id == str(ctx.guild.id)) respected = args[0] if args else "Harambe" - message = guild.RIP_text +" "+ respected - embed = discord.Embed(title="", description='', color=0xeee657) + message = guild.RIP_text + " " + respected + embed = discord.Embed(title="", description="", color=0xEEE657) embed.set_image(url=guild.RIP_image_url) - - await ctx.send(content=message,embed=embed) + + await ctx.send(content=message, embed=embed) diff --git a/harambot/cogs/yahoo.py b/harambot/cogs/yahoo.py index fc1c9ec..042ff8c 100644 --- a/harambot/cogs/yahoo.py +++ b/harambot/cogs/yahoo.py @@ -2,7 +2,6 @@ import logging import urllib3 -from discord import embeds from discord.ext import commands from yahoo_oauth import OAuth2 from playhouse.shortcuts import model_to_dict @@ -14,9 +13,12 @@ logger = logging.getLogger(__file__) logger.setLevel(logging.INFO) -class Yahoo(commands.Cog): - error_message = "I'm having trouble getting that right now please try again later" +class YahooCog(commands.Cog): + + error_message = ( + "I'm having trouble getting that right now please try again later" + ) def __init__(self, bot, KEY, SECRET): self.bot = bot @@ -24,12 +26,18 @@ def __init__(self, bot, KEY, SECRET): self.KEY = KEY self.SECRET = SECRET self.yahoo_api = None - + async def cog_before_invoke(self, ctx): guild = Guild.get(Guild.guild_id == str(ctx.guild.id)) - self.yahoo_api = Yahoo(OAuth2(self.KEY, self.SECRET,store_file=False, **model_to_dict(guild)), guild.league_id, guild.league_type) + self.yahoo_api = Yahoo( + OAuth2( + self.KEY, self.SECRET, store_file=False, **model_to_dict(guild) + ), + guild.league_id, + guild.league_type, + ) return - + @commands.command("standings") async def standings(self, ctx): logger.info("standings called") @@ -40,36 +48,38 @@ async def standings(self, ctx): await ctx.send(self.error_message) @commands.command("roster") - async def roster(self, ctx, *, content:str): + async def roster(self, ctx, *, content: str): logger.info("roster called") roster = self.yahoo_api.get_roster(content) if roster: await ctx.send(embed=roster) else: await ctx.send(self.error_message) - @commands.command("trade") async def trade(self, ctx): logger.info("trade called") latest_trade = self.yahoo_api.get_latest_trade() - if latest_trade == None: + if latest_trade is None: await ctx.send("No trades up for approval at this time") return teams = self.yahoo_api.league().teams() - trader = teams[latest_trade['trader_team_key']] - tradee = teams[latest_trade['tradee_team_key']] - managers = [trader['name'], tradee['name']] - + trader = teams[latest_trade["trader_team_key"]] + tradee = teams[latest_trade["tradee_team_key"]] + managers = [trader["name"], tradee["name"]] + player_set0 = [] player_set0_details = "" - for player in latest_trade['trader_players']: - player_set0.append(player['name']) - api_details = self.yahoo_api.get_player_details(player['name'])["text"]+"\n" - if api_details: + for player in latest_trade["trader_players"]: + player_set0.append(player["name"]) + api_details = ( + self.yahoo_api.get_player_details(player["name"])["text"] + + "\n" + ) + if api_details: player_set0_details = player_set0_details + api_details else: await ctx.send(self.error_message) @@ -77,42 +87,64 @@ async def trade(self, ctx): player_set1 = [] player_set1_details = "" - for player in latest_trade['tradee_players']: - player_set1.append(player['name']) - api_details = self.yahoo_api.get_player_details(player['name'])["text"]+"\n" - if api_details: + for player in latest_trade["tradee_players"]: + player_set1.append(player["name"]) + api_details = ( + self.yahoo_api.get_player_details(player["name"])["text"] + + "\n" + ) + if api_details: player_set1_details = player_set1_details + api_details else: await ctx.send(self.error_message) return - confirm_trade_message = "{} sends {} to {} for {}".format(managers[0],', '.join(player_set0),managers[1],', '.join(player_set1)) + confirm_trade_message = "{} sends {} to {} for {}".format( + managers[0], + ", ".join(player_set0), + managers[1], + ", ".join(player_set1), + ) announcement = "There's collusion afoot!\n" - embed = discord.Embed(title="The following trade is up for approval:", description=confirm_trade_message, color=0xeee657) - embed.add_field(name="{} sends:".format(managers[0]), value=player_set0_details, inline=False) - embed.add_field(name="to {} for:".format(managers[1]), value=player_set1_details, inline=False) - embed.add_field(name="Voting", value=" Click :white_check_mark: for yes, :no_entry_sign: for no") - msg = await ctx.send(content=announcement, embed=embed) - yes_emoji = '\U00002705' - no_emoji = '\U0001F6AB' + embed = discord.Embed( + title="The following trade is up for approval:", + description=confirm_trade_message, + color=0xEEE657, + ) + embed.add_field( + name="{} sends:".format(managers[0]), + value=player_set0_details, + inline=False, + ) + embed.add_field( + name="to {} for:".format(managers[1]), + value=player_set1_details, + inline=False, + ) + embed.add_field( + name="Voting", + value=" Click :white_check_mark: for yes,\ + :no_entry_sign: for no", + ) + msg = await ctx.send(content=announcement, embed=embed) + yes_emoji = "\U00002705" + no_emoji = "\U0001F6AB" await msg.add_reaction(yes_emoji) await msg.add_reaction(no_emoji) - @commands.command("stats") - async def stats(self, ctx, *, content:str): + async def stats(self, ctx, *, content: str): logger.info("player_details called") details = self.yahoo_api.get_player_details(content) if details: - await ctx.send(embed=details['embed']) + await ctx.send(embed=details["embed"]) else: await ctx.send("Player not found") - @commands.command("matchups") - async def matchups(self,ctx): + async def matchups(self, ctx): embed = self.yahoo_api.get_matchups() if embed: await ctx.send(embed=embed) else: - await ctx.send(self.error_message) \ No newline at end of file + await ctx.send(self.error_message) diff --git a/harambot/config.py b/harambot/config.py index 1716c90..4434786 100644 --- a/harambot/config.py +++ b/harambot/config.py @@ -1,8 +1,7 @@ - from dynaconf import Dynaconf settings = Dynaconf( envvar_prefix=False, - settings_files=['settings.toml', '.secrets.toml'], + settings_files=["settings.toml", ".secrets.toml"], environments=True, -) \ No newline at end of file +) diff --git a/harambot/database/models.py b/harambot/database/models.py index 7e0ddeb..4260128 100644 --- a/harambot/database/models.py +++ b/harambot/database/models.py @@ -1,11 +1,13 @@ -from peewee import * +from peewee import SqliteDatabase +from peewee import Model, TextField, IntegerField, BigIntegerField from playhouse.db_url import connect from harambot.config import settings -if 'DATABASE_URL' in settings: +if "DATABASE_URL" in settings: database = connect(settings.database_url) else: - database = SqliteDatabase(':memory:') + database = SqliteDatabase(":memory:") + class BaseModel(Model): class Meta: @@ -22,6 +24,5 @@ class Guild(BaseModel): token_time = BigIntegerField() league_id = TextField() league_type = TextField() - RIP_text = TextField() + RIP_text = TextField() RIP_image_url = TextField() - diff --git a/harambot/utils.py b/harambot/utils.py index be5a355..90af7d6 100644 --- a/harambot/utils.py +++ b/harambot/utils.py @@ -5,42 +5,58 @@ from harambot.config import settings from harambot.database.models import Guild -async def configure_guild(bot,owner, id): +async def configure_guild(bot, owner, id): def check(m): return m.author == owner await owner.send("Thank you for adding Harambot to your server!") - await owner.send("Please open the following link to authorize with Yahoo, respond with the code given after authorization") - await owner.send("https://api.login.yahoo.com/oauth2/request_auth?redirect_uri=oob&response_type=code&client_id={}".format(settings.yahoo_key)) - code = await bot.wait_for('message', timeout=60, check=check) - encoded_creds = base64.b64encode(('{0}:{1}'.format(settings.yahoo_key, settings.yahoo_secret )).encode('utf-8')) + await owner.send( + "Please open the following link to authorize with Yahoo, respond with\ + the code given after authorization" + ) + await owner.send( + "https://api.login.yahoo.com/oauth2/request_auth?\ + redirect_uri=oob&response_type=code&client_id={}".format( + settings.yahoo_key + ) + ) + code = await bot.wait_for("message", timeout=60, check=check) + encoded_creds = base64.b64encode( + ("{0}:{1}".format(settings.yahoo_key, settings.yahoo_secret)).encode( + "utf-8" + ) + ) details = requests.post( - url='https://api.login.yahoo.com/oauth2/get_token', - data={"code": code.clean_content, 'redirect_uri': 'oob', 'grant_type': 'authorization_code'}, - headers = { - 'Authorization': 'Basic {0}'.format(encoded_creds.decode('utf-8')), - 'Content-Type': 'application/x-www-form-urlencoded' - } + url="https://api.login.yahoo.com/oauth2/get_token", + data={ + "code": code.clean_content, + "redirect_uri": "oob", + "grant_type": "authorization_code", + }, + headers={ + "Authorization": "Basic {0}".format(encoded_creds.decode("utf-8")), + "Content-Type": "application/x-www-form-urlencoded", + }, ).json() - details['token_time'] = time.time() + details["token_time"] = time.time() await owner.send("Enter Yahoo League ID") - leauge_id = await bot.wait_for('message', timeout=60, check=check) + leauge_id = await bot.wait_for("message", timeout=60, check=check) await owner.send("Enter Yahoo League Type(nfl, nhl, nba, mlb)") - leauge_type = await bot.wait_for('message', timeout=60, check=check) + leauge_type = await bot.wait_for("message", timeout=60, check=check) await owner.send("Enter text to use with $RIP command") - RIP_text = await bot.wait_for('message', timeout=60, check=check) + RIP_text = await bot.wait_for("message", timeout=60, check=check) await owner.send("Enter image url to use with $RIP command") - RIP_image_url = await bot.wait_for('message', timeout=60, check=check) + RIP_image_url = await bot.wait_for("message", timeout=60, check=check) details["league_id"] = leauge_id.clean_content details["league_type"] = leauge_type.clean_content details["RIP_text"] = RIP_text.clean_content details["RIP_image_url"] = RIP_image_url.clean_content guild = Guild.get_or_none(Guild.guild_id == str(id)) if guild: - query = (Guild.update(details).where(Guild.guild_id == str(id))) + query = Guild.update(details).where(Guild.guild_id == str(id)) query.execute() else: guild = Guild(guild_id=str(id), **details) guild.save() - return \ No newline at end of file + return diff --git a/harambot/yahoo_api.py b/harambot/yahoo_api.py index f3758d3..6f5148f 100644 --- a/harambot/yahoo_api.py +++ b/harambot/yahoo_api.py @@ -6,15 +6,14 @@ from yahoo_fantasy_api import game from cachetools import cached, TTLCache - logger = logging.getLogger() logger.setLevel(logging.INFO) logging.disable(logging.DEBUG) - dir_path = os.path.dirname(os.path.realpath(__file__)) + class Yahoo: oauth = None @@ -25,48 +24,71 @@ def __init__(self, oauth, league_id, league_type): self.league_id = league_id self.league_type = league_type - @cached(cache=TTLCache(maxsize=1024, ttl=600)) def league(self): if not self.oauth.token_is_valid(): self.oauth.refresh_access_token() gm = game.Game(self.oauth, self.league_type) - league = gm.to_league('{}.l.{}'.format(gm.game_id(), self.league_id)) - self.scoring_type = league.settings()['scoring_type'] + league = gm.to_league("{}.l.{}".format(gm.game_id(), self.league_id)) + self.scoring_type = league.settings()["scoring_type"] return league - - + @cached(cache=TTLCache(maxsize=1024, ttl=600)) def get_standings(self): try: - embed = discord.Embed(title="Standings", description='Team Name\n W-L-T', color=0xeee657) + embed = discord.Embed( + title="Standings", + description="Team Name\n W-L-T", + color=0xEEE657, + ) for idx, team in enumerate(self.league().standings()): - outcomes = team['outcome_totals'] - record = '{}-{}-{}'.format(outcomes['wins'], outcomes['losses'], outcomes['ties']) - embed.add_field(name=str(idx+1) + '. '+team['name'],value=record, inline=False) + outcomes = team["outcome_totals"] + record = "{}-{}-{}".format( + outcomes["wins"], outcomes["losses"], outcomes["ties"] + ) + embed.add_field( + name=str(idx + 1) + ". " + team["name"], + value=record, + inline=False, + ) return embed except Exception: - logger.exception("Error while fetching standings for league {}".format(self.league_id)) + logger.exception( + "Error while fetching standings for league {}".format( + self.league_id + ) + ) return None - @cached(cache=TTLCache(maxsize=1024, ttl=600)) def get_team(self, team_name): try: - for id,team in self.league().teams().items(): - if team['name'] == team_name: + for id, team in self.league().teams().items(): + if team["name"] == team_name: return self.league().to_team(id) except Exception: - logger.exception("Error while fetching team: {} from league: {}".format(team_name, self.league_id)) + logger.exception( + "Error while fetching team: {} from league: {}".format( + team_name, self.league_id + ) + ) return None - + @cached(cache=TTLCache(maxsize=1024, ttl=600)) def get_roster(self, team_name): team = self.get_team(team_name) if team: - embed = discord.Embed(title="{}'s Roster".format(team_name), description='', color=0xeee657) + embed = discord.Embed( + title="{}'s Roster".format(team_name), + description="", + color=0xEEE657, + ) for player in team.roster(self.league().current_week()): - embed.add_field(name=player['selected_position'], value=player['name'], inline=False) + embed.add_field( + name=player["selected_position"], + value=player["name"], + inline=False, + ) return embed else: return None @@ -75,97 +97,193 @@ def get_roster(self, team_name): def get_player_details(self, player_name): try: player = self.league().player_details(player_name)[0] - - embed = discord.Embed(title=player['name']['full'], description='#' + player['uniform_number'], color=0xeee657) - embed.add_field(name="Postion", value=player['primary_position']) - embed.add_field(name="Team", value=player['editorial_team_abbr']) - if 'bye_weeks' in player: - embed.add_field(name="Bye", value=player['bye_weeks']['week']) - if self.scoring_type == 'head': - embed.add_field(name="Total Points", value=player['player_points']['total']) - embed.add_field(name="Owner", value=self.get_player_owner(player['player_id'])) - embed.set_image(url=player['image_url']) - - player_details_text = player['name']['full'] + ' #' + player['uniform_number'] + '\n' - player_details_text = player_details_text + "Position: "+player['primary_position']+'\n' - player_details_text = player_details_text + "Team: "+player['editorial_team_abbr']+'\n' - if 'bye_weeks' in player: - player_details_text = player_details_text + "Bye: "+player['bye_weeks']['week']+'\n' - if self.scoring_type == 'head': - player_details_text = player_details_text + "Total Points: "+player['player_points']['total']+'\n' - player_details_text = player_details_text + "Owner: " + self.get_player_owner(player['player_id']) - + + embed = discord.Embed( + title=player["name"]["full"], + description="#" + player["uniform_number"], + color=0xEEE657, + ) + embed.add_field(name="Postion", value=player["primary_position"]) + embed.add_field(name="Team", value=player["editorial_team_abbr"]) + if "bye_weeks" in player: + embed.add_field(name="Bye", value=player["bye_weeks"]["week"]) + if self.scoring_type == "head": + embed.add_field( + name="Total Points", value=player["player_points"]["total"] + ) + embed.add_field( + name="Owner", value=self.get_player_owner(player["player_id"]) + ) + embed.set_image(url=player["image_url"]) + + player_details_text = ( + player["name"]["full"] + " #" + player["uniform_number"] + "\n" + ) + player_details_text = ( + player_details_text + + "Position: " + + player["primary_position"] + + "\n" + ) + player_details_text = ( + player_details_text + + "Team: " + + player["editorial_team_abbr"] + + "\n" + ) + if "bye_weeks" in player: + player_details_text = ( + player_details_text + + "Bye: " + + player["bye_weeks"]["week"] + + "\n" + ) + if self.scoring_type == "head": + player_details_text = ( + player_details_text + + "Total Points: " + + player["player_points"]["total"] + + "\n" + ) + player_details_text = ( + player_details_text + + "Owner: " + + self.get_player_owner(player["player_id"]) + ) + player_details = {} - player_details['embed'] = embed - player_details['text'] = player_details_text + player_details["embed"] = embed + player_details["text"] = player_details_text return player_details - except Exception as e: - logger.exception("Error while fetching player details for player: {} in league {}".format(player_name, self.league_id)) + except Exception: + logger.exception( + "Error while fetching player details for player: \ + {} in league {}".format( + player_name, self.league_id + ) + ) return None - + @cached(cache=TTLCache(maxsize=1024, ttl=600)) def get_player_owner(self, player_id): try: - player_ownership = self.league().ownership([player_id])[str(player_id)] - if 'owner_team_name' in player_ownership: - return player_ownership['owner_team_name'] + player_ownership = self.league().ownership([player_id])[ + str(player_id) + ] + if "owner_team_name" in player_ownership: + return player_ownership["owner_team_name"] else: ownership_map = { "freeagents": "Free Agent", - "waivers": "On Waviers" + "waivers": "On Waviers", } - return ownership_map.get(player_ownership['ownership_type'], "") + return ownership_map.get( + player_ownership["ownership_type"], "" + ) except Exception: - logger.exception("Error while fetching ownership for player id: {} in league {}".format(player_id, self.league_id)) + logger.exception( + "Error while fetching ownership for player id: \ + {} in league {}".format( + player_id, self.league_id + ) + ) return None - - - @cached(cache=TTLCache(maxsize=1024, ttl=600)) def get_matchups(self): try: - embed = discord.Embed(title="Matchups for Week {}".format(str(self.league().current_week())), description='', color=0xeee657) + embed = discord.Embed( + title="Matchups for Week {}".format( + str(self.league().current_week()) + ), + description="", + color=0xEEE657, + ) matchups_json = objectpath.Tree(self.league().matchups()) - matchups = matchups_json.execute('$..scoreboard..matchups..matchup..teams') + matchups = matchups_json.execute( + "$..scoreboard..matchups..matchup..teams" + ) team1_details = "" team2_details = "" for matchup in matchups: team1 = matchup["0"]["team"] team1_name = team1[0][2]["name"] - if self.scoring_type == 'head': - team1_actual_points = team1[1]['team_points']['total'] - team1_projected_points = team1[1]['team_projected_points']['total'] - if 'win_probability' in team1[1]: - team1_win_probability = "{:.0%}".format(team1[1]['win_probability']) - team1_details = '***{}*** \n Projected Score: {} \n Actual Score: {} \n Win Probability: {} \n'.format(team1_name, team1_projected_points, team1_actual_points, team1_win_probability) + if self.scoring_type == "head": + team1_actual_points = team1[1]["team_points"]["total"] + team1_projected_points = team1[1]["team_projected_points"][ + "total" + ] + if "win_probability" in team1[1]: + team1_win_probability = "{:.0%}".format( + team1[1]["win_probability"] + ) + team1_details = "***{}*** \n Projected Score: {} \n \ + Actual Score: {} \n Win Probability: {} \n".format( + team1_name, + team1_projected_points, + team1_actual_points, + team1_win_probability, + ) else: - team1_details = '***{}*** \n Projected Score: {} \n Actual Score: {} \n'.format(team1_name, team1_projected_points, team1_actual_points) + team1_details = "***{}*** \n Projected Score: {} \n \ + Actual Score: {} \n".format( + team1_name, + team1_projected_points, + team1_actual_points, + ) team2 = matchup["1"]["team"] team2_name = team2[0][2]["name"] - if self.scoring_type == 'head': - team2_actual_points = team2[1]['team_points']['total'] - team2_projected_points = team2[1]['team_projected_points']['total'] - if 'win_probability' in team2[1]: - team2_win_probability = "{:.0%}".format(team2[1]['win_probability']) - team2_details = '\n***{}*** \n Projected Score: {} \n Actual Score: {} \n Win Probability: {}\n'.format(team2_name, team2_projected_points, team2_actual_points, team2_win_probability) + if self.scoring_type == "head": + team2_actual_points = team2[1]["team_points"]["total"] + team2_projected_points = team2[1]["team_projected_points"][ + "total" + ] + if "win_probability" in team2[1]: + team2_win_probability = "{:.0%}".format( + team2[1]["win_probability"] + ) + team2_details = "\n***{}*** \n Projected Score: {} \n \ + Actual Score: {} \n Win Probability: {}\n".format( + team2_name, + team2_projected_points, + team2_actual_points, + team2_win_probability, + ) else: - team2_details = '\n***{}*** \n Projected Score: {} \n Actual Score: {} \n'.format(team2_name, team2_projected_points, team2_actual_points) - divider = '--------------------------------------' - embed.add_field(name="{} vs {}".format(team1_name, team2_name), value=team1_details + team2_details+divider, inline=False) + team2_details = "\n***{}*** \n Projected Score: {} \n \ + Actual Score: {} \n".format( + team2_name, + team2_projected_points, + team2_actual_points, + ) + divider = "--------------------------------------" + embed.add_field( + name="{} vs {}".format(team1_name, team2_name), + value=team1_details + team2_details + divider, + inline=False, + ) return embed except Exception: - logger.exception("Error while fetching matchups for league: {}".format(self.league_id)) + logger.exception( + "Error while fetching matchups for league: {}".format( + self.league_id + ) + ) @cached(cache=TTLCache(maxsize=1024, ttl=600)) def get_latest_trade(self): try: for key, values in self.league().teams().items(): - if 'is_owned_by_current_login' in values: + if "is_owned_by_current_login" in values: team = self.league().to_team(key) - accepted_trades = list(filter(lambda d: d['status'] == 'accepted', team.proposed_trades())) + accepted_trades = list( + filter( + lambda d: d["status"] == "accepted", + team.proposed_trades(), + ) + ) if accepted_trades: return accepted_trades[0] return except Exception: - logger.exception("Error while fetching latest trade") \ No newline at end of file + logger.exception("Error while fetching latest trade") diff --git a/poetry.lock b/poetry.lock index 74acce5..41f236e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -51,6 +51,28 @@ docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] +[[package]] +name = "black" +version = "22.10.0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + [[package]] name = "cachetools" version = "5.2.0" @@ -67,6 +89,14 @@ category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "cfgv" +version = "3.3.1" +description = "Validate configuration and produce human readable error messages." +category = "dev" +optional = false +python-versions = ">=3.6.1" + [[package]] name = "charset-normalizer" version = "2.1.1" @@ -78,6 +108,17 @@ python-versions = ">=3.6.0" [package.extras] unicode-backport = ["unicodedata2"] +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + [[package]] name = "colorama" version = "0.4.6" @@ -86,6 +127,20 @@ category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +[[package]] +name = "coverage" +version = "6.5.0" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + [[package]] name = "discord" version = "2.1.0" @@ -114,6 +169,14 @@ speed = ["Brotli", "aiodns (>=1.1)", "cchardet (==2.1.7)", "orjson (>=3.5.4)"] test = ["coverage[toml]", "pytest", "pytest-asyncio", "pytest-cov", "pytest-mock", "typing-extensions (>=4.3,<5)"] voice = ["PyNaCl (>=1.3.0,<1.6)"] +[[package]] +name = "distlib" +version = "0.3.6" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "docopt" version = "0.6.2" @@ -151,6 +214,31 @@ python-versions = ">=3.7" [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "filelock" +version = "3.8.0" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"] +testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "flake8" +version = "5.0.4" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.9.0,<2.10.0" +pyflakes = ">=2.5.0,<2.6.0" + [[package]] name = "frozenlist" version = "1.3.3" @@ -159,6 +247,17 @@ category = "main" optional = false python-versions = ">=3.7" +[[package]] +name = "identify" +version = "2.5.8" +description = "File identification library for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +license = ["ukkonen"] + [[package]] name = "idna" version = "3.4" @@ -175,6 +274,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = ">=3.6" + [[package]] name = "multidict" version = "6.0.2" @@ -183,6 +290,14 @@ category = "main" optional = false python-versions = ">=3.7" +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "mysqlclient" version = "2.1.1" @@ -191,6 +306,17 @@ category = "main" optional = false python-versions = ">=3.5" +[[package]] +name = "nodeenv" +version = "1.7.0" +description = "Node.js virtual environment builder" +category = "dev" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" + +[package.dependencies] +setuptools = "*" + [[package]] name = "objectpath" version = "0.6.1" @@ -210,6 +336,14 @@ python-versions = ">=3.6" [package.dependencies] pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" +[[package]] +name = "pathspec" +version = "0.10.2" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=3.7" + [[package]] name = "peewee" version = "3.15.4" @@ -218,6 +352,18 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "platformdirs" +version = "2.5.4" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] +test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] + [[package]] name = "pluggy" version = "1.0.0" @@ -230,6 +376,22 @@ python-versions = ">=3.6" dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pre-commit" +version = "2.20.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +toml = "*" +virtualenv = ">=20.0.8" + [[package]] name = "psycopg2" version = "2.9.5" @@ -249,6 +411,22 @@ python-versions = "*" [package.dependencies] PyYAML = "*" +[[package]] +name = "pycodestyle" +version = "2.9.1" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "pyflakes" +version = "2.5.0" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=3.6" + [[package]] name = "pyparsing" version = "3.0.9" @@ -280,6 +458,21 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +[[package]] +name = "pytest-cov" +version = "4.0.0" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + [[package]] name = "pytz" version = "2022.6" @@ -325,6 +518,27 @@ urllib3 = ">=1.21.1,<1.27" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "setuptools" +version = "65.5.1" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + [[package]] name = "tomli" version = "2.0.1" @@ -333,6 +547,14 @@ category = "dev" optional = false python-versions = ">=3.7" +[[package]] +name = "typing-extensions" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "dev" +optional = false +python-versions = ">=3.7" + [[package]] name = "urllib3" version = "1.26.12" @@ -346,6 +568,23 @@ brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +[[package]] +name = "virtualenv" +version = "20.16.7" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +distlib = ">=0.3.6,<1" +filelock = ">=3.4.1,<4" +platformdirs = ">=2.4,<3" + +[package.extras] +docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"] +testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] + [[package]] name = "yahoo-fantasy-api" version = "2.6.0" @@ -387,7 +626,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "d61130143a91a5c0caa6f1b001f945056b3f0aeec6a13be14e12baf1fab5bc93" +content-hash = "84615cf3da8ad6ec805acac5b828c3126ee2c0bba37c4b70bb076b6c7056ec13" [metadata.files] aiohttp = [ @@ -491,6 +730,29 @@ attrs = [ {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, ] +black = [ + {file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"}, + {file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"}, + {file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"}, + {file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"}, + {file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"}, + {file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"}, + {file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"}, + {file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"}, + {file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"}, + {file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"}, + {file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"}, + {file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"}, + {file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"}, + {file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"}, + {file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"}, + {file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"}, + {file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"}, + {file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"}, + {file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"}, + {file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"}, + {file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"}, +] cachetools = [ {file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"}, {file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"}, @@ -499,14 +761,74 @@ certifi = [ {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, ] +cfgv = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] charset-normalizer = [ {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, ] +click = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] colorama = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +coverage = [ + {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, + {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, + {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, + {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, + {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, + {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, + {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, + {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, + {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, + {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, + {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, + {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, + {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, + {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, + {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, + {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, +] discord = [ {file = "discord-2.1.0-py3-none-any.whl", hash = "sha256:44515e6d02e61528b2a81e511e03b3f60ce0b3737ba9efc69c2defce2f1ebc1d"}, {file = "discord-2.1.0.tar.gz", hash = "sha256:3f479fcab569c621528c0190092d6383b4da13ec9631711ce3c14f33aedff4ff"}, @@ -515,6 +837,10 @@ discord-py = [ {file = "discord.py-2.1.0-py3-none-any.whl", hash = "sha256:a2cfa9f09e3013aaaa43600cc8dfaf67c532dd34afcb71e550f5a0dc9133a5e0"}, {file = "discord.py-2.1.0.tar.gz", hash = "sha256:027ccdd22b5bb66a9e19cbd8daa1bc74b49271a16a074d57e52f288fcfa208e8"}, ] +distlib = [ + {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, + {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, +] docopt = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] @@ -526,6 +852,14 @@ exceptiongroup = [ {file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"}, {file = "exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"}, ] +filelock = [ + {file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"}, + {file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"}, +] +flake8 = [ + {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, + {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, +] frozenlist = [ {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff8bf625fe85e119553b5383ba0fb6aa3d0ec2ae980295aaefa552374926b3f4"}, {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dfbac4c2dfcc082fcf8d942d1e49b6aa0766c19d3358bd86e2000bf0fa4a9cf0"}, @@ -602,6 +936,10 @@ frozenlist = [ {file = "frozenlist-1.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfe33efc9cb900a4c46f91a5ceba26d6df370ffddd9ca386eb1d4f0ad97b9ea9"}, {file = "frozenlist-1.3.3.tar.gz", hash = "sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a"}, ] +identify = [ + {file = "identify-2.5.8-py2.py3-none-any.whl", hash = "sha256:48b7925fe122720088aeb7a6c34f17b27e706b72c61070f27fe3789094233440"}, + {file = "identify-2.5.8.tar.gz", hash = "sha256:7a214a10313b9489a0d61467db2856ae8d0b8306fc923e03a9effa53d8aedc58"}, +] idna = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, @@ -610,6 +948,10 @@ iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] +mccabe = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] multidict = [ {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"}, {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"}, @@ -671,6 +1013,10 @@ multidict = [ {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, ] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] mysqlclient = [ {file = "mysqlclient-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:c1ed71bd6244993b526113cca3df66428609f90e4652f37eb51c33496d478b37"}, {file = "mysqlclient-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:c812b67e90082a840efb82a8978369e6e69fc62ce1bda4ca8f3084a9d862308b"}, @@ -680,6 +1026,10 @@ mysqlclient = [ {file = "mysqlclient-2.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:dea88c8d3f5a5d9293dfe7f087c16dd350ceb175f2f6631c9cf4caf3e19b7a96"}, {file = "mysqlclient-2.1.1.tar.gz", hash = "sha256:828757e419fb11dd6c5ed2576ec92c3efaa93a0f7c39e263586d1ee779c3d782"}, ] +nodeenv = [ + {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, + {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, +] objectpath = [ {file = "objectpath-0.6.1-py2.py3-none-any.whl", hash = "sha256:7ac2c507e7e5bfbf245701044453edc8e4073ded01d53349237d918124d7ca87"}, {file = "objectpath-0.6.1.tar.gz", hash = "sha256:461263136c79292e42431fbb85cdcaac4c6a256f6b1aa5b3ae9316e4965ad819"}, @@ -688,13 +1038,25 @@ packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] +pathspec = [ + {file = "pathspec-0.10.2-py3-none-any.whl", hash = "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5"}, + {file = "pathspec-0.10.2.tar.gz", hash = "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0"}, +] peewee = [ {file = "peewee-3.15.4.tar.gz", hash = "sha256:2581520c8dfbacd9d580c2719ae259f0637a9e46eda47dfc0ce01864c6366205"}, ] +platformdirs = [ + {file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"}, + {file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"}, +] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] +pre-commit = [ + {file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"}, + {file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"}, +] psycopg2 = [ {file = "psycopg2-2.9.5-cp310-cp310-win32.whl", hash = "sha256:d3ef67e630b0de0779c42912fe2cbae3805ebaba30cda27fea2a3de650a9414f"}, {file = "psycopg2-2.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:4cb9936316d88bfab614666eb9e32995e794ed0f8f6b3b718666c22819c1d7ee"}, @@ -714,6 +1076,14 @@ pyaml = [ {file = "pyaml-21.10.1-py2.py3-none-any.whl", hash = "sha256:19985ed303c3a985de4cf8fd329b6d0a5a5b5c9035ea240eccc709ebacbaf4a0"}, {file = "pyaml-21.10.1.tar.gz", hash = "sha256:c6519fee13bf06e3bb3f20cacdea8eba9140385a7c2546df5dbae4887f768383"}, ] +pycodestyle = [ + {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, + {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, +] +pyflakes = [ + {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, + {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, +] pyparsing = [ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, @@ -722,6 +1092,10 @@ pytest = [ {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, ] +pytest-cov = [ + {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, + {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, +] pytz = [ {file = "pytz-2022.6-py2.py3-none-any.whl", hash = "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427"}, {file = "pytz-2022.6.tar.gz", hash = "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2"}, @@ -776,14 +1150,30 @@ requests = [ {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, ] +setuptools = [ + {file = "setuptools-65.5.1-py3-none-any.whl", hash = "sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31"}, + {file = "setuptools-65.5.1.tar.gz", hash = "sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +typing-extensions = [ + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, +] urllib3 = [ {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, ] +virtualenv = [ + {file = "virtualenv-20.16.7-py3-none-any.whl", hash = "sha256:efd66b00386fdb7dbe4822d172303f40cd05e50e01740b19ea42425cbe653e29"}, + {file = "virtualenv-20.16.7.tar.gz", hash = "sha256:8691e3ff9387f743e00f6bb20f70121f5e4f596cae754531f2b3b3a1b1ac696e"}, +] yahoo-fantasy-api = [ {file = "yahoo_fantasy_api-2.6.0.tar.gz", hash = "sha256:8dbc247c0d3128b03b132044b559e4aa47a57de88dff7a841c2147ce5d3e0075"}, ] diff --git a/pyproject.toml b/pyproject.toml index 1e0f841..fd6992d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ readme = "README.md" homepage = "https://github.com/DMcP89/harambot" repository = "https://github.com/DMcP89/harambot" keywords = [ - "yahoo", + "yahoo", "yahoo fantasy sports", "fantasy football", "fantasy basketball", @@ -39,6 +39,10 @@ mysqlclient = "^2.1.1" [tool.poetry.group.dev.dependencies] pytest = "^7.2.0" +pytest-cov = "^4.0.0" +pre-commit = "^2.20.0" +flake8 = "^5.0.4" +black = "^22.10.0" [tool.poetry.scripts] harambot = "harambot.bot:run" @@ -46,3 +50,28 @@ harambot = "harambot.bot:run" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" + + +[tool.black] +line-length = 79 +target-version = ['py38'] +include = '\.pyi?$' +exclude = ''' + +( + /( + \.eggs # exclude a few common directories in the + | \.git # root of the project + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist + )/ + | foo.py # also separately exclude a file named foo.py in + # the root of the project +) +''' diff --git a/pytest.ini b/pytest.ini index db75e7a..fb67425 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,4 @@ # pytest.ini [pytest] testpaths = - tests \ No newline at end of file + tests diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..a40a644 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[flake8] +extend-ignore = E203 diff --git a/tests/conftest.py b/tests/conftest.py index d873050..d277d4c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,3 @@ -from json import load import os import json import pytest @@ -9,12 +8,14 @@ root_path = os.path.dirname(os.path.realpath(__file__)) + def load_test_data(filename): - with open(root_path+"/"+filename, 'r') as f: - test_data = json.load(f) - f.close() + with open(root_path + "/" + filename, "r") as f: + test_data = json.load(f) + f.close() return test_data + @pytest.fixture def mock_oauth(): oauth = MagicMock() @@ -24,41 +25,53 @@ def mock_oauth(): @pytest.fixture def mock_standings(): - return load_test_data('test-standings.json')['standings'] + return load_test_data("test-standings.json")["standings"] + @pytest.fixture def mock_teams(): - return load_test_data('test-teams.json') + return load_test_data("test-teams.json") + @pytest.fixture def mock_roster(): - return load_test_data('test-roster.json')['roster'] + return load_test_data("test-roster.json")["roster"] + @pytest.fixture def mock_player_details(): - return load_test_data('test-player-details.json')['details'] + return load_test_data("test-player-details.json")["details"] + @pytest.fixture def mock_ownership(): - return load_test_data('test-player-details.json')['ownership'] + return load_test_data("test-player-details.json")["ownership"] + @pytest.fixture def mock_matchups(): - return load_test_data('test-matchups.json') + return load_test_data("test-matchups.json") @pytest.fixture -def api(mock_oauth, mock_standings, mock_teams, mock_player_details, mock_ownership, mock_matchups): - api = Yahoo(mock_oauth, "123456", "nfl") - api.scoring_type = "head" - league = None - with patch.object(game.Game, 'game_id', return_value='319'): - league = League(mock_oauth, 123456) - league.standings = MagicMock(return_value = mock_standings) - league.teams = MagicMock(return_value = mock_teams) - league.current_week = MagicMock(return_value = 1) - league.player_details = MagicMock(return_value = mock_player_details) - league.ownership = MagicMock(return_value = mock_ownership) - league.matchups = MagicMock(return_value = mock_matchups) - api.league = MagicMock(return_value=league) - return api \ No newline at end of file +def api( + mock_oauth, + mock_standings, + mock_teams, + mock_player_details, + mock_ownership, + mock_matchups, +): + api = Yahoo(mock_oauth, "123456", "nfl") + api.scoring_type = "head" + league = None + with patch.object(game.Game, "game_id", return_value="319"): + league = League(mock_oauth, 123456) + league.standings = MagicMock(return_value=mock_standings) + league.teams = MagicMock(return_value=mock_teams) + league.current_week = MagicMock(return_value=1) + league.player_details = MagicMock(return_value=mock_player_details) + league.ownership = MagicMock(return_value=mock_ownership) + league.matchups = MagicMock(return_value=mock_matchups) + api.league = MagicMock(return_value=league) + return api diff --git a/tests/test-matchups.json b/tests/test-matchups.json index 7e44030..2f1ec3a 100644 --- a/tests/test-matchups.json +++ b/tests/test-matchups.json @@ -1294,4 +1294,4 @@ "copyright": "Data provided by Yahoo! and STATS, LLC", "refresh_rate": "60" } -} \ No newline at end of file +} diff --git a/tests/test-player-details.json b/tests/test-player-details.json index bbf71e4..64335bb 100644 --- a/tests/test-player-details.json +++ b/tests/test-player-details.json @@ -141,4 +141,4 @@ } }], "ownership": { "30977": { "ownership_type": "team", "owner_team_name": "Hide and Go Zeke" } } -} \ No newline at end of file +} diff --git a/tests/test-roster.json b/tests/test-roster.json index 509c175..f278765 100644 --- a/tests/test-roster.json +++ b/tests/test-roster.json @@ -172,4 +172,4 @@ "selected_position": "BN" } ] -} \ No newline at end of file +} diff --git a/tests/test-standings.json b/tests/test-standings.json index fcb2870..25e18c7 100644 --- a/tests/test-standings.json +++ b/tests/test-standings.json @@ -54,4 +54,4 @@ "team_key": "399.l.710921.t.1" } ] -} \ No newline at end of file +} diff --git a/tests/test-teams.json b/tests/test-teams.json index d149993..43c9cef 100644 --- a/tests/test-teams.json +++ b/tests/test-teams.json @@ -400,4 +400,4 @@ } }] } -} \ No newline at end of file +} diff --git a/tests/test_yahoo_api.py b/tests/test_yahoo_api.py index 95d1037..fe6f369 100644 --- a/tests/test_yahoo_api.py +++ b/tests/test_yahoo_api.py @@ -3,41 +3,44 @@ from discord import Embed - - - def test_league(api): assert api.league() + def test_get_standings(api): return_value = api.get_standings() assert isinstance(return_value, Embed) assert len(return_value.fields) == 3 - assert return_value.fields[0].name == '1. Hide and Go Zeke' - + assert return_value.fields[0].name == "1. Hide and Go Zeke" + + def test_get_team(api): - return_value = api.get_team('Too Many Cooks') - assert isinstance(return_value, team.Team) - assert return_value.team_key == '399.l.710921.t.9' + return_value = api.get_team("Too Many Cooks") + assert isinstance(return_value, team.Team) + assert return_value.team_key == "399.l.710921.t.9" + def test_get_roster(api, mock_roster): - with patch.object(team.Team, 'roster', return_value=mock_roster): - return_value = api.get_roster('Too Many Cooks') + with patch.object(team.Team, "roster", return_value=mock_roster): + return_value = api.get_roster("Too Many Cooks") assert isinstance(return_value, Embed) - assert return_value.fields[0].name == 'QB' - assert return_value.fields[0].value == 'Josh Allen' + assert return_value.fields[0].name == "QB" + assert return_value.fields[0].value == "Josh Allen" + def test_get_player_owner(api): - return_value = api.get_player_owner('30977') + return_value = api.get_player_owner("30977") assert isinstance(return_value, str) - assert return_value == 'Hide and Go Zeke' + assert return_value == "Hide and Go Zeke" + def test_get_player_details(api): - return_value = api.get_player_details('Josh Allen') + return_value = api.get_player_details("Josh Allen") assert isinstance(return_value, dict) - assert isinstance(return_value['embed'], Embed) - assert len(return_value['embed'].fields) == 5 - assert isinstance(return_value['text'], str) + assert isinstance(return_value["embed"], Embed) + assert len(return_value["embed"].fields) == 5 + assert isinstance(return_value["text"], str) + def test_get_matchups(api): return_value = api.get_matchups() From f194905c766d0e6d0c9a702fd25bb24638005297 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Thu, 17 Nov 2022 22:02:59 -0500 Subject: [PATCH 08/51] adding dist folder to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index d09e44f..e4bc45e 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ answers.txt # Pytest .coverage + +dist/ From 9321e0bbeb68f2b84fa5f5f5cb5b545ce78637f7 Mon Sep 17 00:00:00 2001 From: Mike Swiss Date: Wed, 23 Nov 2022 12:25:14 -0500 Subject: [PATCH 09/51] added pr template --- .github/pull_request_template.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..1608cd3 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,6 @@ +```[tasklist] + ### Tasks + - [ ] lint checks pass + - [ ] unit tests pass + - [ ] reviewed by code owner +``` \ No newline at end of file From 6e2b46e5fa46ab800505a40ae41d2ab4e55a7694 Mon Sep 17 00:00:00 2001 From: Mike Swiss Date: Wed, 23 Nov 2022 13:18:47 -0500 Subject: [PATCH 10/51] updated pr template --- .github/pull_request_template.md | 33 ++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 1608cd3..6f9b595 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,6 +1,27 @@ -```[tasklist] - ### Tasks - - [ ] lint checks pass - - [ ] unit tests pass - - [ ] reviewed by code owner -``` \ No newline at end of file +## Description + + + +## Types of Changes + + +- [ ] Core +- [ ] Bugfix +- [ ] New feature +- [ ] Enhancement/optimization + +## Issues Fixed or Closed by This PR + +* + +## Checklist + + + +- [ ] My code follows the code style of this project: [**Python**]() +- [ ] I have read the [**PR Checklist** document]() and have made the appropriate changes. +- [ ] My change requires a change to the documentation. +- [ ] I have updated the documentation accordingly. +- [ ] I have read the [**CONTRIBUTING** document](). +- [ ] I have added tests to cover my changes. +- [ ] I have tested the changes and verified that they work and don't break anything (as well as I can manage). From 3fd4b44b3829e11cca2ec40eac9a2e31a299c955 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Sun, 11 Dec 2022 21:54:11 -0500 Subject: [PATCH 11/51] Adding dist to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index d09e44f..e4bc45e 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ answers.txt # Pytest .coverage + +dist/ From 9d44abbdb8112f6a4be2b828cf66cb51e5acd180 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Wed, 14 Dec 2022 18:27:17 -0500 Subject: [PATCH 12/51] Refactored get_matchups Added additional details for category leagues --- harambot/yahoo_api.py | 110 +++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 56 deletions(-) diff --git a/harambot/yahoo_api.py b/harambot/yahoo_api.py index 6f5148f..64f4d37 100644 --- a/harambot/yahoo_api.py +++ b/harambot/yahoo_api.py @@ -199,67 +199,27 @@ def get_matchups(self): description="", color=0xEEE657, ) - matchups_json = objectpath.Tree(self.league().matchups()) - matchups = matchups_json.execute( + matchups = objectpath.Tree(self.league().matchups()).execute( "$..scoreboard..matchups..matchup..teams" ) - team1_details = "" - team2_details = "" + + # loop through each matchup element for matchup in matchups: - team1 = matchup["0"]["team"] - team1_name = team1[0][2]["name"] - if self.scoring_type == "head": - team1_actual_points = team1[1]["team_points"]["total"] - team1_projected_points = team1[1]["team_projected_points"][ - "total" - ] - if "win_probability" in team1[1]: - team1_win_probability = "{:.0%}".format( - team1[1]["win_probability"] - ) - team1_details = "***{}*** \n Projected Score: {} \n \ - Actual Score: {} \n Win Probability: {} \n".format( - team1_name, - team1_projected_points, - team1_actual_points, - team1_win_probability, - ) - else: - team1_details = "***{}*** \n Projected Score: {} \n \ - Actual Score: {} \n".format( - team1_name, - team1_projected_points, - team1_actual_points, - ) - team2 = matchup["1"]["team"] - team2_name = team2[0][2]["name"] - if self.scoring_type == "head": - team2_actual_points = team2[1]["team_points"]["total"] - team2_projected_points = team2[1]["team_projected_points"][ - "total" - ] - if "win_probability" in team2[1]: - team2_win_probability = "{:.0%}".format( - team2[1]["win_probability"] - ) - team2_details = "\n***{}*** \n Projected Score: {} \n \ - Actual Score: {} \n Win Probability: {}\n".format( - team2_name, - team2_projected_points, - team2_actual_points, - team2_win_probability, - ) - else: - team2_details = "\n***{}*** \n Projected Score: {} \n \ - Actual Score: {} \n".format( - team2_name, - team2_projected_points, - team2_actual_points, - ) + # handle team 1 + team1_details = self.get_matchup_details(matchup["0"]["team"]) + + # handle team 2 + team2_details = self.get_matchup_details(matchup["1"]["team"]) divider = "--------------------------------------" + + # Add details to embed embed.add_field( - name="{} vs {}".format(team1_name, team2_name), - value=team1_details + team2_details + divider, + name="{} vs {}".format( + team1_details["name"], team2_details["name"] + ), + value=team1_details["text"] + + team2_details["text"] + + divider, inline=False, ) return embed @@ -270,6 +230,44 @@ def get_matchups(self): ) ) + def get_matchup_details(self, team): + team_name = team[0][2]["name"] + team_details = "" + if self.scoring_type == "head": + # handle data for head to head scoring + team1_actual_points = team[1]["team_points"]["total"] + team1_projected_points = team[1]["team_projected_points"]["total"] + if "win_probability" in team[1]: + team1_win_probability = "{:.0%}".format( + team[1]["win_probability"] + ) + team_details = "***{}*** \n Projected Score: {} \n \ + Actual Score: {} \n Win Probability: {} \n".format( + team_name, + team1_projected_points, + team1_actual_points, + team1_win_probability, + ) + else: + team_details = "***{}*** \n Projected Score: {} \n \ + Actual Score: {} \n".format( + team_name, + team1_projected_points, + team1_actual_points, + ) + else: + team_details = "***{}*** \n Score: {} \n \ + Remaining Games: {} \n \ + Live Games: {} \n \ + Completed Games: {} \n".format( + team_name, + team[1]["team_points"]["total"], + team[1]["team_remaining_games"]["total"]["remaining_games"], + team[1]["team_remaining_games"]["total"]["live_games"], + team[1]["team_remaining_games"]["total"]["completed_games"], + ) + return {"name": team_name, "text": team_details} + @cached(cache=TTLCache(maxsize=1024, ttl=600)) def get_latest_trade(self): try: From 0d6d4446ba027fee879d3214d1879824087cca66 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Wed, 14 Dec 2022 18:54:08 -0500 Subject: [PATCH 13/51] Adding test for category matchups --- tests/conftest.py | 30 + tests/test-matchups-category.json | 2859 +++++++++++++++++++++++++++++ tests/test_yahoo_api.py | 6 + 3 files changed, 2895 insertions(+) create mode 100644 tests/test-matchups-category.json diff --git a/tests/conftest.py b/tests/conftest.py index d277d4c..ff87caf 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -53,6 +53,11 @@ def mock_matchups(): return load_test_data("test-matchups.json") +@pytest.fixture +def mock_matchups_category(): + return load_test_data("test-matchups-category.json") + + @pytest.fixture def api( mock_oauth, @@ -61,6 +66,7 @@ def api( mock_player_details, mock_ownership, mock_matchups, + mock_matchups_category, ): api = Yahoo(mock_oauth, "123456", "nfl") api.scoring_type = "head" @@ -75,3 +81,27 @@ def api( league.matchups = MagicMock(return_value=mock_matchups) api.league = MagicMock(return_value=league) return api + + +@pytest.fixture +def category_api( + mock_oauth, + mock_standings, + mock_teams, + mock_player_details, + mock_ownership, + mock_matchups_category, +): + api = Yahoo(mock_oauth, "123456", "nfl") + api.scoring_type = "headone" + league = None + with patch.object(game.Game, "game_id", return_value="319"): + league = League(mock_oauth, 123456) + league.standings = MagicMock(return_value=mock_standings) + league.teams = MagicMock(return_value=mock_teams) + league.current_week = MagicMock(return_value=1) + league.player_details = MagicMock(return_value=mock_player_details) + league.ownership = MagicMock(return_value=mock_ownership) + league.matchups = MagicMock(return_value=mock_matchups_category) + api.league = MagicMock(return_value=league) + return api diff --git a/tests/test-matchups-category.json b/tests/test-matchups-category.json new file mode 100644 index 0000000..5c37c7e --- /dev/null +++ b/tests/test-matchups-category.json @@ -0,0 +1,2859 @@ +{ + "fantasy_content": { + "xml:lang": "en-US", + "yahoo:uri": "\/fantasy\/v2\/league\/418.l.15944\/scoreboard", + "league": [ + { + "league_key": "418.l.15944", + "league_id": "15944", + "name": "G.O.A.T. League", + "url": "https:\/\/basketball.fantasysports.yahoo.com\/nba\/15944", + "logo_url": "https:\/\/yahoofantasysports-res.cloudinary.com\/image\/upload\/t_s192sq\/fantasy-logos\/361f47fe7e1167521d79495d54614a38194f5ea17c9cd841ba39c519479154d3.jpg", + "password": "", + "draft_status": "postdraft", + "num_teams": 14, + "edit_key": "2022-12-14", + "weekly_deadline": "intraday", + "league_update_timestamp": "1671007651", + "scoring_type": "headone", + "league_type": "private", + "renew": "410_37174", + "renewed": "", + "felo_tier": "gold", + "iris_group_chat_id": "", + "short_invitation_url": "https:\/\/basketball.fantasysports.yahoo.com\/nba\/15944\/invitation?key=06b1a2c4a86da770&ikey=f5be1f6c780842d9", + "allow_add_to_dl_extra_pos": 1, + "is_pro_league": "0", + "is_cash_league": "0", + "current_week": 9, + "start_week": "1", + "start_date": "2022-10-18", + "end_week": "22", + "end_date": "2023-03-26", + "game_code": "nba", + "season": "2022" + }, + { + "scoreboard": { + "week": 9, + "0": { + "matchups": { + "0": { + "matchup": { + "week": "9", + "week_start": "2022-12-12", + "week_end": "2022-12-18", + "status": "midevent", + "is_playoffs": "0", + "is_consolation": "0", + "stat_winners": [ + { + "stat_winner": { + "stat_id": "5", + "winner_team_key": "418.l.15944.t.1" + } + }, + { + "stat_winner": { + "stat_id": "8", + "winner_team_key": "418.l.15944.t.6" + } + }, + { + "stat_winner": { + "stat_id": "10", + "winner_team_key": "418.l.15944.t.6" + } + }, + { + "stat_winner": { + "stat_id": "12", + "winner_team_key": "418.l.15944.t.6" + } + }, + { + "stat_winner": { + "stat_id": "15", + "winner_team_key": "418.l.15944.t.1" + } + }, + { + "stat_winner": { + "stat_id": "16", + "winner_team_key": "418.l.15944.t.6" + } + }, + { + "stat_winner": { + "stat_id": "17", + "winner_team_key": "418.l.15944.t.1" + } + }, + { + "stat_winner": { + "stat_id": "18", + "winner_team_key": "418.l.15944.t.6" + } + }, + { + "stat_winner": { + "stat_id": "19", + "winner_team_key": "418.l.15944.t.1" + } + } + ], + "0": { + "teams": { + "0": { + "team": [ + [ + { + "team_key": "418.l.15944.t.1" + }, + { + "team_id": "1" + }, + { + "name": "LOS DIOSES" + }, + [], + { + "url": "https:\/\/basketball.fantasysports.yahoo.com\/nba\/15944\/1" + }, + { + "team_logos": [ + { + "team_logo": { + "size": "large", + "url": "https:\/\/yahoofantasysports-res.cloudinary.com\/image\/upload\/t_s192sq\/fantasy-logos\/25a5906964cc932fb0f766497ca3bb5f43caf88ea38df4f254e48f6b28aa96cf.jpg" + } + } + ] + }, + { + "division_id": "1" + }, + { + "waiver_priority": 8 + }, + [], + { + "number_of_moves": "18" + }, + { + "number_of_trades": "5" + }, + { + "roster_adds": { + "coverage_type": "week", + "coverage_value": 9, + "value": "0" + } + }, + [], + { + "league_scoring_type": "headone" + }, + [], + [], + { + "has_draft_grade": 0 + }, + [], + [], + { + "managers": [ + { + "manager": { + "manager_id": "1", + "nickname": "Israel III", + "guid": "ONVHWMWVEBRZHGYCHWPDPIVZFY", + "is_commissioner": "1", + "email": "junni5ruiz@gmail.com", + "image_url": "https:\/\/s.yimg.com\/ag\/images\/2845f35c-f9de-43a2-84c2-2e4ce90e3983_64sq.jpg", + "felo_score": "695", + "felo_tier": "silver" + } + } + ] + } + ], + { + "team_stats": { + "coverage_type": "week", + "week": "9", + "stats": [ + { + "stat": { + "stat_id": "9004003", + "value": "61\/128" + } + }, + { + "stat": { + "stat_id": "5", + "value": ".477" + } + }, + { + "stat": { + "stat_id": "9007006", + "value": "18\/30" + } + }, + { + "stat": { + "stat_id": "8", + "value": ".600" + } + }, + { + "stat": { + "stat_id": "10", + "value": "17" + } + }, + { + "stat": { + "stat_id": "12", + "value": "157" + } + }, + { + "stat": { + "stat_id": "15", + "value": "87" + } + }, + { + "stat": { + "stat_id": "16", + "value": "35" + } + }, + { + "stat": { + "stat_id": "17", + "value": "18" + } + }, + { + "stat": { + "stat_id": "18", + "value": "3" + } + }, + { + "stat": { + "stat_id": "19", + "value": "20" + } + } + ] + }, + "team_points": { + "coverage_type": "week", + "week": "9", + "total": "4" + }, + "team_remaining_games": { + "coverage_type": "week", + "week": "9", + "total": { + "remaining_games": 32, + "live_games": 0, + "completed_games": 13 + } + } + } + ] + }, + "1": { + "team": [ + [ + { + "team_key": "418.l.15944.t.6" + }, + { + "team_id": "6" + }, + { + "name": "Gandi Guerrilla" + }, + [], + { + "url": "https:\/\/basketball.fantasysports.yahoo.com\/nba\/15944\/6" + }, + { + "team_logos": [ + { + "team_logo": { + "size": "large", + "url": "https:\/\/yahoofantasysports-res.cloudinary.com\/image\/upload\/t_s192sq\/fantasy-logos\/d2e301e23d0ad2ce8122832effbe9d6e4f0df2001fa998c71744bcb34c82470d.jpg" + } + } + ] + }, + { + "division_id": "2" + }, + { + "waiver_priority": 11 + }, + [], + { + "number_of_moves": "24" + }, + { + "number_of_trades": "3" + }, + { + "roster_adds": { + "coverage_type": "week", + "coverage_value": 9, + "value": "0" + } + }, + [], + { + "league_scoring_type": "headone" + }, + [], + [], + { + "has_draft_grade": 0 + }, + [], + [], + { + "managers": [ + { + "manager": { + "manager_id": "6", + "nickname": "Gandi", + "guid": "FRYP6QHNO6BX2LNLJHVEKXW4PU", + "email": "ggfsports@gmail.com", + "image_url": "https:\/\/s.yimg.com\/ag\/images\/default_user_profile_pic_64sq.jpg", + "felo_score": "672", + "felo_tier": "silver" + } + } + ] + } + ], + { + "team_stats": { + "coverage_type": "week", + "week": "9", + "stats": [ + { + "stat": { + "stat_id": "9004003", + "value": "55\/122" + } + }, + { + "stat": { + "stat_id": "5", + "value": ".451" + } + }, + { + "stat": { + "stat_id": "9007006", + "value": "28\/30" + } + }, + { + "stat": { + "stat_id": "8", + "value": ".933" + } + }, + { + "stat": { + "stat_id": "10", + "value": "21" + } + }, + { + "stat": { + "stat_id": "12", + "value": "159" + } + }, + { + "stat": { + "stat_id": "15", + "value": "43" + } + }, + { + "stat": { + "stat_id": "16", + "value": "43" + } + }, + { + "stat": { + "stat_id": "17", + "value": "6" + } + }, + { + "stat": { + "stat_id": "18", + "value": "4" + } + }, + { + "stat": { + "stat_id": "19", + "value": "25" + } + } + ] + }, + "team_points": { + "coverage_type": "week", + "week": "9", + "total": "5" + }, + "team_remaining_games": { + "coverage_type": "week", + "week": "9", + "total": { + "remaining_games": 33, + "live_games": 0, + "completed_games": 9 + } + } + } + ] + }, + "count": 2 + } + } + } + }, + "1": { + "matchup": { + "week": "9", + "week_start": "2022-12-12", + "week_end": "2022-12-18", + "status": "midevent", + "is_playoffs": "0", + "is_consolation": "0", + "stat_winners": [ + { + "stat_winner": { + "stat_id": "5", + "winner_team_key": "418.l.15944.t.2" + } + }, + { + "stat_winner": { + "stat_id": "8", + "winner_team_key": "418.l.15944.t.2" + } + }, + { + "stat_winner": { + "stat_id": "10", + "winner_team_key": "418.l.15944.t.5" + } + }, + { + "stat_winner": { + "stat_id": "12", + "winner_team_key": "418.l.15944.t.5" + } + }, + { + "stat_winner": { + "stat_id": "15", + "winner_team_key": "418.l.15944.t.5" + } + }, + { + "stat_winner": { + "stat_id": "16", + "winner_team_key": "418.l.15944.t.5" + } + }, + { + "stat_winner": { + "stat_id": "17", + "winner_team_key": "418.l.15944.t.5" + } + }, + { + "stat_winner": { + "stat_id": "18", + "winner_team_key": "418.l.15944.t.5" + } + }, + { + "stat_winner": { + "stat_id": "19", + "winner_team_key": "418.l.15944.t.2" + } + } + ], + "0": { + "teams": { + "0": { + "team": [ + [ + { + "team_key": "418.l.15944.t.2" + }, + { + "team_id": "2" + }, + { + "name": "Double Dare" + }, + { + "is_owned_by_current_login": 1 + }, + { + "url": "https:\/\/basketball.fantasysports.yahoo.com\/nba\/15944\/2" + }, + { + "team_logos": [ + { + "team_logo": { + "size": "large", + "url": "https:\/\/yahoofantasysports-res.cloudinary.com\/image\/upload\/t_s192sq\/fantasy-logos\/fed74bac74af05e0e291ad1cddc8a83c3780e43e7833367ab3b6602d668c2f5e.png" + } + } + ] + }, + { + "division_id": "2" + }, + { + "waiver_priority": 12 + }, + [], + { + "number_of_moves": "20" + }, + { + "number_of_trades": "7" + }, + { + "roster_adds": { + "coverage_type": "week", + "coverage_value": 9, + "value": "0" + } + }, + [], + { + "league_scoring_type": "headone" + }, + [], + [], + { + "has_draft_grade": 0 + }, + [], + [], + { + "managers": [ + { + "manager": { + "manager_id": "2", + "nickname": "Jean", + "guid": "AXWPRW2UPA4JGR6RYVBNOZN44U", + "is_commissioner": "1", + "is_current_login": "1", + "email": "jp.czerniak@yahoo.com", + "image_url": "https:\/\/s.yimg.com\/ag\/images\/fd6707c9-fa76-476b-b629-0111c2b42592_64sq.jpg", + "felo_score": "708", + "felo_tier": "gold" + } + } + ] + } + ], + { + "team_stats": { + "coverage_type": "week", + "week": "9", + "stats": [ + { + "stat": { + "stat_id": "9004003", + "value": "37\/87" + } + }, + { + "stat": { + "stat_id": "5", + "value": ".425" + } + }, + { + "stat": { + "stat_id": "9007006", + "value": "25\/31" + } + }, + { + "stat": { + "stat_id": "8", + "value": ".806" + } + }, + { + "stat": { + "stat_id": "10", + "value": "10" + } + }, + { + "stat": { + "stat_id": "12", + "value": "109" + } + }, + { + "stat": { + "stat_id": "15", + "value": "36" + } + }, + { + "stat": { + "stat_id": "16", + "value": "32" + } + }, + { + "stat": { + "stat_id": "17", + "value": "10" + } + }, + { + "stat": { + "stat_id": "18", + "value": "4" + } + }, + { + "stat": { + "stat_id": "19", + "value": "16" + } + } + ] + }, + "team_points": { + "coverage_type": "week", + "week": "9", + "total": "3" + }, + "team_remaining_games": { + "coverage_type": "week", + "week": "9", + "total": { + "remaining_games": 31, + "live_games": 0, + "completed_games": 11 + } + } + } + ] + }, + "1": { + "team": [ + [ + { + "team_key": "418.l.15944.t.5" + }, + { + "team_id": "5" + }, + { + "name": "Sangre Nueva Inc." + }, + [], + { + "url": "https:\/\/basketball.fantasysports.yahoo.com\/nba\/15944\/5" + }, + { + "team_logos": [ + { + "team_logo": { + "size": "large", + "url": "https:\/\/yahoofantasysports-res.cloudinary.com\/image\/upload\/t_s192sq\/fantasy-logos\/4a3a9038c7f1dd434485b5cd56f895d0e8591d2f4f5349d4ea7285fc0bf2c2d1.png" + } + } + ] + }, + { + "division_id": "1" + }, + { + "waiver_priority": 10 + }, + [], + { + "number_of_moves": "30" + }, + { + "number_of_trades": "4" + }, + { + "roster_adds": { + "coverage_type": "week", + "coverage_value": 9, + "value": "0" + } + }, + [], + { + "league_scoring_type": "headone" + }, + [], + [], + { + "has_draft_grade": 0 + }, + [], + [], + { + "managers": [ + { + "manager": { + "manager_id": "5", + "nickname": "Richard", + "guid": "TARJ7VVBNLTTBQB4S2AZGNEE7Q", + "email": "richardmedina@pucpr.edu", + "image_url": "https:\/\/s.yimg.com\/ag\/images\/default_user_profile_pic_64sq.jpg", + "felo_score": "604", + "felo_tier": "silver" + } + }, + { + "manager": { + "manager_id": "15", + "nickname": "Roberto", + "guid": "KWWZTQ44TDCWVBGKBP3UQ3MDXI", + "is_comanager": "1", + "email": "robertoa11@hotmail.com", + "image_url": "https:\/\/s.yimg.com\/ag\/images\/default_user_profile_pic_64sq.jpg", + "felo_score": "790", + "felo_tier": "gold" + } + } + ] + } + ], + { + "team_stats": { + "coverage_type": "week", + "week": "9", + "stats": [ + { + "stat": { + "stat_id": "9004003", + "value": "58\/144" + } + }, + { + "stat": { + "stat_id": "5", + "value": ".403" + } + }, + { + "stat": { + "stat_id": "9007006", + "value": "36\/52" + } + }, + { + "stat": { + "stat_id": "8", + "value": ".692" + } + }, + { + "stat": { + "stat_id": "10", + "value": "11" + } + }, + { + "stat": { + "stat_id": "12", + "value": "163" + } + }, + { + "stat": { + "stat_id": "15", + "value": "93" + } + }, + { + "stat": { + "stat_id": "16", + "value": "41" + } + }, + { + "stat": { + "stat_id": "17", + "value": "13" + } + }, + { + "stat": { + "stat_id": "18", + "value": "9" + } + }, + { + "stat": { + "stat_id": "19", + "value": "32" + } + } + ] + }, + "team_points": { + "coverage_type": "week", + "week": "9", + "total": "6" + }, + "team_remaining_games": { + "coverage_type": "week", + "week": "9", + "total": { + "remaining_games": 24, + "live_games": 0, + "completed_games": 13 + } + } + } + ] + }, + "count": 2 + } + } + } + }, + "2": { + "matchup": { + "week": "9", + "week_start": "2022-12-12", + "week_end": "2022-12-18", + "status": "midevent", + "is_playoffs": "0", + "is_consolation": "0", + "stat_winners": [ + { + "stat_winner": { + "stat_id": "5", + "winner_team_key": "418.l.15944.t.3" + } + }, + { + "stat_winner": { + "stat_id": "8", + "winner_team_key": "418.l.15944.t.4" + } + }, + { + "stat_winner": { + "stat_id": "10", + "winner_team_key": "418.l.15944.t.4" + } + }, + { + "stat_winner": { + "stat_id": "12", + "winner_team_key": "418.l.15944.t.4" + } + }, + { + "stat_winner": { + "stat_id": "15", + "winner_team_key": "418.l.15944.t.4" + } + }, + { + "stat_winner": { + "stat_id": "16", + "winner_team_key": "418.l.15944.t.3" + } + }, + { + "stat_winner": { + "stat_id": "17", + "winner_team_key": "418.l.15944.t.4" + } + }, + { + "stat_winner": { + "stat_id": "18", + "winner_team_key": "418.l.15944.t.4" + } + }, + { + "stat_winner": { + "stat_id": "19", + "winner_team_key": "418.l.15944.t.3" + } + } + ], + "0": { + "teams": { + "0": { + "team": [ + [ + { + "team_key": "418.l.15944.t.3" + }, + { + "team_id": "3" + }, + { + "name": "FIX IT FEELO" + }, + [], + { + "url": "https:\/\/basketball.fantasysports.yahoo.com\/nba\/15944\/3" + }, + { + "team_logos": [ + { + "team_logo": { + "size": "large", + "url": "https:\/\/yahoofantasysports-res.cloudinary.com\/image\/upload\/t_s192sq\/fantasy-logos\/f0f9ea7af69f10233fcd8b887cd684e6898b042de3ddfb633df1e497bf4159fc.jpg" + } + } + ] + }, + { + "division_id": "1" + }, + { + "waiver_priority": 7 + }, + [], + { + "number_of_moves": "10" + }, + { + "number_of_trades": "2" + }, + { + "roster_adds": { + "coverage_type": "week", + "coverage_value": 9, + "value": "0" + } + }, + [], + { + "league_scoring_type": "headone" + }, + [], + [], + { + "has_draft_grade": 0 + }, + [], + [], + { + "managers": [ + { + "manager": { + "manager_id": "3", + "nickname": "felix", + "guid": "2D573MJK6RWYCOAE3GHO5VIZEM", + "email": "fmaldonadoaleno@gmail.com", + "image_url": "https:\/\/s.yimg.com\/ag\/images\/default_user_profile_pic_64sq.jpg", + "felo_score": "569", + "felo_tier": "bronze" + } + } + ] + } + ], + { + "team_stats": { + "coverage_type": "week", + "week": "9", + "stats": [ + { + "stat": { + "stat_id": "9004003", + "value": "29\/57" + } + }, + { + "stat": { + "stat_id": "5", + "value": ".509" + } + }, + { + "stat": { + "stat_id": "9007006", + "value": "15\/19" + } + }, + { + "stat": { + "stat_id": "8", + "value": ".789" + } + }, + { + "stat": { + "stat_id": "10", + "value": "3" + } + }, + { + "stat": { + "stat_id": "12", + "value": "76" + } + }, + { + "stat": { + "stat_id": "15", + "value": "31" + } + }, + { + "stat": { + "stat_id": "16", + "value": "19" + } + }, + { + "stat": { + "stat_id": "17", + "value": "4" + } + }, + { + "stat": { + "stat_id": "18", + "value": "2" + } + }, + { + "stat": { + "stat_id": "19", + "value": "8" + } + } + ] + }, + "team_points": { + "coverage_type": "week", + "week": "9", + "total": "3" + }, + "team_remaining_games": { + "coverage_type": "week", + "week": "9", + "total": { + "remaining_games": 17, + "live_games": 0, + "completed_games": 8 + } + } + } + ] + }, + "1": { + "team": [ + [ + { + "team_key": "418.l.15944.t.4" + }, + { + "team_id": "4" + }, + { + "name": "Flor Meléndez" + }, + [], + { + "url": "https:\/\/basketball.fantasysports.yahoo.com\/nba\/15944\/4" + }, + { + "team_logos": [ + { + "team_logo": { + "size": "large", + "url": "https:\/\/yahoofantasysports-res.cloudinary.com\/image\/upload\/t_s192sq\/fantasy-logos\/62d569785f0a86b908baa51acf3970395dd2e7e98ec3f49f6cc1fdf1961b9497.jpg" + } + } + ] + }, + { + "division_id": "2" + }, + { + "waiver_priority": 13 + }, + [], + { + "number_of_moves": "25" + }, + { + "number_of_trades": "11" + }, + { + "roster_adds": { + "coverage_type": "week", + "coverage_value": 9, + "value": "0" + } + }, + [], + { + "league_scoring_type": "headone" + }, + [], + [], + { + "has_draft_grade": 0 + }, + [], + [], + { + "managers": [ + { + "manager": { + "manager_id": "4", + "nickname": "Flor", + "guid": "GWJL34FSVYSLUK2UIIRH7FX7UY", + "email": "eliezer627140@gmail.com", + "image_url": "https:\/\/s.yimg.com\/ag\/images\/08ec16df-8550-4c18-be28-0333a5f508d5_64sq.jpg", + "felo_score": "741", + "felo_tier": "gold" + } + } + ] + } + ], + { + "team_stats": { + "coverage_type": "week", + "week": "9", + "stats": [ + { + "stat": { + "stat_id": "9004003", + "value": "39\/105" + } + }, + { + "stat": { + "stat_id": "5", + "value": ".371" + } + }, + { + "stat": { + "stat_id": "9007006", + "value": "28\/31" + } + }, + { + "stat": { + "stat_id": "8", + "value": ".903" + } + }, + { + "stat": { + "stat_id": "10", + "value": "12" + } + }, + { + "stat": { + "stat_id": "12", + "value": "118" + } + }, + { + "stat": { + "stat_id": "15", + "value": "43" + } + }, + { + "stat": { + "stat_id": "16", + "value": "17" + } + }, + { + "stat": { + "stat_id": "17", + "value": "5" + } + }, + { + "stat": { + "stat_id": "18", + "value": "3" + } + }, + { + "stat": { + "stat_id": "19", + "value": "20" + } + } + ] + }, + "team_points": { + "coverage_type": "week", + "week": "9", + "total": "6" + }, + "team_remaining_games": { + "coverage_type": "week", + "week": "9", + "total": { + "remaining_games": 30, + "live_games": 0, + "completed_games": 10 + } + } + } + ] + }, + "count": 2 + } + } + } + }, + "3": { + "matchup": { + "week": "9", + "week_start": "2022-12-12", + "week_end": "2022-12-18", + "status": "midevent", + "is_playoffs": "0", + "is_consolation": "0", + "stat_winners": [ + { + "stat_winner": { + "stat_id": "5", + "winner_team_key": "418.l.15944.t.9" + } + }, + { + "stat_winner": { + "stat_id": "8", + "winner_team_key": "418.l.15944.t.9" + } + }, + { + "stat_winner": { + "stat_id": "10", + "winner_team_key": "418.l.15944.t.9" + } + }, + { + "stat_winner": { + "stat_id": "12", + "winner_team_key": "418.l.15944.t.9" + } + }, + { + "stat_winner": { + "stat_id": "15", + "winner_team_key": "418.l.15944.t.7" + } + }, + { + "stat_winner": { + "stat_id": "16", + "winner_team_key": "418.l.15944.t.9" + } + }, + { + "stat_winner": { + "stat_id": "17", + "winner_team_key": "418.l.15944.t.9" + } + }, + { + "stat_winner": { + "stat_id": "18", + "winner_team_key": "418.l.15944.t.9" + } + }, + { + "stat_winner": { + "stat_id": "19", + "winner_team_key": "418.l.15944.t.7" + } + } + ], + "0": { + "teams": { + "0": { + "team": [ + [ + { + "team_key": "418.l.15944.t.7" + }, + { + "team_id": "7" + }, + { + "name": "Las Turbas" + }, + [], + { + "url": "https:\/\/basketball.fantasysports.yahoo.com\/nba\/15944\/7" + }, + { + "team_logos": [ + { + "team_logo": { + "size": "large", + "url": "https:\/\/yahoofantasysports-res.cloudinary.com\/image\/upload\/t_s192sq\/fantasy-logos\/ce3bf6b6d4060f9e064a649cb2b8c59e0ae9c645d997cfd32c6f1781ac87e2ff.png" + } + } + ] + }, + { + "division_id": "1" + }, + { + "waiver_priority": 9 + }, + [], + { + "number_of_moves": "27" + }, + { + "number_of_trades": "3" + }, + { + "roster_adds": { + "coverage_type": "week", + "coverage_value": 9, + "value": "0" + } + }, + [], + { + "league_scoring_type": "headone" + }, + [], + [], + { + "has_draft_grade": 0 + }, + [], + [], + { + "managers": [ + { + "manager": { + "manager_id": "7", + "nickname": "Adriel", + "guid": "6OO53YPELMYFLGX7GYKBYCJQIE", + "email": "adriel3ruiz@gmail.com", + "image_url": "https:\/\/s.yimg.com\/ag\/images\/default_user_profile_pic_64sq.jpg", + "felo_score": "552", + "felo_tier": "bronze" + } + } + ] + } + ], + { + "team_stats": { + "coverage_type": "week", + "week": "9", + "stats": [ + { + "stat": { + "stat_id": "9004003", + "value": "21\/59" + } + }, + { + "stat": { + "stat_id": "5", + "value": ".356" + } + }, + { + "stat": { + "stat_id": "9007006", + "value": "6\/7" + } + }, + { + "stat": { + "stat_id": "8", + "value": ".857" + } + }, + { + "stat": { + "stat_id": "10", + "value": "3" + } + }, + { + "stat": { + "stat_id": "12", + "value": "51" + } + }, + { + "stat": { + "stat_id": "15", + "value": "51" + } + }, + { + "stat": { + "stat_id": "16", + "value": "24" + } + }, + { + "stat": { + "stat_id": "17", + "value": "5" + } + }, + { + "stat": { + "stat_id": "18", + "value": "6" + } + }, + { + "stat": { + "stat_id": "19", + "value": "8" + } + } + ] + }, + "team_points": { + "coverage_type": "week", + "week": "9", + "total": "2" + }, + "team_remaining_games": { + "coverage_type": "week", + "week": "9", + "total": { + "remaining_games": 31, + "live_games": 0, + "completed_games": 10 + } + } + } + ] + }, + "1": { + "team": [ + [ + { + "team_key": "418.l.15944.t.9" + }, + { + "team_id": "9" + }, + { + "name": "Pablo's Wonderful Team" + }, + [], + { + "url": "https:\/\/basketball.fantasysports.yahoo.com\/nba\/15944\/9" + }, + { + "team_logos": [ + { + "team_logo": { + "size": "large", + "url": "https:\/\/yahoofantasysports-res.cloudinary.com\/image\/upload\/t_s192sq\/fantasy-logos\/6c40a4e4bebb2d4776ff51efd7af27fb1f5cf94a780c33e23f281d4b7d8b6ac0.jpg" + } + } + ] + }, + { + "division_id": "1" + }, + { + "waiver_priority": 14 + }, + [], + { + "number_of_moves": "20" + }, + { + "number_of_trades": "3" + }, + { + "roster_adds": { + "coverage_type": "week", + "coverage_value": 9, + "value": "1" + } + }, + [], + { + "league_scoring_type": "headone" + }, + [], + [], + { + "has_draft_grade": 0 + }, + [], + [], + { + "managers": [ + { + "manager": { + "manager_id": "9", + "nickname": "Pablo", + "guid": "GSRSCMIVMOXGMYL4NFTKYX26HY", + "email": "pablojcolon10@gmail.com", + "image_url": "https:\/\/s.yimg.com\/ag\/images\/default_user_profile_pic_64sq.jpg", + "felo_score": "683", + "felo_tier": "silver" + } + } + ] + } + ], + { + "team_stats": { + "coverage_type": "week", + "week": "9", + "stats": [ + { + "stat": { + "stat_id": "9004003", + "value": "58\/125" + } + }, + { + "stat": { + "stat_id": "5", + "value": ".464" + } + }, + { + "stat": { + "stat_id": "9007006", + "value": "32\/37" + } + }, + { + "stat": { + "stat_id": "8", + "value": ".865" + } + }, + { + "stat": { + "stat_id": "10", + "value": "12" + } + }, + { + "stat": { + "stat_id": "12", + "value": "160" + } + }, + { + "stat": { + "stat_id": "15", + "value": "40" + } + }, + { + "stat": { + "stat_id": "16", + "value": "33" + } + }, + { + "stat": { + "stat_id": "17", + "value": "7" + } + }, + { + "stat": { + "stat_id": "18", + "value": "12" + } + }, + { + "stat": { + "stat_id": "19", + "value": "20" + } + } + ] + }, + "team_points": { + "coverage_type": "week", + "week": "9", + "total": "7" + }, + "team_remaining_games": { + "coverage_type": "week", + "week": "9", + "total": { + "remaining_games": 25, + "live_games": 0, + "completed_games": 10 + } + } + } + ] + }, + "count": 2 + } + } + } + }, + "4": { + "matchup": { + "week": "9", + "week_start": "2022-12-12", + "week_end": "2022-12-18", + "status": "midevent", + "is_playoffs": "0", + "is_consolation": "0", + "stat_winners": [ + { + "stat_winner": { + "stat_id": "5", + "winner_team_key": "418.l.15944.t.8" + } + }, + { + "stat_winner": { + "stat_id": "8", + "winner_team_key": "418.l.15944.t.13" + } + }, + { + "stat_winner": { + "stat_id": "10", + "winner_team_key": "418.l.15944.t.13" + } + }, + { + "stat_winner": { + "stat_id": "12", + "winner_team_key": "418.l.15944.t.13" + } + }, + { + "stat_winner": { + "stat_id": "15", + "winner_team_key": "418.l.15944.t.13" + } + }, + { + "stat_winner": { + "stat_id": "16", + "winner_team_key": "418.l.15944.t.13" + } + }, + { + "stat_winner": { + "stat_id": "17", + "winner_team_key": "418.l.15944.t.13" + } + }, + { + "stat_winner": { + "stat_id": "18", + "winner_team_key": "418.l.15944.t.13" + } + }, + { + "stat_winner": { + "stat_id": "19", + "winner_team_key": "418.l.15944.t.8" + } + } + ], + "0": { + "teams": { + "0": { + "team": [ + [ + { + "team_key": "418.l.15944.t.8" + }, + { + "team_id": "8" + }, + { + "name": "LOS LEGENDARIOS" + }, + [], + { + "url": "https:\/\/basketball.fantasysports.yahoo.com\/nba\/15944\/8" + }, + { + "team_logos": [ + { + "team_logo": { + "size": "large", + "url": "https:\/\/yahoofantasysports-res.cloudinary.com\/image\/upload\/t_s192sq\/fantasy-logos\/c821ebd5545216ad8e0b61bd0dc645b510cb70743119c1b63a430649193e40c1.jpg" + } + } + ] + }, + { + "division_id": "2" + }, + { + "waiver_priority": 3 + }, + [], + { + "number_of_moves": "25" + }, + { + "number_of_trades": "1" + }, + { + "roster_adds": { + "coverage_type": "week", + "coverage_value": 9, + "value": "1" + } + }, + [], + { + "league_scoring_type": "headone" + }, + [], + [], + { + "has_draft_grade": 0 + }, + [], + [], + { + "managers": [ + { + "manager": { + "manager_id": "8", + "nickname": "luis", + "guid": "YHSJUEK4VFB2KZUN7BNOJODPHM", + "email": "luis.gonzalezpagan@yahoo.com", + "image_url": "https:\/\/s.yimg.com\/ag\/images\/default_user_profile_pic_64sq.jpg", + "felo_score": "843", + "felo_tier": "platinum" + } + } + ] + } + ], + { + "team_stats": { + "coverage_type": "week", + "week": "9", + "stats": [ + { + "stat": { + "stat_id": "9004003", + "value": "33\/61" + } + }, + { + "stat": { + "stat_id": "5", + "value": ".541" + } + }, + { + "stat": { + "stat_id": "9007006", + "value": "10\/13" + } + }, + { + "stat": { + "stat_id": "8", + "value": ".769" + } + }, + { + "stat": { + "stat_id": "10", + "value": "10" + } + }, + { + "stat": { + "stat_id": "12", + "value": "86" + } + }, + { + "stat": { + "stat_id": "15", + "value": "30" + } + }, + { + "stat": { + "stat_id": "16", + "value": "22" + } + }, + { + "stat": { + "stat_id": "17", + "value": "6" + } + }, + { + "stat": { + "stat_id": "18", + "value": "2" + } + }, + { + "stat": { + "stat_id": "19", + "value": "13" + } + } + ] + }, + "team_points": { + "coverage_type": "week", + "week": "9", + "total": "2" + }, + "team_remaining_games": { + "coverage_type": "week", + "week": "9", + "total": { + "remaining_games": 34, + "live_games": 0, + "completed_games": 8 + } + } + } + ] + }, + "1": { + "team": [ + [ + { + "team_key": "418.l.15944.t.13" + }, + { + "team_id": "13" + }, + { + "name": "HWBT" + }, + [], + { + "url": "https:\/\/basketball.fantasysports.yahoo.com\/nba\/15944\/13" + }, + { + "team_logos": [ + { + "team_logo": { + "size": "large", + "url": "https:\/\/s.yimg.com\/cv\/apiv2\/default\/nba\/nba_4_r.png" + } + } + ] + }, + { + "division_id": "1" + }, + { + "waiver_priority": 2 + }, + [], + { + "number_of_moves": "17" + }, + { + "number_of_trades": "9" + }, + { + "roster_adds": { + "coverage_type": "week", + "coverage_value": 9, + "value": "1" + } + }, + [], + { + "league_scoring_type": "headone" + }, + [], + [], + { + "has_draft_grade": 0 + }, + [], + [], + { + "managers": [ + { + "manager": { + "manager_id": "13", + "nickname": "rafael", + "guid": "V3LDWXTHPRWOAWKXQS7SRJ3U5M", + "email": "rafael.contreras@upr.edu", + "image_url": "https:\/\/s.yimg.com\/ag\/images\/default_user_profile_pic_64sq.jpg", + "felo_score": "794", + "felo_tier": "gold" + } + } + ] + } + ], + { + "team_stats": { + "coverage_type": "week", + "week": "9", + "stats": [ + { + "stat": { + "stat_id": "9004003", + "value": "76\/158" + } + }, + { + "stat": { + "stat_id": "5", + "value": ".481" + } + }, + { + "stat": { + "stat_id": "9007006", + "value": "37\/45" + } + }, + { + "stat": { + "stat_id": "8", + "value": ".822" + } + }, + { + "stat": { + "stat_id": "10", + "value": "20" + } + }, + { + "stat": { + "stat_id": "12", + "value": "209" + } + }, + { + "stat": { + "stat_id": "15", + "value": "57" + } + }, + { + "stat": { + "stat_id": "16", + "value": "33" + } + }, + { + "stat": { + "stat_id": "17", + "value": "12" + } + }, + { + "stat": { + "stat_id": "18", + "value": "4" + } + }, + { + "stat": { + "stat_id": "19", + "value": "24" + } + } + ] + }, + "team_points": { + "coverage_type": "week", + "week": "9", + "total": "7" + }, + "team_remaining_games": { + "coverage_type": "week", + "week": "9", + "total": { + "remaining_games": 33, + "live_games": 0, + "completed_games": 11 + } + } + } + ] + }, + "count": 2 + } + } + } + }, + "5": { + "matchup": { + "week": "9", + "week_start": "2022-12-12", + "week_end": "2022-12-18", + "status": "midevent", + "is_playoffs": "0", + "is_consolation": "0", + "stat_winners": [ + { + "stat_winner": { + "stat_id": "5", + "winner_team_key": "418.l.15944.t.10" + } + }, + { + "stat_winner": { + "stat_id": "8", + "winner_team_key": "418.l.15944.t.10" + } + }, + { + "stat_winner": { + "stat_id": "10", + "winner_team_key": "418.l.15944.t.10" + } + }, + { + "stat_winner": { + "stat_id": "12", + "winner_team_key": "418.l.15944.t.10" + } + }, + { + "stat_winner": { + "stat_id": "15", + "winner_team_key": "418.l.15944.t.10" + } + }, + { + "stat_winner": { + "stat_id": "16", + "is_tied": 1 + } + }, + { + "stat_winner": { + "stat_id": "17", + "winner_team_key": "418.l.15944.t.10" + } + }, + { + "stat_winner": { + "stat_id": "18", + "winner_team_key": "418.l.15944.t.10" + } + }, + { + "stat_winner": { + "stat_id": "19", + "winner_team_key": "418.l.15944.t.10" + } + } + ], + "0": { + "teams": { + "0": { + "team": [ + [ + { + "team_key": "418.l.15944.t.10" + }, + { + "team_id": "10" + }, + { + "name": "SHAKA" + }, + [], + { + "url": "https:\/\/basketball.fantasysports.yahoo.com\/nba\/15944\/10" + }, + { + "team_logos": [ + { + "team_logo": { + "size": "large", + "url": "https:\/\/yahoofantasysports-res.cloudinary.com\/image\/upload\/t_s192sq\/fantasy-logos\/3f22644734194be651de7c7fde5a56634e0c08036140f4accbce6c7b522d7de3.png" + } + } + ] + }, + { + "division_id": "2" + }, + { + "waiver_priority": 6 + }, + [], + { + "number_of_moves": "15" + }, + { + "number_of_trades": "3" + }, + { + "roster_adds": { + "coverage_type": "week", + "coverage_value": 9, + "value": "0" + } + }, + [], + { + "league_scoring_type": "headone" + }, + [], + [], + { + "has_draft_grade": 0 + }, + [], + [], + { + "managers": [ + { + "manager": { + "manager_id": "10", + "nickname": "Gustavo", + "guid": "GS7SI4JSILHPLOCQIJ3GMQ5Q54", + "email": "gustavovelez011@gmail.com", + "image_url": "https:\/\/s.yimg.com\/ag\/images\/default_user_profile_pic_64sq.jpg", + "felo_score": "715", + "felo_tier": "gold" + } + } + ] + } + ], + { + "team_stats": { + "coverage_type": "week", + "week": "9", + "stats": [ + { + "stat": { + "stat_id": "9004003", + "value": "71\/149" + } + }, + { + "stat": { + "stat_id": "5", + "value": ".477" + } + }, + { + "stat": { + "stat_id": "9007006", + "value": "37\/45" + } + }, + { + "stat": { + "stat_id": "8", + "value": ".822" + } + }, + { + "stat": { + "stat_id": "10", + "value": "21" + } + }, + { + "stat": { + "stat_id": "12", + "value": "200" + } + }, + { + "stat": { + "stat_id": "15", + "value": "76" + } + }, + { + "stat": { + "stat_id": "16", + "value": "31" + } + }, + { + "stat": { + "stat_id": "17", + "value": "12" + } + }, + { + "stat": { + "stat_id": "18", + "value": "7" + } + }, + { + "stat": { + "stat_id": "19", + "value": "17" + } + } + ] + }, + "team_points": { + "coverage_type": "week", + "week": "9", + "total": "8" + }, + "team_remaining_games": { + "coverage_type": "week", + "week": "9", + "total": { + "remaining_games": 34, + "live_games": 0, + "completed_games": 11 + } + } + } + ] + }, + "1": { + "team": [ + [ + { + "team_key": "418.l.15944.t.12" + }, + { + "team_id": "12" + }, + { + "name": "Wafret Inc." + }, + [], + { + "url": "https:\/\/basketball.fantasysports.yahoo.com\/nba\/15944\/12" + }, + { + "team_logos": [ + { + "team_logo": { + "size": "large", + "url": "https:\/\/yahoofantasysports-res.cloudinary.com\/image\/upload\/t_s192sq\/fantasy-logos\/70a441d004ec88c92787d934618338aa87e5097d352b2f4f7c654a3e3ba10413.jpg" + } + } + ] + }, + { + "division_id": "2" + }, + { + "waiver_priority": 5 + }, + [], + { + "number_of_moves": "26" + }, + { + "number_of_trades": "1" + }, + { + "roster_adds": { + "coverage_type": "week", + "coverage_value": 9, + "value": "0" + } + }, + [], + { + "league_scoring_type": "headone" + }, + [], + [], + { + "has_draft_grade": 0 + }, + [], + [], + { + "managers": [ + { + "manager": { + "manager_id": "12", + "nickname": "Gabriel", + "guid": "JKGQOQ3NF3X5K2J36YHUPCHVLM", + "email": "wabby4115@gmail.com", + "image_url": "https:\/\/s.yimg.com\/ag\/images\/default_user_profile_pic_64sq.jpg", + "felo_score": "773", + "felo_tier": "gold" + } + }, + { + "manager": { + "manager_id": "16", + "nickname": "Adam", + "guid": "3XRIWNJITGN3O5PQUWIJ3AHAZ4", + "is_comanager": "1", + "email": "asgfrontoffice@gmail.com", + "image_url": "https:\/\/s.yimg.com\/ag\/images\/default_user_profile_pic_64sq.jpg", + "felo_score": "594", + "felo_tier": "bronze" + } + } + ] + } + ], + { + "team_stats": { + "coverage_type": "week", + "week": "9", + "stats": [ + { + "stat": { + "stat_id": "9004003", + "value": "50\/119" + } + }, + { + "stat": { + "stat_id": "5", + "value": ".420" + } + }, + { + "stat": { + "stat_id": "9007006", + "value": "28\/35" + } + }, + { + "stat": { + "stat_id": "8", + "value": ".800" + } + }, + { + "stat": { + "stat_id": "10", + "value": "19" + } + }, + { + "stat": { + "stat_id": "12", + "value": "147" + } + }, + { + "stat": { + "stat_id": "15", + "value": "57" + } + }, + { + "stat": { + "stat_id": "16", + "value": "31" + } + }, + { + "stat": { + "stat_id": "17", + "value": "7" + } + }, + { + "stat": { + "stat_id": "18", + "value": "2" + } + }, + { + "stat": { + "stat_id": "19", + "value": "20" + } + } + ] + }, + "team_points": { + "coverage_type": "week", + "week": "9", + "total": "0" + }, + "team_remaining_games": { + "coverage_type": "week", + "week": "9", + "total": { + "remaining_games": 33, + "live_games": 0, + "completed_games": 9 + } + } + } + ] + }, + "count": 2 + } + } + } + }, + "6": { + "matchup": { + "week": "9", + "week_start": "2022-12-12", + "week_end": "2022-12-18", + "status": "midevent", + "is_playoffs": "0", + "is_consolation": "0", + "stat_winners": [ + { + "stat_winner": { + "stat_id": "5", + "winner_team_key": "418.l.15944.t.11" + } + }, + { + "stat_winner": { + "stat_id": "8", + "winner_team_key": "418.l.15944.t.14" + } + }, + { + "stat_winner": { + "stat_id": "10", + "winner_team_key": "418.l.15944.t.11" + } + }, + { + "stat_winner": { + "stat_id": "12", + "winner_team_key": "418.l.15944.t.11" + } + }, + { + "stat_winner": { + "stat_id": "15", + "winner_team_key": "418.l.15944.t.11" + } + }, + { + "stat_winner": { + "stat_id": "16", + "winner_team_key": "418.l.15944.t.14" + } + }, + { + "stat_winner": { + "stat_id": "17", + "is_tied": 1 + } + }, + { + "stat_winner": { + "stat_id": "18", + "winner_team_key": "418.l.15944.t.14" + } + }, + { + "stat_winner": { + "stat_id": "19", + "winner_team_key": "418.l.15944.t.14" + } + } + ], + "0": { + "teams": { + "0": { + "team": [ + [ + { + "team_key": "418.l.15944.t.11" + }, + { + "team_id": "11" + }, + { + "name": "Sky Dancers" + }, + [], + { + "url": "https:\/\/basketball.fantasysports.yahoo.com\/nba\/15944\/11" + }, + { + "team_logos": [ + { + "team_logo": { + "size": "large", + "url": "https:\/\/yahoofantasysports-res.cloudinary.com\/image\/upload\/t_s192sq\/fantasy-logos\/387d77d5a313d3742b963f4f590893b0958e3ac513435b0e30dbc2ee23ffba49.jpg" + } + } + ] + }, + { + "division_id": "1" + }, + { + "waiver_priority": 4 + }, + [], + { + "number_of_moves": "29" + }, + { + "number_of_trades": "16" + }, + { + "roster_adds": { + "coverage_type": "week", + "coverage_value": 9, + "value": "0" + } + }, + [], + { + "league_scoring_type": "headone" + }, + [], + [], + { + "has_draft_grade": 0 + }, + [], + [], + { + "managers": [ + { + "manager": { + "manager_id": "11", + "nickname": "Aniel", + "guid": "TH5VMI3C67WZISY5TDL3HPNFAQ", + "email": "anielfantasy@yahoo.com", + "image_url": "https:\/\/s.yimg.com\/ag\/images\/1781\/30529711168_e09d5d_64sq.jpg", + "felo_score": "915", + "felo_tier": "diamond" + } + } + ] + } + ], + { + "team_stats": { + "coverage_type": "week", + "week": "9", + "stats": [ + { + "stat": { + "stat_id": "9004003", + "value": "69\/135" + } + }, + { + "stat": { + "stat_id": "5", + "value": ".511" + } + }, + { + "stat": { + "stat_id": "9007006", + "value": "15\/25" + } + }, + { + "stat": { + "stat_id": "8", + "value": ".600" + } + }, + { + "stat": { + "stat_id": "10", + "value": "19" + } + }, + { + "stat": { + "stat_id": "12", + "value": "172" + } + }, + { + "stat": { + "stat_id": "15", + "value": "57" + } + }, + { + "stat": { + "stat_id": "16", + "value": "26" + } + }, + { + "stat": { + "stat_id": "17", + "value": "8" + } + }, + { + "stat": { + "stat_id": "18", + "value": "8" + } + }, + { + "stat": { + "stat_id": "19", + "value": "24" + } + } + ] + }, + "team_points": { + "coverage_type": "week", + "week": "9", + "total": "4" + }, + "team_remaining_games": { + "coverage_type": "week", + "week": "9", + "total": { + "remaining_games": 32, + "live_games": 0, + "completed_games": 12 + } + } + } + ] + }, + "1": { + "team": [ + [ + { + "team_key": "418.l.15944.t.14" + }, + { + "team_id": "14" + }, + { + "name": "El Drinking Team" + }, + [], + { + "url": "https:\/\/basketball.fantasysports.yahoo.com\/nba\/15944\/14" + }, + { + "team_logos": [ + { + "team_logo": { + "size": "large", + "url": "https:\/\/s.yimg.com\/cv\/apiv2\/default\/nba\/nba_1_h.png" + } + } + ] + }, + { + "division_id": "2" + }, + { + "waiver_priority": 1 + }, + [], + { + "number_of_moves": "32" + }, + { + "number_of_trades": "4" + }, + { + "roster_adds": { + "coverage_type": "week", + "coverage_value": 9, + "value": "3" + } + }, + [], + { + "league_scoring_type": "headone" + }, + [], + [], + { + "has_draft_grade": 0 + }, + [], + [], + { + "managers": [ + { + "manager": { + "manager_id": "14", + "nickname": "Harry", + "guid": "SUZIWBT42KGWOBBMQTN4UUZDIM", + "email": "p.harry12@yahoo.com", + "image_url": "https:\/\/s.yimg.com\/ag\/images\/default_user_profile_pic_64sq.jpg", + "felo_score": "867", + "felo_tier": "platinum" + } + } + ] + } + ], + { + "team_stats": { + "coverage_type": "week", + "week": "9", + "stats": [ + { + "stat": { + "stat_id": "9004003", + "value": "51\/110" + } + }, + { + "stat": { + "stat_id": "5", + "value": ".464" + } + }, + { + "stat": { + "stat_id": "9007006", + "value": "13\/19" + } + }, + { + "stat": { + "stat_id": "8", + "value": ".684" + } + }, + { + "stat": { + "stat_id": "10", + "value": "16" + } + }, + { + "stat": { + "stat_id": "12", + "value": "131" + } + }, + { + "stat": { + "stat_id": "15", + "value": "51" + } + }, + { + "stat": { + "stat_id": "16", + "value": "27" + } + }, + { + "stat": { + "stat_id": "17", + "value": "8" + } + }, + { + "stat": { + "stat_id": "18", + "value": "9" + } + }, + { + "stat": { + "stat_id": "19", + "value": "16" + } + } + ] + }, + "team_points": { + "coverage_type": "week", + "week": "9", + "total": "4" + }, + "team_remaining_games": { + "coverage_type": "week", + "week": "9", + "total": { + "remaining_games": 28, + "live_games": 0, + "completed_games": 11 + } + } + } + ] + }, + "count": 2 + } + } + } + }, + "count": 7 + } + } + } + } + ], + "time": "187.21103668213ms", + "copyright": "Data provided by Yahoo! and STATS, LLC", + "refresh_rate": "60" + } +} diff --git a/tests/test_yahoo_api.py b/tests/test_yahoo_api.py index fe6f369..fb23d9d 100644 --- a/tests/test_yahoo_api.py +++ b/tests/test_yahoo_api.py @@ -46,3 +46,9 @@ def test_get_matchups(api): return_value = api.get_matchups() assert isinstance(return_value, Embed) assert len(return_value.fields) == 6 + + +def test_get_matchups_category(category_api): + return_value = category_api.get_matchups() + assert isinstance(return_value, Embed) + assert len(return_value.fields) == 7 From 2c50e59dbdb91a5c31e532f229f6929af3853950 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Thu, 15 Dec 2022 09:46:40 -0500 Subject: [PATCH 14/51] Moving embed to yahoo cog Updating test for get standings --- harambot/cogs/yahoo.py | 12 +++++++++++- harambot/yahoo_api.py | 17 +++++++---------- tests/test_yahoo_api.py | 6 +++--- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/harambot/cogs/yahoo.py b/harambot/cogs/yahoo.py index 042ff8c..5f179cf 100644 --- a/harambot/cogs/yahoo.py +++ b/harambot/cogs/yahoo.py @@ -41,7 +41,17 @@ async def cog_before_invoke(self, ctx): @commands.command("standings") async def standings(self, ctx): logger.info("standings called") - embed = self.yahoo_api.get_standings() + embed = discord.Embed( + title="Standings", + description="Team Name\n W-L-T", + color=0xEEE657, + ) + for team in self.yahoo_api.get_standings(): + embed.add_field( + name=team["place"], + value=team["record"], + inline=False, + ) if embed: await ctx.send(embed=embed) else: diff --git a/harambot/yahoo_api.py b/harambot/yahoo_api.py index 64f4d37..3199b66 100644 --- a/harambot/yahoo_api.py +++ b/harambot/yahoo_api.py @@ -36,22 +36,19 @@ def league(self): @cached(cache=TTLCache(maxsize=1024, ttl=600)) def get_standings(self): try: - embed = discord.Embed( - title="Standings", - description="Team Name\n W-L-T", - color=0xEEE657, - ) + standings = [] for idx, team in enumerate(self.league().standings()): outcomes = team["outcome_totals"] record = "{}-{}-{}".format( outcomes["wins"], outcomes["losses"], outcomes["ties"] ) - embed.add_field( - name=str(idx + 1) + ". " + team["name"], - value=record, - inline=False, + standings.append( + { + "place": str(idx + 1) + ". " + team["name"], + "record": record, + } ) - return embed + return standings except Exception: logger.exception( "Error while fetching standings for league {}".format( diff --git a/tests/test_yahoo_api.py b/tests/test_yahoo_api.py index fb23d9d..cc6ef09 100644 --- a/tests/test_yahoo_api.py +++ b/tests/test_yahoo_api.py @@ -9,9 +9,9 @@ def test_league(api): def test_get_standings(api): return_value = api.get_standings() - assert isinstance(return_value, Embed) - assert len(return_value.fields) == 3 - assert return_value.fields[0].name == "1. Hide and Go Zeke" + assert isinstance(return_value, list) + assert len(return_value) == 3 + assert return_value[0]["place"] == "1. Hide and Go Zeke" def test_get_team(api): From ddf0620cf9e7ab2285e754d17d5082592ff1d3c3 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Fri, 16 Dec 2022 13:25:57 -0500 Subject: [PATCH 15/51] Removing get_team function --- harambot/yahoo_api.py | 22 ++---- poetry.lock | 152 +----------------------------------------- pyproject.toml | 2 +- tests/conftest.py | 6 +- 4 files changed, 12 insertions(+), 170 deletions(-) diff --git a/harambot/yahoo_api.py b/harambot/yahoo_api.py index 3199b66..0b87f55 100644 --- a/harambot/yahoo_api.py +++ b/harambot/yahoo_api.py @@ -57,30 +57,18 @@ def get_standings(self): ) return None - @cached(cache=TTLCache(maxsize=1024, ttl=600)) - def get_team(self, team_name): - try: - for id, team in self.league().teams().items(): - if team["name"] == team_name: - return self.league().to_team(id) - except Exception: - logger.exception( - "Error while fetching team: {} from league: {}".format( - team_name, self.league_id - ) - ) - return None - @cached(cache=TTLCache(maxsize=1024, ttl=600)) def get_roster(self, team_name): - team = self.get_team(team_name) - if team: + team_details = self.league().get_team(team_name) + if team_details: embed = discord.Embed( title="{}'s Roster".format(team_name), description="", color=0xEEE657, ) - for player in team.roster(self.league().current_week()): + for player in team_details[team_name].roster( + self.league().current_week() + ): embed.add_field( name=player["selected_position"], value=player["name"], diff --git a/poetry.lock b/poetry.lock index 41f236e..102e4fe 100644 --- a/poetry.lock +++ b/poetry.lock @@ -81,14 +81,6 @@ category = "main" optional = false python-versions = "~=3.7" -[[package]] -name = "certifi" -version = "2022.9.24" -description = "Python package for providing Mozilla's CA Bundle." -category = "main" -optional = false -python-versions = ">=3.6" - [[package]] name = "cfgv" version = "3.3.1" @@ -177,14 +169,6 @@ category = "dev" optional = false python-versions = "*" -[[package]] -name = "docopt" -version = "0.6.2" -description = "Pythonic argument parser, that will make you smile" -category = "main" -optional = false -python-versions = "*" - [[package]] name = "dynaconf" version = "3.1.11" @@ -317,14 +301,6 @@ python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.* [package.dependencies] setuptools = "*" -[[package]] -name = "objectpath" -version = "0.6.1" -description = "The agile query language for semi-structured data. #JSON" -category = "main" -optional = false -python-versions = "*" - [[package]] name = "packaging" version = "21.3" @@ -400,17 +376,6 @@ category = "main" optional = false python-versions = ">=3.6" -[[package]] -name = "pyaml" -version = "21.10.1" -description = "PyYAML-based module to produce pretty and readable YAML-serialized data" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -PyYAML = "*" - [[package]] name = "pycodestyle" version = "2.9.1" @@ -473,51 +438,14 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] -[[package]] -name = "pytz" -version = "2022.6" -description = "World timezone definitions, modern and historical" -category = "main" -optional = false -python-versions = "*" - [[package]] name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" -[[package]] -name = "rauth" -version = "0.7.3" -description = "A Python library for OAuth 1.0/a, 2.0, and Ofly." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -requests = ">=1.2.3" - -[[package]] -name = "requests" -version = "2.28.1" -description = "Python HTTP for Humans." -category = "main" -optional = false -python-versions = ">=3.7, <4" - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<3" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - [[package]] name = "setuptools" version = "65.5.1" @@ -555,19 +483,6 @@ category = "dev" optional = false python-versions = ">=3.7" -[[package]] -name = "urllib3" -version = "1.26.12" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - [[package]] name = "virtualenv" version = "20.16.7" @@ -585,32 +500,6 @@ platformdirs = ">=2.4,<3" docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"] testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] -[[package]] -name = "yahoo-fantasy-api" -version = "2.6.0" -description = "Python bindings to access the Yahoo! Fantasy APIs" -category = "main" -optional = false -python-versions = ">=3" - -[package.dependencies] -docopt = "*" -objectpath = "*" -pytz = "*" -yahoo_oauth = "*" - -[[package]] -name = "yahoo-oauth" -version = "2.0" -description = "Python Yahoo OAuth Library. Supports OAuth1 and OAuth2" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -pyaml = "*" -rauth = "*" - [[package]] name = "yarl" version = "1.8.1" @@ -626,7 +515,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "84615cf3da8ad6ec805acac5b828c3126ee2c0bba37c4b70bb076b6c7056ec13" +content-hash = "8155a81d642c470ea2d821fc66750883522ff0d10a09307f52ccd75e5d2839d7" [metadata.files] aiohttp = [ @@ -757,10 +646,6 @@ cachetools = [ {file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"}, {file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"}, ] -certifi = [ - {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, - {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, -] cfgv = [ {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, @@ -841,9 +726,6 @@ distlib = [ {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, ] -docopt = [ - {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, -] dynaconf = [ {file = "dynaconf-3.1.11-py2.py3-none-any.whl", hash = "sha256:87e0b3b12b5db9e8fb465e1f8c7fdb926cd2ec5b6d88aa7f821f316df93fb165"}, {file = "dynaconf-3.1.11.tar.gz", hash = "sha256:d9cfb50fd4a71a543fd23845d4f585b620b6ff6d9d3cc1825c614f7b2097cb39"}, @@ -1030,10 +912,6 @@ nodeenv = [ {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, ] -objectpath = [ - {file = "objectpath-0.6.1-py2.py3-none-any.whl", hash = "sha256:7ac2c507e7e5bfbf245701044453edc8e4073ded01d53349237d918124d7ca87"}, - {file = "objectpath-0.6.1.tar.gz", hash = "sha256:461263136c79292e42431fbb85cdcaac4c6a256f6b1aa5b3ae9316e4965ad819"}, -] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, @@ -1072,10 +950,6 @@ psycopg2 = [ {file = "psycopg2-2.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:190d51e8c1b25a47484e52a79638a8182451d6f6dff99f26ad9bd81e5359a0fa"}, {file = "psycopg2-2.9.5.tar.gz", hash = "sha256:a5246d2e683a972e2187a8714b5c2cf8156c064629f9a9b1a873c1730d9e245a"}, ] -pyaml = [ - {file = "pyaml-21.10.1-py2.py3-none-any.whl", hash = "sha256:19985ed303c3a985de4cf8fd329b6d0a5a5b5c9035ea240eccc709ebacbaf4a0"}, - {file = "pyaml-21.10.1.tar.gz", hash = "sha256:c6519fee13bf06e3bb3f20cacdea8eba9140385a7c2546df5dbae4887f768383"}, -] pycodestyle = [ {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, @@ -1096,10 +970,6 @@ pytest-cov = [ {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, ] -pytz = [ - {file = "pytz-2022.6-py2.py3-none-any.whl", hash = "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427"}, - {file = "pytz-2022.6.tar.gz", hash = "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2"}, -] pyyaml = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, @@ -1142,14 +1012,6 @@ pyyaml = [ {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] -rauth = [ - {file = "rauth-0.7.3-py2-none-any.whl", hash = "sha256:b18590fbd77bc3d871936bbdb851377d1b0c08e337b219c303f8fc2b5a42ef2d"}, - {file = "rauth-0.7.3.tar.gz", hash = "sha256:524cdbc1c28560eacfc9a9d40c59525eb8d00fdf07fbad86107ea24411477b0a"}, -] -requests = [ - {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, - {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, -] setuptools = [ {file = "setuptools-65.5.1-py3-none-any.whl", hash = "sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31"}, {file = "setuptools-65.5.1.tar.gz", hash = "sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f"}, @@ -1166,20 +1028,10 @@ typing-extensions = [ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] -urllib3 = [ - {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, - {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, -] virtualenv = [ {file = "virtualenv-20.16.7-py3-none-any.whl", hash = "sha256:efd66b00386fdb7dbe4822d172303f40cd05e50e01740b19ea42425cbe653e29"}, {file = "virtualenv-20.16.7.tar.gz", hash = "sha256:8691e3ff9387f743e00f6bb20f70121f5e4f596cae754531f2b3b3a1b1ac696e"}, ] -yahoo-fantasy-api = [ - {file = "yahoo_fantasy_api-2.6.0.tar.gz", hash = "sha256:8dbc247c0d3128b03b132044b559e4aa47a57de88dff7a841c2147ce5d3e0075"}, -] -yahoo-oauth = [ - {file = "yahoo_oauth-2.0.tar.gz", hash = "sha256:912921bf8724ced6e5d7924308b2b15c2b51e14ed7d25c9c42cb8b52e3e75158"}, -] yarl = [ {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:abc06b97407868ef38f3d172762f4069323de52f2b70d133d096a48d72215d28"}, {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:07b21e274de4c637f3e3b7104694e53260b5fc10d51fb3ec5fed1da8e0f754e3"}, diff --git a/pyproject.toml b/pyproject.toml index fd6992d..08a4749 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,12 +29,12 @@ packages = [ [tool.poetry.dependencies] python = "^3.8" discord = "^2.1.0" -yahoo-fantasy-api = "^2.6.0" dynaconf = "^3.1.11" peewee = "^3.15.4" cachetools = "^5.2.0" psycopg2 = "^2.9.5" mysqlclient = "^2.1.1" +yahoo_fantasy_api = { git = "https://github.com/DMcP89/yahoo_fantasy_api.git" , branch = "feature/get_team_by_name" } [tool.poetry.group.dev.dependencies] diff --git a/tests/conftest.py b/tests/conftest.py index ff87caf..d0af737 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,7 @@ from unittest.mock import MagicMock, patch from harambot.yahoo_api import Yahoo -from yahoo_fantasy_api import game, League +from yahoo_fantasy_api import game, League, Team root_path = os.path.dirname(os.path.realpath(__file__)) @@ -66,7 +66,6 @@ def api( mock_player_details, mock_ownership, mock_matchups, - mock_matchups_category, ): api = Yahoo(mock_oauth, "123456", "nfl") api.scoring_type = "head" @@ -79,6 +78,9 @@ def api( league.player_details = MagicMock(return_value=mock_player_details) league.ownership = MagicMock(return_value=mock_ownership) league.matchups = MagicMock(return_value=mock_matchups) + league.get_team = MagicMock( + return_value={"Too Many Cooks": Team(mock_oauth, "")} + ) api.league = MagicMock(return_value=league) return api From 2fd1070278b483adccd819ee35998037e83c73ce Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Fri, 16 Dec 2022 13:35:31 -0500 Subject: [PATCH 16/51] Moving embed handling to yahoo cog --- harambot/cogs/yahoo.py | 13 ++++++++++++- harambot/yahoo_api.py | 15 +-------------- tests/test_yahoo_api.py | 6 +++--- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/harambot/cogs/yahoo.py b/harambot/cogs/yahoo.py index 5f179cf..3384d56 100644 --- a/harambot/cogs/yahoo.py +++ b/harambot/cogs/yahoo.py @@ -60,9 +60,20 @@ async def standings(self, ctx): @commands.command("roster") async def roster(self, ctx, *, content: str): logger.info("roster called") + embed = discord.Embed( + title="{}'s Roster".format(content), + description="", + color=0xEEE657, + ) roster = self.yahoo_api.get_roster(content) if roster: - await ctx.send(embed=roster) + for player in roster: + embed.add_field( + name=player["selected_position"], + value=player["name"], + inline=False, + ) + await ctx.send(embed=embed) else: await ctx.send(self.error_message) diff --git a/harambot/yahoo_api.py b/harambot/yahoo_api.py index 0b87f55..c7bc39b 100644 --- a/harambot/yahoo_api.py +++ b/harambot/yahoo_api.py @@ -61,20 +61,7 @@ def get_standings(self): def get_roster(self, team_name): team_details = self.league().get_team(team_name) if team_details: - embed = discord.Embed( - title="{}'s Roster".format(team_name), - description="", - color=0xEEE657, - ) - for player in team_details[team_name].roster( - self.league().current_week() - ): - embed.add_field( - name=player["selected_position"], - value=player["name"], - inline=False, - ) - return embed + return team_details[team_name].roster(self.league().current_week()) else: return None diff --git a/tests/test_yahoo_api.py b/tests/test_yahoo_api.py index cc6ef09..1153cd2 100644 --- a/tests/test_yahoo_api.py +++ b/tests/test_yahoo_api.py @@ -23,9 +23,9 @@ def test_get_team(api): def test_get_roster(api, mock_roster): with patch.object(team.Team, "roster", return_value=mock_roster): return_value = api.get_roster("Too Many Cooks") - assert isinstance(return_value, Embed) - assert return_value.fields[0].name == "QB" - assert return_value.fields[0].value == "Josh Allen" + assert isinstance(return_value, list) + assert return_value[0]["selected_position"] == "QB" + assert return_value[0]["name"] == "Josh Allen" def test_get_player_owner(api): From 9818cb0359c48aee1d545057ae721d645e970329 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Fri, 30 Dec 2022 00:17:20 -0500 Subject: [PATCH 17/51] Updating yahoo_fantasy_api version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 08a4749..1cb186d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ peewee = "^3.15.4" cachetools = "^5.2.0" psycopg2 = "^2.9.5" mysqlclient = "^2.1.1" -yahoo_fantasy_api = { git = "https://github.com/DMcP89/yahoo_fantasy_api.git" , branch = "feature/get_team_by_name" } +yahoo_fantasy_api = "2.7.0" [tool.poetry.group.dev.dependencies] From ef4a5ed441a5f45b0466e0290499e0466646f0f0 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Fri, 30 Dec 2022 00:18:07 -0500 Subject: [PATCH 18/51] Removing embed from get matchups method --- harambot/cogs/yahoo.py | 14 ++++++++++++-- harambot/yahoo_api.py | 35 ++++++++++++----------------------- tests/test_yahoo_api.py | 6 +++--- 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/harambot/cogs/yahoo.py b/harambot/cogs/yahoo.py index 3384d56..df02cf7 100644 --- a/harambot/cogs/yahoo.py +++ b/harambot/cogs/yahoo.py @@ -164,8 +164,18 @@ async def stats(self, ctx, *, content: str): @commands.command("matchups") async def matchups(self, ctx): - embed = self.yahoo_api.get_matchups() - if embed: + week, details = self.yahoo_api.get_matchups() + + if details: + embed = discord.Embed( + title="Matchups for Week {}".format(week), + description="", + color=0xEEE657, + ) + for detail in details: + embed.add_field( + name=detail["name"], value=detail["value"], inline=False + ) await ctx.send(embed=embed) else: await ctx.send(self.error_message) diff --git a/harambot/yahoo_api.py b/harambot/yahoo_api.py index c7bc39b..4cea6e5 100644 --- a/harambot/yahoo_api.py +++ b/harambot/yahoo_api.py @@ -164,37 +164,26 @@ def get_player_owner(self, player_id): @cached(cache=TTLCache(maxsize=1024, ttl=600)) def get_matchups(self): try: - embed = discord.Embed( - title="Matchups for Week {}".format( - str(self.league().current_week()) - ), - description="", - color=0xEEE657, - ) matchups = objectpath.Tree(self.league().matchups()).execute( "$..scoreboard..matchups..matchup..teams" ) - # loop through each matchup element + details = [] + divider = "--------------------------------------" for matchup in matchups: - # handle team 1 team1_details = self.get_matchup_details(matchup["0"]["team"]) - - # handle team 2 team2_details = self.get_matchup_details(matchup["1"]["team"]) - divider = "--------------------------------------" - - # Add details to embed - embed.add_field( - name="{} vs {}".format( - team1_details["name"], team2_details["name"] - ), - value=team1_details["text"] - + team2_details["text"] - + divider, - inline=False, + details.append( + { + "name": "{} vs {}".format( + team1_details["name"], team2_details["name"] + ), + "value": team1_details["text"] + + team2_details["text"] + + divider, + } ) - return embed + return str(self.league().current_week()), details except Exception: logger.exception( "Error while fetching matchups for league: {}".format( diff --git a/tests/test_yahoo_api.py b/tests/test_yahoo_api.py index 1153cd2..1718b6f 100644 --- a/tests/test_yahoo_api.py +++ b/tests/test_yahoo_api.py @@ -43,9 +43,9 @@ def test_get_player_details(api): def test_get_matchups(api): - return_value = api.get_matchups() - assert isinstance(return_value, Embed) - assert len(return_value.fields) == 6 + week, details = api.get_matchups() + assert isinstance(details, list) + assert week == "1" def test_get_matchups_category(category_api): From ff0fae6a70a21f48542870e6fc360d5725c70e00 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Fri, 30 Dec 2022 15:30:13 -0500 Subject: [PATCH 19/51] Removing embed from yahoo api player details function --- harambot/cogs/yahoo.py | 69 ++++++++++++++++++++++++++++++++++++++--- harambot/yahoo_api.py | 61 ++---------------------------------- tests/test_yahoo_api.py | 5 ++- 3 files changed, 68 insertions(+), 67 deletions(-) diff --git a/harambot/cogs/yahoo.py b/harambot/cogs/yahoo.py index df02cf7..9a487ec 100644 --- a/harambot/cogs/yahoo.py +++ b/harambot/cogs/yahoo.py @@ -97,7 +97,9 @@ async def trade(self, ctx): for player in latest_trade["trader_players"]: player_set0.append(player["name"]) api_details = ( - self.yahoo_api.get_player_details(player["name"])["text"] + self.get_player_text( + self.yahoo_api.get_player_details(player["name"]) + ) + "\n" ) if api_details: @@ -111,7 +113,9 @@ async def trade(self, ctx): for player in latest_trade["tradee_players"]: player_set1.append(player["name"]) api_details = ( - self.yahoo_api.get_player_details(player["name"])["text"] + self.get_player_text( + self.yahoo_api.get_player_details(player["name"]) + ) + "\n" ) if api_details: @@ -156,12 +160,67 @@ async def trade(self, ctx): @commands.command("stats") async def stats(self, ctx, *, content: str): logger.info("player_details called") - details = self.yahoo_api.get_player_details(content) - if details: - await ctx.send(embed=details["embed"]) + player = self.yahoo_api.get_player_details(content) + if player: + embed = self.get_player_embed(player) + await ctx.send(embed=embed) else: await ctx.send("Player not found") + def get_player_embed(self, player): + embed = discord.Embed( + title=player["name"]["full"], + description="#" + player["uniform_number"], + color=0xEEE657, + ) + embed.add_field(name="Postion", value=player["primary_position"]) + embed.add_field(name="Team", value=player["editorial_team_abbr"]) + if "bye_weeks" in player: + embed.add_field(name="Bye", value=player["bye_weeks"]["week"]) + if "player_points" in player: + embed.add_field( + name="Total Points", value=player["player_points"]["total"] + ) + embed.add_field(name="Owner", value=player["owner"]) + embed.set_image(url=player["image_url"]) + return embed + + def get_player_text(self, player): + player_details_text = ( + player["name"]["full"] + " #" + player["uniform_number"] + "\n" + ) + player_details_text = ( + player_details_text + + "Position: " + + player["primary_position"] + + "\n" + ) + player_details_text = ( + player_details_text + + "Team: " + + player["editorial_team_abbr"] + + "\n" + ) + if "bye_weeks" in player: + player_details_text = ( + player_details_text + + "Bye: " + + player["bye_weeks"]["week"] + + "\n" + ) + if "player_points" in player: + player_details_text = ( + player_details_text + + "Total Points: " + + player["player_points"]["total"] + + "\n" + ) + player_details_text = ( + player_details_text + + "Owner: " + + self.get_player_owner(player["player_id"]) + ) + @commands.command("matchups") async def matchups(self, ctx): week, details = self.yahoo_api.get_matchups() diff --git a/harambot/yahoo_api.py b/harambot/yahoo_api.py index 4cea6e5..c687440 100644 --- a/harambot/yahoo_api.py +++ b/harambot/yahoo_api.py @@ -1,6 +1,5 @@ import logging import os -import discord import objectpath from yahoo_fantasy_api import game @@ -69,64 +68,8 @@ def get_roster(self, team_name): def get_player_details(self, player_name): try: player = self.league().player_details(player_name)[0] - - embed = discord.Embed( - title=player["name"]["full"], - description="#" + player["uniform_number"], - color=0xEEE657, - ) - embed.add_field(name="Postion", value=player["primary_position"]) - embed.add_field(name="Team", value=player["editorial_team_abbr"]) - if "bye_weeks" in player: - embed.add_field(name="Bye", value=player["bye_weeks"]["week"]) - if self.scoring_type == "head": - embed.add_field( - name="Total Points", value=player["player_points"]["total"] - ) - embed.add_field( - name="Owner", value=self.get_player_owner(player["player_id"]) - ) - embed.set_image(url=player["image_url"]) - - player_details_text = ( - player["name"]["full"] + " #" + player["uniform_number"] + "\n" - ) - player_details_text = ( - player_details_text - + "Position: " - + player["primary_position"] - + "\n" - ) - player_details_text = ( - player_details_text - + "Team: " - + player["editorial_team_abbr"] - + "\n" - ) - if "bye_weeks" in player: - player_details_text = ( - player_details_text - + "Bye: " - + player["bye_weeks"]["week"] - + "\n" - ) - if self.scoring_type == "head": - player_details_text = ( - player_details_text - + "Total Points: " - + player["player_points"]["total"] - + "\n" - ) - player_details_text = ( - player_details_text - + "Owner: " - + self.get_player_owner(player["player_id"]) - ) - - player_details = {} - player_details["embed"] = embed - player_details["text"] = player_details_text - return player_details + player["owner"] = self.get_player_owner(player["player_id"]) + return player except Exception: logger.exception( "Error while fetching player details for player: \ diff --git a/tests/test_yahoo_api.py b/tests/test_yahoo_api.py index 1718b6f..eff4504 100644 --- a/tests/test_yahoo_api.py +++ b/tests/test_yahoo_api.py @@ -37,9 +37,8 @@ def test_get_player_owner(api): def test_get_player_details(api): return_value = api.get_player_details("Josh Allen") assert isinstance(return_value, dict) - assert isinstance(return_value["embed"], Embed) - assert len(return_value["embed"].fields) == 5 - assert isinstance(return_value["text"], str) + assert return_value["player_key"] == "399.p.30977" + assert return_value["owner"] == "Hide and Go Zeke" def test_get_matchups(api): From 93d476ce8810453e323882595a53b203d7cd639f Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Fri, 30 Dec 2022 16:02:16 -0500 Subject: [PATCH 20/51] Removing get team test Fixing get matchups category test --- tests/test_yahoo_api.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/tests/test_yahoo_api.py b/tests/test_yahoo_api.py index eff4504..ac1462f 100644 --- a/tests/test_yahoo_api.py +++ b/tests/test_yahoo_api.py @@ -1,6 +1,5 @@ from unittest.mock import patch from yahoo_fantasy_api import team -from discord import Embed def test_league(api): @@ -14,12 +13,6 @@ def test_get_standings(api): assert return_value[0]["place"] == "1. Hide and Go Zeke" -def test_get_team(api): - return_value = api.get_team("Too Many Cooks") - assert isinstance(return_value, team.Team) - assert return_value.team_key == "399.l.710921.t.9" - - def test_get_roster(api, mock_roster): with patch.object(team.Team, "roster", return_value=mock_roster): return_value = api.get_roster("Too Many Cooks") @@ -48,6 +41,7 @@ def test_get_matchups(api): def test_get_matchups_category(category_api): - return_value = category_api.get_matchups() - assert isinstance(return_value, Embed) - assert len(return_value.fields) == 7 + week, details = category_api.get_matchups() + assert isinstance(details, list) + assert len(details) == 7 + assert week == "1" From eaad04f14b48deb4ae756117a69e3264e332a58a Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Tue, 3 Jan 2023 23:04:36 -0500 Subject: [PATCH 21/51] Migrating RIP command to slash command --- harambot/cogs/misc.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/harambot/cogs/misc.py b/harambot/cogs/misc.py index cb515af..7203441 100644 --- a/harambot/cogs/misc.py +++ b/harambot/cogs/misc.py @@ -1,9 +1,9 @@ -from discord.ext import commands - - import discord import logging +from discord.ext import commands +from discord import app_commands +from typing import Optional from harambot.database.models import Guild logger = logging.getLogger(__file__) @@ -14,13 +14,15 @@ class Misc(commands.Cog): def __init__(self, bot): self.bot = bot - @commands.command("RIP") - async def RIP(self, ctx, *args): + @app_commands.command(name="rip", description="Pay respects to Harambe") + @app_commands.describe(deceased="Who you are paying respects to") + async def rip( + self, interaction: discord.Interaction, deceased: Optional[str] = None + ): + logger.info("RIP called") - guild = Guild.get(Guild.guild_id == str(ctx.guild.id)) - respected = args[0] if args else "Harambe" - message = guild.RIP_text + " " + respected + guild = Guild.get(Guild.guild_id == str(interaction.guild_id)) + message = guild.RIP_text + " " + (deceased if deceased else "Harambe") embed = discord.Embed(title="", description="", color=0xEEE657) embed.set_image(url=guild.RIP_image_url) - - await ctx.send(content=message, embed=embed) + await interaction.response.send_message(content=message, embed=embed) From d271c2fc7bec85cce69668b04c3ad4c00b42c59b Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Wed, 4 Jan 2023 12:22:50 -0500 Subject: [PATCH 22/51] Migrate ping command to slash command --- harambot/cogs/meta.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/harambot/cogs/meta.py b/harambot/cogs/meta.py index bb13f5c..c17f573 100644 --- a/harambot/cogs/meta.py +++ b/harambot/cogs/meta.py @@ -1,4 +1,5 @@ from discord.ext import commands +from discord import app_commands import discord import logging @@ -53,11 +54,11 @@ async def help(self, ctx): ) await ctx.send(embed=embed) - @commands.command("ping") - async def ping(self, ctx): - logger.info("Ping called") - latency = self.bot.latency # Included in the Discord.py library - await ctx.send(latency) + @app_commands.command( + name="ping", description="Gives the latency of harambot" + ) + async def ping(self, interaction: discord.Interaction): + await interaction.response.send_message(self.bot.latency) @commands.hybrid_command() async def configure(self, ctx): From 163262b02392b245121db501d4f7d9b9024e36bc Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Thu, 5 Jan 2023 23:30:06 -0500 Subject: [PATCH 23/51] migrating help command to slash command --- harambot/cogs/meta.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/harambot/cogs/meta.py b/harambot/cogs/meta.py index c17f573..ada4658 100644 --- a/harambot/cogs/meta.py +++ b/harambot/cogs/meta.py @@ -14,45 +14,45 @@ class Meta(commands.Cog): def __init__(self, bot): self.bot = bot - @commands.command("help") - async def help(self, ctx): + @app_commands.command(name="help", description="View available commands") + async def help(self, interaction: discord.Interaction): embed = discord.Embed( title="Harambot", description="Yahoo Fantasy Sports Bot for Discord", color=0xEEE657, ) embed.add_field( - name="$ping", value="Gives the latency of harambot", inline=False + name="/ping", value="Gives the latency of harambot", inline=False ) embed.add_field( - name="$RIP", value="Pay respects to Harambe", inline=False + name="/rip", value="Pay respects to Harambe", inline=False ) embed.add_field( - name="$standings", + name="/standings", value="Returns the current standings of your league", inline=False, ) embed.add_field( - name="$roster team_name", + name="/roster team_name", value="Returns the roster of the given team", inline=False, ) embed.add_field( - name="$stats player_name", + name="/stats player_name", value="Returns the details of the given player", inline=False, ) embed.add_field( - name="$trade", + name="/trade", value="Create poll for latest trade for league approval", inline=False, ) embed.add_field( - name="$matchups", + name="/matchups", value="Returns the current weeks matchups", inline=False, ) - await ctx.send(embed=embed) + await interaction.response.send_message(embed=embed) @app_commands.command( name="ping", description="Gives the latency of harambot" From c74ee58845b43ef9b45d4f59b916fd52971beb22 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Mon, 9 Jan 2023 14:45:41 -0500 Subject: [PATCH 24/51] Migrating standings to slash command --- harambot/cogs/yahoo.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/harambot/cogs/yahoo.py b/harambot/cogs/yahoo.py index 9a487ec..0b511a7 100644 --- a/harambot/cogs/yahoo.py +++ b/harambot/cogs/yahoo.py @@ -3,6 +3,7 @@ import urllib3 from discord.ext import commands +from discord import app_commands from yahoo_oauth import OAuth2 from playhouse.shortcuts import model_to_dict @@ -38,14 +39,31 @@ async def cog_before_invoke(self, ctx): ) return - @commands.command("standings") - async def standings(self, ctx): + async def set_yahoo_from_interaction( + self, interaction: discord.Interaction + ): + guild = Guild.get(Guild.guild_id == str(interaction.guild_id)) + self.yahoo_api = Yahoo( + OAuth2( + self.KEY, self.SECRET, store_file=False, **model_to_dict(guild) + ), + guild.league_id, + guild.league_type, + ) + return + + @app_commands.command( + name="standings", + description="Returns the current standings of your league", + ) + async def standings(self, interaction: discord.Interaction): logger.info("standings called") embed = discord.Embed( title="Standings", description="Team Name\n W-L-T", color=0xEEE657, ) + await self.set_yahoo_from_interaction(interaction) for team in self.yahoo_api.get_standings(): embed.add_field( name=team["place"], @@ -53,9 +71,9 @@ async def standings(self, ctx): inline=False, ) if embed: - await ctx.send(embed=embed) + await interaction.response.send_message(embed=embed) else: - await ctx.send(self.error_message) + await interaction.response.send_message(self.error_message) @commands.command("roster") async def roster(self, ctx, *, content: str): From 16887547a121df803a8eba56dc57e9b3559701c9 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Wed, 11 Jan 2023 22:54:53 -0500 Subject: [PATCH 25/51] Migrating roster & stats command to app commands --- harambot/cogs/yahoo.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/harambot/cogs/yahoo.py b/harambot/cogs/yahoo.py index 0b511a7..3e71864 100644 --- a/harambot/cogs/yahoo.py +++ b/harambot/cogs/yahoo.py @@ -75,15 +75,18 @@ async def standings(self, interaction: discord.Interaction): else: await interaction.response.send_message(self.error_message) - @commands.command("roster") - async def roster(self, ctx, *, content: str): + @app_commands.command( + name="roster", description="Returns the roster of the given team" + ) + async def roster(self, interaction: discord.Interaction, team_name: str): logger.info("roster called") + await self.set_yahoo_from_interaction(interaction) embed = discord.Embed( - title="{}'s Roster".format(content), + title="{}'s Roster".format(team_name), description="", color=0xEEE657, ) - roster = self.yahoo_api.get_roster(content) + roster = self.yahoo_api.get_roster(team_name) if roster: for player in roster: embed.add_field( @@ -91,9 +94,9 @@ async def roster(self, ctx, *, content: str): value=player["name"], inline=False, ) - await ctx.send(embed=embed) + await interaction.response.send_message(embed=embed) else: - await ctx.send(self.error_message) + await interaction.response.send_message(self.error_message) @commands.command("trade") async def trade(self, ctx): @@ -175,15 +178,18 @@ async def trade(self, ctx): await msg.add_reaction(yes_emoji) await msg.add_reaction(no_emoji) - @commands.command("stats") - async def stats(self, ctx, *, content: str): + @app_commands.command( + name="stats", description="Returns the details of the given player" + ) + async def stats(self, interaction: discord.Interaction, player_name: str): logger.info("player_details called") - player = self.yahoo_api.get_player_details(content) + await self.set_yahoo_from_interaction(interaction) + player = self.yahoo_api.get_player_details(player_name) if player: embed = self.get_player_embed(player) - await ctx.send(embed=embed) + await interaction.response.send_message(embed=embed) else: - await ctx.send("Player not found") + await interaction.response.send_message("Player not found") def get_player_embed(self, player): embed = discord.Embed( From b125e27e8306dddd681a025067c3cc8bfbef9410 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Thu, 12 Jan 2023 15:35:19 -0500 Subject: [PATCH 26/51] migrated matchup command to app command --- harambot/cogs/yahoo.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/harambot/cogs/yahoo.py b/harambot/cogs/yahoo.py index 3e71864..ad07d1d 100644 --- a/harambot/cogs/yahoo.py +++ b/harambot/cogs/yahoo.py @@ -245,10 +245,12 @@ def get_player_text(self, player): + self.get_player_owner(player["player_id"]) ) - @commands.command("matchups") - async def matchups(self, ctx): + @app_commands.command( + name="matchups", description="Returns the current weeks matchups" + ) + async def matchups(self, interaction: discord.Interaction): + await self.set_yahoo_from_interaction(interaction) week, details = self.yahoo_api.get_matchups() - if details: embed = discord.Embed( title="Matchups for Week {}".format(week), @@ -259,6 +261,6 @@ async def matchups(self, ctx): embed.add_field( name=detail["name"], value=detail["value"], inline=False ) - await ctx.send(embed=embed) + await interaction.response.send_message(embed=embed) else: - await ctx.send(self.error_message) + await interaction.response.send_message(self.error_message) From 65ca3c0aa80e3a555c69bf004160302afbdd0899 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Sat, 14 Jan 2023 20:50:48 -0500 Subject: [PATCH 27/51] Migrating trade to app_command --- harambot/cogs/yahoo.py | 49 ++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/harambot/cogs/yahoo.py b/harambot/cogs/yahoo.py index ad07d1d..7f22e5c 100644 --- a/harambot/cogs/yahoo.py +++ b/harambot/cogs/yahoo.py @@ -98,13 +98,19 @@ async def roster(self, interaction: discord.Interaction, team_name: str): else: await interaction.response.send_message(self.error_message) - @commands.command("trade") - async def trade(self, ctx): + @app_commands.command( + name="trade", + description="Create poll for latest trade for league approval", + ) + async def trade(self, interaction: discord.Interaction): logger.info("trade called") + await self.set_yahoo_from_interaction(interaction) latest_trade = self.yahoo_api.get_latest_trade() if latest_trade is None: - await ctx.send("No trades up for approval at this time") + await interaction.response.send_message( + "No trades up for approval at this time" + ) return teams = self.yahoo_api.league().teams() @@ -116,18 +122,19 @@ async def trade(self, ctx): player_set0 = [] player_set0_details = "" for player in latest_trade["trader_players"]: - player_set0.append(player["name"]) - api_details = ( - self.get_player_text( - self.yahoo_api.get_player_details(player["name"]) + if player: + player_set0.append(player["name"]) + api_details = ( + self.get_player_text( + self.yahoo_api.get_player_details(player["name"]) + ) + + "\n" ) - + "\n" - ) - if api_details: - player_set0_details = player_set0_details + api_details - else: - await ctx.send(self.error_message) - return + if api_details: + player_set0_details = player_set0_details + api_details + else: + await interaction.send(self.error_message) + return player_set1 = [] player_set1_details = "" @@ -142,7 +149,7 @@ async def trade(self, ctx): if api_details: player_set1_details = player_set1_details + api_details else: - await ctx.send(self.error_message) + await interaction.response.send_message(self.error_message) return confirm_trade_message = "{} sends {} to {} for {}".format( @@ -172,11 +179,14 @@ async def trade(self, ctx): value=" Click :white_check_mark: for yes,\ :no_entry_sign: for no", ) - msg = await ctx.send(content=announcement, embed=embed) + await interaction.response.send_message( + content=announcement, embed=embed + ) + response_message = await interaction.original_response() yes_emoji = "\U00002705" no_emoji = "\U0001F6AB" - await msg.add_reaction(yes_emoji) - await msg.add_reaction(no_emoji) + await response_message.add_reaction(yes_emoji) + await response_message.add_reaction(no_emoji) @app_commands.command( name="stats", description="Returns the details of the given player" @@ -242,8 +252,9 @@ def get_player_text(self, player): player_details_text = ( player_details_text + "Owner: " - + self.get_player_owner(player["player_id"]) + + self.yahoo_api.get_player_owner(player["player_id"]) ) + return player_details_text @app_commands.command( name="matchups", description="Returns the current weeks matchups" From 07fd5be990ec64f51851c7ddec5d5380ca0ff0dd Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Wed, 18 Jan 2023 22:38:49 -0500 Subject: [PATCH 28/51] Migrating configure command to app command Refactoring configure_guild function --- harambot/cogs/meta.py | 16 +++++++++++----- harambot/utils.py | 16 +++++++--------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/harambot/cogs/meta.py b/harambot/cogs/meta.py index ada4658..2c89e4a 100644 --- a/harambot/cogs/meta.py +++ b/harambot/cogs/meta.py @@ -60,8 +60,14 @@ async def help(self, interaction: discord.Interaction): async def ping(self, interaction: discord.Interaction): await interaction.response.send_message(self.bot.latency) - @commands.hybrid_command() - async def configure(self, ctx): - await ctx.send("Configuring guild...") - await configure_guild(self.bot, ctx.guild.owner, ctx.guild.id) - await ctx.send("Guild configured successfully") + @app_commands.command( + name="configure", description="Configure your guild for Harambot" + ) + async def configure(self, interaction: discord.Interaction): + await interaction.response.send_message("configuring guild") + await configure_guild( + self.bot, interaction.guild.owner, interaction.guild_id + ) + await interaction.response.send_message( + "Guild configured successfully" + ) diff --git a/harambot/utils.py b/harambot/utils.py index 90af7d6..2e80d12 100644 --- a/harambot/utils.py +++ b/harambot/utils.py @@ -5,6 +5,9 @@ from harambot.config import settings from harambot.database.models import Guild +YAHOO_API_URL = "https://api.login.yahoo.com/oauth2/" +YAHOO_AUTH_URI = "request_auth?redirect_uri=oob&response_type=code&client_id=" + async def configure_guild(bot, owner, id): def check(m): @@ -12,15 +15,10 @@ def check(m): await owner.send("Thank you for adding Harambot to your server!") await owner.send( - "Please open the following link to authorize with Yahoo, respond with\ - the code given after authorization" - ) - await owner.send( - "https://api.login.yahoo.com/oauth2/request_auth?\ - redirect_uri=oob&response_type=code&client_id={}".format( - settings.yahoo_key - ) + "Please open the following link to authorize with Yahoo, respond with \ +the code given after authorization" ) + await owner.send(YAHOO_API_URL + YAHOO_AUTH_URI + settings.yahoo_key) code = await bot.wait_for("message", timeout=60, check=check) encoded_creds = base64.b64encode( ("{0}:{1}".format(settings.yahoo_key, settings.yahoo_secret)).encode( @@ -28,7 +26,7 @@ def check(m): ) ) details = requests.post( - url="https://api.login.yahoo.com/oauth2/get_token", + url="{}get_token".format(YAHOO_API_URL), data={ "code": code.clean_content, "redirect_uri": "oob", From 662df782b64ddc51b71aba2e7e38d4587d973e2f Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Mon, 23 Jan 2023 20:00:26 -0500 Subject: [PATCH 29/51] Creating modal class for guild configuration --- harambot/ui/modals.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 harambot/ui/modals.py diff --git a/harambot/ui/modals.py b/harambot/ui/modals.py new file mode 100644 index 0000000..bdfa1cb --- /dev/null +++ b/harambot/ui/modals.py @@ -0,0 +1,22 @@ +import discord + + +class ConfigModal(discord.ui.Modal, title="Configure Guild"): + + name = discord.ui.TextInput( + label="Name", + placeholder="Your name here...", + ) + + async def on_submit(self, interaction: discord.Interaction): + await interaction.response.send_message( + f"Thanks for your feedback, {self.name.value}!", ephemeral=True + ) + + async def on_error( + self, interaction: discord.Interaction, error: Exception + ) -> None: + + await interaction.response.send_message( + "Oops! Something went wrong.", ephemeral=True + ) From 7fe039c1d804bbf367f1efe6d55fdf8ffcde6cd4 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Tue, 24 Jan 2023 16:08:43 -0500 Subject: [PATCH 30/51] Adding required fields to config modal --- harambot/ui/modals.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/harambot/ui/modals.py b/harambot/ui/modals.py index bdfa1cb..8fd7b99 100644 --- a/harambot/ui/modals.py +++ b/harambot/ui/modals.py @@ -3,14 +3,31 @@ class ConfigModal(discord.ui.Modal, title="Configure Guild"): - name = discord.ui.TextInput( - label="Name", - placeholder="Your name here...", + yahoo_auth_code = discord.ui.TextInput( + label="Yahoo Auth Code", + placeholder="Enter code from Yahoo Authentication Url", + ) + + league_id = discord.ui.TextInput( + label="Yahoo League ID", placeholder="Enter Yahoo League ID" + ) + leauge_type = discord.ui.TextInput( + label="Yahoo League Type", + placeholder="Enter Yahoo League Type(nfl, nhl, nba, mlb)", + ) + RIP_text = discord.ui.TextInput( + label="RIP command text", + placeholder="Enter text to use with $RIP command", + ) + RIP_image_url = discord.ui.TextInput( + label="RIP Image", + placeholder="Enter image url to use with $RIP command", ) async def on_submit(self, interaction: discord.Interaction): await interaction.response.send_message( - f"Thanks for your feedback, {self.name.value}!", ephemeral=True + f"Thanks for your feedback, {self.yahoo_auth_code.value}!", + ephemeral=True, ) async def on_error( From 328e6b1019c816f8a273d2bf9b70c1912e5a3415 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Wed, 25 Jan 2023 19:47:39 -0500 Subject: [PATCH 31/51] Changing design to have modal only handle existing guilds Adding views.py --- harambot/ui/modals.py | 5 ----- harambot/ui/views.py | 0 2 files changed, 5 deletions(-) create mode 100644 harambot/ui/views.py diff --git a/harambot/ui/modals.py b/harambot/ui/modals.py index 8fd7b99..0cc35af 100644 --- a/harambot/ui/modals.py +++ b/harambot/ui/modals.py @@ -3,11 +3,6 @@ class ConfigModal(discord.ui.Modal, title="Configure Guild"): - yahoo_auth_code = discord.ui.TextInput( - label="Yahoo Auth Code", - placeholder="Enter code from Yahoo Authentication Url", - ) - league_id = discord.ui.TextInput( label="Yahoo League ID", placeholder="Enter Yahoo League ID" ) diff --git a/harambot/ui/views.py b/harambot/ui/views.py new file mode 100644 index 0000000..e69de29 From 90124790f564b941161394e57b73e8107c8cf6cd Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Fri, 27 Jan 2023 20:48:15 -0500 Subject: [PATCH 32/51] Finished implementing config modal for updating existing guilds --- harambot/ui/modals.py | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/harambot/ui/modals.py b/harambot/ui/modals.py index 0cc35af..03e19d0 100644 --- a/harambot/ui/modals.py +++ b/harambot/ui/modals.py @@ -1,5 +1,9 @@ import discord +from discord.utils import MISSING +from typing import Optional +from harambot.database.models import Guild + class ConfigModal(discord.ui.Modal, title="Configure Guild"): @@ -19,16 +23,43 @@ class ConfigModal(discord.ui.Modal, title="Configure Guild"): placeholder="Enter image url to use with $RIP command", ) + guild = None + + def __init__( + self, + *, + title: str = MISSING, + timeout: Optional[float] = None, + custom_id: str = MISSING, + guild: Guild = None, + ) -> None: + super().__init__(title=title, timeout=timeout, custom_id=custom_id) + self.guild = guild + self.league_id.default = guild.league_id + self.leauge_type.default = guild.league_type + self.RIP_text.default = guild.RIP_text + self.RIP_image_url.default = guild.RIP_image_url + async def on_submit(self, interaction: discord.Interaction): + details = { + "league_id": self.league_id.value, + "league_type": self.leauge_type.value, + "RIP_text": self.RIP_text.value, + "RIP_image_url": self.RIP_image_url.value, + } + Guild.update(details).where( + Guild.guild_id == self.guild.guild_id + ).execute() await interaction.response.send_message( - f"Thanks for your feedback, {self.yahoo_auth_code.value}!", + "Guild settings updated!", ephemeral=True, ) async def on_error( self, interaction: discord.Interaction, error: Exception ) -> None: - await interaction.response.send_message( - "Oops! Something went wrong.", ephemeral=True + "Oops! Something went wrong with configuring your guild.\ + Please try again", + ephemeral=True, ) From add584369beb6539878dc3c6c42ba6cbedad84f5 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Mon, 30 Jan 2023 22:53:10 -0500 Subject: [PATCH 33/51] Fixing typo in ConfigModal --- harambot/ui/modals.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/harambot/ui/modals.py b/harambot/ui/modals.py index 03e19d0..ae2113e 100644 --- a/harambot/ui/modals.py +++ b/harambot/ui/modals.py @@ -10,7 +10,7 @@ class ConfigModal(discord.ui.Modal, title="Configure Guild"): league_id = discord.ui.TextInput( label="Yahoo League ID", placeholder="Enter Yahoo League ID" ) - leauge_type = discord.ui.TextInput( + league_type = discord.ui.TextInput( label="Yahoo League Type", placeholder="Enter Yahoo League Type(nfl, nhl, nba, mlb)", ) @@ -36,14 +36,14 @@ def __init__( super().__init__(title=title, timeout=timeout, custom_id=custom_id) self.guild = guild self.league_id.default = guild.league_id - self.leauge_type.default = guild.league_type + self.league_type.default = guild.league_type self.RIP_text.default = guild.RIP_text self.RIP_image_url.default = guild.RIP_image_url async def on_submit(self, interaction: discord.Interaction): details = { "league_id": self.league_id.value, - "league_type": self.leauge_type.value, + "league_type": self.league_type.value, "RIP_text": self.RIP_text.value, "RIP_image_url": self.RIP_image_url.value, } From 54a681d30db5d7b5777136f57bdabae7c3912d7a Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Fri, 3 Feb 2023 23:32:04 -0500 Subject: [PATCH 34/51] Updated modal to show yahoo token field for new guilds --- harambot/ui/modals.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/harambot/ui/modals.py b/harambot/ui/modals.py index ae2113e..0c7019a 100644 --- a/harambot/ui/modals.py +++ b/harambot/ui/modals.py @@ -7,6 +7,11 @@ class ConfigModal(discord.ui.Modal, title="Configure Guild"): + yahoo_token = discord.ui.TextInput( + label="Yahoo Token", + placeholder="Enter the token from the Yahoo login link", + ) + league_id = discord.ui.TextInput( label="Yahoo League ID", placeholder="Enter Yahoo League ID" ) @@ -35,10 +40,12 @@ def __init__( ) -> None: super().__init__(title=title, timeout=timeout, custom_id=custom_id) self.guild = guild - self.league_id.default = guild.league_id - self.league_type.default = guild.league_type - self.RIP_text.default = guild.RIP_text - self.RIP_image_url.default = guild.RIP_image_url + if self.guild: + self.remove_item(self.yahoo_token) + self.league_id.default = guild.league_id + self.league_type.default = guild.league_type + self.RIP_text.default = guild.RIP_text + self.RIP_image_url.default = guild.RIP_image_url async def on_submit(self, interaction: discord.Interaction): details = { From 188027449684b67c634bf4ed319a40afb1951fa7 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Fri, 3 Feb 2023 23:32:40 -0500 Subject: [PATCH 35/51] Refactored utils --- harambot/utils.py | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/harambot/utils.py b/harambot/utils.py index 2e80d12..d55e8cf 100644 --- a/harambot/utils.py +++ b/harambot/utils.py @@ -20,24 +20,7 @@ def check(m): ) await owner.send(YAHOO_API_URL + YAHOO_AUTH_URI + settings.yahoo_key) code = await bot.wait_for("message", timeout=60, check=check) - encoded_creds = base64.b64encode( - ("{0}:{1}".format(settings.yahoo_key, settings.yahoo_secret)).encode( - "utf-8" - ) - ) - details = requests.post( - url="{}get_token".format(YAHOO_API_URL), - data={ - "code": code.clean_content, - "redirect_uri": "oob", - "grant_type": "authorization_code", - }, - headers={ - "Authorization": "Basic {0}".format(encoded_creds.decode("utf-8")), - "Content-Type": "application/x-www-form-urlencoded", - }, - ).json() - details["token_time"] = time.time() + details = yahoo_auth(code) await owner.send("Enter Yahoo League ID") leauge_id = await bot.wait_for("message", timeout=60, check=check) await owner.send("Enter Yahoo League Type(nfl, nhl, nba, mlb)") @@ -58,3 +41,26 @@ def check(m): guild = Guild(guild_id=str(id), **details) guild.save() return + + +def yahoo_auth(code): + encoded_creds = base64.b64encode( + ("{0}:{1}".format(settings.yahoo_key, settings.yahoo_secret)).encode( + "utf-8" + ) + ) + details = requests.post( + url="{}get_token".format(YAHOO_API_URL), + data={ + "code": code.clean_content, + "redirect_uri": "oob", + "grant_type": "authorization_code", + }, + headers={ + "Authorization": "Basic {0}".format(encoded_creds.decode("utf-8")), + "Content-Type": "application/x-www-form-urlencoded", + }, + ).json() + + details["token_time"] = time.time() + return details From ee8d636b7c303d9e025306638bf04e6b465eb09b Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Sat, 4 Feb 2023 10:37:40 -0500 Subject: [PATCH 36/51] Created view for handling new guilds --- harambot/ui/views.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/harambot/ui/views.py b/harambot/ui/views.py index e69de29..5b4dca5 100644 --- a/harambot/ui/views.py +++ b/harambot/ui/views.py @@ -0,0 +1,30 @@ +import discord + +from harambot.config import settings +from harambot.utils import YAHOO_API_URL, YAHOO_AUTH_URI +from harambot.ui.modals import ConfigModal + + +class YahooAuthButton(discord.ui.Button): + def __init__(self): + super().__init__( + style=discord.ButtonStyle.link, + label="Login to Yahoo", + url=f"{YAHOO_API_URL}{YAHOO_AUTH_URI}{settings.yahoo_key}", + ) + + +class ConfigGuildButton(discord.ui.Button): + async def callback(self, interaction: discord.Interaction): + await interaction.response.send_modal(ConfigModal()) + + +class ConfigView(discord.ui.View): + def __init__(self): + super().__init__() + self.add_item(YahooAuthButton()) + self.add_item( + ConfigGuildButton( + label="Configure Guild", style=discord.ButtonStyle.blurple + ) + ) From 23ab7f433d83f26f368d39f0b36f252f53979989 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Sun, 5 Feb 2023 07:55:53 -0500 Subject: [PATCH 37/51] Format fix on views.py Updating modal to handle new guilds --- harambot/ui/modals.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/harambot/ui/modals.py b/harambot/ui/modals.py index 0c7019a..c378501 100644 --- a/harambot/ui/modals.py +++ b/harambot/ui/modals.py @@ -3,6 +3,7 @@ from discord.utils import MISSING from typing import Optional from harambot.database.models import Guild +from harambot.utils import yahoo_auth class ConfigModal(discord.ui.Modal, title="Configure Guild"): @@ -54,9 +55,14 @@ async def on_submit(self, interaction: discord.Interaction): "RIP_text": self.RIP_text.value, "RIP_image_url": self.RIP_image_url.value, } - Guild.update(details).where( - Guild.guild_id == self.guild.guild_id - ).execute() + if self.guild: + Guild.update(details).where( + Guild.guild_id == self.guild.guild_id + ).execute() + else: + details.update(yahoo_auth(self.yahoo_token.value)) + self.guild = Guild(guild_id=str(interaction.guild_id), **details) + self.guild.save() await interaction.response.send_message( "Guild settings updated!", ephemeral=True, From 375c99655b7e98b9e3f15dc135381eea1f74baa8 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Tue, 7 Feb 2023 16:07:12 -0500 Subject: [PATCH 38/51] Reworking config command to use view for all cases Updated Config modal to pull guild info itself Updating Config view to pass guild id to modal Commenting out config_guild from guild join event Removing configure_guild function --- harambot/bot.py | 3 +-- harambot/cogs/meta.py | 13 +++++++------ harambot/ui/modals.py | 12 ++++++------ harambot/ui/views.py | 8 ++++++-- harambot/utils.py | 35 ----------------------------------- 5 files changed, 20 insertions(+), 51 deletions(-) diff --git a/harambot/bot.py b/harambot/bot.py index d3a7be9..ad6f01c 100644 --- a/harambot/bot.py +++ b/harambot/bot.py @@ -8,7 +8,6 @@ from harambot.cogs.yahoo import YahooCog from harambot.config import settings from harambot.database.models import Guild -from harambot.utils import configure_guild # logging.basicConfig(level=logging.INFO) logger = logging.getLogger("harambot.py") @@ -43,7 +42,7 @@ async def on_ready(): async def on_guild_join(guild): logger.info("Joined {}".format(guild.name)) if not Guild.select().where(Guild.guild_id == str(guild.id)).exists(): - await configure_guild(bot, guild.owner, guild.id) + # await configure_guild(bot, guild.owner, guild.id) logger.info("Guild not configured!") diff --git a/harambot/cogs/meta.py b/harambot/cogs/meta.py index 2c89e4a..b25b90d 100644 --- a/harambot/cogs/meta.py +++ b/harambot/cogs/meta.py @@ -4,7 +4,7 @@ import discord import logging -from harambot.utils import configure_guild +from harambot.ui.views import ConfigView logger = logging.getLogger(__file__) logger.setLevel(logging.INFO) @@ -64,10 +64,11 @@ async def ping(self, interaction: discord.Interaction): name="configure", description="Configure your guild for Harambot" ) async def configure(self, interaction: discord.Interaction): - await interaction.response.send_message("configuring guild") - await configure_guild( - self.bot, interaction.guild.owner, interaction.guild_id - ) await interaction.response.send_message( - "Guild configured successfully" + """ + Lets setup your guild + 1. Login into Yahoo and copy you authentication token + 2. Configure harambot with your league information + """, + view=ConfigView(), ) diff --git a/harambot/ui/modals.py b/harambot/ui/modals.py index c378501..bee8e7b 100644 --- a/harambot/ui/modals.py +++ b/harambot/ui/modals.py @@ -37,16 +37,16 @@ def __init__( title: str = MISSING, timeout: Optional[float] = None, custom_id: str = MISSING, - guild: Guild = None, + guild_id: str = None, ) -> None: super().__init__(title=title, timeout=timeout, custom_id=custom_id) - self.guild = guild + self.guild = Guild.get_or_none(Guild.guild_id == str(guild_id)) if self.guild: self.remove_item(self.yahoo_token) - self.league_id.default = guild.league_id - self.league_type.default = guild.league_type - self.RIP_text.default = guild.RIP_text - self.RIP_image_url.default = guild.RIP_image_url + self.league_id.default = self.guild.league_id + self.league_type.default = self.guild.league_type + self.RIP_text.default = self.guild.RIP_text + self.RIP_image_url.default = self.guild.RIP_image_url async def on_submit(self, interaction: discord.Interaction): details = { diff --git a/harambot/ui/views.py b/harambot/ui/views.py index 5b4dca5..5b38e64 100644 --- a/harambot/ui/views.py +++ b/harambot/ui/views.py @@ -16,11 +16,15 @@ def __init__(self): class ConfigGuildButton(discord.ui.Button): async def callback(self, interaction: discord.Interaction): - await interaction.response.send_modal(ConfigModal()) + await interaction.response.send_modal( + ConfigModal(guild_id=str(interaction.guild_id)) + ) class ConfigView(discord.ui.View): - def __init__(self): + def __init__( + self, + ): super().__init__() self.add_item(YahooAuthButton()) self.add_item( diff --git a/harambot/utils.py b/harambot/utils.py index d55e8cf..56dac1a 100644 --- a/harambot/utils.py +++ b/harambot/utils.py @@ -3,46 +3,11 @@ import time from harambot.config import settings -from harambot.database.models import Guild YAHOO_API_URL = "https://api.login.yahoo.com/oauth2/" YAHOO_AUTH_URI = "request_auth?redirect_uri=oob&response_type=code&client_id=" -async def configure_guild(bot, owner, id): - def check(m): - return m.author == owner - - await owner.send("Thank you for adding Harambot to your server!") - await owner.send( - "Please open the following link to authorize with Yahoo, respond with \ -the code given after authorization" - ) - await owner.send(YAHOO_API_URL + YAHOO_AUTH_URI + settings.yahoo_key) - code = await bot.wait_for("message", timeout=60, check=check) - details = yahoo_auth(code) - await owner.send("Enter Yahoo League ID") - leauge_id = await bot.wait_for("message", timeout=60, check=check) - await owner.send("Enter Yahoo League Type(nfl, nhl, nba, mlb)") - leauge_type = await bot.wait_for("message", timeout=60, check=check) - await owner.send("Enter text to use with $RIP command") - RIP_text = await bot.wait_for("message", timeout=60, check=check) - await owner.send("Enter image url to use with $RIP command") - RIP_image_url = await bot.wait_for("message", timeout=60, check=check) - details["league_id"] = leauge_id.clean_content - details["league_type"] = leauge_type.clean_content - details["RIP_text"] = RIP_text.clean_content - details["RIP_image_url"] = RIP_image_url.clean_content - guild = Guild.get_or_none(Guild.guild_id == str(id)) - if guild: - query = Guild.update(details).where(Guild.guild_id == str(id)) - query.execute() - else: - guild = Guild(guild_id=str(id), **details) - guild.save() - return - - def yahoo_auth(code): encoded_creds = base64.b64encode( ("{0}:{1}".format(settings.yahoo_key, settings.yahoo_secret)).encode( From 5cb6b34f023ddf03de4b9dd140c8281659a7972b Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Mon, 13 Feb 2023 13:51:07 -0500 Subject: [PATCH 39/51] Setting config view to be ephemeral Updating modal submit to also stop view --- harambot/cogs/meta.py | 1 + harambot/ui/modals.py | 4 ++++ harambot/ui/views.py | 19 +++++++++++++------ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/harambot/cogs/meta.py b/harambot/cogs/meta.py index b25b90d..630df3f 100644 --- a/harambot/cogs/meta.py +++ b/harambot/cogs/meta.py @@ -71,4 +71,5 @@ async def configure(self, interaction: discord.Interaction): 2. Configure harambot with your league information """, view=ConfigView(), + ephemeral=True, ) diff --git a/harambot/ui/modals.py b/harambot/ui/modals.py index bee8e7b..ebe3b2c 100644 --- a/harambot/ui/modals.py +++ b/harambot/ui/modals.py @@ -30,6 +30,7 @@ class ConfigModal(discord.ui.Modal, title="Configure Guild"): ) guild = None + view = None def __init__( self, @@ -38,8 +39,10 @@ def __init__( timeout: Optional[float] = None, custom_id: str = MISSING, guild_id: str = None, + view: discord.ui.View = None, ) -> None: super().__init__(title=title, timeout=timeout, custom_id=custom_id) + self.view = view self.guild = Guild.get_or_none(Guild.guild_id == str(guild_id)) if self.guild: self.remove_item(self.yahoo_token) @@ -67,6 +70,7 @@ async def on_submit(self, interaction: discord.Interaction): "Guild settings updated!", ephemeral=True, ) + self.view.stop() async def on_error( self, interaction: discord.Interaction, error: Exception diff --git a/harambot/ui/views.py b/harambot/ui/views.py index 5b38e64..a023259 100644 --- a/harambot/ui/views.py +++ b/harambot/ui/views.py @@ -15,9 +15,20 @@ def __init__(self): class ConfigGuildButton(discord.ui.Button): + + parent_view: None + + def __init__(self, parent_view: discord.ui.View): + super().__init__( + label="Configure Guild", style=discord.ButtonStyle.blurple + ) + self.parent_view = parent_view + async def callback(self, interaction: discord.Interaction): await interaction.response.send_modal( - ConfigModal(guild_id=str(interaction.guild_id)) + ConfigModal( + guild_id=str(interaction.guild_id), view=self.parent_view + ) ) @@ -27,8 +38,4 @@ def __init__( ): super().__init__() self.add_item(YahooAuthButton()) - self.add_item( - ConfigGuildButton( - label="Configure Guild", style=discord.ButtonStyle.blurple - ) - ) + self.add_item(ConfigGuildButton(parent_view=self)) From 3c6e49922d0204a7b5f4f7e21627230410f17966 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Mon, 13 Feb 2023 13:58:04 -0500 Subject: [PATCH 40/51] Updated join event w/ configure instructions --- harambot/bot.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/harambot/bot.py b/harambot/bot.py index ad6f01c..8dd144b 100644 --- a/harambot/bot.py +++ b/harambot/bot.py @@ -42,8 +42,11 @@ async def on_ready(): async def on_guild_join(guild): logger.info("Joined {}".format(guild.name)) if not Guild.select().where(Guild.guild_id == str(guild.id)).exists(): - # await configure_guild(bot, guild.owner, guild.id) logger.info("Guild not configured!") + await guild.owner.send( + """Thank you for adding Harambot to your server! + Please complete your setup by running the /configure command!""" + ) def run(): From 1ae1ed2e9d353d9b7470d2f272062c9ee6e1fc60 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Mon, 6 Mar 2023 10:38:56 -0500 Subject: [PATCH 41/51] Cleaning up comment --- harambot/bot.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/harambot/bot.py b/harambot/bot.py index 8dd144b..fd48b1d 100644 --- a/harambot/bot.py +++ b/harambot/bot.py @@ -50,9 +50,7 @@ async def on_guild_join(guild): def run(): - bot.run( - settings.discord_token, reconnect=True - ) # Where 'TOKEN' is your bot token + bot.run(settings.discord_token, reconnect=True) run() From c42a2897f08586bcdb4777f8451a47306129e17c Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Fri, 10 Mar 2023 00:08:52 -0500 Subject: [PATCH 42/51] Adding last_transaction_check field Setting up transaction alert task --- harambot/cogs/yahoo.py | 11 ++++++++++- harambot/database/models.py | 4 +++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/harambot/cogs/yahoo.py b/harambot/cogs/yahoo.py index 7f22e5c..0b5e036 100644 --- a/harambot/cogs/yahoo.py +++ b/harambot/cogs/yahoo.py @@ -2,7 +2,7 @@ import logging import urllib3 -from discord.ext import commands +from discord.ext import commands, tasks from discord import app_commands from yahoo_oauth import OAuth2 from playhouse.shortcuts import model_to_dict @@ -27,6 +27,11 @@ def __init__(self, bot, KEY, SECRET): self.KEY = KEY self.SECRET = SECRET self.yahoo_api = None + self.transaction_alerts.start() + + def cog_unload(self) -> None: + self.transaction_alerts.cancel() + return super().cog_unload() async def cog_before_invoke(self, ctx): guild = Guild.get(Guild.guild_id == str(ctx.guild.id)) @@ -275,3 +280,7 @@ async def matchups(self, interaction: discord.Interaction): await interaction.response.send_message(embed=embed) else: await interaction.response.send_message(self.error_message) + + @tasks.loop(seconds=15.0) + async def transaction_alerts(self): + logger.info("Place holder for transaction alerts") diff --git a/harambot/database/models.py b/harambot/database/models.py index 4260128..d709c14 100644 --- a/harambot/database/models.py +++ b/harambot/database/models.py @@ -1,5 +1,6 @@ from peewee import SqliteDatabase -from peewee import Model, TextField, IntegerField, BigIntegerField +from peewee import Model +from peewee import TextField, IntegerField, BigIntegerField, TimestampField from playhouse.db_url import connect from harambot.config import settings @@ -26,3 +27,4 @@ class Guild(BaseModel): league_type = TextField() RIP_text = TextField() RIP_image_url = TextField() + last_transaction_check = TimestampField() From 8a6e60fdab2ab5509c99bfe468e9b6a79aa68131 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Thu, 23 Mar 2023 21:36:30 -0400 Subject: [PATCH 43/51] Setting up migrations for schema changes --- config/settings.toml | 2 ++ harambot/bot.py | 3 +++ harambot/database/migrations.py | 35 +++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 harambot/database/migrations.py diff --git a/config/settings.toml b/config/settings.toml index e3ba27b..24faf08 100644 --- a/config/settings.toml +++ b/config/settings.toml @@ -1,2 +1,4 @@ [default] LOGLEVEL = "DEBUG" +VERSION = "0.0.3-Beta" +RUN_MIGRATIONS = false diff --git a/harambot/bot.py b/harambot/bot.py index fd48b1d..b2e939e 100644 --- a/harambot/bot.py +++ b/harambot/bot.py @@ -8,6 +8,7 @@ from harambot.cogs.yahoo import YahooCog from harambot.config import settings from harambot.database.models import Guild +from harambot.database.migrations import migrations # logging.basicConfig(level=logging.INFO) logger = logging.getLogger("harambot.py") @@ -32,6 +33,8 @@ async def on_ready(): await bot.add_cog(Misc(bot)) if not Guild.table_exists(): Guild.create_table() + if settings.run_migrations: + migrations[settings.version]() for guild in bot.guilds: bot.tree.copy_global_to(guild=guild) await bot.tree.sync(guild=guild) diff --git a/harambot/database/migrations.py b/harambot/database/migrations.py new file mode 100644 index 0000000..8e80711 --- /dev/null +++ b/harambot/database/migrations.py @@ -0,0 +1,35 @@ +from playhouse.migrate import SqliteMigrator, MySQLMigrator, PostgresqlMigrator +from playhouse.migrate import migrate +from peewee import PostgresqlDatabase, MySQLDatabase, SqliteDatabase +from peewee import TimestampField +from playhouse.db_url import connect +from harambot.config import settings + +if "DATABASE_URL" in settings: + database = connect(settings.database_url) +else: + database = SqliteDatabase(":memory:") + +# Get migrator +migrator = None +if isinstance(database, MySQLDatabase): + migrator = MySQLMigrator(database) +elif isinstance(database, PostgresqlDatabase): + migrator = PostgresqlMigrator(database) +else: + migrator = SqliteMigrator(database) + + +# Migration Functions +def beta003_migrations(): + last_transaction_check = TimestampField() + migrate( + migrator.add_column( + "guild", "last_transaction_check", last_transaction_check + ) + ) + + +# Migration dictionary +migrations = {} +migrations["0.0.3-Beta"] = beta003_migrations From 0dd61c9c2123598b2a8b82c7aef6f7e3c2382018 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Sat, 25 Mar 2023 00:13:37 -0400 Subject: [PATCH 44/51] Adding logging to config modal Fixing exception in yahoo_auth function --- harambot/ui/modals.py | 5 +++++ harambot/utils.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/harambot/ui/modals.py b/harambot/ui/modals.py index ebe3b2c..8ee596f 100644 --- a/harambot/ui/modals.py +++ b/harambot/ui/modals.py @@ -1,10 +1,14 @@ import discord +import logging from discord.utils import MISSING from typing import Optional from harambot.database.models import Guild from harambot.utils import yahoo_auth +logger = logging.getLogger(__file__) +logger.setLevel(logging.INFO) + class ConfigModal(discord.ui.Modal, title="Configure Guild"): @@ -75,6 +79,7 @@ async def on_submit(self, interaction: discord.Interaction): async def on_error( self, interaction: discord.Interaction, error: Exception ) -> None: + logger.exception(error) await interaction.response.send_message( "Oops! Something went wrong with configuring your guild.\ Please try again", diff --git a/harambot/utils.py b/harambot/utils.py index 56dac1a..727fba0 100644 --- a/harambot/utils.py +++ b/harambot/utils.py @@ -17,7 +17,7 @@ def yahoo_auth(code): details = requests.post( url="{}get_token".format(YAHOO_API_URL), data={ - "code": code.clean_content, + "code": code, "redirect_uri": "oob", "grant_type": "authorization_code", }, From ed5f8e78f2a17a0d4f038ba149ce8c6bdc1fa02c Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Thu, 30 Mar 2023 15:00:29 -0400 Subject: [PATCH 45/51] Implementing transactions command --- harambot/cogs/yahoo.py | 20 +++-- harambot/yahoo_api.py | 12 +++ poetry.lock | 199 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 220 insertions(+), 11 deletions(-) diff --git a/harambot/cogs/yahoo.py b/harambot/cogs/yahoo.py index 0b5e036..017931a 100644 --- a/harambot/cogs/yahoo.py +++ b/harambot/cogs/yahoo.py @@ -2,7 +2,7 @@ import logging import urllib3 -from discord.ext import commands, tasks +from discord.ext import commands from discord import app_commands from yahoo_oauth import OAuth2 from playhouse.shortcuts import model_to_dict @@ -27,11 +27,6 @@ def __init__(self, bot, KEY, SECRET): self.KEY = KEY self.SECRET = SECRET self.yahoo_api = None - self.transaction_alerts.start() - - def cog_unload(self) -> None: - self.transaction_alerts.cancel() - return super().cog_unload() async def cog_before_invoke(self, ctx): guild = Guild.get(Guild.guild_id == str(ctx.guild.id)) @@ -281,6 +276,13 @@ async def matchups(self, interaction: discord.Interaction): else: await interaction.response.send_message(self.error_message) - @tasks.loop(seconds=15.0) - async def transaction_alerts(self): - logger.info("Place holder for transaction alerts") + @app_commands.command( + name="transactions", + description="Returns the transactions fomr the last 24 hours", + ) + async def transactions(self, interaction: discord.Interaction): + await self.set_yahoo_from_interaction(interaction) + embed = discord.Embed(title="Transactions from last day") + for transaction in self.yahoo_api.get_latest_transactions(): + embed.add_field(name="", value="", inline=False) + await interaction.response.send_message(embed=embed) diff --git a/harambot/yahoo_api.py b/harambot/yahoo_api.py index c687440..5333dc1 100644 --- a/harambot/yahoo_api.py +++ b/harambot/yahoo_api.py @@ -2,8 +2,11 @@ import os import objectpath + from yahoo_fantasy_api import game from cachetools import cached, TTLCache +from datetime import datetime, timedelta + logger = logging.getLogger() logger.setLevel(logging.INFO) @@ -189,3 +192,12 @@ def get_latest_trade(self): return except Exception: logger.exception("Error while fetching latest trade") + + @cached(cache=TTLCache(maxsize=1024, ttl=600)) + def get_latest_transactions(self): + ts = datetime.now() - timedelta(days=365) + transactions = self.league().transactions("add,drop,trade", "") + filtered_transactions = [ + t for t in transactions if int(t["timestamp"]) > ts.timestamp() + ] + return filtered_transactions diff --git a/poetry.lock b/poetry.lock index 102e4fe..1cd12e8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -81,6 +81,14 @@ category = "main" optional = false python-versions = "~=3.7" +[[package]] +name = "certifi" +version = "2022.12.7" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "cfgv" version = "3.3.1" @@ -169,6 +177,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "docopt" +version = "0.6.2" +description = "Pythonic argument parser, that will make you smile" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "dynaconf" version = "3.1.11" @@ -301,6 +317,14 @@ python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.* [package.dependencies] setuptools = "*" +[[package]] +name = "objectpath" +version = "0.6.1" +description = "The agile query language for semi-structured data. #JSON" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "packaging" version = "21.3" @@ -376,6 +400,17 @@ category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "pyaml" +version = "21.10.1" +description = "PyYAML-based module to produce pretty and readable YAML-serialized data" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +PyYAML = "*" + [[package]] name = "pycodestyle" version = "2.9.1" @@ -438,14 +473,51 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +[[package]] +name = "pytz" +version = "2023.2" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "rauth" +version = "0.7.3" +description = "A Python library for OAuth 1.0/a, 2.0, and Ofly." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +requests = ">=1.2.3" + +[[package]] +name = "requests" +version = "2.28.2" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=3.7, <4" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + [[package]] name = "setuptools" version = "65.5.1" @@ -483,6 +555,19 @@ category = "dev" optional = false python-versions = ">=3.7" +[[package]] +name = "urllib3" +version = "1.26.15" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + [[package]] name = "virtualenv" version = "20.16.7" @@ -500,6 +585,32 @@ platformdirs = ">=2.4,<3" docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"] testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] +[[package]] +name = "yahoo-fantasy-api" +version = "2.7.0" +description = "Python bindings to access the Yahoo! Fantasy APIs" +category = "main" +optional = false +python-versions = ">=3" + +[package.dependencies] +docopt = "*" +objectpath = "*" +pytz = "*" +yahoo_oauth = "*" + +[[package]] +name = "yahoo-oauth" +version = "2.0" +description = "Python Yahoo OAuth Library. Supports OAuth1 and OAuth2" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pyaml = "*" +rauth = "*" + [[package]] name = "yarl" version = "1.8.1" @@ -515,7 +626,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "8155a81d642c470ea2d821fc66750883522ff0d10a09307f52ccd75e5d2839d7" +content-hash = "20765a0003d20c8c936bc036530b75ec7d688445d2a89db8787ac7032eb749d4" [metadata.files] aiohttp = [ @@ -646,6 +757,10 @@ cachetools = [ {file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"}, {file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"}, ] +certifi = [ + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, +] cfgv = [ {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, @@ -654,6 +769,53 @@ charset-normalizer = [ {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, ] +ciso8601 = [ + {file = "ciso8601-2.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8f884d6a0b7384f8b1c57f740196988dd1229242c1be7c30a75424725590e0b3"}, + {file = "ciso8601-2.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58517dfe06c30ad65fb1b4e9de66ccb72752d79bc71d7b7d26cbc0d008b7265a"}, + {file = "ciso8601-2.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c66032757d314ad232904f91a54df4907bd9af41b0d0b4acc19bfde1ab52983b"}, + {file = "ciso8601-2.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6cae7a74d9485a2f191adc5aad2563756af89cc1f3190e7d89f401b2349eb2b"}, + {file = "ciso8601-2.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:47cc66899e5facdccc28f183b978ace9edbebdea6545c013ec1d369fdea3de61"}, + {file = "ciso8601-2.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b4596c9d92719af4f06082c59182ce9de3a73e2bda67304498d9ac78264dd5c"}, + {file = "ciso8601-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:a002a8dc91e63730f7ca8eae0cb1e2832ee057fedf65e5b9bf416aefb1dd8cab"}, + {file = "ciso8601-2.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:87a6f58bdda833cb8d78c6482a179fff663903a8f562755e119bf815b1014f2e"}, + {file = "ciso8601-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7667faf021314315a3c498e4c7c8cf57a7014af0960ddd5b671bcf03b2d0132b"}, + {file = "ciso8601-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa90488666ee44796932850fc419cd55863b320f77b1474991e60f321b5ac7d2"}, + {file = "ciso8601-2.3.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1aba1f59b6d27ec694128f9ba85e22c1f17e67ffc5b1b0a991628bb402e25e81"}, + {file = "ciso8601-2.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:896dd46c7f2129140fc36dbe9ccf78cec02143b941b5a608e652cd40e39f6064"}, + {file = "ciso8601-2.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2cf6dfa22f21f838b730f977bc7ad057c37646f683bf42a727b4e763f44d47dc"}, + {file = "ciso8601-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:a8c4aa6880fd698075d5478615d4668e70af6424d90b1686c560c1ec3459926a"}, + {file = "ciso8601-2.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b12d314415ba1e4e4bfcfa3db782335949ca1866a2b6fe22c47099fed9c82826"}, + {file = "ciso8601-2.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d115fc2501a316256dd0b961b0b384a12998c626ab1e91cd06164f7792e3908"}, + {file = "ciso8601-2.3.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5817bd895c0d083c161ea38459de8e2b90d798de09769aaba003fe53c1418aba"}, + {file = "ciso8601-2.3.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:7d68741fe53cd0134e8e94109ede36d7aeaa65a36682680d53b69f790291d80f"}, + {file = "ciso8601-2.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:74c4b0fe3fd0ce1a0da941f3f50af1a81970d7e4536cbae43f27e041b4ae4d3e"}, + {file = "ciso8601-2.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0136d49f2265bf3d06ffb7bc649a64ed316e921ba6cd05e0fecc477c80fe5097"}, + {file = "ciso8601-2.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2188dd4784d87e4008cc765c80e26a503450c57a98655321de777679c556b133"}, + {file = "ciso8601-2.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4e0fa37c6d58be990c10d537ed286a35c018b5f038039ad796cf2352bc26799e"}, + {file = "ciso8601-2.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fa1085b47c15df627d6bea783a8f7c89a59268af85e204992a013df174b339aa"}, + {file = "ciso8601-2.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:352809f24dc0fa7e05b85046f8bd34165a20fa5ebb5b43e053668fa69d57e657"}, + {file = "ciso8601-2.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7e8e78f8c7d35e6b43ad7316f652e2d53bf4b8798725d481abff14657852a88c"}, + {file = "ciso8601-2.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4cc04399f79a62338d4f4c19560d2b30f2d257021df1b0e55bae9209d8844c0c"}, + {file = "ciso8601-2.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e4affe0e72debf18c98d2f9e41c24a8ec8421ea65fafba96919f20a8d0f9bf87"}, + {file = "ciso8601-2.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d7d0f84fb0276c031bf606da484e9dc52ebdf121695732609dc49b30e8cf7c"}, + {file = "ciso8601-2.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8b1a217967083ac295d9239f5ba5235c66697fdadc2d5399c7bac53353218201"}, + {file = "ciso8601-2.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2785f374388e48c21420e820295d36a8d0734542e4e7bd3899467dc4d56016da"}, + {file = "ciso8601-2.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:59e6ac990dc31b14a39344a6a0f651658829bc59666cfff13c8deca37e360d86"}, + {file = "ciso8601-2.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3b135cda50be4ed52e44e815794cb19b268baf75d6c2a2a34eb6c2851bbe9423"}, + {file = "ciso8601-2.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b247b4a854119d438d28e0efd0258a5bb710be59ffeba3d2bea5bdab82f90ef3"}, + {file = "ciso8601-2.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:243ffcbee824ed74b21bd1cede72050d36095df5fad8f1704730669d2b0db5be"}, + {file = "ciso8601-2.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39aa3d7148fcd9db1007c258e47c9e0174f383d82f5504b80db834c6215b7e4"}, + {file = "ciso8601-2.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e838b694b009e2d9b3b680008fa4c56e52f83935a31ea86fe4203dfff0086f88"}, + {file = "ciso8601-2.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:aa58f55ed5c8b1e9962b56b2ecbfcca32f056edf8ecdce73b6623c55a2fd11e8"}, + {file = "ciso8601-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:161dc428d1735ed6dee6ce599c4275ef3fe280fe37308e3cc2efd4301781a7ff"}, + {file = "ciso8601-2.3.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:374275a329138b9b70c857c9ea460f65dc7f01ed2513f991e57090f39bf01de5"}, + {file = "ciso8601-2.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:58910c03b5464d6b766ac5d894c6089ee8279432b85181283571b0e2bf502df4"}, + {file = "ciso8601-2.3.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9f7608a276fa46d28255906c341752a87fe5353d8060932e0ec71745148a4d8"}, + {file = "ciso8601-2.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e20d14155f7b069f2aa2387a3f31de98f93bb94da63ad1b5aae78445b33f0529"}, + {file = "ciso8601-2.3.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3f781561401c8666accae823ed8f2a5d1fa50b3e65eb65c21a2bd0374e14f19"}, + {file = "ciso8601-2.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a0f4a649e9693e5a46843b0ebd288de1e45b8852a2cff684e3a6b6f3fd56ec4e"}, + {file = "ciso8601-2.3.0.tar.gz", hash = "sha256:19e3fbd786d8bec3358eac94d8774d365b694b604fd1789244b87083f66c8900"}, +] click = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, @@ -726,6 +888,9 @@ distlib = [ {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, ] +docopt = [ + {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, +] dynaconf = [ {file = "dynaconf-3.1.11-py2.py3-none-any.whl", hash = "sha256:87e0b3b12b5db9e8fb465e1f8c7fdb926cd2ec5b6d88aa7f821f316df93fb165"}, {file = "dynaconf-3.1.11.tar.gz", hash = "sha256:d9cfb50fd4a71a543fd23845d4f585b620b6ff6d9d3cc1825c614f7b2097cb39"}, @@ -912,6 +1077,10 @@ nodeenv = [ {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, ] +objectpath = [ + {file = "objectpath-0.6.1-py2.py3-none-any.whl", hash = "sha256:7ac2c507e7e5bfbf245701044453edc8e4073ded01d53349237d918124d7ca87"}, + {file = "objectpath-0.6.1.tar.gz", hash = "sha256:461263136c79292e42431fbb85cdcaac4c6a256f6b1aa5b3ae9316e4965ad819"}, +] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, @@ -950,6 +1119,10 @@ psycopg2 = [ {file = "psycopg2-2.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:190d51e8c1b25a47484e52a79638a8182451d6f6dff99f26ad9bd81e5359a0fa"}, {file = "psycopg2-2.9.5.tar.gz", hash = "sha256:a5246d2e683a972e2187a8714b5c2cf8156c064629f9a9b1a873c1730d9e245a"}, ] +pyaml = [ + {file = "pyaml-21.10.1-py2.py3-none-any.whl", hash = "sha256:19985ed303c3a985de4cf8fd329b6d0a5a5b5c9035ea240eccc709ebacbaf4a0"}, + {file = "pyaml-21.10.1.tar.gz", hash = "sha256:c6519fee13bf06e3bb3f20cacdea8eba9140385a7c2546df5dbae4887f768383"}, +] pycodestyle = [ {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, @@ -970,6 +1143,10 @@ pytest-cov = [ {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, ] +pytz = [ + {file = "pytz-2023.2-py2.py3-none-any.whl", hash = "sha256:8a8baaf1e237175b02f5c751eea67168043a749c843989e2b3015aa1ad9db68b"}, + {file = "pytz-2023.2.tar.gz", hash = "sha256:a27dcf612c05d2ebde626f7d506555f10dfc815b3eddccfaadfc7d99b11c9a07"}, +] pyyaml = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, @@ -1012,6 +1189,14 @@ pyyaml = [ {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] +rauth = [ + {file = "rauth-0.7.3-py2-none-any.whl", hash = "sha256:b18590fbd77bc3d871936bbdb851377d1b0c08e337b219c303f8fc2b5a42ef2d"}, + {file = "rauth-0.7.3.tar.gz", hash = "sha256:524cdbc1c28560eacfc9a9d40c59525eb8d00fdf07fbad86107ea24411477b0a"}, +] +requests = [ + {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, + {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, +] setuptools = [ {file = "setuptools-65.5.1-py3-none-any.whl", hash = "sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31"}, {file = "setuptools-65.5.1.tar.gz", hash = "sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f"}, @@ -1028,10 +1213,20 @@ typing-extensions = [ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] +urllib3 = [ + {file = "urllib3-1.26.15-py2.py3-none-any.whl", hash = "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"}, + {file = "urllib3-1.26.15.tar.gz", hash = "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305"}, +] virtualenv = [ {file = "virtualenv-20.16.7-py3-none-any.whl", hash = "sha256:efd66b00386fdb7dbe4822d172303f40cd05e50e01740b19ea42425cbe653e29"}, {file = "virtualenv-20.16.7.tar.gz", hash = "sha256:8691e3ff9387f743e00f6bb20f70121f5e4f596cae754531f2b3b3a1b1ac696e"}, ] +yahoo-fantasy-api = [ + {file = "yahoo_fantasy_api-2.7.0.tar.gz", hash = "sha256:77231856225fd10041fce01d2a4c5177e9714e28b94a47565eff6e14b6e822b2"}, +] +yahoo-oauth = [ + {file = "yahoo_oauth-2.0.tar.gz", hash = "sha256:912921bf8724ced6e5d7924308b2b15c2b51e14ed7d25c9c42cb8b52e3e75158"}, +] yarl = [ {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:abc06b97407868ef38f3d172762f4069323de52f2b70d133d096a48d72215d28"}, {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:07b21e274de4c637f3e3b7104694e53260b5fc10d51fb3ec5fed1da8e0f754e3"}, From 361f386ffb102c35d3122aa29e4ec0e1650eca37 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Tue, 4 Apr 2023 00:14:22 -0400 Subject: [PATCH 46/51] Creating methods for each transaction type --- harambot/cogs/yahoo.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/harambot/cogs/yahoo.py b/harambot/cogs/yahoo.py index 017931a..8df53ca 100644 --- a/harambot/cogs/yahoo.py +++ b/harambot/cogs/yahoo.py @@ -282,7 +282,27 @@ async def matchups(self, interaction: discord.Interaction): ) async def transactions(self, interaction: discord.Interaction): await self.set_yahoo_from_interaction(interaction) - embed = discord.Embed(title="Transactions from last day") + embed_functions_dict = { + "add/drop": self.create_add_drop_embed, + "add": self.create_add_embed, + "drop": self.create_drop_embed, + "trade": self.create_trade_embed, + } + embeds = [] for transaction in self.yahoo_api.get_latest_transactions(): - embed.add_field(name="", value="", inline=False) - await interaction.response.send_message(embed=embed) + embeds.append( + embed_functions_dict[transaction["type"]](transaction) + ) + await interaction.response.send_message(embeds=embeds) + + def create_add_embed(self, transaction): + return discord.Embed(title="Add Transaction") + + def create_drop_embed(self, transaction): + return discord.Embed(title="Drop Transaction") + + def create_add_drop_embed(self, transaction): + return discord.Embed(title="Add/Drop Transaction") + + def create_trade_embed(self, transaction): + return discord.Embed(title="Trade Transaction") From acf317eabbb08aae7b43855c113d08ee6cc76b79 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Tue, 18 Apr 2023 23:56:28 -0400 Subject: [PATCH 47/51] Repurposing transaction command for waviers --- harambot/cogs/yahoo.py | 67 +++++++++++++++++++++++++++++++++--------- harambot/yahoo_api.py | 6 ++-- poetry.lock | 49 +----------------------------- 3 files changed, 57 insertions(+), 65 deletions(-) diff --git a/harambot/cogs/yahoo.py b/harambot/cogs/yahoo.py index 8df53ca..1e87b42 100644 --- a/harambot/cogs/yahoo.py +++ b/harambot/cogs/yahoo.py @@ -277,32 +277,71 @@ async def matchups(self, interaction: discord.Interaction): await interaction.response.send_message(self.error_message) @app_commands.command( - name="transactions", - description="Returns the transactions fomr the last 24 hours", + name="waviers", + description="Returns the wavier transactions from the last 24 hours", ) - async def transactions(self, interaction: discord.Interaction): + async def waviers(self, interaction: discord.Interaction): await self.set_yahoo_from_interaction(interaction) + await interaction.response.defer(thinking=True) embed_functions_dict = { "add/drop": self.create_add_drop_embed, "add": self.create_add_embed, "drop": self.create_drop_embed, - "trade": self.create_trade_embed, } - embeds = [] - for transaction in self.yahoo_api.get_latest_transactions(): - embeds.append( - embed_functions_dict[transaction["type"]](transaction) + for transaction in self.yahoo_api.get_latest_waiver_transactions(): + await interaction.followup.send( + embed=embed_functions_dict[transaction["type"]](transaction) ) - await interaction.response.send_message(embeds=embeds) def create_add_embed(self, transaction): - return discord.Embed(title="Add Transaction") + embed = discord.Embed(title="Player Added") + self.add_player_fields_to_embed( + embed, transaction["players"]["0"]["player"][0] + ) + embed.add_field( + name="Owner", + value=transaction["players"]["0"]["player"][1]["transaction_data"][ + 0 + ]["destination_team_name"], + ) + return embed def create_drop_embed(self, transaction): - return discord.Embed(title="Drop Transaction") + embed = discord.Embed(title="Player Dropped") + self.add_player_fields_to_embed( + embed, transaction["players"]["0"]["player"][0] + ) + embed.add_field( + name="Owner", + value=transaction["players"]["0"]["player"][1]["transaction_data"][ + "source_team_name" + ], + ) + return embed def create_add_drop_embed(self, transaction): - return discord.Embed(title="Add/Drop Transaction") + embed = discord.Embed(title="Player Added/ Player Dropped") + embed.add_field( + name="Player Added", value="=====================", inline=True + ) + self.add_player_fields_to_embed( + embed, transaction["players"]["0"]["player"][0] + ) + embed.add_field( + name="Player Dropped", value="=====================", inline=True + ) + self.add_player_fields_to_embed( + embed, transaction["players"]["1"]["player"][0] + ) + return embed - def create_trade_embed(self, transaction): - return discord.Embed(title="Trade Transaction") + def add_player_fields_to_embed(self, embed, player): + embed.add_field( + name="Player", value=player[2]["name"]["full"], inline=True + ) + embed.add_field( + name="Team", value=player[3]["editorial_team_abbr"], inline=True + ) + embed.add_field( + name="Position", value=player[4]["display_position"], inline=True + ) diff --git a/harambot/yahoo_api.py b/harambot/yahoo_api.py index 5333dc1..fe23582 100644 --- a/harambot/yahoo_api.py +++ b/harambot/yahoo_api.py @@ -194,9 +194,9 @@ def get_latest_trade(self): logger.exception("Error while fetching latest trade") @cached(cache=TTLCache(maxsize=1024, ttl=600)) - def get_latest_transactions(self): - ts = datetime.now() - timedelta(days=365) - transactions = self.league().transactions("add,drop,trade", "") + def get_latest_waiver_transactions(self): + ts = datetime.now() - timedelta(days=1) + transactions = self.league().transactions("add,drop", "") filtered_transactions = [ t for t in transactions if int(t["timestamp"]) > ts.timestamp() ] diff --git a/poetry.lock b/poetry.lock index 1cd12e8..cfe0c98 100644 --- a/poetry.lock +++ b/poetry.lock @@ -626,7 +626,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "20765a0003d20c8c936bc036530b75ec7d688445d2a89db8787ac7032eb749d4" +content-hash = "eeff1c458c0ae945b3fa82c94e4de8a7873b148f873770d045bc16759cd1dff1" [metadata.files] aiohttp = [ @@ -769,53 +769,6 @@ charset-normalizer = [ {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, ] -ciso8601 = [ - {file = "ciso8601-2.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8f884d6a0b7384f8b1c57f740196988dd1229242c1be7c30a75424725590e0b3"}, - {file = "ciso8601-2.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58517dfe06c30ad65fb1b4e9de66ccb72752d79bc71d7b7d26cbc0d008b7265a"}, - {file = "ciso8601-2.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c66032757d314ad232904f91a54df4907bd9af41b0d0b4acc19bfde1ab52983b"}, - {file = "ciso8601-2.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6cae7a74d9485a2f191adc5aad2563756af89cc1f3190e7d89f401b2349eb2b"}, - {file = "ciso8601-2.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:47cc66899e5facdccc28f183b978ace9edbebdea6545c013ec1d369fdea3de61"}, - {file = "ciso8601-2.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b4596c9d92719af4f06082c59182ce9de3a73e2bda67304498d9ac78264dd5c"}, - {file = "ciso8601-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:a002a8dc91e63730f7ca8eae0cb1e2832ee057fedf65e5b9bf416aefb1dd8cab"}, - {file = "ciso8601-2.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:87a6f58bdda833cb8d78c6482a179fff663903a8f562755e119bf815b1014f2e"}, - {file = "ciso8601-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7667faf021314315a3c498e4c7c8cf57a7014af0960ddd5b671bcf03b2d0132b"}, - {file = "ciso8601-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa90488666ee44796932850fc419cd55863b320f77b1474991e60f321b5ac7d2"}, - {file = "ciso8601-2.3.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1aba1f59b6d27ec694128f9ba85e22c1f17e67ffc5b1b0a991628bb402e25e81"}, - {file = "ciso8601-2.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:896dd46c7f2129140fc36dbe9ccf78cec02143b941b5a608e652cd40e39f6064"}, - {file = "ciso8601-2.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2cf6dfa22f21f838b730f977bc7ad057c37646f683bf42a727b4e763f44d47dc"}, - {file = "ciso8601-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:a8c4aa6880fd698075d5478615d4668e70af6424d90b1686c560c1ec3459926a"}, - {file = "ciso8601-2.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b12d314415ba1e4e4bfcfa3db782335949ca1866a2b6fe22c47099fed9c82826"}, - {file = "ciso8601-2.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d115fc2501a316256dd0b961b0b384a12998c626ab1e91cd06164f7792e3908"}, - {file = "ciso8601-2.3.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5817bd895c0d083c161ea38459de8e2b90d798de09769aaba003fe53c1418aba"}, - {file = "ciso8601-2.3.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:7d68741fe53cd0134e8e94109ede36d7aeaa65a36682680d53b69f790291d80f"}, - {file = "ciso8601-2.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:74c4b0fe3fd0ce1a0da941f3f50af1a81970d7e4536cbae43f27e041b4ae4d3e"}, - {file = "ciso8601-2.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0136d49f2265bf3d06ffb7bc649a64ed316e921ba6cd05e0fecc477c80fe5097"}, - {file = "ciso8601-2.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2188dd4784d87e4008cc765c80e26a503450c57a98655321de777679c556b133"}, - {file = "ciso8601-2.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4e0fa37c6d58be990c10d537ed286a35c018b5f038039ad796cf2352bc26799e"}, - {file = "ciso8601-2.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fa1085b47c15df627d6bea783a8f7c89a59268af85e204992a013df174b339aa"}, - {file = "ciso8601-2.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:352809f24dc0fa7e05b85046f8bd34165a20fa5ebb5b43e053668fa69d57e657"}, - {file = "ciso8601-2.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7e8e78f8c7d35e6b43ad7316f652e2d53bf4b8798725d481abff14657852a88c"}, - {file = "ciso8601-2.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4cc04399f79a62338d4f4c19560d2b30f2d257021df1b0e55bae9209d8844c0c"}, - {file = "ciso8601-2.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e4affe0e72debf18c98d2f9e41c24a8ec8421ea65fafba96919f20a8d0f9bf87"}, - {file = "ciso8601-2.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d7d0f84fb0276c031bf606da484e9dc52ebdf121695732609dc49b30e8cf7c"}, - {file = "ciso8601-2.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8b1a217967083ac295d9239f5ba5235c66697fdadc2d5399c7bac53353218201"}, - {file = "ciso8601-2.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2785f374388e48c21420e820295d36a8d0734542e4e7bd3899467dc4d56016da"}, - {file = "ciso8601-2.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:59e6ac990dc31b14a39344a6a0f651658829bc59666cfff13c8deca37e360d86"}, - {file = "ciso8601-2.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3b135cda50be4ed52e44e815794cb19b268baf75d6c2a2a34eb6c2851bbe9423"}, - {file = "ciso8601-2.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b247b4a854119d438d28e0efd0258a5bb710be59ffeba3d2bea5bdab82f90ef3"}, - {file = "ciso8601-2.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:243ffcbee824ed74b21bd1cede72050d36095df5fad8f1704730669d2b0db5be"}, - {file = "ciso8601-2.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39aa3d7148fcd9db1007c258e47c9e0174f383d82f5504b80db834c6215b7e4"}, - {file = "ciso8601-2.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e838b694b009e2d9b3b680008fa4c56e52f83935a31ea86fe4203dfff0086f88"}, - {file = "ciso8601-2.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:aa58f55ed5c8b1e9962b56b2ecbfcca32f056edf8ecdce73b6623c55a2fd11e8"}, - {file = "ciso8601-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:161dc428d1735ed6dee6ce599c4275ef3fe280fe37308e3cc2efd4301781a7ff"}, - {file = "ciso8601-2.3.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:374275a329138b9b70c857c9ea460f65dc7f01ed2513f991e57090f39bf01de5"}, - {file = "ciso8601-2.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:58910c03b5464d6b766ac5d894c6089ee8279432b85181283571b0e2bf502df4"}, - {file = "ciso8601-2.3.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9f7608a276fa46d28255906c341752a87fe5353d8060932e0ec71745148a4d8"}, - {file = "ciso8601-2.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e20d14155f7b069f2aa2387a3f31de98f93bb94da63ad1b5aae78445b33f0529"}, - {file = "ciso8601-2.3.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3f781561401c8666accae823ed8f2a5d1fa50b3e65eb65c21a2bd0374e14f19"}, - {file = "ciso8601-2.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a0f4a649e9693e5a46843b0ebd288de1e45b8852a2cff684e3a6b6f3fd56ec4e"}, - {file = "ciso8601-2.3.0.tar.gz", hash = "sha256:19e3fbd786d8bec3358eac94d8774d365b694b604fd1789244b87083f66c8900"}, -] click = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, From 5ccd97cfa350eca52200b84fd4a1942f0f833565 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Sat, 22 Apr 2023 19:52:17 -0400 Subject: [PATCH 48/51] prepping for PyPI upload --- Makefile | 8 +++- README.md | 51 ++++++++++++++++------ assests/harambot_configure_1.png | Bin 0 -> 40246 bytes assests/harambot_configure_2.png | Bin 0 -> 65725 bytes assests/harambot_configure_3.png | Bin 0 -> 49651 bytes assests/harambot_configure_4.png | Bin 0 -> 36723 bytes harambot/bot.py | 2 +- harambot/cogs/meta.py | 12 +++++- requirements.txt | 72 ++++++++++++------------------- 9 files changed, 84 insertions(+), 61 deletions(-) create mode 100644 assests/harambot_configure_1.png create mode 100644 assests/harambot_configure_2.png create mode 100644 assests/harambot_configure_3.png create mode 100644 assests/harambot_configure_4.png diff --git a/Makefile b/Makefile index 94749d3..e8f933c 100644 --- a/Makefile +++ b/Makefile @@ -29,4 +29,10 @@ run-docker: @echo "${BLUE}Running docker image.." @echo "name: ${MODULE}" @echo "tag: ${MODULE}:${TAG}${NC}\n" - @docker run --name ${MODULE} -d ${MODULE}:${TAG} + @docker run --name ${MODULE}\ + -e DISCORD_TOKEN=${DISCORD_TOKEN}\ + -e YAHOO_KEY=${YAHOO_KEY}\ + -e YAHOO_SECRET=${YAHOO_SECRET}\ + -e DATABASE_URL=${DATABASE_URL}\ + -e RUN_MIGRATIONS=${RUN_MIGRATIONS}\ + --rm ${MODULE}:${TAG} diff --git a/README.md b/README.md index c21d478..d54747d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Harambot -![Python](https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10-blue) ![License](https://img.shields.io/badge/License-MIT-green) ![Build](https://img.shields.io/github/workflow/status/DMcP89/harambot/Pytest) ![Version](https://img.shields.io/badge/version-0.2.1--Beta-red) +![Python](https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10-blue) ![License](https://img.shields.io/badge/License-MIT-green) ![Build](https://img.shields.io/github/actions/workflow/status/DMcP89/harambot/pytest.yml?branch=main) ![Version](https://img.shields.io/badge/version-0.3.0--Beta-red) [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) @@ -7,13 +7,15 @@ A Yahoo Fantasy sports bot for Discord. ## Commands - $ping - Gives the latency of harambot - $RIP - Pay respects - $standings - Returns the current standings of the current league - $roster "Team name" - Returns the roster of the given team - $stats "Player Name" - Returns the details of the given player - $trade - Create poll for latest trade for league approval - $matchups - Returns the current weeks matchups + /ping - Gives the latency of harambot + /RIP - Pay respects + /standings - Returns the current standings of the current league + /roster "Team name" - Returns the roster of the given team + /stats "Player Name" - Returns the details of the given player + /trade - Create poll for latest trade for league approval + /matchups - Returns the current weeks matchups + /waiver - Returns the waiver wire tranasactions from the previous 24 hours + /configure - Configure the bot for your guild ## Prerequisites @@ -68,7 +70,16 @@ Once the deployment is complete enable the dyno git clone git@github.com:DMcP89/harambot.git cd harambot -2. Run the bot. +2. Export the following environment variables + + ``` + export DISCORD_TOKEN='[YOUR DISCORD TOKEN]' + export YAHOO_KEY='[YOUR YAHOO API CLIENT ID]' + export YAHOO_SECRET='[YOUR YAHOO API CLIENT SECRET]' + export DATABASE_URL='[YOUR DATABASE URL]' + ``` + +3. Run the bot. ### On local machine make run @@ -109,13 +120,27 @@ In order for the bot to work properly it requires the following intents: ### Configure your guild -* When the bot joins your guild for the first time it will DM you to complete the guild setup +* Once your bot is added to your guild you can configure it by sending a direct message to the bot with the following command: + + +![discord-config-commnd](/assests/harambot_configure_1.png) + +* Use the Login with Yahoo button to authenticate with Yahoo and get your Yahoo token + + +![discord-config-yahoo](/assests/harambot_configure_4.png) + +* Use the Configure Guild button to configure your guild for the bot + + +![discord-config-guild](/assests/harambot_configure_2.png) + + +* You can reconfigure your guild by running the configure command and clicking the Configure Guild button. -![discord-config-dm](/assests/discord-config-dm.png) -* You can reconfigure your guild by running the configure slash command from your guild +![discord-config-guild](/assests/harambot_configure_3.png) -![discord-config-command](/assests/discord-config-command.png) ## Command Examples diff --git a/assests/harambot_configure_1.png b/assests/harambot_configure_1.png new file mode 100644 index 0000000000000000000000000000000000000000..57e4da6a31643bcf433e4bd3acaa6022fc26b2dc GIT binary patch literal 40246 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

!&!h{nABN;Liz6 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%0DD#w!9O`squIxt9#&jH|_lps6!dEf4Nt#o6}j0 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 PfRDVi%KI|Ok6-^64W=yt literal 0 HcmV?d00001 diff --git a/assests/harambot_configure_4.png b/assests/harambot_configure_4.png new file mode 100644 index 0000000000000000000000000000000000000000..4a562fb849978d9125a0a4114acc278b835f88a7 GIT binary patch literal 36723 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 literal 0 HcmV?d00001 diff --git a/harambot/bot.py b/harambot/bot.py index b2e939e..a3a7111 100644 --- a/harambot/bot.py +++ b/harambot/bot.py @@ -33,7 +33,7 @@ async def on_ready(): await bot.add_cog(Misc(bot)) if not Guild.table_exists(): Guild.create_table() - if settings.run_migrations: + if "RUN_MIGRATIONS" in settings and settings.run_migrations: migrations[settings.version]() for guild in bot.guilds: bot.tree.copy_global_to(guild=guild) diff --git a/harambot/cogs/meta.py b/harambot/cogs/meta.py index 630df3f..978cb51 100644 --- a/harambot/cogs/meta.py +++ b/harambot/cogs/meta.py @@ -52,6 +52,16 @@ async def help(self, interaction: discord.Interaction): value="Returns the current weeks matchups", inline=False, ) + embed.add_field( + name="/wavier", + value="Returns the waiver wire transactions for the last 24 hours", + inline=False, + ) + embed.add_field( + name="/configure", + value="Configure your guild for Harambot", + inline=False, + ) await interaction.response.send_message(embed=embed) @app_commands.command( @@ -68,7 +78,7 @@ async def configure(self, interaction: discord.Interaction): """ Lets setup your guild 1. Login into Yahoo and copy you authentication token - 2. Configure harambot with your league information +2. Configure harambot with your league information """, view=ConfigView(), ephemeral=True, diff --git a/requirements.txt b/requirements.txt index 8b023e2..7d50488 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,45 +1,27 @@ -aiohttp==3.7.4 -async-timeout==3.0.1 -attrs==19.2.0 -cachetools==4.1.1 -certifi==2022.12.7 -cffi==1.15.1 -chardet==3.0.4 -charset-normalizer==2.0.5 -discord.py==2.0.1 -docopt==0.6.2 -dynaconf==3.1.2 -flake8==3.8.4 -idna==2.8 -importlib-metadata==2.0.0 -iniconfig==1.1.1 -mccabe==0.6.1 -multidict==4.5.2 -mysql==0.0.3 -mysqlclient==2.1.1 -objectpath==0.6.1 -packaging==20.4 -peewee==3.15.1 -pluggy==0.13.1 -psycopg2==2.9.3 -py==1.10.0 -pyaml==19.4.1 -pycodestyle==2.6.0 -pycparser==2.19 -pyflakes==2.2.0 -PyNaCl==1.3.0 -pyparsing==2.4.7 -pytest==6.2.5 -pytz==2019.1 -PyYAML==5.4 -rauth==0.7.3 -requests==2.26.0 -six==1.12.0 -toml==0.10.2 -typing-extensions==4.3.0 -urllib3==1.26.6 -websockets==9.1 -yahoo-fantasy-api==2.5.1 -yahoo-oauth==2.0 -yarl==1.3.0 -zipp==3.4.0 +aiohttp==3.8.3 ; python_version >= "3.8" and python_version < "4.0" +aiosignal==1.3.1 ; python_version >= "3.8" and python_version < "4.0" +async-timeout==4.0.2 ; python_version >= "3.8" and python_version < "4.0" +attrs==22.1.0 ; python_version >= "3.8" and python_version < "4.0" +cachetools==5.2.0 ; python_version >= "3.8" and python_version < "4.0" +certifi==2022.12.7 ; python_version >= "3.8" and python_version < "4" +charset-normalizer==2.1.1 ; python_version >= "3.8" and python_version < "4.0" +discord-py==2.1.0 ; python_version >= "3.8" and python_version < "4.0" +discord==2.1.0 ; python_version >= "3.8" and python_version < "4.0" +docopt==0.6.2 ; python_version >= "3.8" and python_version < "4.0" +dynaconf==3.1.11 ; python_version >= "3.8" and python_version < "4.0" +frozenlist==1.3.3 ; python_version >= "3.8" and python_version < "4.0" +idna==3.4 ; python_version >= "3.8" and python_version < "4.0" +multidict==6.0.2 ; python_version >= "3.8" and python_version < "4.0" +mysqlclient==2.1.1 ; python_version >= "3.8" and python_version < "4.0" +objectpath==0.6.1 ; python_version >= "3.8" and python_version < "4.0" +peewee==3.15.4 ; python_version >= "3.8" and python_version < "4.0" +psycopg2==2.9.5 ; python_version >= "3.8" and python_version < "4.0" +pyaml==21.10.1 ; python_version >= "3.8" and python_version < "4.0" +pytz==2023.2 ; python_version >= "3.8" and python_version < "4.0" +pyyaml==6.0 ; python_version >= "3.8" and python_version < "4.0" +rauth==0.7.3 ; python_version >= "3.8" and python_version < "4.0" +requests==2.28.2 ; python_version >= "3.8" and python_version < "4" +urllib3==1.26.15 ; python_version >= "3.8" and python_version < "4" +yahoo-fantasy-api==2.7.0 ; python_version >= "3.8" and python_version < "4.0" +yahoo-oauth==2.0 ; python_version >= "3.8" and python_version < "4.0" +yarl==1.8.1 ; python_version >= "3.8" and python_version < "4.0" From 904175248ea283a379b679077a8b931f13486677 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Sat, 22 Apr 2023 20:11:53 -0400 Subject: [PATCH 49/51] Updating version in pyproject.toml Adding pip install steps to README.md --- README.md | 21 ++++++++++++++++++++- pyproject.toml | 3 ++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d54747d..55131bc 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,26 @@ Once the deployment is complete enable the dyno ![heroku-dyno](/assests/heroku-dyno.png) -### Local deployment +### Install package from PIP + +1. Install the harambot package using pip + + pip install harambot + +2. Export the following environment variables + + ``` + export DISCORD_TOKEN='[YOUR DISCORD TOKEN]' + export YAHOO_KEY='[YOUR YAHOO API CLIENT ID]' + export YAHOO_SECRET='[YOUR YAHOO API CLIENT SECRET]' + export DATABASE_URL='[YOUR DATABASE URL]' + ``` + +3. Run the bot + + harambot + +### Run from source 1. Clone this repository git clone git@github.com:DMcP89/harambot.git diff --git a/pyproject.toml b/pyproject.toml index 1cb186d..e1ce002 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "harambot" -version = "0.3.0-Beta" +version = "0.3.0" description = "A Yahoo Fantasy Sports bot for Discord" authors = ["DMcP89 "] license = "MIT" @@ -20,6 +20,7 @@ keywords = [ include = [ "LICENSE.md", + "assests" ] packages = [ From 21fdc5f7e9980a85ebb9b073d1e83d1392dd7057 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Sun, 23 Apr 2023 11:53:54 -0400 Subject: [PATCH 50/51] Updating dockerfile to pull package from pip --- Dockerfile | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 635b192..84cbf00 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,5 @@ FROM python:3.10.7-slim -WORKDIR /app/harambot - -ADD ./Makefile /app/harambot -ADD ./requirements.txt /app/harambot/ -ADD ./harambot /app/harambot/harambot - RUN apt-get update RUN apt-get upgrade -y RUN apt-get install -y gcc libc-dev make git libffi-dev python3-dev libxml2-dev libxslt-dev @@ -16,6 +10,6 @@ RUN apt-get install -y libpq-dev RUN pip install -U pip -RUN pip install -r requirements.txt +RUN pip install harambot -CMD ["python", "./harambot/bot.py"] +CMD ["harambot"] From 5296327c68b0807542ec8132d636b47c8973c1d6 Mon Sep 17 00:00:00 2001 From: DMcP89 Date: Sun, 23 Apr 2023 11:58:23 -0400 Subject: [PATCH 51/51] adding dev dependencies to requirements.txt --- requirements.txt | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/requirements.txt b/requirements.txt index 7d50488..7bdefa6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,26 +2,55 @@ aiohttp==3.8.3 ; python_version >= "3.8" and python_version < "4.0" aiosignal==1.3.1 ; python_version >= "3.8" and python_version < "4.0" async-timeout==4.0.2 ; python_version >= "3.8" and python_version < "4.0" attrs==22.1.0 ; python_version >= "3.8" and python_version < "4.0" +black==22.10.0 ; python_version >= "3.8" and python_version < "4.0" cachetools==5.2.0 ; python_version >= "3.8" and python_version < "4.0" certifi==2022.12.7 ; python_version >= "3.8" and python_version < "4" +cfgv==3.3.1 ; python_version >= "3.8" and python_version < "4.0" charset-normalizer==2.1.1 ; python_version >= "3.8" and python_version < "4.0" +click==8.1.3 ; python_version >= "3.8" and python_version < "4.0" +colorama==0.4.6 ; python_version >= "3.8" and python_version < "4.0" and sys_platform == "win32" or python_version >= "3.8" and python_version < "4.0" and platform_system == "Windows" +coverage[toml]==6.5.0 ; python_version >= "3.8" and python_version < "4.0" discord-py==2.1.0 ; python_version >= "3.8" and python_version < "4.0" discord==2.1.0 ; python_version >= "3.8" and python_version < "4.0" +distlib==0.3.6 ; python_version >= "3.8" and python_version < "4.0" docopt==0.6.2 ; python_version >= "3.8" and python_version < "4.0" dynaconf==3.1.11 ; python_version >= "3.8" and python_version < "4.0" +exceptiongroup==1.0.4 ; python_version >= "3.8" and python_version < "3.11" +filelock==3.8.0 ; python_version >= "3.8" and python_version < "4.0" +flake8==5.0.4 ; python_version >= "3.8" and python_version < "4.0" frozenlist==1.3.3 ; python_version >= "3.8" and python_version < "4.0" +identify==2.5.8 ; python_version >= "3.8" and python_version < "4.0" idna==3.4 ; python_version >= "3.8" and python_version < "4.0" +iniconfig==1.1.1 ; python_version >= "3.8" and python_version < "4.0" +mccabe==0.7.0 ; python_version >= "3.8" and python_version < "4.0" multidict==6.0.2 ; python_version >= "3.8" and python_version < "4.0" +mypy-extensions==0.4.3 ; python_version >= "3.8" and python_version < "4.0" mysqlclient==2.1.1 ; python_version >= "3.8" and python_version < "4.0" +nodeenv==1.7.0 ; python_version >= "3.8" and python_version < "4.0" objectpath==0.6.1 ; python_version >= "3.8" and python_version < "4.0" +packaging==21.3 ; python_version >= "3.8" and python_version < "4.0" +pathspec==0.10.2 ; python_version >= "3.8" and python_version < "4.0" peewee==3.15.4 ; python_version >= "3.8" and python_version < "4.0" +platformdirs==2.5.4 ; python_version >= "3.8" and python_version < "4.0" +pluggy==1.0.0 ; python_version >= "3.8" and python_version < "4.0" +pre-commit==2.20.0 ; python_version >= "3.8" and python_version < "4.0" psycopg2==2.9.5 ; python_version >= "3.8" and python_version < "4.0" pyaml==21.10.1 ; python_version >= "3.8" and python_version < "4.0" +pycodestyle==2.9.1 ; python_version >= "3.8" and python_version < "4.0" +pyflakes==2.5.0 ; python_version >= "3.8" and python_version < "4.0" +pyparsing==3.0.9 ; python_version >= "3.8" and python_version < "4.0" +pytest-cov==4.0.0 ; python_version >= "3.8" and python_version < "4.0" +pytest==7.2.0 ; python_version >= "3.8" and python_version < "4.0" pytz==2023.2 ; python_version >= "3.8" and python_version < "4.0" pyyaml==6.0 ; python_version >= "3.8" and python_version < "4.0" rauth==0.7.3 ; python_version >= "3.8" and python_version < "4.0" requests==2.28.2 ; python_version >= "3.8" and python_version < "4" +setuptools==65.5.1 ; python_version >= "3.8" and python_version < "4.0" +toml==0.10.2 ; python_version >= "3.8" and python_version < "4.0" +tomli==2.0.1 ; python_version >= "3.8" and python_full_version < "3.11.0a7" +typing-extensions==4.4.0 ; python_version >= "3.8" and python_version < "3.10" urllib3==1.26.15 ; python_version >= "3.8" and python_version < "4" +virtualenv==20.16.7 ; python_version >= "3.8" and python_version < "4.0" yahoo-fantasy-api==2.7.0 ; python_version >= "3.8" and python_version < "4.0" yahoo-oauth==2.0 ; python_version >= "3.8" and python_version < "4.0" yarl==1.8.1 ; python_version >= "3.8" and python_version < "4.0"