diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml new file mode 100644 index 0000000..5168903 --- /dev/null +++ b/.github/workflows/typecheck.yml @@ -0,0 +1,33 @@ +name: Static Type Check + +on: [pull_request] + +env: + PYTHON_VERSION: '3.10' + POETRY_VERSION: '1.4.2' + +jobs: + types: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install Poetry + uses: snok/install-poetry@v1 + with: + version: ${{ env.POETRY_VERSION }} + virtualenvs-create: true + virtualenvs-in-project: true + installer-parallel: true + + - name: Install dependencies + run: poetry install --no-interaction + + - name: Type with pyright + run: poetry run pyright uqcsbot diff --git a/poetry.lock b/poetry.lock index 4d9c15b..72560c4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. +# This file is automatically @generated by Poetry and should not be changed by hand. [[package]] name = "aio-mc-rcon" @@ -391,14 +391,14 @@ files = [ [[package]] name = "discord-py" -version = "2.2.3" +version = "2.3.0" description = "A Python wrapper for the Discord API" category = "main" optional = false python-versions = ">=3.8.0" files = [ - {file = "discord.py-2.2.3-py3-none-any.whl", hash = "sha256:792bdcfe71cfe013c446cf379b2e83e08b5050604322ad6fb592864e63511abd"}, - {file = "discord.py-2.2.3.tar.gz", hash = "sha256:f9df95795c6f52c5db43b7ab43634993e12ef233288636a759166dd9c134d077"}, + {file = "discord.py-2.3.0-py3-none-any.whl", hash = "sha256:3e9498967822ad4499f8f72deb9173f942d9827d92b6e4e4e7732d24f78f300c"}, + {file = "discord.py-2.3.0.tar.gz", hash = "sha256:c71066a30f037d069218e59092505c3e8945fd175e396a80748056d989756806"}, ] [package.dependencies] @@ -600,14 +600,14 @@ tests = ["freezegun", "pytest", "pytest-cov"] [[package]] name = "icalendar" -version = "5.0.5" +version = "5.0.7" description = "iCalendar parser/generator" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "icalendar-5.0.5-py3-none-any.whl", hash = "sha256:022c3fa7421fe274889007c12582510ab2e4ba0ac612b73dc35982c644356540"}, - {file = "icalendar-5.0.5.tar.gz", hash = "sha256:ee76771d4eccebae3683beeb9c24c24feb2f8cceade72b92e4437f0144f81584"}, + {file = "icalendar-5.0.7-py3-none-any.whl", hash = "sha256:18ad51f9d1741d33795ddaf5c886c59f6575f287d30e5a145c2d42ef72910c7f"}, + {file = "icalendar-5.0.7.tar.gz", hash = "sha256:e306014a64dc4dcf638da0acb2487ee4ada57b871b03a62ed7b513dfc135655c"}, ] [package.dependencies] @@ -734,6 +734,21 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] +[[package]] +name = "nodeenv" +version = "1.8.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.*" +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] + +[package.dependencies] +setuptools = "*" + [[package]] name = "packaging" version = "23.1" @@ -760,30 +775,30 @@ files = [ [[package]] name = "platformdirs" -version = "3.5.0" +version = "3.8.0" 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" files = [ - {file = "platformdirs-3.5.0-py3-none-any.whl", hash = "sha256:47692bc24c1958e8b0f13dd727307cff1db103fca36399f457da8e05f222fdc4"}, - {file = "platformdirs-3.5.0.tar.gz", hash = "sha256:7954a68d0ba23558d753f73437c55f89027cf8f5108c19844d4b82e5af396335"}, + {file = "platformdirs-3.8.0-py3-none-any.whl", hash = "sha256:ca9ed98ce73076ba72e092b23d3c93ea6c4e186b3f1c3dad6edd98ff6ffcca2e"}, + {file = "platformdirs-3.8.0.tar.gz", hash = "sha256:b0cabcb11063d21a0b261d557acb0a9d2126350e63b70cdf7db6347baea456dc"}, ] [package.extras] -docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] [[package]] name = "pluggy" -version = "1.0.0" +version = "1.2.0" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, + {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, ] [package.extras] @@ -862,16 +877,35 @@ files = [ {file = "psycopg2_binary-2.9.6-cp39-cp39-win_amd64.whl", hash = "sha256:f6a88f384335bb27812293fdb11ac6aee2ca3f51d3c7820fe03de0a304ab6249"}, ] +[[package]] +name = "pyright" +version = "1.1.316" +description = "Command line wrapper for pyright" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyright-1.1.316-py3-none-any.whl", hash = "sha256:7259d73287c882f933d8cd88c238ef02336e172171ae95117a963a962a1fed4a"}, + {file = "pyright-1.1.316.tar.gz", hash = "sha256:bac1baf8567b90f2082ec95b61fc1cb50a68917119212c5608a72210870c6a9a"}, +] + +[package.dependencies] +nodeenv = ">=1.6.0" + +[package.extras] +all = ["twine (>=3.4.1)"] +dev = ["twine (>=3.4.1)"] + [[package]] name = "pytest" -version = "7.3.1" +version = "7.4.0" description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.3.1-py3-none-any.whl", hash = "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362"}, - {file = "pytest-7.3.1.tar.gz", hash = "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3"}, + {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, + {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, ] [package.dependencies] @@ -883,7 +917,7 @@ pluggy = ">=0.12,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-datafiles" @@ -942,31 +976,16 @@ files = [ {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, ] -[[package]] -name = "pytz-deprecation-shim" -version = "0.1.0.post0" -description = "Shims to make deprecation of pytz easier" -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -files = [ - {file = "pytz_deprecation_shim-0.1.0.post0-py2.py3-none-any.whl", hash = "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6"}, - {file = "pytz_deprecation_shim-0.1.0.post0.tar.gz", hash = "sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"}, -] - -[package.dependencies] -tzdata = {version = "*", markers = "python_version >= \"3.6\""} - [[package]] name = "requests" -version = "2.30.0" +version = "2.31.0" description = "Python HTTP for Humans." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "requests-2.30.0-py3-none-any.whl", hash = "sha256:10e94cc4f3121ee6da529d358cdaeaff2f1c409cd377dbc72b825852f2f7e294"}, - {file = "requests-2.30.0.tar.gz", hash = "sha256:239d7d4458afcb28a692cdd298d87542235f4ca8d36d03a15bfc128a6559a2f4"}, + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, ] [package.dependencies] @@ -981,19 +1000,19 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "setuptools" -version = "67.7.2" +version = "68.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "setuptools-67.7.2-py3-none-any.whl", hash = "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b"}, - {file = "setuptools-67.7.2.tar.gz", hash = "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990"}, + {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, + {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, ] [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-lint", "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 = ["build[virtualenv]", "filelock (>=3.4.0)", "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-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "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]] @@ -1022,53 +1041,53 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.12" +version = "2.0.17" description = "Database Abstraction Library" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10f1ff0ebe21d2cea89ead231ba3ecf75678463ab85f19ce2ce91207620737f3"}, - {file = "SQLAlchemy-2.0.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:978bee4ecbcdadf087220618409fb9be9509458df479528b70308f0599c7c519"}, - {file = "SQLAlchemy-2.0.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53b2c8adbcbb59732fb21a024aaa261983655845d86e3fc26a5676cec0ebaa09"}, - {file = "SQLAlchemy-2.0.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91f4b1bdc987ef85fe3a0ce5d26ac72ff8f60207b08272aa2a65494836391d69"}, - {file = "SQLAlchemy-2.0.12-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dfd6385b662aea83e63dd4db5fe116eb11914022deb1745f0b57fa8470c18ffe"}, - {file = "SQLAlchemy-2.0.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5e9d390727c11b9a7e583bf6770de36895c0936bddb98ae93ae99282e6428d5f"}, - {file = "SQLAlchemy-2.0.12-cp310-cp310-win32.whl", hash = "sha256:a4709457f1c317e347051498b91fa2b86c4bcdebf93c84e6d121a4fc8a397307"}, - {file = "SQLAlchemy-2.0.12-cp310-cp310-win_amd64.whl", hash = "sha256:f0843132168b44ca33c5e5a2046c954775dde8c580ce27f5cf2e134d0d9919e4"}, - {file = "SQLAlchemy-2.0.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:32762dba51b663609757f861584a722093487f53737e76474cc6e190904dc31b"}, - {file = "SQLAlchemy-2.0.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d709f43caee115b03b707b8cbbcb8b303045dd7cdc825b6d29857d71f3425ae"}, - {file = "SQLAlchemy-2.0.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fe98e9d26778d7711ceee2c671741b4f54c74677668481d733d6f70747d7690"}, - {file = "SQLAlchemy-2.0.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a3101252f3de9a18561c1fb0a68b1ee465485990aba458d4510f214bd5a582c"}, - {file = "SQLAlchemy-2.0.12-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b1fa0ffc378a7061c452cb4a1f804fad1b3b8aa8d0552725531d27941b2e3ed"}, - {file = "SQLAlchemy-2.0.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c5268ec05c21e2ecf5bca09314bcaadfec01f02163088cd602db4379862958dd"}, - {file = "SQLAlchemy-2.0.12-cp311-cp311-win32.whl", hash = "sha256:77a06b0983faf9aa48ee6219d41ade39dee16ce90857cc181dbcf6918acd234d"}, - {file = "SQLAlchemy-2.0.12-cp311-cp311-win_amd64.whl", hash = "sha256:a022c588c0f413f8cddf9fcc597dbf317efeac4186d8bff9aa7f3219258348b0"}, - {file = "SQLAlchemy-2.0.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6ceca432ce88ad12aab5b5896c343a1993c90b325d9193dcd055e73e18a0439"}, - {file = "SQLAlchemy-2.0.12-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e5501c78b5ab917f0f0f75ce7f0018f683a0a76e95f30e6561bf61c9ff69d43"}, - {file = "SQLAlchemy-2.0.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc67efd00ce7f428a446ce012673c03c63c5abb5dec3f33750087b8bdc173bf0"}, - {file = "SQLAlchemy-2.0.12-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1fac17c866111283cbcdb7024d646abb71fdd95f3ce975cf3710258bc55742fd"}, - {file = "SQLAlchemy-2.0.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:f30c5608c64fc9c1fa9a16277eb4784f782362566fe40ff8d283358c8f2c5fe0"}, - {file = "SQLAlchemy-2.0.12-cp37-cp37m-win32.whl", hash = "sha256:85b0efe1c71459ba435a6593f54a0e39334b16ba383e8010fdb9d0127ca51ba8"}, - {file = "SQLAlchemy-2.0.12-cp37-cp37m-win_amd64.whl", hash = "sha256:b76c2fde827522e21922418325c1b95c2d795cdecfb4bc261e4d37965199ee7f"}, - {file = "SQLAlchemy-2.0.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:aec5fb36b53125554ecc2285526eb5cc31b21f6cb059993c1c5ca831959de052"}, - {file = "SQLAlchemy-2.0.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4ad525b9dd17b478a2ed8580d7f2bc46b0f5889153c6b1c099729583e395b4b9"}, - {file = "SQLAlchemy-2.0.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9796d5c13b2b7f05084d0ce52528cf919f9bde9e0f10672a6393a4490415695"}, - {file = "SQLAlchemy-2.0.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e1d50592cb24d1947c374c666add65ded7c181ec98a89ed17abbe9b8b2e2ff4"}, - {file = "SQLAlchemy-2.0.12-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bf83700faa9642388fbd3167db3f6cbb2e88cc8367b8c22204f3f408ee782d25"}, - {file = "SQLAlchemy-2.0.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:297b752d4f30350b64175bbbd57dc94c061a35f5d1dba088d0a367dbbebabc94"}, - {file = "SQLAlchemy-2.0.12-cp38-cp38-win32.whl", hash = "sha256:369f6564e68a9c60f0b9dde121def491e651a4ba8dcdd652a93f1cd5977cd85c"}, - {file = "SQLAlchemy-2.0.12-cp38-cp38-win_amd64.whl", hash = "sha256:7eb25b981cbc9e7df9f56ad7ec4c6d77323090ca4b7147fcdc09d66535377759"}, - {file = "SQLAlchemy-2.0.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f6ebadefc4331dda83c22519e1ea1e61104df6eb38abbb80ab91b0a8527a5c19"}, - {file = "SQLAlchemy-2.0.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3745dee26a7ee012598577ad3b8f6e6cd50a49b2afa0cde9db668da6bf2c2319"}, - {file = "SQLAlchemy-2.0.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09205893a84b6bedae0453d3f384f5d2a6499b6e45ad977549894cdcd85d8f1c"}, - {file = "SQLAlchemy-2.0.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8aad66215a3817a7a1d535769773333250de2653c89b53f7e2d42b677d398027"}, - {file = "SQLAlchemy-2.0.12-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e495ad05a13171fbb5d72fe5993469c8bceac42bcf6b8f9f117a518ee7fbc353"}, - {file = "SQLAlchemy-2.0.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:03206576ca53f55b9de6e890273e498f4b2e6e687a9db9859bdcd21df5a63e53"}, - {file = "SQLAlchemy-2.0.12-cp39-cp39-win32.whl", hash = "sha256:87b2c2d13c3d1384859b60eabb3139e169ce68ada1d2963dbd0c7af797f16efe"}, - {file = "SQLAlchemy-2.0.12-cp39-cp39-win_amd64.whl", hash = "sha256:3c053c3f4c4e45d4c8b27977647566c140d6de3f61a4e2acb92ea24cf9911c7f"}, - {file = "SQLAlchemy-2.0.12-py3-none-any.whl", hash = "sha256:e752c34f7a2057ebe82c856698b9f277c633d4aad006bddf7af74598567c8931"}, - {file = "SQLAlchemy-2.0.12.tar.gz", hash = "sha256:bddfc5bd1dee5db0fddc9dab26f800c283f3243e7281bbf107200fed30125f9c"}, + {file = "SQLAlchemy-2.0.17-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:04383f1e3452f6739084184e427e9d5cb4e68ddc765d52157bf5ef30d5eca14f"}, + {file = "SQLAlchemy-2.0.17-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:724355973297bbe547f3eb98b46ade65a67a3d5a6303f17ab59a2dc6fb938943"}, + {file = "SQLAlchemy-2.0.17-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf07ff9920cb3ca9d73525dfd4f36ddf9e1a83734ea8b4f724edfd9a2c6e82d9"}, + {file = "SQLAlchemy-2.0.17-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f389f77c68dc22cb51f026619291c4a38aeb4b7ecb5f998fd145b2d81ca513"}, + {file = "SQLAlchemy-2.0.17-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ba03518e64d86f000dc24ab3d3a1aa876bcbaa8aa15662ac2df5e81537fa3394"}, + {file = "SQLAlchemy-2.0.17-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:218fb20c01e95004f50a3062bf4c447dcb360cab8274232f31947e254f118298"}, + {file = "SQLAlchemy-2.0.17-cp310-cp310-win32.whl", hash = "sha256:b47be4c6281a86670ea5cfbbbe6c3a65366a8742f5bc8b986f790533c60b5ddb"}, + {file = "SQLAlchemy-2.0.17-cp310-cp310-win_amd64.whl", hash = "sha256:74ddcafb6488f382854a7da851c404c394be3729bb3d91b02ad86c5458140eff"}, + {file = "SQLAlchemy-2.0.17-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:51736cfb607cf4e8fafb693906f9bc4e5ee55be0b096d44bd7f20cd8489b8571"}, + {file = "SQLAlchemy-2.0.17-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8741d3d401383e54b2aada37cbd10f55c5d444b360eae3a82f74a2be568a7710"}, + {file = "SQLAlchemy-2.0.17-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ead58cae2a089eee1b0569060999cb5f2b2462109498a0937cc230a7556945a1"}, + {file = "SQLAlchemy-2.0.17-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f40e3a7d0a464f1c8593f2991e5520b2f5b26da24e88000bbd4423f86103d4f"}, + {file = "SQLAlchemy-2.0.17-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:21583808d37f126a647652c90332ac1d3a102edf3c94bcc3319edcc0ea2300cc"}, + {file = "SQLAlchemy-2.0.17-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f593170fc09c5abb1205a738290b39532f7380094dc151805009a07ae0e85330"}, + {file = "SQLAlchemy-2.0.17-cp311-cp311-win32.whl", hash = "sha256:b0eaf82cc844f6b46defe15ad243ea00d1e39ed3859df61130c263dc7204da6e"}, + {file = "SQLAlchemy-2.0.17-cp311-cp311-win_amd64.whl", hash = "sha256:1822620c89779b85f7c23d535c8e04b79c517739ae07aaed48c81e591ed5498e"}, + {file = "SQLAlchemy-2.0.17-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2269b1f9b8be47e52b70936069a25a3771eff53367aa5cc59bb94f28a6412e13"}, + {file = "SQLAlchemy-2.0.17-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48111d56afea5699bab72c38ec95561796b81befff9e13d1dd5ce251ab25f51d"}, + {file = "SQLAlchemy-2.0.17-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28da17059ecde53e2d10ba813d38db942b9f6344360b2958b25872d5cb729d35"}, + {file = "SQLAlchemy-2.0.17-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:48b40dc2895841ea89d89df9eb3ac69e2950a659db20a369acf4259f68e6dc1f"}, + {file = "SQLAlchemy-2.0.17-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7f31d4e7ca1dd8ca5a27fd5eaa0f9e2732fe769ff7dd35bf7bba179597e4df07"}, + {file = "SQLAlchemy-2.0.17-cp37-cp37m-win32.whl", hash = "sha256:7830e01b02d440c27f2a5be68296e74ccb55e6a5b5962ffafd360b98930b2e5e"}, + {file = "SQLAlchemy-2.0.17-cp37-cp37m-win_amd64.whl", hash = "sha256:234678ed6576531b8e4be255b980f20368bf07241a2e67b84e6b0fe679edb9c4"}, + {file = "SQLAlchemy-2.0.17-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c6ff5767d954f6091113fedcaaf49cdec2197ae4c5301fe83d5ae4393c82f33"}, + {file = "SQLAlchemy-2.0.17-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:aa995b21f853864996e4056d9fde479bcecf8b7bff4beb3555eebbbba815f35d"}, + {file = "SQLAlchemy-2.0.17-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:125f9f7e62ddf8b590c069729080ffe18b68a20d9882eb0947f72e06274601d7"}, + {file = "SQLAlchemy-2.0.17-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b114a16bc03dfe20b625062e456affd7b9938286e05a3f904a025b9aacc29dd4"}, + {file = "SQLAlchemy-2.0.17-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cf175d26f6787cce30fe6c04303ca0aeeb0ad40eeb22e3391f24b32ec432a1e1"}, + {file = "SQLAlchemy-2.0.17-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e2d5c3596254cf1a96474b98e7ce20041c74c008b0f101c1cb4f8261cb77c6d3"}, + {file = "SQLAlchemy-2.0.17-cp38-cp38-win32.whl", hash = "sha256:513411d73503a6fc5804f01fae3b3d44f267c1b3a06cfeac02e9286a7330e857"}, + {file = "SQLAlchemy-2.0.17-cp38-cp38-win_amd64.whl", hash = "sha256:40a3dc52b2b16f08b5c16b9ee7646329e4b3411e9280e5e8d57b19eaa51cbef4"}, + {file = "SQLAlchemy-2.0.17-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e3189432db2f5753b4fde1aa90a61c69976f4e7e31d1cf4611bfe3514ed07478"}, + {file = "SQLAlchemy-2.0.17-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6150560fcffc6aee5ec9a97419ac768c7a9f56baf7a7eb59cb4b1b6a4d463ad9"}, + {file = "SQLAlchemy-2.0.17-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:910d45bf3673f0e4ef13858674bd23cfdafdc8368b45b948bf511797dbbb401d"}, + {file = "SQLAlchemy-2.0.17-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0aeb3afaa19f187a70fa592fbe3c20a056b57662691fd3abf60f016aa5c1848"}, + {file = "SQLAlchemy-2.0.17-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:36a87e26fe8fa8c466fae461a8fcb780d0a1cbf8206900759fc6fe874475a3ce"}, + {file = "SQLAlchemy-2.0.17-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e3a6b2788f193756076061626679c5c5a6d600ddf8324f986bc72004c3e9d92e"}, + {file = "SQLAlchemy-2.0.17-cp39-cp39-win32.whl", hash = "sha256:af7e2ba75bf84b64adb331918188dda634689a2abb151bc1a583e488363fd2f8"}, + {file = "SQLAlchemy-2.0.17-cp39-cp39-win_amd64.whl", hash = "sha256:394ac3adf3676fad76d4b8fcecddf747627f17f0738dc94bac15f303d05b03d4"}, + {file = "SQLAlchemy-2.0.17-py3-none-any.whl", hash = "sha256:cc9c2630c423ac4973492821b2969f5fe99d9736f3025da670095668fbfcd4d5"}, + {file = "SQLAlchemy-2.0.17.tar.gz", hash = "sha256:e186e9e95fb5d993b075c33fe4f38a22105f7ce11cecb5c17b5618181e356702"}, ] [package.dependencies] @@ -1096,6 +1115,7 @@ postgresql-pg8000 = ["pg8000 (>=1.29.1)"] postgresql-psycopg = ["psycopg (>=3.0.7)"] postgresql-psycopg2binary = ["psycopg2-binary"] postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] pymysql = ["pymysql"] sqlcipher = ["sqlcipher3-binary"] @@ -1111,16 +1131,94 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "types-beautifulsoup4" +version = "4.12.0.5" +description = "Typing stubs for beautifulsoup4" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "types-beautifulsoup4-4.12.0.5.tar.gz", hash = "sha256:d9be456416a62a5b9740559592e1063a69d4b0a1b83911d878558c8ae8e07074"}, + {file = "types_beautifulsoup4-4.12.0.5-py3-none-any.whl", hash = "sha256:718364c8e6787884501c700b1d22b6c0a8711bf9d6c9bf96e1fba81495bc46a8"}, +] + +[package.dependencies] +types-html5lib = "*" + +[[package]] +name = "types-html5lib" +version = "1.1.11.14" +description = "Typing stubs for html5lib" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "types-html5lib-1.1.11.14.tar.gz", hash = "sha256:091e9e74e0ee37c93fd789a164e99b2af80ecf5a314280450c6a763d027ea209"}, + {file = "types_html5lib-1.1.11.14-py3-none-any.whl", hash = "sha256:758c1a27f3b63363a346f3646be9f8b1f25df4fc1f96f88af6d1d831f24ad675"}, +] + +[[package]] +name = "types-python-dateutil" +version = "2.8.19.13" +description = "Typing stubs for python-dateutil" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "types-python-dateutil-2.8.19.13.tar.gz", hash = "sha256:09a0275f95ee31ce68196710ed2c3d1b9dc42e0b61cc43acc369a42cb939134f"}, + {file = "types_python_dateutil-2.8.19.13-py3-none-any.whl", hash = "sha256:0b0e7c68e7043b0354b26a1e0225cb1baea7abb1b324d02b50e2d08f1221043f"}, +] + +[[package]] +name = "types-pytz" +version = "2023.3.0.0" +description = "Typing stubs for pytz" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "types-pytz-2023.3.0.0.tar.gz", hash = "sha256:ecdc70d543aaf3616a7e48631543a884f74205f284cefd6649ddf44c6a820aac"}, + {file = "types_pytz-2023.3.0.0-py3-none-any.whl", hash = "sha256:4fc2a7fbbc315f0b6630e0b899fd6c743705abe1094d007b0e612d10da15e0f3"}, +] + +[[package]] +name = "types-requests" +version = "2.31.0.1" +description = "Typing stubs for requests" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "types-requests-2.31.0.1.tar.gz", hash = "sha256:3de667cffa123ce698591de0ad7db034a5317457a596eb0b4944e5a9d9e8d1ac"}, + {file = "types_requests-2.31.0.1-py3-none-any.whl", hash = "sha256:afb06ef8f25ba83d59a1d424bd7a5a939082f94b94e90ab5e6116bd2559deaa3"}, +] + +[package.dependencies] +types-urllib3 = "*" + +[[package]] +name = "types-urllib3" +version = "1.26.25.13" +description = "Typing stubs for urllib3" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "types-urllib3-1.26.25.13.tar.gz", hash = "sha256:3300538c9dc11dad32eae4827ac313f5d986b8b21494801f1bf97a1ac6c03ae5"}, + {file = "types_urllib3-1.26.25.13-py3-none-any.whl", hash = "sha256:5dbd1d2bef14efee43f5318b5d36d805a489f6600252bb53626d4bfafd95e27c"}, +] + [[package]] name = "typing-extensions" -version = "4.5.0" +version = "4.6.3" description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, - {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, + {file = "typing_extensions-4.6.3-py3-none-any.whl", hash = "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26"}, + {file = "typing_extensions-4.6.3.tar.gz", hash = "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"}, ] [[package]] @@ -1137,18 +1235,17 @@ files = [ [[package]] name = "tzlocal" -version = "4.3" +version = "5.0.1" description = "tzinfo object for the local timezone" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "tzlocal-4.3-py3-none-any.whl", hash = "sha256:b44c4388f3d34f25862cfbb387578a4d70fec417649da694a132f628a23367e2"}, - {file = "tzlocal-4.3.tar.gz", hash = "sha256:3f21d09e1b2aa9f2dacca12da240ca37de3ba5237a93addfd6d593afe9073355"}, + {file = "tzlocal-5.0.1-py3-none-any.whl", hash = "sha256:f3596e180296aaf2dbd97d124fe76ae3a0e3d32b258447de7b939b3fd4be992f"}, + {file = "tzlocal-5.0.1.tar.gz", hash = "sha256:46eb99ad4bdb71f3f72b7d24f4267753e240944ecfc16f25d2719ba89827a803"}, ] [package.dependencies] -pytz-deprecation-shim = "*" tzdata = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] @@ -1156,14 +1253,14 @@ devenv = ["black", "check-manifest", "flake8", "pyroma", "pytest (>=4.3)", "pyte [[package]] name = "urllib3" -version = "2.0.2" +version = "2.0.3" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "urllib3-2.0.2-py3-none-any.whl", hash = "sha256:d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e"}, - {file = "urllib3-2.0.2.tar.gz", hash = "sha256:61717a1095d7e155cdb737ac7bb2f4324a858a1e2e6466f6d03ff630ca68d3cc"}, + {file = "urllib3-2.0.3-py3-none-any.whl", hash = "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1"}, + {file = "urllib3-2.0.3.tar.gz", hash = "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825"}, ] [package.extras] @@ -1263,4 +1360,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "7a9d6ee144f27a9e67eb4295cfb22c8e7b1a252f38e6830546423f2dd8d3f386" +content-hash = "279be46d6277bf0ca0b5d9e0934296b6008a3dc8c3ed43e00bbcde989094ee3c" diff --git a/pyproject.toml b/pyproject.toml index 0e39106..a815ef9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,33 @@ pytest = "^7.3.1" pytest-datafiles = "^3.0.0" python-dotenv = "^1.0.0" black = "^23.3.0" +pyright = "^1.1.316" +types-requests = "^2.30.0.0" +types-beautifulsoup4 = "^4.12.0.4" +types-python-dateutil = "^2.8.19.12" +types-pytz = "^2023.3.0.0" [build-system] requires = ["poetry-core>=1.3.0"] build-backend = "poetry.core.masonry.api" + +[tool.pyright] +strict = ["**"] +exclude = [ + "**/advent.py", + "**/bot.py", + "**/error_handler.py", + "**/events.py", + "**/gaming.py", + "**/haiku.py", + "**/member_counter.py", + "**/remindme.py", + "**/snailrace.py", + "**/starboard.py", + "**/uptime.py", + "**/whatsdue.py", + "**/working_on.py", + "**/utils/command_utils.py", + "**/utils/snailrace_utils.py", + "**/utils/uq_course_utils.py" +] \ No newline at end of file diff --git a/uqcsbot/__main__.py b/uqcsbot/__main__.py index 94689fb..9234200 100644 --- a/uqcsbot/__main__.py +++ b/uqcsbot/__main__.py @@ -22,12 +22,10 @@ async def main(): intents.members = True intents.message_content = True - DISCORD_TOKEN = os.environ.get("DISCORD_BOT_TOKEN") - - DATABASE_URI = os.environ.get("POSTGRES_URI_BOT") - if DATABASE_URI == None: - # If the database env variable is not defined, default to SQLite in memory db. - DATABASE_URI = "sqlite:///" + if (discord_token := os.environ.get("DISCORD_BOT_TOKEN")) is None: + raise RuntimeError("Bot token is not set!") + if (database_uri := os.environ.get("POSTGRES_URI_BOT")) is None: + database_uri = "sqlite:///" # If you need to override the allowed mentions that can be done on a per message basis, but default to off allowed_mentions = discord.AllowedMentions.none() @@ -72,11 +70,11 @@ async def main(): for cog in cogs: await bot.load_extension(f"uqcsbot.{cog}") - db_engine = create_engine(DATABASE_URI, echo=True) + db_engine = create_engine(database_uri, echo=True) Base.metadata.create_all(db_engine) bot.set_db_engine(db_engine) - await bot.start(DISCORD_TOKEN) + await bot.start(discord_token) asyncio.run(main()) diff --git a/uqcsbot/advent.py b/uqcsbot/advent.py index 31843bb..d37f656 100644 --- a/uqcsbot/advent.py +++ b/uqcsbot/advent.py @@ -15,17 +15,17 @@ from uqcsbot.bot import UQCSBot from uqcsbot.models import AOCWinner from uqcsbot.utils.command_utils import loading_status +from uqcsbot.utils.err_log_utils import FatalErrorWithLog # Leaderboard API URL with placeholders for year and code. LEADERBOARD_URL = "https://adventofcode.com/{year}/leaderboard/private/view/{code}.json" -# Session cookie (will expire in approx 30 days). -# See: https://github.com/UQComputingSociety/uqcsbot-discord/wiki/Tokens-and-Environment-Variables#aoc_session_id -SESSION_ID = os.environ.get("AOC_SESSION_ID") + # UQCS leaderboard ID. UQCS_LEADERBOARD = 989288 # Days in Advent of Code. List of numbers 1 to 25. ADVENT_DAYS = list(range(1, 25 + 1)) + # Puzzles are unlocked at midnight EST. EST_TIMEZONE = timezone(timedelta(hours=-5)) @@ -159,6 +159,10 @@ def sort_key(sort: SortMode) -> Callable[["Member"], Any]: class Advent(commands.Cog): CHANNEL_NAME = "contests" + # Session cookie (will expire in approx 30 days). + # See: https://github.com/UQComputingSociety/uqcsbot-discord/wiki/Tokens-and-Environment-Variables#aoc_session_id + SESSION_ID: str = "" + def __init__(self, bot: UQCSBot): self.bot = bot self.bot.schedule_task( @@ -179,6 +183,13 @@ def __init__(self, bot: UQCSBot): month=12, ) + if os.environ.get("AOC_SESSION_ID") is not None: + SESSION_ID = os.environ.get("AOC_SESSION_ID") + else: + raise FatalErrorWithLog( + bot, "Unable to find AoC session ID. Not loading advent cog." + ) + def star_char(self, num_stars: int): """ Given a number of stars (0, 1, or 2), returns its leaderboard @@ -334,7 +345,7 @@ def parse_arguments(self, argv: List[str]) -> Namespace: def usage_error(message, *args, **kwargs): raise ValueError(message) - parser.error = usage_error # type: ignore + parser.error = usage_error args = parser.parse_args(argv) @@ -343,14 +354,14 @@ def usage_error(message, *args, **kwargs): return args - def get_leaderboard(self, year: int, code: int) -> Dict: + def get_leaderboard(self, year: int, code: int) -> Optional[Dict]: """ Returns a json dump of the leaderboard """ try: response = requests.get( LEADERBOARD_URL.format(year=year, code=code), - cookies={"session": SESSION_ID}, + cookies={"session": self.SESSION_ID}, ) return response.json() except ValueError as exception: # json.JSONDecodeError @@ -543,4 +554,5 @@ async def advent_winners( async def setup(bot: UQCSBot): cog = Advent(bot) + await bot.add_cog(cog) diff --git a/uqcsbot/basic.py b/uqcsbot/basic.py index 7a62dfb..7af7a9b 100644 --- a/uqcsbot/basic.py +++ b/uqcsbot/basic.py @@ -40,9 +40,10 @@ async def on_ready(self): ) @commands.Cog.listener() - async def on_member_join(self, member): + async def on_member_join(self, member: discord.Member): """Member join listener""" - channel = member.guild.system_channel + if (channel := member.guild.system_channel) is None: + return # On user joining, a system join message will appear in the system channel # This should prevent the bot waving on a user message when #general is busy async for msg in channel.history(limit=5): @@ -83,7 +84,7 @@ def format_repo_message(self, repos: List[str]) -> str: :param repos: list of strings of repo names :return: a single string with a formatted message containing repo info for the given names """ - repo_strings = [] + repo_strings: List[str] = [] for potential_repo in repos: repo_strings.append(self.find_repo(potential_repo)) return "".join(repo_strings) @@ -98,8 +99,7 @@ async def repo_list(self, interaction: discord.Interaction): + self.format_repo_message(list(REPOS.keys())) ) - @repo_group.command(name="find") - @app_commands.describe(name="Name of the repo to find") + @repo_group.command(name="find", description="Name of the repo to find") async def repo_find(self, interaction: discord.Interaction, name: str): """Finds a specific UQCS GitHub repository""" await interaction.response.send_message( diff --git a/uqcsbot/bot.py b/uqcsbot/bot.py index 59ee29f..e2b045c 100644 --- a/uqcsbot/bot.py +++ b/uqcsbot/bot.py @@ -1,6 +1,7 @@ import logging import os -from typing import List +from typing import List, Optional, Tuple, Any, Callable, Coroutine + import discord from discord.ext import commands from apscheduler.schedulers.asyncio import AsyncIOScheduler @@ -8,19 +9,33 @@ from sqlalchemy.orm import sessionmaker from datetime import datetime from aiohttp import web +from pytz import timezone -ADMIN_ALERTS = "admin-alerts" +""" +TODO: TYPE ISSUES IN THIS FILE: + - apscheduler has no stubs. They're planned for the 4.0 release... in the future. + - aiohttp handler witchery +""" class UQCSBot(commands.Bot): """An extended bot client to add extra functionality.""" - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any): super().__init__(*args, **kwargs) self._scheduler = AsyncIOScheduler() self.start_time = datetime.now() - def schedule_task(self, func, *args, **kwargs): + # Important channel names & constants go here + self.ADMIN_ALERTS_CNAME = "admin-alerts" + self.GENERAL_CNAME = "general" + self.BOT_TIMEZONE = timezone("Australia/Brisbane") + + self.uqcs_server: discord.Guild + + def schedule_task( + self, func: Callable[..., Coroutine[Any, Any, None]], *args: Any, **kwargs: Any + ): """Schedule a function to be run at a later time. A wrapper for apscheduler add_job.""" self._scheduler.add_job(func, *args, **kwargs) @@ -35,15 +50,17 @@ async def admin_alert( self, title: str, colour: discord.Colour, - description: str = None, - footer: str = None, - fields: List[tuple] = None, + description: Optional[str] = None, + footer: Optional[str] = None, + fields: Optional[List[Tuple[str, str]]] = None, fields_inline: bool = True, ): """Sends an alert to the admin channel for logging.""" - admin_channel = discord.utils.get(self.uqcs_server.channels, name=ADMIN_ALERTS) + admin_channel = discord.utils.get( + self.uqcs_server.channels, name=self.ADMIN_ALERTS_CNAME + ) - if admin_channel == None: + if admin_channel == None or not isinstance(admin_channel, discord.TextChannel): return admin_message = discord.Embed(title=title, colour=colour) @@ -75,12 +92,22 @@ def handle(request): async def on_ready(self): """Once the bot is loaded and has connected, run these commands first.""" self._scheduler.start() + + if (user := self.user) is None: + raise RuntimeError("Ready... but not logged in!") + self.safe_user = user + logging.info( - f'Bot online and logged in: [Name="{self.user.name}", ID={self.user.id}]' + f'Bot online and logged in: [Name="{self.safe_user.id}", ID={self.safe_user.id}]' ) # Get the UQCS server object and store it centrally - self.uqcs_server = self.get_guild(int(os.environ.get("SERVER_ID"))) + if (server_id := os.environ.get("SERVER_ID")) is None: + raise RuntimeError("Server ID is not set!") + if (server := self.get_guild(int(server_id))) is None: + raise RuntimeError("Unable to find server with id {server_id}") + self.uqcs_server: discord.Guild = server + logging.info(f"Active in the {self.uqcs_server} server.") # Sync the app comamand tree with servers. diff --git a/uqcsbot/cat.py b/uqcsbot/cat.py index 30b0035..7a3d7a0 100644 --- a/uqcsbot/cat.py +++ b/uqcsbot/cat.py @@ -27,7 +27,7 @@ async def cat(self, interaction: discord.Interaction): order = deque([pink, red, yellow, green, cyan, blue]) # randomly shifts starting colout shift = randrange(0, 5) - for i in range(shift): + for _ in range(shift): order.append(order.popleft()) cat = "\n".join( diff --git a/uqcsbot/cowsay.py b/uqcsbot/cowsay.py index f2f3be9..58a91ab 100644 --- a/uqcsbot/cowsay.py +++ b/uqcsbot/cowsay.py @@ -140,7 +140,7 @@ def draw_cow( """ # Set the tongue if the cow is dead or if the tongue is set to True. - tongue = "U" if tongue or mood == "Dead" else " " + tongue_out = "U" if tongue or mood == "Dead" else " " # Set the bubble connection based on whether the cow is thinking or # speaking. @@ -155,9 +155,9 @@ def draw_cow( # Draw the cow. cow = f" {bubble_connect} ^__^\n" - cow += f" {bubble_connect} ({cow_eyes})\_______\n" - cow += f" (__)\ )\/\ \n" - cow += f" {tongue} ||----w |\n" + cow += f" {bubble_connect} ({cow_eyes})\\_______\n" + cow += f" (__)\\ )\\/\\ \n" + cow += f" {tongue_out} ||----w |\n" cow += f" || ||\n" return cow @@ -186,10 +186,10 @@ def draw_tux( tux += f" .--. \n" tux += f" |{tux_eyes} | \n" tux += f" |:_/ | \n" - tux += f" // \ \ \n" + tux += f" // \\ \\ \n" tux += f" (| | ) \n" - tux += f" /'\_ _/`\ \n" - tux += f" \___)=(___/ \n" + tux += f" /'\\_ _/`\\ \n" + tux += f" \\___)=(___/ \n" return tux @staticmethod @@ -215,7 +215,7 @@ def sanitise_emotes(message: str) -> str: """ # Regex to match emotes. - emotes: List[str] = re.findall("", message) + emotes: List[str] = re.findall(r"", message) # Replace each emote with its name. for emote in emotes: @@ -242,7 +242,7 @@ def word_wrap(message: str, wrap: int) -> List[str]: # As requested by the audience, you can manually break lines by # adding "\n" anywhere in the message and it will be respected. if "\\n" in word: - parts: str = word.split("\\n", 1) + parts = word.split("\\n", 1) # The `\n` is by itself, so start a new line. if parts[0] == "" and parts[1] == "": @@ -271,7 +271,7 @@ def word_wrap(message: str, wrap: int) -> List[str]: # the list of words to be processed. if len(word) > wrap: # Cut the word to the remaining space on the line. - cut_word: str = word[: (wrap - len(line))] + cut_word = word[: (wrap - len(line))] # Add the rest of the word to the list of words to be processed. words.insert(index, word[len(cut_word) :]) diff --git a/uqcsbot/error_handler.py b/uqcsbot/error_handler.py index e57310b..98a55bc 100644 --- a/uqcsbot/error_handler.py +++ b/uqcsbot/error_handler.py @@ -2,6 +2,10 @@ from discord.ext.commands.errors import MissingRequiredArgument import logging +""" +TODO: this is bundled with advent.py and should be removed. +""" + class ErrorHandler(commands.Cog): @commands.Cog.listener() diff --git a/uqcsbot/events.py b/uqcsbot/events.py index a5a7576..dd10e6d 100644 --- a/uqcsbot/events.py +++ b/uqcsbot/events.py @@ -1,7 +1,7 @@ import logging from calendar import day_abbr, month_abbr, month_name from datetime import date, datetime, timedelta -from typing import List, Optional, Tuple +from typing import List, Optional, Tuple, Dict import discord from discord import app_commands @@ -17,15 +17,18 @@ "https://calendar.google.com/calendar/ical/" "q3n3pce86072n9knt3pt65fhio%40group.calendar.google.com/public/basic.ics" ) -# EXTERNAL_CALENDAR_URL = "https://calendar.google.com/calendar/ical/" \ +# EXTERNAL_CALENDAR_URL =d "https://calendar.google.com/calendar/ical/" \ # "72abf01afvsl3bjd9oq2g1avgg%40group.calendar.google.com/public/basic.ics" # Testing calendar: "https://calendar.google.com/calendar/ical/7djv171v2mdr4dmufq612j6uj4%40group.calendar.google.com/public/basic.ics" -MONTH_NUMBER = {month.lower(): index for index, month in enumerate(month_abbr)} +MONTH_NUMBER: Dict[str, int] = { + month.lower(): index for index, month in enumerate(month_abbr) +} -MAX_RECURRING_EVENTS = 3 BRISBANE_TZ = timezone("Australia/Brisbane") +MAX_RECURRING_EVENTS = 3 + class EventFilter(object): def __init__(self, full=False, weeks=None, cap=None, month=None, is_valid=True): diff --git a/uqcsbot/gaming.py b/uqcsbot/gaming.py index 33f20bf..9793f6e 100644 --- a/uqcsbot/gaming.py +++ b/uqcsbot/gaming.py @@ -1,7 +1,6 @@ from difflib import SequenceMatcher -from html import unescape from json import loads -from typing import Optional +from typing import Optional, Dict, Any from urllib.error import HTTPError from urllib.request import urlopen from xml.etree.ElementTree import fromstring @@ -12,7 +11,6 @@ from requests import get from uqcsbot.bot import UQCSBot -from uqcsbot.utils.command_utils import loading_status class Gaming(commands.Cog): @@ -23,7 +21,6 @@ class Gaming(commands.Cog): def __init__(self, bot: UQCSBot): self.bot = bot - @classmethod def get_bgg_id(self, search_name: str) -> Optional[str]: """ returns the bgg id, searching by name @@ -50,8 +47,7 @@ def get_bgg_id(self, search_name: str) -> Optional[str]: ).ratio() return max(match, key=match.get) - @classmethod - def get_board_game_parameters(self, identity: str) -> Optional[dict]: + def get_board_game_parameters(self, identity: str) -> Optional[Dict[str, str]]: """ returns the various parameters of a board game from bgg """ @@ -174,8 +170,7 @@ def get_board_game_parameters(self, identity: str) -> Optional[dict]: return parameters - @classmethod - def format_board_game_parameters(self, parameters: dict) -> discord.Embed: + def format_board_game_parameters(self, parameters: Dict[str, str]) -> discord.Embed: embed = discord.Embed(title=parameters.get("name", ":question:")) embed.add_field( name="Summary", @@ -231,13 +226,15 @@ async def bgg(self, interaction: discord.Interaction, board_game: str): identity = self.get_bgg_id(board_game) if identity is None: await interaction.edit_original_response( - "Could not find board game with that name." + content="Could not find board game with that name." ) return parameters = self.get_board_game_parameters(identity) if parameters is None: - await interaction.edit_original_response("Something has gone wrong.") + await interaction.edit_original_response( + content="Something has gone wrong." + ) return embed = self.format_board_game_parameters(parameters) @@ -269,28 +266,30 @@ async def scry(self, interaction: discord.Interaction, card: Optional[str]): fault = loads(e.read()) if fault.get("type") == "ambiguous": await interaction.edit_original_response( - "Request 404'd; Multiple Possible Cards" + content="Request 404'd; Multiple Possible Cards" ) else: await interaction.edit_original_response( - "Request 404'd; No Cards Found" + content="Request 404'd; No Cards Found" ) return - await interaction.edit_original_response(str(e)) + await interaction.edit_original_response(content=str(e)) return - card = loads(response.read()) - if "image_uris" in card: + card_obj: Any = loads(response.read()) + if "image_uris" in card_obj: # single faced cards - await interaction.edit_original_response(content=card["image_uris"]["png"]) + await interaction.edit_original_response( + content=card_obj["image_uris"]["png"] + ) else: # double faced cards await interaction.edit_original_response( content="\n".join( - face["image_uris"]["png"] for face in card["card_faces"] + face["image_uris"]["png"] for face in card_obj["card_faces"] ) ) -async def setup(bot: commands.Bot): +async def setup(bot: UQCSBot): await bot.add_cog(Gaming(bot)) diff --git a/uqcsbot/holidays.py b/uqcsbot/holidays.py index fe7cbe7..d4eaea0 100644 --- a/uqcsbot/holidays.py +++ b/uqcsbot/holidays.py @@ -40,7 +40,7 @@ def get_holiday() -> Holiday | None: return None geek_holidays = get_holidays_from_csv() - holidays = get_holidays_from_page(holiday_page) + holidays = get_holidays_from_page(holiday_page.decode("utf-8")) holidays_today = [ holiday for holiday in holidays + geek_holidays if holiday.is_today() @@ -49,7 +49,7 @@ def get_holiday() -> Holiday | None: return choice(holidays_today) if holidays_today else None -def get_holidays_from_page(holiday_page) -> List[Holiday]: +def get_holidays_from_page(holiday_page: str) -> List[Holiday]: """Strips results from html page""" soup = BeautifulSoup(holiday_page, "html.parser") soup_holidays = ( @@ -58,7 +58,7 @@ def get_holidays_from_page(holiday_page) -> List[Holiday]: + soup.find_all(class_="hl") ) - holidays = [] + holidays: List[Holiday] = [] for soup_holiday in soup_holidays: date_string = soup_holiday.find("th").get_text(strip=True) @@ -76,7 +76,7 @@ def get_holidays_from_csv() -> List[Holiday]: Returns list of holiday objects, one for each holiday in csv file csv rows in format: date,description,link """ - holidays = [] + holidays: List[Holiday] = [] with open(HOLIDAY_CSV_PATH, "r") as csvfile: for row in csv.reader(csvfile): date = datetime.strptime(row[0], "%d %b") @@ -95,7 +95,6 @@ def get_holiday_page() -> bytes | None: return response.content except RequestException as e: logging.warning(e.response.content) - return None class Holidays(commands.Cog): @@ -118,12 +117,8 @@ async def holiday(self): if holiday is None: return - if self.bot.uqcs_server is None: - logging.warning("UQCS guild not found (?!).") - return - general_channel = discord.utils.get( - self.bot.uqcs_server.channels, name=GENERAL_CHANNEL + self.bot.uqcs_server.channels, name=self.bot.GENERAL_CNAME ) if general_channel is None: logging.warning(f"Could not find required channel #{GENERAL_CHANNEL}") diff --git a/uqcsbot/hoogle.py b/uqcsbot/hoogle.py index 78c3056..04cb054 100644 --- a/uqcsbot/hoogle.py +++ b/uqcsbot/hoogle.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Dict import discord from discord import app_commands @@ -26,7 +26,7 @@ def get_endpoint(self, type_sig: str) -> str: def get_hoogle_page(self, type_sig: str) -> str: return f"https://hoogle.haskell.org/?hoogle={html.unescape(type_sig)}" - def pretty_hoogle_result(self, result: dict) -> str: + def pretty_hoogle_result(self, result: Dict[str, str]) -> str: url = result["url"] # convert url special chars to readable form type_sig = ( diff --git a/uqcsbot/intros.py b/uqcsbot/intros.py index dc3e028..f529329 100644 --- a/uqcsbot/intros.py +++ b/uqcsbot/intros.py @@ -1,6 +1,5 @@ import discord from discord.ext import commands - from uqcsbot.bot import UQCSBot @@ -23,5 +22,5 @@ async def on_message(self, msg: discord.Message): await msg.add_reaction("👋") -async def setup(bot: commands.Bot): +async def setup(bot: UQCSBot): await bot.add_cog(Intros(bot)) diff --git a/uqcsbot/jobs_bulletin.py b/uqcsbot/jobs_bulletin.py index afdcdb7..d79a683 100644 --- a/uqcsbot/jobs_bulletin.py +++ b/uqcsbot/jobs_bulletin.py @@ -2,6 +2,8 @@ import logging from discord.ext import commands +from uqcsbot.bot import UQCSBot + class JobsBulletin(commands.Cog): CHANNEL_NAME = "jobs-bulletin" @@ -41,7 +43,7 @@ class JobsBulletin(commands.Cog): + f" #uqcs-meta or by email at {UQCS_EMAIL}.", ] - def __init__(self, bot: commands.Bot): + def __init__(self, bot: UQCSBot): self.bot = bot @commands.Cog.listener() @@ -99,5 +101,5 @@ async def on_message(self, msg: discord.Message): await msg.author.send(embed=user_message) -async def setup(bot: commands.Bot): +async def setup(bot: UQCSBot): await bot.add_cog(JobsBulletin(bot)) diff --git a/uqcsbot/minecraft.py b/uqcsbot/minecraft.py index 211f8f2..93f2a83 100644 --- a/uqcsbot/minecraft.py +++ b/uqcsbot/minecraft.py @@ -3,12 +3,13 @@ from datetime import datetime import discord -from aiomcrcon import Client, IncorrectPasswordError, RCONConnectionError +from aiomcrcon import Client, IncorrectPasswordError, RCONConnectionError # type: ignore from discord import Member, app_commands, Colour from discord.ext import commands from uqcsbot.bot import UQCSBot from uqcsbot.models import MCWhitelist +from uqcsbot.utils.err_log_utils import FatalErrorWithLog RCON_ADDRESS = os.environ.get("MC_RCON_ADDRESS") RCON_PORT = os.environ.get("MC_RCON_PORT") @@ -31,7 +32,12 @@ async def send_rcon_command(self, command: str): An ID of -1 is returned if there was an issue connecting to the server. """ try: - async with Client(RCON_ADDRESS, RCON_PORT, RCON_PASSWORD) as client: + if RCON_ADDRESS is None or RCON_PORT is None or RCON_PASSWORD is None: + raise FatalErrorWithLog( + self.bot, "Attempted to send RCON command but couldn't log in!" + ) + + async with Client(RCON_ADDRESS, int(RCON_PORT), RCON_PASSWORD) as client: response = await client.send_cmd(command) except RCONConnectionError: @@ -127,5 +133,5 @@ async def mcadmin(self, interaction: discord.Interaction, command: str): ) -async def setup(bot: commands.Bot): +async def setup(bot: UQCSBot): await bot.add_cog(Minecraft(bot)) diff --git a/uqcsbot/models.py b/uqcsbot/models.py index 6e01a04..42bae75 100644 --- a/uqcsbot/models.py +++ b/uqcsbot/models.py @@ -1,56 +1,56 @@ -from sqlalchemy.orm import declarative_base +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column from sqlalchemy import ( BigInteger, Boolean, - Column, Date, DateTime, Integer, String, Time, ) +from typing import Optional +from datetime import datetime -Base = declarative_base() - -# Used for linking a message to a bot function. -# Previously used for the channel cog, currently unused. -class Message(Base): - __tablename__ = "messages" - - id = Column("id", BigInteger, primary_key=True, nullable=False) - type = Column("type", String, nullable=False) +class Base(DeclarativeBase): + pass class AOCWinner(Base): __tablename__ = "aoc_winner" - id = Column("id", BigInteger, primary_key=True, nullable=False) - aoc_userid = Column("aoc_userid", Integer, nullable=False) - year = Column("year", Integer, nullable=False) + id: Mapped[int] = mapped_column("id", BigInteger, primary_key=True, nullable=False) + aoc_userid: Mapped[int] = mapped_column("aoc_userid", Integer, nullable=False) + year: Mapped[int] = mapped_column("year", Integer, nullable=False) class MCWhitelist(Base): __tablename__ = "mc_whitelisted" - mc_username = Column("mcuser", String, primary_key=True, nullable=False) - discord_id = Column("discordid", BigInteger, nullable=False) - admin_whitelisted = Column("adminwl", Boolean) - added_dt = Column("added_dt", DateTime, nullable=False) + mc_username: Mapped[str] = mapped_column( + "mcuser", String, primary_key=True, nullable=False + ) + discord_id: Mapped[str] = mapped_column("discordid", BigInteger, nullable=False) + admin_whitelisted: Mapped[bool] = mapped_column("adminwl", Boolean) + added_dt: Mapped[datetime] = mapped_column("added_dt", DateTime, nullable=False) class Reminders(Base): __tablename__ = "reminders" - id = Column("id", BigInteger, primary_key=True, nullable=False) - user_id = Column("user_id", BigInteger, nullable=False) - channel_id = Column("channel_id", BigInteger, nullable=True) - time_created = Column("time_created", DateTime, nullable=False) - message = Column("message", String, nullable=False) - time = Column("time", Time, nullable=False) - start_date = Column("start_date", Date, nullable=False) - end_date = Column("end_date", Date, nullable=True) - week_frequency = Column("week_frequency", Integer, nullable=True) + id: Mapped[int] = mapped_column("id", BigInteger, primary_key=True, nullable=False) + user_id: Mapped[int] = mapped_column("user_id", BigInteger, nullable=False) + channel_id: Mapped[Optional[int]] = mapped_column( + "channel_id", BigInteger, nullable=True + ) + time_created: Mapped[int] = mapped_column("time_created", DateTime, nullable=False) + message: Mapped[str] = mapped_column("message", String, nullable=False) + time = mapped_column("time", Time, nullable=False) + start_date = mapped_column("start_date", Date, nullable=False) + end_date = mapped_column("end_date", Date, nullable=True) + week_frequency: Mapped[Optional[int]] = mapped_column( + "week_frequency", Integer, nullable=True + ) class Starboard(Base): @@ -61,6 +61,12 @@ class Starboard(Base): # recv == null implies deleted recv message. # recv_location == null implies deleted recv channel. recv should also be null. # sent == null implies blacklisted recv message. - recv = Column("recv", BigInteger, primary_key=True, nullable=True) - recv_location = Column("recv_location", BigInteger, nullable=True, unique=False) - sent = Column("sent", BigInteger, primary_key=True, nullable=True, unique=True) + recv: Mapped[Optional[int]] = mapped_column( + "recv", BigInteger, primary_key=True, nullable=True + ) + recv_location: Mapped[Optional[int]] = mapped_column( + "recv_location", BigInteger, nullable=True, unique=False + ) + sent: Mapped[Optional[int]] = mapped_column( + "sent", BigInteger, primary_key=True, nullable=True, unique=True + ) diff --git a/uqcsbot/text.py b/uqcsbot/text.py index b7979a6..c8f4171 100644 --- a/uqcsbot/text.py +++ b/uqcsbot/text.py @@ -1,6 +1,6 @@ from random import choice, randrange from string import hexdigits -from typing import Optional +from typing import Optional, List import discord from discord import app_commands @@ -74,11 +74,13 @@ async def binify( self, interaction: discord.Interaction, message: str, - encoding: Optional[str] = "utf-8", + encoding: Optional[str], ): """ Converts a binary string to text or vice versa. """ + encoding = encoding if encoding is not None else "utf-8" + if not message: response = "Please include string to convert." elif set(message).issubset(["0", "1"]) and len(message) > 2: @@ -114,13 +116,15 @@ async def caesar( self, interaction: discord.Interaction, message: str, - distance: Optional[int] = 13, + distance: Optional[int], ): """ Performs caesar shift with a shift of N on given text. N defaults to 13 if not given. """ + distance = distance if distance is not None else 13 result = "" + for c in message: if ord("A") <= ord(c) <= ord("Z"): result += chr((ord(c) - ord("A") + distance) % 26 + ord("A")) @@ -140,21 +144,23 @@ async def hexify( self, interaction: discord.Interaction, message: str, - encoding: Optional[str] = "utf-8", + encoding: Optional[str], ): """ Converts a hexadecimal string to text or vice versa. """ + encoding = encoding if encoding is not None else "utf-8" + if not message: response = "Please include string to convert." elif all(c in hexdigits for c in message) and len(message) > 2: try: decoded_message = bytes.fromhex(message) response = decoded_message.decode(encoding) - except ValueError: - response = "Hexadecimal string contains partial byte." except UnicodeDecodeError as e: response = e.reason + except ValueError: + response = "Hexadecimal string contains partial byte." except LookupError: response = "Invalid encoding. A list of valid encodings can be found at " else: @@ -181,17 +187,17 @@ async def httpcat(self, interaction: discord.Interaction, code: int): @app_commands.command() @app_commands.describe(number="Number of coins to flip, defaults to 1.") - async def coin(self, interaction: discord.Interaction, number: Optional[int] = 1): + async def coin(self, interaction: discord.Interaction, number: Optional[int]): """ Flips 1 to 99 coins. Defaults to 1 coin if number not given. """ - if not (1 <= number and number <= 99): + if number is not None and not (1 <= number and number <= 99): await interaction.response.send_message("Number of coins invalid.") else: - response = [] + response: List[str] = [] result = ("H", "T") - for i in range(number): + for _ in range((number if number is not None else 1)): response.append(choice(result)) await interaction.response.send_message(f"`{', '.join(response)}`") @@ -239,7 +245,7 @@ def zalgo_common(self, message: str) -> str: response = "" for c in " ".join(message): response += c - for i in range(randrange(7) // 3): + for _ in range(randrange(7) // 3): response += choice(self.zalgo_marks) return response diff --git a/uqcsbot/utils/command_utils.py b/uqcsbot/utils/command_utils.py index 51d26c0..b81bd74 100644 --- a/uqcsbot/utils/command_utils.py +++ b/uqcsbot/utils/command_utils.py @@ -23,7 +23,7 @@ async def wrapper(self, ctx: commands.Context, *args): return react = choice(LOADING_REACTS) - reaction = await ctx.message.add_reaction(react) + await ctx.message.add_reaction(react) res = await command_fn(self, ctx, *args) await ctx.message.remove_reaction(react, ctx.bot.user) return res diff --git a/uqcsbot/utils/err_log_utils.py b/uqcsbot/utils/err_log_utils.py index f0c1483..c003f41 100644 --- a/uqcsbot/utils/err_log_utils.py +++ b/uqcsbot/utils/err_log_utils.py @@ -5,17 +5,11 @@ class FatalErrorWithLog(Exception): - def __init__( - self, - client: commands.Bot, - message: str, - *args, - **kwargs, - ): + def __init__(self, client: commands.Bot, message: str): modlog = discord.utils.get(client.get_all_channels(), name=MODLOG_CHANNEL_NAME) - if modlog is not None: + if modlog is not None and isinstance(modlog, discord.TextChannel): client.loop.create_task(modlog.send(message)) else: message += f" ...And also, I couldn't find #{MODLOG_CHANNEL_NAME} to log this properly." - super().__init__(message, *args, **kwargs) + super().__init__(message) diff --git a/uqcsbot/whatsdue.py b/uqcsbot/whatsdue.py index cbdddc6..8c41828 100644 --- a/uqcsbot/whatsdue.py +++ b/uqcsbot/whatsdue.py @@ -6,7 +6,6 @@ from discord import app_commands from discord.ext import commands -from uqcsbot.utils.command_utils import loading_status from uqcsbot.utils.uq_course_utils import ( CourseNotFoundException, HttpException, diff --git a/uqcsbot/xkcd.py b/uqcsbot/xkcd.py index 7e927ff..3cb6368 100644 --- a/uqcsbot/xkcd.py +++ b/uqcsbot/xkcd.py @@ -2,7 +2,7 @@ import re import html -from typing import Optional +from typing import Optional, Tuple import discord from discord import app_commands @@ -73,7 +73,7 @@ async def xkcd_command( await interaction.edit_original_response(embed=message) @staticmethod - def get_xkcd_data(url: str) -> (int, str, str, str): + def get_xkcd_data(url: str) -> Tuple[int, str, str, str]: """ Returns the xkcd data from the given url. @@ -90,7 +90,7 @@ def get_xkcd_data(url: str) -> (int, str, str, str): return Xkcd.parse_xkcd_page(response.content) @staticmethod - def parse_xkcd_page(content: str) -> (int, str, str, str): + def parse_xkcd_page(content: bytes) -> Tuple[int, str, str, str]: """ Parses the xkcd page content and returns the xkcd number, title, description and image url. This function can allow offline testing.