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.