diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index ed8f52f..f5e5bfa 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -9,7 +9,7 @@ permissions: jobs: Linting: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: 'Checkout Repository' uses: actions/checkout@v4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b5f9238..755664d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/gitleaks/gitleaks - rev: v8.18.4 + rev: v8.19.2 hooks: - id: gitleaks @@ -12,7 +12,7 @@ repos: - id: check-toml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.4 + rev: v0.6.7 hooks: # Run the linter. - id: ruff diff --git a/config/settings.yml.example b/config/settings.yml.example index 8cfa314..5437cb0 100644 --- a/config/settings.yml.example +++ b/config/settings.yml.example @@ -10,8 +10,8 @@ USER_IDS: - 123456789012345679 BOT_OWNER: 123456789012345679 -TEMPVC_CATEGORY_ID: 123456789012345 -TEMPVC_CHANNEL_ID: 123456789012345 +TEMPVC_CATEGORY_ID: 123456789012345679 +TEMPVC_CHANNEL_ID: 123456789012345679 EMBED_COLORS: DEFAULT: 16044058 diff --git a/poetry.lock b/poetry.lock index e090a35..907d6d8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,13 +2,13 @@ [[package]] name = "aiocache" -version = "0.12.2" +version = "0.12.3" description = "multi backend asyncio cache" optional = false python-versions = "*" files = [ - {file = "aiocache-0.12.2-py2.py3-none-any.whl", hash = "sha256:9b6fa30634ab0bfc3ecc44928a91ff07c6ea16d27d55469636b296ebc6eb5918"}, - {file = "aiocache-0.12.2.tar.gz", hash = "sha256:b41c9a145b050a5dcbae1599f847db6dd445193b1f3bd172d8e0fe0cb9e96684"}, + {file = "aiocache-0.12.3-py2.py3-none-any.whl", hash = "sha256:889086fc24710f431937b87ad3720a289f7fc31c4fd8b68e9f918b9bacd8270d"}, + {file = "aiocache-0.12.3.tar.gz", hash = "sha256:f528b27bf4d436b497a1d0d1a8f59a542c153ab1e37c3621713cb376d44c4713"}, ] [package.extras] @@ -190,13 +190,13 @@ files = [ [[package]] name = "anyio" -version = "4.4.0" +version = "4.5.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.8" files = [ - {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, - {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, + {file = "anyio-4.5.0-py3-none-any.whl", hash = "sha256:fdeb095b7cc5a5563175eedd926ec4ae55413bb4be5770c424af0ba46ccb4a78"}, + {file = "anyio-4.5.0.tar.gz", hash = "sha256:c5a275fe5ca0afd788001f58fca1e69e29ce706d746e317d660e21f70c530ef9"}, ] [package.dependencies] @@ -204,9 +204,9 @@ idna = ">=2.8" sniffio = ">=1.1" [package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.23)"] +doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.21.0b1)"] +trio = ["trio (>=0.26.1)"] [[package]] name = "astunparse" @@ -692,18 +692,18 @@ files = [ [[package]] name = "filelock" -version = "3.16.0" +version = "3.16.1" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.16.0-py3-none-any.whl", hash = "sha256:f6ed4c963184f4c84dd5557ce8fece759a3724b37b80c6c4f20a2f63a4dc6609"}, - {file = "filelock-3.16.0.tar.gz", hash = "sha256:81de9eb8453c769b63369f87f11131a7ab04e367f8d97ad39dc230daa07e3bec"}, + {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, + {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, ] [package.extras] -docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.1.1)", "pytest (>=8.3.2)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.3)"] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] typing = ["typing-extensions (>=4.12.2)"] [[package]] @@ -811,13 +811,13 @@ dev = ["flake8", "markdown", "twine", "wheel"] [[package]] name = "githubkit" -version = "0.11.9" +version = "0.11.10" description = "GitHub SDK for Python" optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "githubkit-0.11.9-py3-none-any.whl", hash = "sha256:68933ff9867467418c90176e2ea77c7623fa7ab8fbf64723c2ca56c09b14a0f6"}, - {file = "githubkit-0.11.9.tar.gz", hash = "sha256:d25da1a667cce32f3d1ab0f33cf52fa328a55e85a6916e19da2765b0602c472a"}, + {file = "githubkit-0.11.10-py3-none-any.whl", hash = "sha256:e95074916af4a5f357d47005022a555984e16729d9e6204161ef67bc29a78789"}, + {file = "githubkit-0.11.10.tar.gz", hash = "sha256:9729783ba44935ca47b3bebaad76971ccc3d3b548b8af54e06d16a489b0ece90"}, ] [package.dependencies] @@ -914,13 +914,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "identify" -version = "2.6.0" +version = "2.6.1" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, - {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, + {file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"}, + {file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"}, ] [package.extras] @@ -928,13 +928,13 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.9" +version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" files = [ - {file = "idna-3.9-py3-none-any.whl", hash = "sha256:69297d5da0cc9281c77efffb4e730254dd45943f45bbfb461de5991713989b1e"}, - {file = "idna-3.9.tar.gz", hash = "sha256:e5c5dafde284f26e9e0f28f6ea2d6400abd5ca099864a67f576f3981c6476124"}, + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] [package.extras] @@ -1160,13 +1160,13 @@ pyyaml = ">=5.1" [[package]] name = "mkdocs-material" -version = "9.5.34" +version = "9.5.38" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.5.34-py3-none-any.whl", hash = "sha256:54caa8be708de2b75167fd4d3b9f3d949579294f49cb242515d4653dbee9227e"}, - {file = "mkdocs_material-9.5.34.tar.gz", hash = "sha256:1e60ddf716cfb5679dfd65900b8a25d277064ed82d9a53cd5190e3f894df7840"}, + {file = "mkdocs_material-9.5.38-py3-none-any.whl", hash = "sha256:d4779051d52ba9f1e7e344b34de95449c7c366c212b388e4a2db9a3db043c228"}, + {file = "mkdocs_material-9.5.38.tar.gz", hash = "sha256:1843c5171ad6b489550aeaf7358e5b7128cc03ddcf0fb4d91d19aa1e691a63b8"}, ] [package.dependencies] @@ -1446,13 +1446,13 @@ xmp = ["defusedxml"] [[package]] name = "platformdirs" -version = "4.3.3" +version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.3.3-py3-none-any.whl", hash = "sha256:50a5450e2e84f44539718293cbb1da0a0885c9d14adf21b77bae4e66fc99d9b5"}, - {file = "platformdirs-4.3.3.tar.gz", hash = "sha256:d4e0b7d8ec176b341fb03cb11ca12d0276faa8c485f9cd218f613840463fc2c0"}, + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, ] [package.extras] @@ -1556,18 +1556,18 @@ files = [ [[package]] name = "pydantic" -version = "2.9.1" +version = "2.9.2" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.9.1-py3-none-any.whl", hash = "sha256:7aff4db5fdf3cf573d4b3c30926a510a10e19a0774d38fc4967f78beb6deb612"}, - {file = "pydantic-2.9.1.tar.gz", hash = "sha256:1363c7d975c7036df0db2b4a61f2e062fbc0aa5ab5f2772e0ffc7191a4f4bce2"}, + {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, + {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.23.3" +pydantic-core = "2.23.4" typing-extensions = [ {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, {version = ">=4.6.1", markers = "python_version < \"3.13\""}, @@ -1579,100 +1579,100 @@ timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.23.3" +version = "2.23.4" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.23.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7f10a5d1b9281392f1bf507d16ac720e78285dfd635b05737c3911637601bae6"}, - {file = "pydantic_core-2.23.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c09a7885dd33ee8c65266e5aa7fb7e2f23d49d8043f089989726391dd7350c5"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6470b5a1ec4d1c2e9afe928c6cb37eb33381cab99292a708b8cb9aa89e62429b"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9172d2088e27d9a185ea0a6c8cebe227a9139fd90295221d7d495944d2367700"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86fc6c762ca7ac8fbbdff80d61b2c59fb6b7d144aa46e2d54d9e1b7b0e780e01"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0cb80fd5c2df4898693aa841425ea1727b1b6d2167448253077d2a49003e0ed"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03667cec5daf43ac4995cefa8aaf58f99de036204a37b889c24a80927b629cec"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:047531242f8e9c2db733599f1c612925de095e93c9cc0e599e96cf536aaf56ba"}, - {file = "pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5499798317fff7f25dbef9347f4451b91ac2a4330c6669821c8202fd354c7bee"}, - {file = "pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bbb5e45eab7624440516ee3722a3044b83fff4c0372efe183fd6ba678ff681fe"}, - {file = "pydantic_core-2.23.3-cp310-none-win32.whl", hash = "sha256:8b5b3ed73abb147704a6e9f556d8c5cb078f8c095be4588e669d315e0d11893b"}, - {file = "pydantic_core-2.23.3-cp310-none-win_amd64.whl", hash = "sha256:2b603cde285322758a0279995b5796d64b63060bfbe214b50a3ca23b5cee3e83"}, - {file = "pydantic_core-2.23.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c889fd87e1f1bbeb877c2ee56b63bb297de4636661cc9bbfcf4b34e5e925bc27"}, - {file = "pydantic_core-2.23.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea85bda3189fb27503af4c45273735bcde3dd31c1ab17d11f37b04877859ef45"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7f7f72f721223f33d3dc98a791666ebc6a91fa023ce63733709f4894a7dc611"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b2b55b0448e9da68f56b696f313949cda1039e8ec7b5d294285335b53104b61"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c24574c7e92e2c56379706b9a3f07c1e0c7f2f87a41b6ee86653100c4ce343e5"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2b05e6ccbee333a8f4b8f4d7c244fdb7a979e90977ad9c51ea31261e2085ce0"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2c409ce1c219c091e47cb03feb3c4ed8c2b8e004efc940da0166aaee8f9d6c8"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d965e8b325f443ed3196db890d85dfebbb09f7384486a77461347f4adb1fa7f8"}, - {file = "pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f56af3a420fb1ffaf43ece3ea09c2d27c444e7c40dcb7c6e7cf57aae764f2b48"}, - {file = "pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b01a078dd4f9a52494370af21aa52964e0a96d4862ac64ff7cea06e0f12d2c5"}, - {file = "pydantic_core-2.23.3-cp311-none-win32.whl", hash = "sha256:560e32f0df04ac69b3dd818f71339983f6d1f70eb99d4d1f8e9705fb6c34a5c1"}, - {file = "pydantic_core-2.23.3-cp311-none-win_amd64.whl", hash = "sha256:c744fa100fdea0d000d8bcddee95213d2de2e95b9c12be083370b2072333a0fa"}, - {file = "pydantic_core-2.23.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e0ec50663feedf64d21bad0809f5857bac1ce91deded203efc4a84b31b2e4305"}, - {file = "pydantic_core-2.23.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db6e6afcb95edbe6b357786684b71008499836e91f2a4a1e55b840955b341dbb"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ccd69edcf49f0875d86942f4418a4e83eb3047f20eb897bffa62a5d419c8fa"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a678c1ac5c5ec5685af0133262103defb427114e62eafeda12f1357a12140162"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01491d8b4d8db9f3391d93b0df60701e644ff0894352947f31fff3e52bd5c801"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fcf31facf2796a2d3b7fe338fe8640aa0166e4e55b4cb108dbfd1058049bf4cb"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7200fd561fb3be06827340da066df4311d0b6b8eb0c2116a110be5245dceb326"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc1636770a809dee2bd44dd74b89cc80eb41172bcad8af75dd0bc182c2666d4c"}, - {file = "pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:67a5def279309f2e23014b608c4150b0c2d323bd7bccd27ff07b001c12c2415c"}, - {file = "pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:748bdf985014c6dd3e1e4cc3db90f1c3ecc7246ff5a3cd4ddab20c768b2f1dab"}, - {file = "pydantic_core-2.23.3-cp312-none-win32.whl", hash = "sha256:255ec6dcb899c115f1e2a64bc9ebc24cc0e3ab097775755244f77360d1f3c06c"}, - {file = "pydantic_core-2.23.3-cp312-none-win_amd64.whl", hash = "sha256:40b8441be16c1e940abebed83cd006ddb9e3737a279e339dbd6d31578b802f7b"}, - {file = "pydantic_core-2.23.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6daaf5b1ba1369a22c8b050b643250e3e5efc6a78366d323294aee54953a4d5f"}, - {file = "pydantic_core-2.23.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d015e63b985a78a3d4ccffd3bdf22b7c20b3bbd4b8227809b3e8e75bc37f9cb2"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3fc572d9b5b5cfe13f8e8a6e26271d5d13f80173724b738557a8c7f3a8a3791"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f6bd91345b5163ee7448bee201ed7dd601ca24f43f439109b0212e296eb5b423"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc379c73fd66606628b866f661e8785088afe2adaba78e6bbe80796baf708a63"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbdce4b47592f9e296e19ac31667daed8753c8367ebb34b9a9bd89dacaa299c9"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3cf31edf405a161a0adad83246568647c54404739b614b1ff43dad2b02e6d5"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e22b477bf90db71c156f89a55bfe4d25177b81fce4aa09294d9e805eec13855"}, - {file = "pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:0a0137ddf462575d9bce863c4c95bac3493ba8e22f8c28ca94634b4a1d3e2bb4"}, - {file = "pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:203171e48946c3164fe7691fc349c79241ff8f28306abd4cad5f4f75ed80bc8d"}, - {file = "pydantic_core-2.23.3-cp313-none-win32.whl", hash = "sha256:76bdab0de4acb3f119c2a4bff740e0c7dc2e6de7692774620f7452ce11ca76c8"}, - {file = "pydantic_core-2.23.3-cp313-none-win_amd64.whl", hash = "sha256:37ba321ac2a46100c578a92e9a6aa33afe9ec99ffa084424291d84e456f490c1"}, - {file = "pydantic_core-2.23.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d063c6b9fed7d992bcbebfc9133f4c24b7a7f215d6b102f3e082b1117cddb72c"}, - {file = "pydantic_core-2.23.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6cb968da9a0746a0cf521b2b5ef25fc5a0bee9b9a1a8214e0a1cfaea5be7e8a4"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edbefe079a520c5984e30e1f1f29325054b59534729c25b874a16a5048028d16"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cbaaf2ef20d282659093913da9d402108203f7cb5955020bd8d1ae5a2325d1c4"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb539d7e5dc4aac345846f290cf504d2fd3c1be26ac4e8b5e4c2b688069ff4cf"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e6f33503c5495059148cc486867e1d24ca35df5fc064686e631e314d959ad5b"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04b07490bc2f6f2717b10c3969e1b830f5720b632f8ae2f3b8b1542394c47a8e"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03795b9e8a5d7fda05f3873efc3f59105e2dcff14231680296b87b80bb327295"}, - {file = "pydantic_core-2.23.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c483dab0f14b8d3f0df0c6c18d70b21b086f74c87ab03c59250dbf6d3c89baba"}, - {file = "pydantic_core-2.23.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b2682038e255e94baf2c473dca914a7460069171ff5cdd4080be18ab8a7fd6e"}, - {file = "pydantic_core-2.23.3-cp38-none-win32.whl", hash = "sha256:f4a57db8966b3a1d1a350012839c6a0099f0898c56512dfade8a1fe5fb278710"}, - {file = "pydantic_core-2.23.3-cp38-none-win_amd64.whl", hash = "sha256:13dd45ba2561603681a2676ca56006d6dee94493f03d5cadc055d2055615c3ea"}, - {file = "pydantic_core-2.23.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:82da2f4703894134a9f000e24965df73cc103e31e8c31906cc1ee89fde72cbd8"}, - {file = "pydantic_core-2.23.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dd9be0a42de08f4b58a3cc73a123f124f65c24698b95a54c1543065baca8cf0e"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89b731f25c80830c76fdb13705c68fef6a2b6dc494402987c7ea9584fe189f5d"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6de1ec30c4bb94f3a69c9f5f2182baeda5b809f806676675e9ef6b8dc936f28"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb68b41c3fa64587412b104294b9cbb027509dc2f6958446c502638d481525ef"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c3980f2843de5184656aab58698011b42763ccba11c4a8c35936c8dd6c7068c"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94f85614f2cba13f62c3c6481716e4adeae48e1eaa7e8bac379b9d177d93947a"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:510b7fb0a86dc8f10a8bb43bd2f97beb63cffad1203071dc434dac26453955cd"}, - {file = "pydantic_core-2.23.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1eba2f7ce3e30ee2170410e2171867ea73dbd692433b81a93758ab2de6c64835"}, - {file = "pydantic_core-2.23.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b259fd8409ab84b4041b7b3f24dcc41e4696f180b775961ca8142b5b21d0e70"}, - {file = "pydantic_core-2.23.3-cp39-none-win32.whl", hash = "sha256:40d9bd259538dba2f40963286009bf7caf18b5112b19d2b55b09c14dde6db6a7"}, - {file = "pydantic_core-2.23.3-cp39-none-win_amd64.whl", hash = "sha256:5a8cd3074a98ee70173a8633ad3c10e00dcb991ecec57263aacb4095c5efb958"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f399e8657c67313476a121a6944311fab377085ca7f490648c9af97fc732732d"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:6b5547d098c76e1694ba85f05b595720d7c60d342f24d5aad32c3049131fa5c4"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0dda0290a6f608504882d9f7650975b4651ff91c85673341789a476b1159f211"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b6e5da855e9c55a0c67f4db8a492bf13d8d3316a59999cfbaf98cc6e401961"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:09e926397f392059ce0afdcac920df29d9c833256354d0c55f1584b0b70cf07e"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:87cfa0ed6b8c5bd6ae8b66de941cece179281239d482f363814d2b986b79cedc"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e61328920154b6a44d98cabcb709f10e8b74276bc709c9a513a8c37a18786cc4"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce3317d155628301d649fe5e16a99528d5680af4ec7aa70b90b8dacd2d725c9b"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e89513f014c6be0d17b00a9a7c81b1c426f4eb9224b15433f3d98c1a071f8433"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:4f62c1c953d7ee375df5eb2e44ad50ce2f5aff931723b398b8bc6f0ac159791a"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2718443bc671c7ac331de4eef9b673063b10af32a0bb385019ad61dcf2cc8f6c"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d90e08b2727c5d01af1b5ef4121d2f0c99fbee692c762f4d9d0409c9da6541"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b676583fc459c64146debea14ba3af54e540b61762dfc0613dc4e98c3f66eeb"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:50e4661f3337977740fdbfbae084ae5693e505ca2b3130a6d4eb0f2281dc43b8"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:68f4cf373f0de6abfe599a38307f4417c1c867ca381c03df27c873a9069cda25"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:59d52cf01854cb26c46958552a21acb10dd78a52aa34c86f284e66b209db8cab"}, - {file = "pydantic_core-2.23.3.tar.gz", hash = "sha256:3cb0f65d8b4121c1b015c60104a685feb929a29d7cf204387c7f2688c7974690"}, + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, + {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, + {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, + {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, + {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, + {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, + {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, + {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, + {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, + {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, + {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, + {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, + {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, + {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, ] [package.dependencies] @@ -1758,21 +1758,23 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] [[package]] name = "pyright" -version = "1.1.380" +version = "1.1.382" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.380-py3-none-any.whl", hash = "sha256:a6404392053d8848bacc7aebcbd9d318bb46baf1a1a000359305481920f43879"}, - {file = "pyright-1.1.380.tar.gz", hash = "sha256:e6ceb1a5f7e9f03106e0aa1d6fbb4d97735a5e7ffb59f3de6b2db590baf935b2"}, + {file = "pyright-1.1.382-py3-none-any.whl", hash = "sha256:b6658802b2eca1107a6d63b6816903da4c7a53dffebc27cdbc02ebd904b7a18e"}, + {file = "pyright-1.1.382.tar.gz", hash = "sha256:0c953837aa9f1e1d8d46772ee7ebae845104db657e9216834dbdde567a11f177"}, ] [package.dependencies] nodeenv = ">=1.6.0" +typing-extensions = ">=4.1" [package.extras] -all = ["twine (>=3.4.1)"] +all = ["nodejs-wheel-binaries", "twine (>=3.4.1)"] dev = ["twine (>=3.4.1)"] +nodejs = ["nodejs-wheel-binaries"] [[package]] name = "python-dateutil" @@ -2043,29 +2045,29 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruff" -version = "0.6.5" +version = "0.6.8" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.6.5-py3-none-linux_armv6l.whl", hash = "sha256:7e4e308f16e07c95fc7753fc1aaac690a323b2bb9f4ec5e844a97bb7fbebd748"}, - {file = "ruff-0.6.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:932cd69eefe4daf8c7d92bd6689f7e8182571cb934ea720af218929da7bd7d69"}, - {file = "ruff-0.6.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3a8d42d11fff8d3143ff4da41742a98f8f233bf8890e9fe23077826818f8d680"}, - {file = "ruff-0.6.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a50af6e828ee692fb10ff2dfe53f05caecf077f4210fae9677e06a808275754f"}, - {file = "ruff-0.6.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:794ada3400a0d0b89e3015f1a7e01f4c97320ac665b7bc3ade24b50b54cb2972"}, - {file = "ruff-0.6.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:381413ec47f71ce1d1c614f7779d88886f406f1fd53d289c77e4e533dc6ea200"}, - {file = "ruff-0.6.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:52e75a82bbc9b42e63c08d22ad0ac525117e72aee9729a069d7c4f235fc4d276"}, - {file = "ruff-0.6.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09c72a833fd3551135ceddcba5ebdb68ff89225d30758027280968c9acdc7810"}, - {file = "ruff-0.6.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:800c50371bdcb99b3c1551d5691e14d16d6f07063a518770254227f7f6e8c178"}, - {file = "ruff-0.6.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e25ddd9cd63ba1f3bd51c1f09903904a6adf8429df34f17d728a8fa11174253"}, - {file = "ruff-0.6.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7291e64d7129f24d1b0c947ec3ec4c0076e958d1475c61202497c6aced35dd19"}, - {file = "ruff-0.6.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9ad7dfbd138d09d9a7e6931e6a7e797651ce29becd688be8a0d4d5f8177b4b0c"}, - {file = "ruff-0.6.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:005256d977021790cc52aa23d78f06bb5090dc0bfbd42de46d49c201533982ae"}, - {file = "ruff-0.6.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:482c1e6bfeb615eafc5899127b805d28e387bd87db38b2c0c41d271f5e58d8cc"}, - {file = "ruff-0.6.5-py3-none-win32.whl", hash = "sha256:cf4d3fa53644137f6a4a27a2b397381d16454a1566ae5335855c187fbf67e4f5"}, - {file = "ruff-0.6.5-py3-none-win_amd64.whl", hash = "sha256:3e42a57b58e3612051a636bc1ac4e6b838679530235520e8f095f7c44f706ff9"}, - {file = "ruff-0.6.5-py3-none-win_arm64.whl", hash = "sha256:51935067740773afdf97493ba9b8231279e9beef0f2a8079188c4776c25688e0"}, - {file = "ruff-0.6.5.tar.gz", hash = "sha256:4d32d87fab433c0cf285c3683dd4dae63be05fd7a1d65b3f5bf7cdd05a6b96fb"}, + {file = "ruff-0.6.8-py3-none-linux_armv6l.whl", hash = "sha256:77944bca110ff0a43b768f05a529fecd0706aac7bcce36d7f1eeb4cbfca5f0f2"}, + {file = "ruff-0.6.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:27b87e1801e786cd6ede4ada3faa5e254ce774de835e6723fd94551464c56b8c"}, + {file = "ruff-0.6.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd48f945da2a6334f1793d7f701725a76ba93bf3d73c36f6b21fb04d5338dcf5"}, + {file = "ruff-0.6.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:677e03c00f37c66cea033274295a983c7c546edea5043d0c798833adf4cf4c6f"}, + {file = "ruff-0.6.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9f1476236b3eacfacfc0f66aa9e6cd39f2a624cb73ea99189556015f27c0bdeb"}, + {file = "ruff-0.6.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f5a2f17c7d32991169195d52a04c95b256378bbf0de8cb98478351eb70d526f"}, + {file = "ruff-0.6.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5fd0d4b7b1457c49e435ee1e437900ced9b35cb8dc5178921dfb7d98d65a08d0"}, + {file = "ruff-0.6.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8034b19b993e9601f2ddf2c517451e17a6ab5cdb1c13fdff50c1442a7171d87"}, + {file = "ruff-0.6.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6cfb227b932ba8ef6e56c9f875d987973cd5e35bc5d05f5abf045af78ad8e098"}, + {file = "ruff-0.6.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef0411eccfc3909269fed47c61ffebdcb84a04504bafa6b6df9b85c27e813b0"}, + {file = "ruff-0.6.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:007dee844738c3d2e6c24ab5bc7d43c99ba3e1943bd2d95d598582e9c1b27750"}, + {file = "ruff-0.6.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ce60058d3cdd8490e5e5471ef086b3f1e90ab872b548814e35930e21d848c9ce"}, + {file = "ruff-0.6.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1085c455d1b3fdb8021ad534379c60353b81ba079712bce7a900e834859182fa"}, + {file = "ruff-0.6.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:70edf6a93b19481affd287d696d9e311388d808671bc209fb8907b46a8c3af44"}, + {file = "ruff-0.6.8-py3-none-win32.whl", hash = "sha256:792213f7be25316f9b46b854df80a77e0da87ec66691e8f012f887b4a671ab5a"}, + {file = "ruff-0.6.8-py3-none-win_amd64.whl", hash = "sha256:ec0517dc0f37cad14a5319ba7bba6e7e339d03fbf967a6d69b0907d61be7a263"}, + {file = "ruff-0.6.8-py3-none-win_arm64.whl", hash = "sha256:8d3bb2e3fbb9875172119021a13eed38849e762499e3cfde9588e4b4d70968dc"}, + {file = "ruff-0.6.8.tar.gz", hash = "sha256:a5bf44b1aa0adaf6d9d20f86162b34f7c593bfedabc51239953e446aefc8ce18"}, ] [[package]] @@ -2218,13 +2220,13 @@ files = [ [[package]] name = "types-pyyaml" -version = "6.0.12.20240808" +version = "6.0.12.20240917" description = "Typing stubs for PyYAML" optional = false python-versions = ">=3.8" files = [ - {file = "types-PyYAML-6.0.12.20240808.tar.gz", hash = "sha256:b8f76ddbd7f65440a8bda5526a9607e4c7a322dc2f8e1a8c405644f9a6f4b9af"}, - {file = "types_PyYAML-6.0.12.20240808-py3-none-any.whl", hash = "sha256:deda34c5c655265fc517b546c902aa6eed2ef8d3e921e4765fe606fe2afe8d35"}, + {file = "types-PyYAML-6.0.12.20240917.tar.gz", hash = "sha256:d1405a86f9576682234ef83bcb4e6fff7c9305c8b1fbad5e0bcd4f7dbdc9c587"}, + {file = "types_PyYAML-6.0.12.20240917-py3-none-any.whl", hash = "sha256:392b267f1c0fe6022952462bf5d6523f31e37f6cea49b14cee7ad634b6301570"}, ] [[package]] @@ -2285,13 +2287,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.26.4" +version = "20.26.5" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.26.4-py3-none-any.whl", hash = "sha256:48f2695d9809277003f30776d155615ffc11328e6a0a8c1f0ec80188d7874a55"}, - {file = "virtualenv-20.26.4.tar.gz", hash = "sha256:c17f4e0f3e6036e9f26700446f85c76ab11df65ff6d8a9cbfad9f71aabfcf23c"}, + {file = "virtualenv-20.26.5-py3-none-any.whl", hash = "sha256:4f3ac17b81fba3ce3bd6f4ead2749a72da5929c01774948e243db9ba41df4ff6"}, + {file = "virtualenv-20.26.5.tar.gz", hash = "sha256:ce489cac131aa58f4b25e321d6d186171f78e6cb13fafbf32a840cee67733ff4"}, ] [package.dependencies] diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 777a9cc..cc45cd9 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -110,6 +110,7 @@ model Reminder { reminder_expires_at DateTime reminder_channel_id BigInt reminder_user_id BigInt + reminder_sent Boolean @default(false) guild_id BigInt guild Guild @relation(fields: [guild_id], references: [guild_id]) @@ -169,4 +170,6 @@ enum CaseType { UNJAIL SNIPPETUNBAN UNTEMPBAN + POLLBAN + POLLUNBAN } diff --git a/tux/cogs/admin/mail.py b/tux/cogs/admin/mail.py index 112aa7e..7b4a302 100644 --- a/tux/cogs/admin/mail.py +++ b/tux/cogs/admin/mail.py @@ -99,7 +99,8 @@ class Mail(commands.Cog): delete_after=30, ) - def _generate_password(self) -> str: + @staticmethod + def _generate_password() -> str: password = "changeme" + "".join(str(random.randint(0, 9)) for _ in range(6)) password += "".join(random.choice("!@#$%^&*") for _ in range(4)) return password @@ -161,7 +162,8 @@ class Mail(commands.Cog): delete_after=30, ) - def _extract_mailbox_info(self, result: list[dict[str, str | None]]) -> str | None: + @staticmethod + def _extract_mailbox_info(result: list[dict[str, str | None]]) -> str | None: for item in result: if "msg" in item: msg = item["msg"] @@ -173,8 +175,8 @@ class Mail(commands.Cog): return None + @staticmethod async def _send_dm( - self, interaction: discord.Interaction, member: discord.Member, mailbox_info: str, diff --git a/tux/cogs/fun/fact.py b/tux/cogs/fun/fact.py index 12b135c..f2fc1ad 100644 --- a/tux/cogs/fun/fact.py +++ b/tux/cogs/fun/fact.py @@ -17,7 +17,6 @@ class Fact(commands.Cog): "Linus Torvalds was around 22 years old when he started work on the Linux Kernel in 1991. In the same year, he also released prototypes of the kernel publicly.", "Linux's 1.0 release was in March 1994.", "Less than 1% of the latest kernel release includes code written by Linus Torvalds.", - "Linux is used by every major space programme in the world.", "Approximately 13.3% of the latest Linux kernel is made up of blank lines.", "Vim has various easter eggs. A notable one is found by typing :help 42 into the command bar.", "Slackware is the oldest active linux distribution being released on the 17th July 1993.", diff --git a/tux/cogs/fun/imgeffect.py b/tux/cogs/fun/imgeffect.py index fe63538..c4dcb1e 100644 --- a/tux/cogs/fun/imgeffect.py +++ b/tux/cogs/fun/imgeffect.py @@ -14,71 +14,46 @@ from tux.ui.embeds import EmbedCreator class ImgEffect(commands.Cog): def __init__(self, bot: Tux) -> None: self.bot = bot - self.allowed_mimetypes = [ - "image/jpeg", - "image/png", - ] + self.allowed_mimetypes = ["image/jpeg", "image/png"] imgeffect = app_commands.Group(name="imgeffect", description="Image effects") - @imgeffect.command( - name="deepfry", - description="Deepfry an image", - ) + @imgeffect.command(name="deepfry", description="Deepfry an image") async def deepfry(self, interaction: discord.Interaction, image: discord.Attachment) -> None: - """ - Deepfry an image. - - Parameters - ---------- - interaction : discord.Interaction - The interaction object for the command. - image : discord.File - The image to deepfry. - """ - - # check if the image is a image - logger.info(f"Content type: {image.content_type}, Filename: {image.filename}, URL: {image.url}") - - if image.content_type not in self.allowed_mimetypes: - logger.error("The file is not a permitted image.") - - embed = EmbedCreator.create_embed( - bot=self.bot, - embed_type=EmbedCreator.ERROR, - user_name=interaction.user.name, - user_display_avatar=interaction.user.display_avatar.url, - title="Invalid File", - description="The file must be an image. Allowed types are PNG, JPEG, and JPG.", - ) - - await interaction.response.send_message(embed=embed, ephemeral=True) + if not self.is_valid_image(image): + await self.send_invalid_image_response(interaction) return - # say that the image is being processed - logger.info("Processing image...") await interaction.response.defer(ephemeral=True) - # open url with PIL - logger.info("Opening image with PIL and HTTPX...") + pil_image = await self.fetch_image(image.url) + + if pil_image: + deepfried_image = self.deepfry_image(pil_image) + await self.send_deepfried_image(interaction, deepfried_image) + + else: + await self.send_error_response(interaction) + + def is_valid_image(self, image: discord.Attachment) -> bool: + logger.info(f"Content type: {image.content_type}, Filename: {image.filename}, URL: {image.url}") + + return image.content_type in self.allowed_mimetypes + + @staticmethod + async def fetch_image(url: str) -> Image.Image: + logger.info("Fetching image from URL with HTTPX...") + async with httpx.AsyncClient() as client: - response = await client.get(image.url) + response = await client.get(url) - pil_image = Image.open(io.BytesIO(response.content)) - pil_image = pil_image.convert("RGB") - logger.info("Image opened with PIL.") + return Image.open(io.BytesIO(response.content)).convert("RGB") - # resize image to 25% then back to original size - logger.info("Resizing image...") + @staticmethod + def deepfry_image(pil_image: Image.Image) -> Image.Image: pil_image = pil_image.resize((int(pil_image.width * 0.25), int(pil_image.height * 0.25))) - logger.info("Image resized.") - - # increase sharpness - logger.info("Increasing sharpness...") pil_image = ImageEnhance.Sharpness(pil_image).enhance(100.0) - logger.info("Sharpness increased.") - logger.info("Adjusting color...") r = pil_image.split()[0] r = ImageEnhance.Contrast(r).enhance(2.0) r = ImageEnhance.Brightness(r).enhance(1.5) @@ -86,14 +61,43 @@ class ImgEffect(commands.Cog): colours = ((254, 0, 2), (255, 255, 15)) r = ImageOps.colorize(r, colours[0], colours[1]) pil_image = Image.blend(pil_image, r, 0.75) - logger.info("Color adjustment complete.") - # send image - logger.info("Sending image...") - pil_image = pil_image.resize((int(pil_image.width * 4), int(pil_image.height * 4))) + return pil_image.resize((int(pil_image.width * 4), int(pil_image.height * 4))) + + async def send_invalid_image_response(self, interaction: discord.Interaction) -> None: + logger.error("The file is not a permitted image.") + + embed = EmbedCreator.create_embed( + bot=self.bot, + embed_type=EmbedCreator.ERROR, + user_name=interaction.user.name, + user_display_avatar=interaction.user.display_avatar.url, + title="Invalid File", + description="The file must be an image. Allowed types are PNG, JPEG, and JPG.", + ) + + await interaction.response.send_message(embed=embed, ephemeral=True) + + async def send_error_response(self, interaction: discord.Interaction) -> None: + logger.error("Error processing the image.") + + embed = EmbedCreator.create_embed( + bot=self.bot, + embed_type=EmbedCreator.ERROR, + user_name=interaction.user.name, + user_display_avatar=interaction.user.display_avatar.url, + title="Error", + description="An error occurred while processing the image.", + ) + + await interaction.response.send_message(embed=embed, ephemeral=True) + + @staticmethod + async def send_deepfried_image(interaction: discord.Interaction, deepfried_image: Image.Image) -> None: arr = io.BytesIO() - pil_image.save(arr, format="JPEG", quality=1) + deepfried_image.save(arr, format="JPEG", quality=1) arr.seek(0) + file = discord.File(arr, filename="deepfried.jpg") await interaction.followup.send(file=file, ephemeral=True) diff --git a/tux/cogs/fun/random.py b/tux/cogs/fun/random.py index 33462c7..ab58ea9 100644 --- a/tux/cogs/fun/random.py +++ b/tux/cogs/fun/random.py @@ -57,7 +57,13 @@ class Random(commands.Cog): aliases=["eightball", "8b"], ) @commands.guild_only() - async def eight_ball(self, ctx: commands.Context[Tux], *, question: str, cow: bool = False) -> None: + async def eight_ball( + self, + ctx: commands.Context[Tux], + *, + question: str, + cow: bool = False, + ) -> None: """ Ask the magic 8ball a question. diff --git a/tux/cogs/info/info.py b/tux/cogs/info/info.py index 4f475b5..41df0af 100644 --- a/tux/cogs/info/info.py +++ b/tux/cogs/info/info.py @@ -65,7 +65,7 @@ class Info(commands.Cog): custom_color=discord.Color.blurple(), custom_author_text="Server Information", custom_author_icon_url=guild.icon.url, - custom_footer_text=f"ID: {guild.id} | Created: {guild.created_at.strftime('%B %d, %Y')}", + custom_footer_text=f"ID: {guild.id} | Created: {guild.created_at.strftime("%B %d, %Y")}", ) .add_field(name="Owner", value=str(guild.owner.mention) if guild.owner else "Unknown") .add_field(name="Vanity URL", value=guild.vanity_url_code or "None") @@ -73,7 +73,7 @@ class Info(commands.Cog): .add_field(name="Text Channels", value=len(guild.text_channels)) .add_field(name="Voice Channels", value=len(guild.voice_channels)) .add_field(name="Forum Channels", value=len(guild.forums)) - .add_field(name="Emojis", value=f"{len(guild.emojis)}/{2*guild.emoji_limit}") + .add_field(name="Emojis", value=f"{len(guild.emojis)}/{2 * guild.emoji_limit}") .add_field(name="Stickers", value=f"{len(guild.stickers)}/{guild.sticker_limit}") .add_field(name="Roles", value=len(guild.roles)) .add_field(name="Humans", value=sum(not member.bot for member in guild.members)) @@ -213,7 +213,7 @@ class Info(commands.Cog): menu: ViewMenu = ViewMenu(ctx, menu_type=ViewMenu.TypeEmbed) for chunk in chunks: page_embed: discord.Embed = embed.copy() - page_embed.description = f"{list_type.capitalize()} list for {guild_name}:\n{' '.join(chunk)}" + page_embed.description = f"{list_type.capitalize()} list for {guild_name}:\n{" ".join(chunk)}" menu.add_page(page_embed) buttons = [ @@ -229,7 +229,8 @@ class Info(commands.Cog): await menu.start() - def _chunks(self, it: Iterator[str], size: int) -> Generator[list[str], None, None]: + @staticmethod + def _chunks(it: Iterator[str], size: int) -> Generator[list[str], None, None]: """ Split an iterator into chunks of a specified size. diff --git a/tux/cogs/moderation/__init__.py b/tux/cogs/moderation/__init__.py index c2e1080..04d61de 100644 --- a/tux/cogs/moderation/__init__.py +++ b/tux/cogs/moderation/__init__.py @@ -100,8 +100,8 @@ class ModerationCogBase(commands.Cog): if isinstance(log_channel, discord.TextChannel): await log_channel.send(embed=embed) + @staticmethod async def send_dm( - self, ctx: commands.Context[Tux], silent: bool, user: discord.Member, @@ -250,3 +250,31 @@ class ModerationCogBase(commands.Cog): await self.send_embed(ctx, embed, log_type="mod") await ctx.send(embed=embed, delete_after=30, ephemeral=True) + + async def is_pollbanned(self, guild_id: int, user_id: int) -> bool: + """ + Check if a user is poll banned. + + Parameters + ---------- + guild_id : int + The ID of the guild to check in. + user_id : int + The ID of the user to check. + + Returns + ------- + bool + True if the user is poll banned, False otherwise. + """ + + # ban_cases = await self.case_controller.get_all_cases_by_type(guild_id, CaseType.POLLBAN) + # unban_cases = await self.case_controller.get_all_cases_by_type(guild_id, CaseType.POLLUNBAN) + + ban_cases = await self.db.case.get_all_cases_by_type(guild_id, CaseType.POLLBAN) + unban_cases = await self.db.case.get_all_cases_by_type(guild_id, CaseType.POLLUNBAN) + + ban_count = sum(case.case_user_id == user_id for case in ban_cases) + unban_count = sum(case.case_user_id == user_id for case in unban_cases) + + return ban_count > unban_count diff --git a/tux/cogs/moderation/cases.py b/tux/cogs/moderation/cases.py index a80e0b7..098b632 100644 --- a/tux/cogs/moderation/cases.py +++ b/tux/cogs/moderation/cases.py @@ -319,8 +319,8 @@ class Cases(ModerationCogBase): await menu.start() + @staticmethod def _create_case_fields( - self, moderator: discord.Member, user: discord.Member | discord.User, reason: str, @@ -361,7 +361,8 @@ class Cases(ModerationCogBase): return embed - def _format_emoji(self, emoji: discord.Emoji | None) -> str: + @staticmethod + def _format_emoji(emoji: discord.Emoji | None) -> str: return f"<:{emoji.name}:{emoji.id}>" if emoji else "" def _get_case_status_emoji(self, case_status: bool | None) -> discord.Emoji | None: @@ -392,16 +393,16 @@ class Cases(ModerationCogBase): def _get_case_action_emoji(self, case_type: CaseType) -> discord.Emoji | None: action = None - if case_type in [ + if case_type in { CaseType.BAN, CaseType.KICK, CaseType.TIMEOUT, CaseType.WARN, CaseType.JAIL, CaseType.SNIPPETBAN, - ]: + }: action = "added" - elif case_type in [CaseType.UNBAN, CaseType.UNTIMEOUT, CaseType.UNJAIL, CaseType.SNIPPETUNBAN]: + elif case_type in {CaseType.UNBAN, CaseType.UNTIMEOUT, CaseType.UNJAIL, CaseType.SNIPPETUNBAN}: action = "removed" if action is not None: @@ -410,8 +411,8 @@ class Cases(ModerationCogBase): return self.bot.get_emoji(emoji_id) return None + @staticmethod def _get_case_description( - self, case: Case, case_status_emoji: str, case_type_emoji: str, diff --git a/tux/cogs/moderation/jail.py b/tux/cogs/moderation/jail.py index 5a51bcb..9155ba3 100644 --- a/tux/cogs/moderation/jail.py +++ b/tux/cogs/moderation/jail.py @@ -105,8 +105,8 @@ class Jail(ModerationCogBase): dm_sent = await self.send_dm(ctx, flags.silent, member, flags.reason, "jailed") await self.handle_case_response(ctx, CaseType.JAIL, case.case_number, flags.reason, member, dm_sent) + @staticmethod def _get_manageable_roles( - self, member: discord.Member, jail_role: discord.Role, ) -> list[discord.Role]: diff --git a/tux/cogs/moderation/pollban.py b/tux/cogs/moderation/pollban.py new file mode 100644 index 0000000..6afbabd --- /dev/null +++ b/tux/cogs/moderation/pollban.py @@ -0,0 +1,71 @@ +import discord +from discord.ext import commands +from loguru import logger + +from prisma.enums import CaseType +from tux.bot import Tux +from tux.database.controllers.case import CaseController +from tux.utils import checks +from tux.utils.flags import PollBanFlags, generate_usage + +from . import ModerationCogBase + + +class PollBan(ModerationCogBase): + def __init__(self, bot: Tux) -> None: + super().__init__(bot) + self.case_controller = CaseController() + self.poll_ban.usage = generate_usage(self.poll_ban, PollBanFlags) + + @commands.hybrid_command( + name="pollban", + aliases=["pb"], + ) + @commands.guild_only() + @checks.has_pl(3) + async def poll_ban( + self, + ctx: commands.Context[Tux], + member: discord.Member, + *, + flags: PollBanFlags, + ) -> None: + """ + Ban a user from creating polls using tux. + + Parameters + ---------- + ctx : commands.Context[Tux] + The context object. + member : discord.Member + The member to poll ban. + flags : PollBanFlags + The flags for the command. (reason: str, silent: bool) + """ + + assert ctx.guild + + if await self.is_pollbanned(ctx.guild.id, member.id): + await ctx.send("User is already poll banned.", delete_after=30, ephemeral=True) + return + + try: + case = await self.db.case.insert_case( + case_user_id=member.id, + case_moderator_id=ctx.author.id, + case_type=CaseType.POLLBAN, + case_reason=flags.reason, + guild_id=ctx.guild.id, + ) + + except Exception as e: + logger.error(f"Failed to ban {member}. {e}") + await ctx.send(f"Failed to ban {member}. {e}", delete_after=30) + return + + dm_sent = await self.send_dm(ctx, flags.silent, member, flags.reason, "poll banned") + await self.handle_case_response(ctx, CaseType.POLLBAN, case.case_number, flags.reason, member, dm_sent) + + +async def setup(bot: Tux) -> None: + await bot.add_cog(PollBan(bot)) diff --git a/tux/cogs/moderation/pollunban.py b/tux/cogs/moderation/pollunban.py new file mode 100644 index 0000000..dcf3ff2 --- /dev/null +++ b/tux/cogs/moderation/pollunban.py @@ -0,0 +1,71 @@ +import discord +from discord.ext import commands +from loguru import logger + +from prisma.enums import CaseType +from tux.bot import Tux +from tux.database.controllers.case import CaseController +from tux.utils import checks +from tux.utils.flags import PollUnbanFlags, generate_usage + +from . import ModerationCogBase + + +class PollUnban(ModerationCogBase): + def __init__(self, bot: Tux) -> None: + super().__init__(bot) + self.case_controller = CaseController() + self.poll_unban.usage = generate_usage(self.poll_unban, PollUnbanFlags) + + @commands.hybrid_command( + name="pollunban", + aliases=["pub"], + ) + @commands.guild_only() + @checks.has_pl(3) + async def poll_unban( + self, + ctx: commands.Context[Tux], + member: discord.Member, + *, + flags: PollUnbanFlags, + ): + """ + Unban a user from creating snippets. + + Parameters + ---------- + ctx : commands.Context[Tux] + The context object. + member : discord.Member + The member to snippet unban. + flags : PollUnbanFlags + The flags for the command. (reason: str, silent: bool) + """ + + assert ctx.guild + + if not await self.is_pollbanned(ctx.guild.id, member.id): + await ctx.send("User is not poll banned.", delete_after=30, ephemeral=True) + return + + try: + case = await self.db.case.insert_case( + case_user_id=member.id, + case_moderator_id=ctx.author.id, + case_type=CaseType.POLLUNBAN, + case_reason=flags.reason, + guild_id=ctx.guild.id, + ) + + except Exception as e: + logger.error(f"Failed to poll unban {member}. {e}") + await ctx.send(f"Failed to poll unban {member}. {e}", delete_after=30, ephemeral=True) + return + + dm_sent = await self.send_dm(ctx, flags.silent, member, flags.reason, "poll unbanned") + await self.handle_case_response(ctx, CaseType.POLLUNBAN, case.case_number, flags.reason, member, dm_sent) + + +async def setup(bot: Tux) -> None: + await bot.add_cog(PollUnban(bot)) diff --git a/tux/cogs/moderation/slowmode.py b/tux/cogs/moderation/slowmode.py index 0d4ec14..a55bd39 100644 --- a/tux/cogs/moderation/slowmode.py +++ b/tux/cogs/moderation/slowmode.py @@ -15,7 +15,8 @@ class Slowmode(commands.Cog): @commands.hybrid_command( name="slowmode", aliases=["sm"], - usage="slowmode [channel]\nor slowmode [channel] ", # only place where generate_usage shouldn't be used + # only place where generate_usage shouldn't be used: + usage="slowmode [channel]\nor slowmode [channel] ", ) @commands.guild_only() @checks.has_pl(2) @@ -83,11 +84,12 @@ class Slowmode(commands.Cog): return action, channel - def _get_channel(self, ctx: commands.Context[Tux]) -> discord.TextChannel | discord.Thread | None: + @staticmethod + def _get_channel(ctx: commands.Context[Tux]) -> discord.TextChannel | discord.Thread | None: return ctx.channel if isinstance(ctx.channel, discord.TextChannel | discord.Thread) else None + @staticmethod async def _get_slowmode( - self, ctx: commands.Context[Tux], channel: discord.TextChannel | discord.Thread, ) -> None: @@ -135,7 +137,8 @@ class Slowmode(commands.Cog): await ctx.send(f"Failed to set slowmode. Error: {error}", delete_after=30, ephemeral=True) logger.error(f"Failed to set slowmode. Error: {error}") - def _parse_delay(self, delay: str) -> int | None: + @staticmethod + def _parse_delay(delay: str) -> int | None: try: if delay.endswith("s"): delay = delay[:-1] diff --git a/tux/cogs/services/bookmarks.py b/tux/cogs/services/bookmarks.py index 7e86a8b..d1135be 100644 --- a/tux/cogs/services/bookmarks.py +++ b/tux/cogs/services/bookmarks.py @@ -52,7 +52,7 @@ class Bookmarks(commands.Cog): message: discord.Message, ) -> discord.Embed: if len(message.content) > CONST.EMBED_MAX_DESC_LENGTH: - message.content = f"{message.content[:CONST.EMBED_MAX_DESC_LENGTH - 3]}..." + message.content = f"{message.content[: CONST.EMBED_MAX_DESC_LENGTH - 3]}..." embed = EmbedCreator.create_embed( bot=self.bot, @@ -71,8 +71,8 @@ class Bookmarks(commands.Cog): return embed + @staticmethod async def _send_bookmark( - self, user: discord.User, message: discord.Message, embed: discord.Embed, diff --git a/tux/cogs/services/gif_limiter.py b/tux/cogs/services/gif_limiter.py index 18efda8..e589ffe 100644 --- a/tux/cogs/services/gif_limiter.py +++ b/tux/cogs/services/gif_limiter.py @@ -96,7 +96,6 @@ class GifLimiter(commands.Cog): self.recent_gifs_by_channel[channel_id] = [ t for t in timestamps if current_time - t < self.recent_gif_age ] - for user_id, timestamps in list(self.recent_gifs_by_user.items()): filtered_timestamps = [t for t in timestamps if current_time - t < self.recent_gif_age] if filtered_timestamps: diff --git a/tux/cogs/utility/poll.py b/tux/cogs/utility/poll.py index 119d53e..513b006 100644 --- a/tux/cogs/utility/poll.py +++ b/tux/cogs/utility/poll.py @@ -3,7 +3,9 @@ from discord import app_commands from discord.ext import commands from loguru import logger +from prisma.enums import CaseType from tux.bot import Tux +from tux.database.controllers import CaseController from tux.ui.embeds import EmbedCreator # TODO: Create option inputs for the poll command instead of using a comma separated string @@ -12,6 +14,35 @@ from tux.ui.embeds import EmbedCreator class Poll(commands.Cog): def __init__(self, bot: Tux) -> None: self.bot = bot + self.case_controller = CaseController() + + # TODO: for the moment this is duplicated code from ModerationCogBase in a attempt to get the code out sooner + async def is_pollbanned(self, guild_id: int, user_id: int) -> bool: + """ + Check if a user is poll banned. + + Parameters + ---------- + guild_id : int + The ID of the guild to check in. + user_id : int + The ID of the user to check. + + Returns + ------- + bool + True if the user is poll banned, False otherwise. + """ + + ban_cases = await self.case_controller.get_all_cases_by_type(guild_id, CaseType.POLLBAN) + unban_cases = await self.case_controller.get_all_cases_by_type(guild_id, CaseType.POLLUNBAN) + + ban_count = sum(case.case_user_id == user_id for case in ban_cases) + unban_count = sum(case.case_user_id == user_id for case in unban_cases) + + return ( + ban_count > unban_count + ) # TODO: this implementation is flawed, if someone bans and unbans the same user multiple times, this will not work as expected @commands.Cog.listener() # listen for messages async def on_message(self, message: discord.Message) -> None: @@ -40,7 +71,6 @@ class Poll(commands.Cog): @commands.Cog.listener() async def on_reaction_add(self, reaction: discord.Reaction, user: discord.User) -> None: # Block any reactions that are not numbers for the poll - if reaction.message.embeds: embed = reaction.message.embeds[0] if ( @@ -64,7 +94,12 @@ class Poll(commands.Cog): The title of the poll. options : str The options for the poll, separated by commas. + + """ + if interaction.guild_id is None: + await interaction.response.send_message("This command can only be used in a server.", ephemeral=True) + return # Split the options by comma options_list = options.split(",") @@ -72,6 +107,17 @@ class Poll(commands.Cog): # Remove any leading or trailing whitespaces from the options options_list = [option.strip() for option in options_list] + if await self.is_pollbanned(interaction.guild_id, interaction.user.id): + embed = EmbedCreator.create_embed( + bot=self.bot, + embed_type=EmbedCreator.ERROR, + user_name=interaction.user.name, + user_display_avatar=interaction.user.display_avatar.url, + title="Poll Banned", + description="You are poll banned and cannot create a poll.", + ) + await interaction.response.send_message(embed=embed, ephemeral=True) + return # Check if the options count is between 2-9 if len(options_list) < 2 or len(options_list) > 9: embed = EmbedCreator.create_embed( diff --git a/tux/cogs/utility/remindme.py b/tux/cogs/utility/remindme.py index 6c84c74..dd59116 100644 --- a/tux/cogs/utility/remindme.py +++ b/tux/cogs/utility/remindme.py @@ -1,10 +1,9 @@ -import asyncio import contextlib import datetime import discord from discord import app_commands -from discord.ext import commands +from discord.ext import commands, tasks from loguru import logger from prisma.models import Reminder @@ -14,40 +13,26 @@ from tux.ui.embeds import EmbedCreator from tux.utils.functions import convert_to_seconds -def get_closest_reminder(reminders: list[Reminder]) -> Reminder | None: - """ - Check if there are any reminders and return the closest one. - - - Parameters - ---------- - reminders : list[Reminder] - A list of reminders to check. - - Returns - ------- - Reminder | None - The closest reminder or None if there are no reminders. - """ - return min(reminders, key=lambda x: x.reminder_expires_at) if reminders else None - - class RemindMe(commands.Cog): def __init__(self, bot: Tux) -> None: self.bot = bot self.db = DatabaseController().reminder - self.bot.loop.create_task(self.update()) + self.check_reminders.start() - async def send_reminders(self, reminder: Reminder) -> None: - """ - Send the reminder to the user. + @tasks.loop(seconds=120) + async def check_reminders(self): + reminders = await self.db.get_unsent_reminders() - Parameters - ---------- - reminder : Reminder - The reminder object. - """ + try: + for reminder in reminders: + await self.send_reminder(reminder) + await self.db.update_reminder_status(reminder.reminder_id, sent=True) + logger.debug(f'Status of reminder {reminder.reminder_id} updated to "sent".') + except Exception as e: + logger.error(f"Error sending reminders: {e}") + + async def send_reminder(self, reminder: Reminder) -> None: user = self.bot.get_user(reminder.reminder_user_id) if user is not None: @@ -64,103 +49,37 @@ class RemindMe(commands.Cog): await user.send(embed=embed) except discord.Forbidden: - # Send a message in the channel if the user has DMs closed - channel: discord.abc.GuildChannel | discord.Thread | discord.abc.PrivateChannel | None = ( - self.bot.get_channel(reminder.reminder_channel_id) - ) + channel = self.bot.get_channel(reminder.reminder_channel_id) - if channel is not None and isinstance( - channel, - discord.TextChannel | discord.Thread | discord.VoiceChannel, - ): + if isinstance(channel, discord.TextChannel | discord.Thread | discord.VoiceChannel): with contextlib.suppress(discord.Forbidden): await channel.send( content=f"{user.mention} Failed to DM you, sending in channel", embed=embed, ) + return else: logger.error( - f"Failed to send reminder to {user.id}, DMs closed and channel not found.", + f"Failed to send reminder {reminder.reminder_id}, DMs closed and channel not found.", ) else: - logger.error(f"Failed to send reminder to {reminder.reminder_user_id}, user not found.") + logger.error( + f"Failed to send reminder {reminder.reminder_id}, user with ID {reminder.reminder_user_id} not found.", + ) - # Delete the reminder after sending - await self.db.delete_reminder_by_id(reminder.reminder_id) - - # wait for a second so that the reminder is deleted before checking for more reminders - # who knows if this works, it seems to - await asyncio.sleep(1) - - # Run update again to check if there are any more reminders - await self.update() - - async def end_timer(self, reminder: Reminder) -> None: - """ - End the timer for the reminder. - - Parameters - ---------- - reminder : Reminder - The reminder object. - """ - - # Wait until the reminder expires - await discord.utils.sleep_until(reminder.reminder_expires_at) - await self.send_reminders(reminder) - - async def update(self) -> None: - """ - Update the reminders - - Check if there are any reminders and send the closest one. - """ - - try: - # Get all reminders - reminders = await self.db.get_all_reminders() - # Get the closest reminder - closest_reminder = get_closest_reminder(reminders) - - except Exception as e: - logger.error(f"Error getting reminders: {e}") - return - - # If there are no reminders, return - if closest_reminder is None: - return - - # Check if it's expired - if closest_reminder.reminder_expires_at < datetime.datetime.now(datetime.UTC): - await self.send_reminders(closest_reminder) - return - - # Create a task to wait until the reminder expires - self.bot.loop.create_task(self.end_timer(closest_reminder)) + @check_reminders.before_loop + async def before_check_reminders(self): + await self.bot.wait_until_ready() @app_commands.command( name="remindme", description="Reminds you after a certain amount of time.", ) async def remindme(self, interaction: discord.Interaction, time: str, *, reminder: str) -> None: - """ - Set a reminder for a certain amount of time. - - Parameters - ---------- - interaction : discord.Interaction - The discord interaction object. - time : str - Time in the format `[number][M/w/d/h/m/s]`. - reminder : str - Reminder content. - """ - seconds = convert_to_seconds(time) - # Check if the time is valid (this is set to 0 if the time is invalid via convert_to_seconds) if seconds == 0: await interaction.response.send_message( "Invalid time format. Please use the format `[number][M/w/d/h/m/s]`.", @@ -169,13 +88,13 @@ class RemindMe(commands.Cog): ) return - seconds = datetime.datetime.now(datetime.UTC) + datetime.timedelta(seconds=seconds) + expires_at = datetime.datetime.now(datetime.UTC) + datetime.timedelta(seconds=seconds) try: await self.db.insert_reminder( reminder_user_id=interaction.user.id, reminder_content=reminder, - reminder_expires_at=seconds, + reminder_expires_at=expires_at, reminder_channel_id=interaction.channel_id or 0, guild_id=interaction.guild_id or 0, ) @@ -186,12 +105,13 @@ class RemindMe(commands.Cog): user_name=interaction.user.name, user_display_avatar=interaction.user.display_avatar.url, title="Reminder Set", - description=f"Reminder set for .", + description=f"Reminder set for .", ) embed.add_field( name="Note", - value="If you have DMs closed, the reminder may not reach you. We will attempt to send it in this channel instead, however it is not guaranteed.", + value="- If you have DMs closed, we will attempt to send it in this channel instead.\n" + "- The reminder may be delayed by up to 120 seconds due to the way Tux works.", ) except Exception as e: @@ -207,9 +127,6 @@ class RemindMe(commands.Cog): await interaction.response.send_message(embed=embed, ephemeral=True) - # Run update again to check if this reminder is the closest - await self.update() - async def setup(bot: Tux) -> None: await bot.add_cog(RemindMe(bot)) diff --git a/tux/cogs/utility/run.py b/tux/cogs/utility/run.py index 72644ef..4fe8e59 100644 --- a/tux/cogs/utility/run.py +++ b/tux/cogs/utility/run.py @@ -46,7 +46,8 @@ class Run(commands.Cog): self.run.usage = generate_usage(self.run) self.languages.usage = generate_usage(self.languages) - def remove_ansi(self, ansi: str) -> str: + @staticmethod + def remove_ansi(ansi: str) -> str: """ Converts ANSI encoded text into non-ANSI. @@ -63,7 +64,8 @@ class Run(commands.Cog): return ansi_re.sub("", ansi) - def remove_backticks(self, st: str) -> str: + @staticmethod + def remove_backticks(st: str) -> str: """ Removes backticks from the provided string. @@ -277,7 +279,7 @@ class Run(commands.Cog): code, ) await msg.delete() - if filtered_output == "" and gen_one == "" and normalized_lang == "": + if not filtered_output and not gen_one and not normalized_lang: return await self.send_embedded_reply( ctx, @@ -340,7 +342,7 @@ class Run(commands.Cog): user_name=ctx.author.name, user_display_avatar=ctx.author.display_avatar.url, title="Supported Languages", - description=f"```{', '.join(compiler_map.keys())}```", + description=f"```{", ".join(compiler_map.keys())}```", ) await ctx.send(embed=embed) diff --git a/tux/cogs/utility/snippets.py b/tux/cogs/utility/snippets.py index dedfb96..8bc9c8d 100644 --- a/tux/cogs/utility/snippets.py +++ b/tux/cogs/utility/snippets.py @@ -152,7 +152,7 @@ class Snippets(commands.Cog): text = "```\n" for i, snippet in enumerate(snippets[:10]): - text += f"{i+1}. {snippet.snippet_name.ljust(20)} | uses: {snippet.uses}\n" + text += f"{i + 1}. {snippet.snippet_name.ljust(20)} | uses: {snippet.uses}\n" text += "```" # only show top 10, no pagination @@ -321,7 +321,7 @@ class Snippets(commands.Cog): embed.add_field(name="Name", value=snippet.snippet_name, inline=False) embed.add_field( name="Author", - value=f"{author.mention if author else f'<@!{snippet.snippet_user_id}>'}", + value=f"{author.mention if author else f"<@!{snippet.snippet_user_id}>"}", inline=False, ) embed.add_field(name="Content", value=f"> {snippet.snippet_content}", inline=False) @@ -495,7 +495,7 @@ class Snippets(commands.Cog): if author := self.bot.get_user(snippet.snippet_user_id): with contextlib.suppress(discord.Forbidden): await author.send( - f"""Your snippet `{snippet.snippet_name}` has been {'locked' if status.locked else 'unlocked'}. + f"""Your snippet `{snippet.snippet_name}` has been {"locked" if status.locked else "unlocked"}. **What does this mean?** If a snippet is locked, it cannot be edited by anyone other than moderators. This means that you can no longer edit this snippet. diff --git a/tux/cogs/utility/tldr.py b/tux/cogs/utility/tldr.py index b6c7ada..029fbb9 100644 --- a/tux/cogs/utility/tldr.py +++ b/tux/cogs/utility/tldr.py @@ -135,7 +135,8 @@ class Tldr(commands.Cog): return self._run_subprocess(["tldr", "--list"], "No TLDR pages found.").split("\n") - def _run_subprocess(self, command_list: list[str], default_response: str) -> str: + @staticmethod + def _run_subprocess(command_list: list[str], default_response: str) -> str: """ Helper method to run subprocesses for CLI interactions. diff --git a/tux/database/controllers/reminder.py b/tux/database/controllers/reminder.py index d0c73fa..c4241a6 100644 --- a/tux/database/controllers/reminder.py +++ b/tux/database/controllers/reminder.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import UTC, datetime from prisma.models import Guild, Reminder from tux.database.client import db @@ -21,6 +21,10 @@ class ReminderController: async def get_reminder_by_id(self, reminder_id: int) -> Reminder | None: return await self.table.find_first(where={"reminder_id": reminder_id}) + async def get_unsent_reminders(self) -> list[Reminder]: + now = datetime.now(UTC) + return await self.table.find_many(where={"reminder_sent": False, "reminder_expires_at": {"lte": now}}) + async def insert_reminder( self, reminder_user_id: int, @@ -38,6 +42,7 @@ class ReminderController: "reminder_expires_at": reminder_expires_at, "reminder_channel_id": reminder_channel_id, "guild_id": guild_id, + "reminder_sent": False, }, ) @@ -53,3 +58,19 @@ class ReminderController: where={"reminder_id": reminder_id}, data={"reminder_content": reminder_content}, ) + + async def update_reminder_status(self, reminder_id: int, sent: bool = True) -> None: + """ + Update the status of a reminder. This sets the value "reminder_sent" to True by default. + + Parameters + ---------- + reminder_id : int + The ID of the reminder to update. + sent : bool + The new status of the reminder. + """ + await self.table.update( + where={"reminder_id": reminder_id}, + data={"reminder_sent": sent}, + ) diff --git a/tux/handlers/activity.py b/tux/handlers/activity.py index 8217e6d..683c0ed 100644 --- a/tux/handlers/activity.py +++ b/tux/handlers/activity.py @@ -13,7 +13,8 @@ class ActivityHandler(commands.Cog): self.delay = delay self.activities = self.build_activity_list() - def build_activity_list(self) -> list[discord.Activity | discord.Streaming]: + @staticmethod + def build_activity_list() -> list[discord.Activity | discord.Streaming]: activity_data = [ {"type": discord.ActivityType.watching, "name": "{member_count} members"}, {"type": discord.ActivityType.watching, "name": "All Things Linux"}, diff --git a/tux/handlers/error.py b/tux/handlers/error.py index d8132cb..5e42eb0 100644 --- a/tux/handlers/error.py +++ b/tux/handlers/error.py @@ -273,7 +273,8 @@ class ErrorHandler(commands.Cog): return error_map.get(type(error), self.error_message).format(error=error) - def log_error_traceback(self, error: Exception) -> None: + @staticmethod + def log_error_traceback(error: Exception) -> None: """ Log the error traceback. diff --git a/tux/handlers/event.py b/tux/handlers/event.py index 4f5a068..868618c 100644 --- a/tux/handlers/event.py +++ b/tux/handlers/event.py @@ -20,7 +20,8 @@ class EventHandler(commands.Cog): async def on_guild_remove(self, guild: discord.Guild) -> None: await self.db.guild.delete_guild_by_id(guild.id) - async def handle_harmful_message(self, message: discord.Message) -> None: + @staticmethod + async def handle_harmful_message(message: discord.Message) -> None: if message.author.bot: return diff --git a/tux/help.py b/tux/help.py index b854b39..bd162a3 100644 --- a/tux/help.py +++ b/tux/help.py @@ -38,7 +38,8 @@ class TuxHelp(commands.HelpCommand): return self.context.clean_prefix or CONST.DEFAULT_PREFIX - def _embed_base(self, title: str, description: str | None = None) -> discord.Embed: + @staticmethod + def _embed_base(title: str, description: str | None = None) -> discord.Embed: """ Creates a base embed with uniform styling. @@ -61,8 +62,8 @@ class TuxHelp(commands.HelpCommand): color=CONST.EMBED_COLORS["DEFAULT"], ) + @staticmethod def _add_command_field( - self, embed: discord.Embed, command: commands.Command[Any, Any, Any], prefix: str, @@ -84,11 +85,12 @@ class TuxHelp(commands.HelpCommand): embed.add_field( name=f"{prefix}{command.qualified_name} ({command_aliases})", - value=f"> {command.short_doc or 'No documentation summary.'}", + value=f"> {command.short_doc or "No documentation summary."}", inline=False, ) - def _get_flag_type(self, flag_annotation: Any) -> str: + @staticmethod + def _get_flag_type(flag_annotation: Any) -> str: """ Determines the type of a flag based on its annotation. @@ -111,7 +113,8 @@ class TuxHelp(commands.HelpCommand): case _: return str(flag_annotation) - def _format_flag_name(self, flag: commands.Flag) -> str: + @staticmethod + def _format_flag_name(flag: commands.Flag) -> str: """ Formats the flag name based on whether it is required. @@ -158,11 +161,11 @@ class TuxHelp(commands.HelpCommand): flag_str = self._format_flag_name(flag) if flag.aliases: - flag_str += f" ({', '.join(flag.aliases)})" + flag_str += f" ({", ".join(flag.aliases)})" # else: # flag_str += f" : {flag_type}" - flag_str += f"\n\t{flag.description or 'No description provided.'}" + flag_str += f"\n\t{flag.description or "No description provided."}" if flag.default is not discord.utils.MISSING: flag_str += f"\n\tDefault: {flag.default}" @@ -289,12 +292,13 @@ class TuxHelp(commands.HelpCommand): for command in mapping_commands: cmd_name_and_aliases = f"`{command.name}`" if command.aliases: - cmd_name_and_aliases += f" ({', '.join(f'`{alias}`' for alias in command.aliases)})" + cmd_name_and_aliases += f" ({", ".join(f"`{alias}`" for alias in command.aliases)})" command_categories[cog_group][command.name] = cmd_name_and_aliases return command_categories - def _get_cog_groups(self) -> list[str]: + @staticmethod + def _get_cog_groups() -> list[str]: """ Retrieves a list of cog groups from the 'cogs' folder. @@ -349,8 +353,8 @@ class TuxHelp(commands.HelpCommand): return select_options + @staticmethod def _add_navigation_and_selection( - self, menu: ViewMenu, select_options: dict[discord.SelectOption, list[Page]], ) -> None: @@ -368,7 +372,8 @@ class TuxHelp(commands.HelpCommand): menu.add_select(ViewSelect(title="Command Categories", options=select_options)) menu.add_button(ViewButton.end_session()) - def _extract_cog_group(self, cog: commands.Cog) -> str | None: + @staticmethod + def _extract_cog_group(cog: commands.Cog) -> str | None: """ Extracts the cog group from a cog's string representation. @@ -424,7 +429,7 @@ class TuxHelp(commands.HelpCommand): embed = self._embed_base( title=f"{prefix}{command.qualified_name}", - description=f"> {command.help or 'No documentation available.'}", + description=f"> {command.help or "No documentation available."}", ) await self._add_command_help_fields(embed, command) @@ -450,12 +455,12 @@ class TuxHelp(commands.HelpCommand): embed.add_field( name="Usage", - value=f"`{prefix}{command.usage or 'No usage.'}`", + value=f"`{prefix}{command.usage or "No usage."}`", inline=False, ) embed.add_field( name="Aliases", - value=(f"`{', '.join(command.aliases)}`" if command.aliases else "No aliases."), + value=(f"`{", ".join(command.aliases)}`" if command.aliases else "No aliases."), inline=False, ) @@ -471,7 +476,7 @@ class TuxHelp(commands.HelpCommand): prefix = await self._get_prefix() - embed = self._embed_base(f"{group.name}", f"> {group.help or 'No documentation available.'}") + embed = self._embed_base(f"{group.name}", f"> {group.help or "No documentation available."}") await self._add_command_help_fields(embed, group) for command in group.commands: diff --git a/tux/utils/flags.py b/tux/utils/flags.py index 176b616..f876dc2 100644 --- a/tux/utils/flags.py +++ b/tux/utils/flags.py @@ -37,7 +37,7 @@ def generate_usage( flags: dict[str, commands.Flag] = flag_converter.get_flags() if flag_converter else {} for param_name, param in parameters.items(): - if param_name in ["ctx", "flags"]: + if param_name in {"ctx", "flags"}: continue is_required = param.default == inspect.Parameter.empty matching_string = get_matching_string(param_name) @@ -60,7 +60,7 @@ def generate_usage( usage += f" {flag}" if optional_flags: - usage += f" [{' | '.join(optional_flags)}]" + usage += f" [{" | ".join(optional_flags)}]" return usage @@ -309,3 +309,33 @@ class SnippetUnbanFlags(commands.FlagConverter, case_insensitive=True, delimiter aliases=["s", "quiet"], default=False, ) + + +class PollBanFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"): + reason: str = commands.flag( + name="reason", + description="Reason for the poll ban.", + aliases=["r"], + default=MISSING, + ) + silent: bool = commands.flag( + name="silent", + description="Do not send a DM to the target.", + aliases=["s", "quiet"], + default=False, + ) + + +class PollUnbanFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"): + reason: str = commands.flag( + name="reason", + description="Reason for the poll unban", + aliases=["r"], + default=MISSING, + ) + silent: bool = commands.flag( + name="silent", + description="Do not send a DM to the target.", + aliases=["s", "quiet"], + default=False, + )