1
Fork 0
mirror of https://github.com/allthingslinux/tux.git synced 2024-10-02 16:43:12 +00:00

Merge branch 'main' into gif_limiter

This commit is contained in:
electron271 2024-09-28 23:39:46 -05:00 committed by GitHub
commit d9e840b2d6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 594 additions and 379 deletions

View file

@ -9,7 +9,7 @@ permissions:
jobs: jobs:
Linting: Linting:
runs-on: ubuntu-22.04 runs-on: ubuntu-24.04
steps: steps:
- name: 'Checkout Repository' - name: 'Checkout Repository'
uses: actions/checkout@v4 uses: actions/checkout@v4

View file

@ -1,6 +1,6 @@
repos: repos:
- repo: https://github.com/gitleaks/gitleaks - repo: https://github.com/gitleaks/gitleaks
rev: v8.18.4 rev: v8.19.2
hooks: hooks:
- id: gitleaks - id: gitleaks
@ -12,7 +12,7 @@ repos:
- id: check-toml - id: check-toml
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.4 rev: v0.6.7
hooks: hooks:
# Run the linter. # Run the linter.
- id: ruff - id: ruff

View file

@ -10,8 +10,8 @@ USER_IDS:
- 123456789012345679 - 123456789012345679
BOT_OWNER: 123456789012345679 BOT_OWNER: 123456789012345679
TEMPVC_CATEGORY_ID: 123456789012345 TEMPVC_CATEGORY_ID: 123456789012345679
TEMPVC_CHANNEL_ID: 123456789012345 TEMPVC_CHANNEL_ID: 123456789012345679
EMBED_COLORS: EMBED_COLORS:
DEFAULT: 16044058 DEFAULT: 16044058

306
poetry.lock generated
View file

@ -2,13 +2,13 @@
[[package]] [[package]]
name = "aiocache" name = "aiocache"
version = "0.12.2" version = "0.12.3"
description = "multi backend asyncio cache" description = "multi backend asyncio cache"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
{file = "aiocache-0.12.2-py2.py3-none-any.whl", hash = "sha256:9b6fa30634ab0bfc3ecc44928a91ff07c6ea16d27d55469636b296ebc6eb5918"}, {file = "aiocache-0.12.3-py2.py3-none-any.whl", hash = "sha256:889086fc24710f431937b87ad3720a289f7fc31c4fd8b68e9f918b9bacd8270d"},
{file = "aiocache-0.12.2.tar.gz", hash = "sha256:b41c9a145b050a5dcbae1599f847db6dd445193b1f3bd172d8e0fe0cb9e96684"}, {file = "aiocache-0.12.3.tar.gz", hash = "sha256:f528b27bf4d436b497a1d0d1a8f59a542c153ab1e37c3621713cb376d44c4713"},
] ]
[package.extras] [package.extras]
@ -190,13 +190,13 @@ files = [
[[package]] [[package]]
name = "anyio" name = "anyio"
version = "4.4.0" version = "4.5.0"
description = "High level compatibility layer for multiple asynchronous event loop implementations" description = "High level compatibility layer for multiple asynchronous event loop implementations"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, {file = "anyio-4.5.0-py3-none-any.whl", hash = "sha256:fdeb095b7cc5a5563175eedd926ec4ae55413bb4be5770c424af0ba46ccb4a78"},
{file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, {file = "anyio-4.5.0.tar.gz", hash = "sha256:c5a275fe5ca0afd788001f58fca1e69e29ce706d746e317d660e21f70c530ef9"},
] ]
[package.dependencies] [package.dependencies]
@ -204,9 +204,9 @@ idna = ">=2.8"
sniffio = ">=1.1" sniffio = ">=1.1"
[package.extras] [package.extras]
doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] 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.17)"] 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.23)"] trio = ["trio (>=0.26.1)"]
[[package]] [[package]]
name = "astunparse" name = "astunparse"
@ -692,18 +692,18 @@ files = [
[[package]] [[package]]
name = "filelock" name = "filelock"
version = "3.16.0" version = "3.16.1"
description = "A platform independent file lock." description = "A platform independent file lock."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "filelock-3.16.0-py3-none-any.whl", hash = "sha256:f6ed4c963184f4c84dd5557ce8fece759a3724b37b80c6c4f20a2f63a4dc6609"}, {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"},
{file = "filelock-3.16.0.tar.gz", hash = "sha256:81de9eb8453c769b63369f87f11131a7ab04e367f8d97ad39dc230daa07e3bec"}, {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"},
] ]
[package.extras] [package.extras]
docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] 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.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)"] 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)"] typing = ["typing-extensions (>=4.12.2)"]
[[package]] [[package]]
@ -811,13 +811,13 @@ dev = ["flake8", "markdown", "twine", "wheel"]
[[package]] [[package]]
name = "githubkit" name = "githubkit"
version = "0.11.9" version = "0.11.10"
description = "GitHub SDK for Python" description = "GitHub SDK for Python"
optional = false optional = false
python-versions = "<4.0,>=3.8" python-versions = "<4.0,>=3.8"
files = [ files = [
{file = "githubkit-0.11.9-py3-none-any.whl", hash = "sha256:68933ff9867467418c90176e2ea77c7623fa7ab8fbf64723c2ca56c09b14a0f6"}, {file = "githubkit-0.11.10-py3-none-any.whl", hash = "sha256:e95074916af4a5f357d47005022a555984e16729d9e6204161ef67bc29a78789"},
{file = "githubkit-0.11.9.tar.gz", hash = "sha256:d25da1a667cce32f3d1ab0f33cf52fa328a55e85a6916e19da2765b0602c472a"}, {file = "githubkit-0.11.10.tar.gz", hash = "sha256:9729783ba44935ca47b3bebaad76971ccc3d3b548b8af54e06d16a489b0ece90"},
] ]
[package.dependencies] [package.dependencies]
@ -914,13 +914,13 @@ zstd = ["zstandard (>=0.18.0)"]
[[package]] [[package]]
name = "identify" name = "identify"
version = "2.6.0" version = "2.6.1"
description = "File identification library for Python" description = "File identification library for Python"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, {file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"},
{file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, {file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"},
] ]
[package.extras] [package.extras]
@ -928,13 +928,13 @@ license = ["ukkonen"]
[[package]] [[package]]
name = "idna" name = "idna"
version = "3.9" version = "3.10"
description = "Internationalized Domain Names in Applications (IDNA)" description = "Internationalized Domain Names in Applications (IDNA)"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
{file = "idna-3.9-py3-none-any.whl", hash = "sha256:69297d5da0cc9281c77efffb4e730254dd45943f45bbfb461de5991713989b1e"}, {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
{file = "idna-3.9.tar.gz", hash = "sha256:e5c5dafde284f26e9e0f28f6ea2d6400abd5ca099864a67f576f3981c6476124"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
] ]
[package.extras] [package.extras]
@ -1160,13 +1160,13 @@ pyyaml = ">=5.1"
[[package]] [[package]]
name = "mkdocs-material" name = "mkdocs-material"
version = "9.5.34" version = "9.5.38"
description = "Documentation that simply works" description = "Documentation that simply works"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "mkdocs_material-9.5.34-py3-none-any.whl", hash = "sha256:54caa8be708de2b75167fd4d3b9f3d949579294f49cb242515d4653dbee9227e"}, {file = "mkdocs_material-9.5.38-py3-none-any.whl", hash = "sha256:d4779051d52ba9f1e7e344b34de95449c7c366c212b388e4a2db9a3db043c228"},
{file = "mkdocs_material-9.5.34.tar.gz", hash = "sha256:1e60ddf716cfb5679dfd65900b8a25d277064ed82d9a53cd5190e3f894df7840"}, {file = "mkdocs_material-9.5.38.tar.gz", hash = "sha256:1843c5171ad6b489550aeaf7358e5b7128cc03ddcf0fb4d91d19aa1e691a63b8"},
] ]
[package.dependencies] [package.dependencies]
@ -1446,13 +1446,13 @@ xmp = ["defusedxml"]
[[package]] [[package]]
name = "platformdirs" 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`." description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "platformdirs-4.3.3-py3-none-any.whl", hash = "sha256:50a5450e2e84f44539718293cbb1da0a0885c9d14adf21b77bae4e66fc99d9b5"}, {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"},
{file = "platformdirs-4.3.3.tar.gz", hash = "sha256:d4e0b7d8ec176b341fb03cb11ca12d0276faa8c485f9cd218f613840463fc2c0"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"},
] ]
[package.extras] [package.extras]
@ -1556,18 +1556,18 @@ files = [
[[package]] [[package]]
name = "pydantic" name = "pydantic"
version = "2.9.1" version = "2.9.2"
description = "Data validation using Python type hints" description = "Data validation using Python type hints"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "pydantic-2.9.1-py3-none-any.whl", hash = "sha256:7aff4db5fdf3cf573d4b3c30926a510a10e19a0774d38fc4967f78beb6deb612"}, {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"},
{file = "pydantic-2.9.1.tar.gz", hash = "sha256:1363c7d975c7036df0db2b4a61f2e062fbc0aa5ab5f2772e0ffc7191a4f4bce2"}, {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"},
] ]
[package.dependencies] [package.dependencies]
annotated-types = ">=0.6.0" annotated-types = ">=0.6.0"
pydantic-core = "2.23.3" pydantic-core = "2.23.4"
typing-extensions = [ typing-extensions = [
{version = ">=4.12.2", markers = "python_version >= \"3.13\""}, {version = ">=4.12.2", markers = "python_version >= \"3.13\""},
{version = ">=4.6.1", markers = "python_version < \"3.13\""}, {version = ">=4.6.1", markers = "python_version < \"3.13\""},
@ -1579,100 +1579,100 @@ timezone = ["tzdata"]
[[package]] [[package]]
name = "pydantic-core" name = "pydantic-core"
version = "2.23.3" version = "2.23.4"
description = "Core functionality for Pydantic validation and serialization" description = "Core functionality for Pydantic validation and serialization"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "pydantic_core-2.23.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7f10a5d1b9281392f1bf507d16ac720e78285dfd635b05737c3911637601bae6"}, {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"},
{file = "pydantic_core-2.23.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c09a7885dd33ee8c65266e5aa7fb7e2f23d49d8043f089989726391dd7350c5"}, {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"},
{file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6470b5a1ec4d1c2e9afe928c6cb37eb33381cab99292a708b8cb9aa89e62429b"}, {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"},
{file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9172d2088e27d9a185ea0a6c8cebe227a9139fd90295221d7d495944d2367700"}, {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"},
{file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86fc6c762ca7ac8fbbdff80d61b2c59fb6b7d144aa46e2d54d9e1b7b0e780e01"}, {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"},
{file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0cb80fd5c2df4898693aa841425ea1727b1b6d2167448253077d2a49003e0ed"}, {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"},
{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.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"},
{file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:047531242f8e9c2db733599f1c612925de095e93c9cc0e599e96cf536aaf56ba"}, {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"},
{file = "pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5499798317fff7f25dbef9347f4451b91ac2a4330c6669821c8202fd354c7bee"}, {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"},
{file = "pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bbb5e45eab7624440516ee3722a3044b83fff4c0372efe183fd6ba678ff681fe"}, {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"},
{file = "pydantic_core-2.23.3-cp310-none-win32.whl", hash = "sha256:8b5b3ed73abb147704a6e9f556d8c5cb078f8c095be4588e669d315e0d11893b"}, {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"},
{file = "pydantic_core-2.23.3-cp310-none-win_amd64.whl", hash = "sha256:2b603cde285322758a0279995b5796d64b63060bfbe214b50a3ca23b5cee3e83"}, {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"},
{file = "pydantic_core-2.23.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c889fd87e1f1bbeb877c2ee56b63bb297de4636661cc9bbfcf4b34e5e925bc27"}, {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"},
{file = "pydantic_core-2.23.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea85bda3189fb27503af4c45273735bcde3dd31c1ab17d11f37b04877859ef45"}, {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"},
{file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7f7f72f721223f33d3dc98a791666ebc6a91fa023ce63733709f4894a7dc611"}, {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"},
{file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b2b55b0448e9da68f56b696f313949cda1039e8ec7b5d294285335b53104b61"}, {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"},
{file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c24574c7e92e2c56379706b9a3f07c1e0c7f2f87a41b6ee86653100c4ce343e5"}, {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"},
{file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2b05e6ccbee333a8f4b8f4d7c244fdb7a979e90977ad9c51ea31261e2085ce0"}, {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"},
{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.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"},
{file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d965e8b325f443ed3196db890d85dfebbb09f7384486a77461347f4adb1fa7f8"}, {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"},
{file = "pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f56af3a420fb1ffaf43ece3ea09c2d27c444e7c40dcb7c6e7cf57aae764f2b48"}, {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"},
{file = "pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b01a078dd4f9a52494370af21aa52964e0a96d4862ac64ff7cea06e0f12d2c5"}, {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"},
{file = "pydantic_core-2.23.3-cp311-none-win32.whl", hash = "sha256:560e32f0df04ac69b3dd818f71339983f6d1f70eb99d4d1f8e9705fb6c34a5c1"}, {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"},
{file = "pydantic_core-2.23.3-cp311-none-win_amd64.whl", hash = "sha256:c744fa100fdea0d000d8bcddee95213d2de2e95b9c12be083370b2072333a0fa"}, {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"},
{file = "pydantic_core-2.23.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e0ec50663feedf64d21bad0809f5857bac1ce91deded203efc4a84b31b2e4305"}, {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"},
{file = "pydantic_core-2.23.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db6e6afcb95edbe6b357786684b71008499836e91f2a4a1e55b840955b341dbb"}, {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"},
{file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ccd69edcf49f0875d86942f4418a4e83eb3047f20eb897bffa62a5d419c8fa"}, {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"},
{file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a678c1ac5c5ec5685af0133262103defb427114e62eafeda12f1357a12140162"}, {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"},
{file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01491d8b4d8db9f3391d93b0df60701e644ff0894352947f31fff3e52bd5c801"}, {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"},
{file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fcf31facf2796a2d3b7fe338fe8640aa0166e4e55b4cb108dbfd1058049bf4cb"}, {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"},
{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.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"},
{file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc1636770a809dee2bd44dd74b89cc80eb41172bcad8af75dd0bc182c2666d4c"}, {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"},
{file = "pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:67a5def279309f2e23014b608c4150b0c2d323bd7bccd27ff07b001c12c2415c"}, {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"},
{file = "pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:748bdf985014c6dd3e1e4cc3db90f1c3ecc7246ff5a3cd4ddab20c768b2f1dab"}, {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"},
{file = "pydantic_core-2.23.3-cp312-none-win32.whl", hash = "sha256:255ec6dcb899c115f1e2a64bc9ebc24cc0e3ab097775755244f77360d1f3c06c"}, {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"},
{file = "pydantic_core-2.23.3-cp312-none-win_amd64.whl", hash = "sha256:40b8441be16c1e940abebed83cd006ddb9e3737a279e339dbd6d31578b802f7b"}, {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"},
{file = "pydantic_core-2.23.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6daaf5b1ba1369a22c8b050b643250e3e5efc6a78366d323294aee54953a4d5f"}, {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"},
{file = "pydantic_core-2.23.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d015e63b985a78a3d4ccffd3bdf22b7c20b3bbd4b8227809b3e8e75bc37f9cb2"}, {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"},
{file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3fc572d9b5b5cfe13f8e8a6e26271d5d13f80173724b738557a8c7f3a8a3791"}, {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"},
{file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f6bd91345b5163ee7448bee201ed7dd601ca24f43f439109b0212e296eb5b423"}, {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"},
{file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc379c73fd66606628b866f661e8785088afe2adaba78e6bbe80796baf708a63"}, {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"},
{file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbdce4b47592f9e296e19ac31667daed8753c8367ebb34b9a9bd89dacaa299c9"}, {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"},
{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.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"},
{file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e22b477bf90db71c156f89a55bfe4d25177b81fce4aa09294d9e805eec13855"}, {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"},
{file = "pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:0a0137ddf462575d9bce863c4c95bac3493ba8e22f8c28ca94634b4a1d3e2bb4"}, {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"},
{file = "pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:203171e48946c3164fe7691fc349c79241ff8f28306abd4cad5f4f75ed80bc8d"}, {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"},
{file = "pydantic_core-2.23.3-cp313-none-win32.whl", hash = "sha256:76bdab0de4acb3f119c2a4bff740e0c7dc2e6de7692774620f7452ce11ca76c8"}, {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"},
{file = "pydantic_core-2.23.3-cp313-none-win_amd64.whl", hash = "sha256:37ba321ac2a46100c578a92e9a6aa33afe9ec99ffa084424291d84e456f490c1"}, {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"},
{file = "pydantic_core-2.23.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d063c6b9fed7d992bcbebfc9133f4c24b7a7f215d6b102f3e082b1117cddb72c"}, {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"},
{file = "pydantic_core-2.23.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6cb968da9a0746a0cf521b2b5ef25fc5a0bee9b9a1a8214e0a1cfaea5be7e8a4"}, {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"},
{file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edbefe079a520c5984e30e1f1f29325054b59534729c25b874a16a5048028d16"}, {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"},
{file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cbaaf2ef20d282659093913da9d402108203f7cb5955020bd8d1ae5a2325d1c4"}, {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"},
{file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb539d7e5dc4aac345846f290cf504d2fd3c1be26ac4e8b5e4c2b688069ff4cf"}, {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"},
{file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e6f33503c5495059148cc486867e1d24ca35df5fc064686e631e314d959ad5b"}, {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"},
{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.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"},
{file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03795b9e8a5d7fda05f3873efc3f59105e2dcff14231680296b87b80bb327295"}, {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"},
{file = "pydantic_core-2.23.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c483dab0f14b8d3f0df0c6c18d70b21b086f74c87ab03c59250dbf6d3c89baba"}, {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"},
{file = "pydantic_core-2.23.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b2682038e255e94baf2c473dca914a7460069171ff5cdd4080be18ab8a7fd6e"}, {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"},
{file = "pydantic_core-2.23.3-cp38-none-win32.whl", hash = "sha256:f4a57db8966b3a1d1a350012839c6a0099f0898c56512dfade8a1fe5fb278710"}, {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"},
{file = "pydantic_core-2.23.3-cp38-none-win_amd64.whl", hash = "sha256:13dd45ba2561603681a2676ca56006d6dee94493f03d5cadc055d2055615c3ea"}, {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"},
{file = "pydantic_core-2.23.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:82da2f4703894134a9f000e24965df73cc103e31e8c31906cc1ee89fde72cbd8"}, {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"},
{file = "pydantic_core-2.23.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dd9be0a42de08f4b58a3cc73a123f124f65c24698b95a54c1543065baca8cf0e"}, {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"},
{file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89b731f25c80830c76fdb13705c68fef6a2b6dc494402987c7ea9584fe189f5d"}, {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"},
{file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6de1ec30c4bb94f3a69c9f5f2182baeda5b809f806676675e9ef6b8dc936f28"}, {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"},
{file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb68b41c3fa64587412b104294b9cbb027509dc2f6958446c502638d481525ef"}, {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"},
{file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c3980f2843de5184656aab58698011b42763ccba11c4a8c35936c8dd6c7068c"}, {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"},
{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.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"},
{file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:510b7fb0a86dc8f10a8bb43bd2f97beb63cffad1203071dc434dac26453955cd"}, {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"},
{file = "pydantic_core-2.23.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1eba2f7ce3e30ee2170410e2171867ea73dbd692433b81a93758ab2de6c64835"}, {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"},
{file = "pydantic_core-2.23.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b259fd8409ab84b4041b7b3f24dcc41e4696f180b775961ca8142b5b21d0e70"}, {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"},
{file = "pydantic_core-2.23.3-cp39-none-win32.whl", hash = "sha256:40d9bd259538dba2f40963286009bf7caf18b5112b19d2b55b09c14dde6db6a7"}, {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"},
{file = "pydantic_core-2.23.3-cp39-none-win_amd64.whl", hash = "sha256:5a8cd3074a98ee70173a8633ad3c10e00dcb991ecec57263aacb4095c5efb958"}, {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"},
{file = "pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f399e8657c67313476a121a6944311fab377085ca7f490648c9af97fc732732d"}, {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"},
{file = "pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:6b5547d098c76e1694ba85f05b595720d7c60d342f24d5aad32c3049131fa5c4"}, {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"},
{file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0dda0290a6f608504882d9f7650975b4651ff91c85673341789a476b1159f211"}, {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"},
{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.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"},
{file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:09e926397f392059ce0afdcac920df29d9c833256354d0c55f1584b0b70cf07e"}, {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"},
{file = "pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:87cfa0ed6b8c5bd6ae8b66de941cece179281239d482f363814d2b986b79cedc"}, {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"},
{file = "pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e61328920154b6a44d98cabcb709f10e8b74276bc709c9a513a8c37a18786cc4"}, {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"},
{file = "pydantic_core-2.23.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce3317d155628301d649fe5e16a99528d5680af4ec7aa70b90b8dacd2d725c9b"}, {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"},
{file = "pydantic_core-2.23.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e89513f014c6be0d17b00a9a7c81b1c426f4eb9224b15433f3d98c1a071f8433"}, {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"},
{file = "pydantic_core-2.23.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:4f62c1c953d7ee375df5eb2e44ad50ce2f5aff931723b398b8bc6f0ac159791a"}, {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"},
{file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2718443bc671c7ac331de4eef9b673063b10af32a0bb385019ad61dcf2cc8f6c"}, {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"},
{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.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"},
{file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b676583fc459c64146debea14ba3af54e540b61762dfc0613dc4e98c3f66eeb"}, {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"},
{file = "pydantic_core-2.23.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:50e4661f3337977740fdbfbae084ae5693e505ca2b3130a6d4eb0f2281dc43b8"}, {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"},
{file = "pydantic_core-2.23.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:68f4cf373f0de6abfe599a38307f4417c1c867ca381c03df27c873a9069cda25"}, {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"},
{file = "pydantic_core-2.23.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:59d52cf01854cb26c46958552a21acb10dd78a52aa34c86f284e66b209db8cab"}, {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"},
{file = "pydantic_core-2.23.3.tar.gz", hash = "sha256:3cb0f65d8b4121c1b015c60104a685feb929a29d7cf204387c7f2688c7974690"}, {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"},
] ]
[package.dependencies] [package.dependencies]
@ -1758,21 +1758,23 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"]
[[package]] [[package]]
name = "pyright" name = "pyright"
version = "1.1.380" version = "1.1.382"
description = "Command line wrapper for pyright" description = "Command line wrapper for pyright"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "pyright-1.1.380-py3-none-any.whl", hash = "sha256:a6404392053d8848bacc7aebcbd9d318bb46baf1a1a000359305481920f43879"}, {file = "pyright-1.1.382-py3-none-any.whl", hash = "sha256:b6658802b2eca1107a6d63b6816903da4c7a53dffebc27cdbc02ebd904b7a18e"},
{file = "pyright-1.1.380.tar.gz", hash = "sha256:e6ceb1a5f7e9f03106e0aa1d6fbb4d97735a5e7ffb59f3de6b2db590baf935b2"}, {file = "pyright-1.1.382.tar.gz", hash = "sha256:0c953837aa9f1e1d8d46772ee7ebae845104db657e9216834dbdde567a11f177"},
] ]
[package.dependencies] [package.dependencies]
nodeenv = ">=1.6.0" nodeenv = ">=1.6.0"
typing-extensions = ">=4.1"
[package.extras] [package.extras]
all = ["twine (>=3.4.1)"] all = ["nodejs-wheel-binaries", "twine (>=3.4.1)"]
dev = ["twine (>=3.4.1)"] dev = ["twine (>=3.4.1)"]
nodejs = ["nodejs-wheel-binaries"]
[[package]] [[package]]
name = "python-dateutil" name = "python-dateutil"
@ -2043,29 +2045,29 @@ pyasn1 = ">=0.1.3"
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.6.5" version = "0.6.8"
description = "An extremely fast Python linter and code formatter, written in Rust." description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "ruff-0.6.5-py3-none-linux_armv6l.whl", hash = "sha256:7e4e308f16e07c95fc7753fc1aaac690a323b2bb9f4ec5e844a97bb7fbebd748"}, {file = "ruff-0.6.8-py3-none-linux_armv6l.whl", hash = "sha256:77944bca110ff0a43b768f05a529fecd0706aac7bcce36d7f1eeb4cbfca5f0f2"},
{file = "ruff-0.6.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:932cd69eefe4daf8c7d92bd6689f7e8182571cb934ea720af218929da7bd7d69"}, {file = "ruff-0.6.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:27b87e1801e786cd6ede4ada3faa5e254ce774de835e6723fd94551464c56b8c"},
{file = "ruff-0.6.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3a8d42d11fff8d3143ff4da41742a98f8f233bf8890e9fe23077826818f8d680"}, {file = "ruff-0.6.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd48f945da2a6334f1793d7f701725a76ba93bf3d73c36f6b21fb04d5338dcf5"},
{file = "ruff-0.6.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a50af6e828ee692fb10ff2dfe53f05caecf077f4210fae9677e06a808275754f"}, {file = "ruff-0.6.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:677e03c00f37c66cea033274295a983c7c546edea5043d0c798833adf4cf4c6f"},
{file = "ruff-0.6.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:794ada3400a0d0b89e3015f1a7e01f4c97320ac665b7bc3ade24b50b54cb2972"}, {file = "ruff-0.6.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9f1476236b3eacfacfc0f66aa9e6cd39f2a624cb73ea99189556015f27c0bdeb"},
{file = "ruff-0.6.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:381413ec47f71ce1d1c614f7779d88886f406f1fd53d289c77e4e533dc6ea200"}, {file = "ruff-0.6.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f5a2f17c7d32991169195d52a04c95b256378bbf0de8cb98478351eb70d526f"},
{file = "ruff-0.6.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:52e75a82bbc9b42e63c08d22ad0ac525117e72aee9729a069d7c4f235fc4d276"}, {file = "ruff-0.6.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5fd0d4b7b1457c49e435ee1e437900ced9b35cb8dc5178921dfb7d98d65a08d0"},
{file = "ruff-0.6.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09c72a833fd3551135ceddcba5ebdb68ff89225d30758027280968c9acdc7810"}, {file = "ruff-0.6.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8034b19b993e9601f2ddf2c517451e17a6ab5cdb1c13fdff50c1442a7171d87"},
{file = "ruff-0.6.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:800c50371bdcb99b3c1551d5691e14d16d6f07063a518770254227f7f6e8c178"}, {file = "ruff-0.6.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6cfb227b932ba8ef6e56c9f875d987973cd5e35bc5d05f5abf045af78ad8e098"},
{file = "ruff-0.6.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e25ddd9cd63ba1f3bd51c1f09903904a6adf8429df34f17d728a8fa11174253"}, {file = "ruff-0.6.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef0411eccfc3909269fed47c61ffebdcb84a04504bafa6b6df9b85c27e813b0"},
{file = "ruff-0.6.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7291e64d7129f24d1b0c947ec3ec4c0076e958d1475c61202497c6aced35dd19"}, {file = "ruff-0.6.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:007dee844738c3d2e6c24ab5bc7d43c99ba3e1943bd2d95d598582e9c1b27750"},
{file = "ruff-0.6.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9ad7dfbd138d09d9a7e6931e6a7e797651ce29becd688be8a0d4d5f8177b4b0c"}, {file = "ruff-0.6.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ce60058d3cdd8490e5e5471ef086b3f1e90ab872b548814e35930e21d848c9ce"},
{file = "ruff-0.6.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:005256d977021790cc52aa23d78f06bb5090dc0bfbd42de46d49c201533982ae"}, {file = "ruff-0.6.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1085c455d1b3fdb8021ad534379c60353b81ba079712bce7a900e834859182fa"},
{file = "ruff-0.6.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:482c1e6bfeb615eafc5899127b805d28e387bd87db38b2c0c41d271f5e58d8cc"}, {file = "ruff-0.6.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:70edf6a93b19481affd287d696d9e311388d808671bc209fb8907b46a8c3af44"},
{file = "ruff-0.6.5-py3-none-win32.whl", hash = "sha256:cf4d3fa53644137f6a4a27a2b397381d16454a1566ae5335855c187fbf67e4f5"}, {file = "ruff-0.6.8-py3-none-win32.whl", hash = "sha256:792213f7be25316f9b46b854df80a77e0da87ec66691e8f012f887b4a671ab5a"},
{file = "ruff-0.6.5-py3-none-win_amd64.whl", hash = "sha256:3e42a57b58e3612051a636bc1ac4e6b838679530235520e8f095f7c44f706ff9"}, {file = "ruff-0.6.8-py3-none-win_amd64.whl", hash = "sha256:ec0517dc0f37cad14a5319ba7bba6e7e339d03fbf967a6d69b0907d61be7a263"},
{file = "ruff-0.6.5-py3-none-win_arm64.whl", hash = "sha256:51935067740773afdf97493ba9b8231279e9beef0f2a8079188c4776c25688e0"}, {file = "ruff-0.6.8-py3-none-win_arm64.whl", hash = "sha256:8d3bb2e3fbb9875172119021a13eed38849e762499e3cfde9588e4b4d70968dc"},
{file = "ruff-0.6.5.tar.gz", hash = "sha256:4d32d87fab433c0cf285c3683dd4dae63be05fd7a1d65b3f5bf7cdd05a6b96fb"}, {file = "ruff-0.6.8.tar.gz", hash = "sha256:a5bf44b1aa0adaf6d9d20f86162b34f7c593bfedabc51239953e446aefc8ce18"},
] ]
[[package]] [[package]]
@ -2218,13 +2220,13 @@ files = [
[[package]] [[package]]
name = "types-pyyaml" name = "types-pyyaml"
version = "6.0.12.20240808" version = "6.0.12.20240917"
description = "Typing stubs for PyYAML" description = "Typing stubs for PyYAML"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "types-PyYAML-6.0.12.20240808.tar.gz", hash = "sha256:b8f76ddbd7f65440a8bda5526a9607e4c7a322dc2f8e1a8c405644f9a6f4b9af"}, {file = "types-PyYAML-6.0.12.20240917.tar.gz", hash = "sha256:d1405a86f9576682234ef83bcb4e6fff7c9305c8b1fbad5e0bcd4f7dbdc9c587"},
{file = "types_PyYAML-6.0.12.20240808-py3-none-any.whl", hash = "sha256:deda34c5c655265fc517b546c902aa6eed2ef8d3e921e4765fe606fe2afe8d35"}, {file = "types_PyYAML-6.0.12.20240917-py3-none-any.whl", hash = "sha256:392b267f1c0fe6022952462bf5d6523f31e37f6cea49b14cee7ad634b6301570"},
] ]
[[package]] [[package]]
@ -2285,13 +2287,13 @@ zstd = ["zstandard (>=0.18.0)"]
[[package]] [[package]]
name = "virtualenv" name = "virtualenv"
version = "20.26.4" version = "20.26.5"
description = "Virtual Python Environment builder" description = "Virtual Python Environment builder"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "virtualenv-20.26.4-py3-none-any.whl", hash = "sha256:48f2695d9809277003f30776d155615ffc11328e6a0a8c1f0ec80188d7874a55"}, {file = "virtualenv-20.26.5-py3-none-any.whl", hash = "sha256:4f3ac17b81fba3ce3bd6f4ead2749a72da5929c01774948e243db9ba41df4ff6"},
{file = "virtualenv-20.26.4.tar.gz", hash = "sha256:c17f4e0f3e6036e9f26700446f85c76ab11df65ff6d8a9cbfad9f71aabfcf23c"}, {file = "virtualenv-20.26.5.tar.gz", hash = "sha256:ce489cac131aa58f4b25e321d6d186171f78e6cb13fafbf32a840cee67733ff4"},
] ]
[package.dependencies] [package.dependencies]

View file

@ -110,6 +110,7 @@ model Reminder {
reminder_expires_at DateTime reminder_expires_at DateTime
reminder_channel_id BigInt reminder_channel_id BigInt
reminder_user_id BigInt reminder_user_id BigInt
reminder_sent Boolean @default(false)
guild_id BigInt guild_id BigInt
guild Guild @relation(fields: [guild_id], references: [guild_id]) guild Guild @relation(fields: [guild_id], references: [guild_id])
@ -169,4 +170,6 @@ enum CaseType {
UNJAIL UNJAIL
SNIPPETUNBAN SNIPPETUNBAN
UNTEMPBAN UNTEMPBAN
POLLBAN
POLLUNBAN
} }

View file

@ -99,7 +99,8 @@ class Mail(commands.Cog):
delete_after=30, 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 = "changeme" + "".join(str(random.randint(0, 9)) for _ in range(6))
password += "".join(random.choice("!@#$%^&*") for _ in range(4)) password += "".join(random.choice("!@#$%^&*") for _ in range(4))
return password return password
@ -161,7 +162,8 @@ class Mail(commands.Cog):
delete_after=30, 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: for item in result:
if "msg" in item: if "msg" in item:
msg = item["msg"] msg = item["msg"]
@ -173,8 +175,8 @@ class Mail(commands.Cog):
return None return None
@staticmethod
async def _send_dm( async def _send_dm(
self,
interaction: discord.Interaction, interaction: discord.Interaction,
member: discord.Member, member: discord.Member,
mailbox_info: str, mailbox_info: str,

View file

@ -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.", "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.", "Linux's 1.0 release was in March 1994.",
"Less than 1% of the latest kernel release includes code written by Linus Torvalds.", "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.", "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.", "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.", "Slackware is the oldest active linux distribution being released on the 17th July 1993.",

View file

@ -14,71 +14,46 @@ from tux.ui.embeds import EmbedCreator
class ImgEffect(commands.Cog): class ImgEffect(commands.Cog):
def __init__(self, bot: Tux) -> None: def __init__(self, bot: Tux) -> None:
self.bot = bot self.bot = bot
self.allowed_mimetypes = [ self.allowed_mimetypes = ["image/jpeg", "image/png"]
"image/jpeg",
"image/png",
]
imgeffect = app_commands.Group(name="imgeffect", description="Image effects") imgeffect = app_commands.Group(name="imgeffect", description="Image effects")
@imgeffect.command( @imgeffect.command(name="deepfry", description="Deepfry an image")
name="deepfry",
description="Deepfry an image",
)
async def deepfry(self, interaction: discord.Interaction, image: discord.Attachment) -> None: async def deepfry(self, interaction: discord.Interaction, image: discord.Attachment) -> None:
""" if not self.is_valid_image(image):
Deepfry an image. await self.send_invalid_image_response(interaction)
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)
return return
# say that the image is being processed
logger.info("Processing image...")
await interaction.response.defer(ephemeral=True) await interaction.response.defer(ephemeral=True)
# open url with PIL pil_image = await self.fetch_image(image.url)
logger.info("Opening image with PIL and HTTPX...")
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: 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)) return Image.open(io.BytesIO(response.content)).convert("RGB")
pil_image = pil_image.convert("RGB")
logger.info("Image opened with PIL.")
# resize image to 25% then back to original size @staticmethod
logger.info("Resizing image...") 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))) 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) pil_image = ImageEnhance.Sharpness(pil_image).enhance(100.0)
logger.info("Sharpness increased.")
logger.info("Adjusting color...")
r = pil_image.split()[0] r = pil_image.split()[0]
r = ImageEnhance.Contrast(r).enhance(2.0) r = ImageEnhance.Contrast(r).enhance(2.0)
r = ImageEnhance.Brightness(r).enhance(1.5) r = ImageEnhance.Brightness(r).enhance(1.5)
@ -86,14 +61,43 @@ class ImgEffect(commands.Cog):
colours = ((254, 0, 2), (255, 255, 15)) colours = ((254, 0, 2), (255, 255, 15))
r = ImageOps.colorize(r, colours[0], colours[1]) r = ImageOps.colorize(r, colours[0], colours[1])
pil_image = Image.blend(pil_image, r, 0.75) pil_image = Image.blend(pil_image, r, 0.75)
logger.info("Color adjustment complete.")
# send image return pil_image.resize((int(pil_image.width * 4), int(pil_image.height * 4)))
logger.info("Sending image...")
pil_image = 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() arr = io.BytesIO()
pil_image.save(arr, format="JPEG", quality=1) deepfried_image.save(arr, format="JPEG", quality=1)
arr.seek(0) arr.seek(0)
file = discord.File(arr, filename="deepfried.jpg") file = discord.File(arr, filename="deepfried.jpg")
await interaction.followup.send(file=file, ephemeral=True) await interaction.followup.send(file=file, ephemeral=True)

View file

@ -57,7 +57,13 @@ class Random(commands.Cog):
aliases=["eightball", "8b"], aliases=["eightball", "8b"],
) )
@commands.guild_only() @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. Ask the magic 8ball a question.

View file

@ -65,7 +65,7 @@ class Info(commands.Cog):
custom_color=discord.Color.blurple(), custom_color=discord.Color.blurple(),
custom_author_text="Server Information", custom_author_text="Server Information",
custom_author_icon_url=guild.icon.url, 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="Owner", value=str(guild.owner.mention) if guild.owner else "Unknown")
.add_field(name="Vanity URL", value=guild.vanity_url_code or "None") .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="Text Channels", value=len(guild.text_channels))
.add_field(name="Voice Channels", value=len(guild.voice_channels)) .add_field(name="Voice Channels", value=len(guild.voice_channels))
.add_field(name="Forum Channels", value=len(guild.forums)) .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="Stickers", value=f"{len(guild.stickers)}/{guild.sticker_limit}")
.add_field(name="Roles", value=len(guild.roles)) .add_field(name="Roles", value=len(guild.roles))
.add_field(name="Humans", value=sum(not member.bot for member in guild.members)) .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) menu: ViewMenu = ViewMenu(ctx, menu_type=ViewMenu.TypeEmbed)
for chunk in chunks: for chunk in chunks:
page_embed: discord.Embed = embed.copy() 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) menu.add_page(page_embed)
buttons = [ buttons = [
@ -229,7 +229,8 @@ class Info(commands.Cog):
await menu.start() 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. Split an iterator into chunks of a specified size.

View file

@ -100,8 +100,8 @@ class ModerationCogBase(commands.Cog):
if isinstance(log_channel, discord.TextChannel): if isinstance(log_channel, discord.TextChannel):
await log_channel.send(embed=embed) await log_channel.send(embed=embed)
@staticmethod
async def send_dm( async def send_dm(
self,
ctx: commands.Context[Tux], ctx: commands.Context[Tux],
silent: bool, silent: bool,
user: discord.Member, user: discord.Member,
@ -250,3 +250,31 @@ class ModerationCogBase(commands.Cog):
await self.send_embed(ctx, embed, log_type="mod") await self.send_embed(ctx, embed, log_type="mod")
await ctx.send(embed=embed, delete_after=30, ephemeral=True) 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

View file

@ -319,8 +319,8 @@ class Cases(ModerationCogBase):
await menu.start() await menu.start()
@staticmethod
def _create_case_fields( def _create_case_fields(
self,
moderator: discord.Member, moderator: discord.Member,
user: discord.Member | discord.User, user: discord.Member | discord.User,
reason: str, reason: str,
@ -361,7 +361,8 @@ class Cases(ModerationCogBase):
return embed 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 "" return f"<:{emoji.name}:{emoji.id}>" if emoji else ""
def _get_case_status_emoji(self, case_status: bool | None) -> discord.Emoji | None: 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: def _get_case_action_emoji(self, case_type: CaseType) -> discord.Emoji | None:
action = None action = None
if case_type in [ if case_type in {
CaseType.BAN, CaseType.BAN,
CaseType.KICK, CaseType.KICK,
CaseType.TIMEOUT, CaseType.TIMEOUT,
CaseType.WARN, CaseType.WARN,
CaseType.JAIL, CaseType.JAIL,
CaseType.SNIPPETBAN, CaseType.SNIPPETBAN,
]: }:
action = "added" 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" action = "removed"
if action is not None: if action is not None:
@ -410,8 +411,8 @@ class Cases(ModerationCogBase):
return self.bot.get_emoji(emoji_id) return self.bot.get_emoji(emoji_id)
return None return None
@staticmethod
def _get_case_description( def _get_case_description(
self,
case: Case, case: Case,
case_status_emoji: str, case_status_emoji: str,
case_type_emoji: str, case_type_emoji: str,

View file

@ -105,8 +105,8 @@ class Jail(ModerationCogBase):
dm_sent = await self.send_dm(ctx, flags.silent, member, flags.reason, "jailed") 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) await self.handle_case_response(ctx, CaseType.JAIL, case.case_number, flags.reason, member, dm_sent)
@staticmethod
def _get_manageable_roles( def _get_manageable_roles(
self,
member: discord.Member, member: discord.Member,
jail_role: discord.Role, jail_role: discord.Role,
) -> list[discord.Role]: ) -> list[discord.Role]:

View file

@ -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))

View file

@ -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))

View file

@ -15,7 +15,8 @@ class Slowmode(commands.Cog):
@commands.hybrid_command( @commands.hybrid_command(
name="slowmode", name="slowmode",
aliases=["sm"], aliases=["sm"],
usage="slowmode <delay|get> [channel]\nor slowmode [channel] <delay|get>", # only place where generate_usage shouldn't be used # only place where generate_usage shouldn't be used:
usage="slowmode <delay|get> [channel]\nor slowmode [channel] <delay|get>",
) )
@commands.guild_only() @commands.guild_only()
@checks.has_pl(2) @checks.has_pl(2)
@ -83,11 +84,12 @@ class Slowmode(commands.Cog):
return action, channel 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 return ctx.channel if isinstance(ctx.channel, discord.TextChannel | discord.Thread) else None
@staticmethod
async def _get_slowmode( async def _get_slowmode(
self,
ctx: commands.Context[Tux], ctx: commands.Context[Tux],
channel: discord.TextChannel | discord.Thread, channel: discord.TextChannel | discord.Thread,
) -> None: ) -> None:
@ -135,7 +137,8 @@ class Slowmode(commands.Cog):
await ctx.send(f"Failed to set slowmode. Error: {error}", delete_after=30, ephemeral=True) await ctx.send(f"Failed to set slowmode. Error: {error}", delete_after=30, ephemeral=True)
logger.error(f"Failed to set slowmode. Error: {error}") 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: try:
if delay.endswith("s"): if delay.endswith("s"):
delay = delay[:-1] delay = delay[:-1]

View file

@ -52,7 +52,7 @@ class Bookmarks(commands.Cog):
message: discord.Message, message: discord.Message,
) -> discord.Embed: ) -> discord.Embed:
if len(message.content) > CONST.EMBED_MAX_DESC_LENGTH: 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( embed = EmbedCreator.create_embed(
bot=self.bot, bot=self.bot,
@ -71,8 +71,8 @@ class Bookmarks(commands.Cog):
return embed return embed
@staticmethod
async def _send_bookmark( async def _send_bookmark(
self,
user: discord.User, user: discord.User,
message: discord.Message, message: discord.Message,
embed: discord.Embed, embed: discord.Embed,

View file

@ -96,7 +96,6 @@ class GifLimiter(commands.Cog):
self.recent_gifs_by_channel[channel_id] = [ self.recent_gifs_by_channel[channel_id] = [
t for t in timestamps if current_time - t < self.recent_gif_age 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()): 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] filtered_timestamps = [t for t in timestamps if current_time - t < self.recent_gif_age]
if filtered_timestamps: if filtered_timestamps:

View file

@ -3,7 +3,9 @@ from discord import app_commands
from discord.ext import commands from discord.ext import commands
from loguru import logger from loguru import logger
from prisma.enums import CaseType
from tux.bot import Tux from tux.bot import Tux
from tux.database.controllers import CaseController
from tux.ui.embeds import EmbedCreator from tux.ui.embeds import EmbedCreator
# TODO: Create option inputs for the poll command instead of using a comma separated string # 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): class Poll(commands.Cog):
def __init__(self, bot: Tux) -> None: def __init__(self, bot: Tux) -> None:
self.bot = bot 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 @commands.Cog.listener() # listen for messages
async def on_message(self, message: discord.Message) -> None: async def on_message(self, message: discord.Message) -> None:
@ -40,7 +71,6 @@ class Poll(commands.Cog):
@commands.Cog.listener() @commands.Cog.listener()
async def on_reaction_add(self, reaction: discord.Reaction, user: discord.User) -> None: async def on_reaction_add(self, reaction: discord.Reaction, user: discord.User) -> None:
# Block any reactions that are not numbers for the poll # Block any reactions that are not numbers for the poll
if reaction.message.embeds: if reaction.message.embeds:
embed = reaction.message.embeds[0] embed = reaction.message.embeds[0]
if ( if (
@ -64,7 +94,12 @@ class Poll(commands.Cog):
The title of the poll. The title of the poll.
options : str options : str
The options for the poll, separated by commas. 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 # Split the options by comma
options_list = options.split(",") options_list = options.split(",")
@ -72,6 +107,17 @@ class Poll(commands.Cog):
# Remove any leading or trailing whitespaces from the options # Remove any leading or trailing whitespaces from the options
options_list = [option.strip() for option in options_list] 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 # Check if the options count is between 2-9
if len(options_list) < 2 or len(options_list) > 9: if len(options_list) < 2 or len(options_list) > 9:
embed = EmbedCreator.create_embed( embed = EmbedCreator.create_embed(

View file

@ -1,10 +1,9 @@
import asyncio
import contextlib import contextlib
import datetime import datetime
import discord import discord
from discord import app_commands from discord import app_commands
from discord.ext import commands from discord.ext import commands, tasks
from loguru import logger from loguru import logger
from prisma.models import Reminder from prisma.models import Reminder
@ -14,40 +13,26 @@ from tux.ui.embeds import EmbedCreator
from tux.utils.functions import convert_to_seconds 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): class RemindMe(commands.Cog):
def __init__(self, bot: Tux) -> None: def __init__(self, bot: Tux) -> None:
self.bot = bot self.bot = bot
self.db = DatabaseController().reminder self.db = DatabaseController().reminder
self.bot.loop.create_task(self.update()) self.check_reminders.start()
async def send_reminders(self, reminder: Reminder) -> None: @tasks.loop(seconds=120)
""" async def check_reminders(self):
Send the reminder to the user. reminders = await self.db.get_unsent_reminders()
Parameters try:
---------- for reminder in reminders:
reminder : Reminder await self.send_reminder(reminder)
The reminder object. 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) user = self.bot.get_user(reminder.reminder_user_id)
if user is not None: if user is not None:
@ -64,103 +49,37 @@ class RemindMe(commands.Cog):
await user.send(embed=embed) await user.send(embed=embed)
except discord.Forbidden: except discord.Forbidden:
# Send a message in the channel if the user has DMs closed channel = self.bot.get_channel(reminder.reminder_channel_id)
channel: discord.abc.GuildChannel | discord.Thread | discord.abc.PrivateChannel | None = (
self.bot.get_channel(reminder.reminder_channel_id)
)
if channel is not None and isinstance( if isinstance(channel, discord.TextChannel | discord.Thread | discord.VoiceChannel):
channel,
discord.TextChannel | discord.Thread | discord.VoiceChannel,
):
with contextlib.suppress(discord.Forbidden): with contextlib.suppress(discord.Forbidden):
await channel.send( await channel.send(
content=f"{user.mention} Failed to DM you, sending in channel", content=f"{user.mention} Failed to DM you, sending in channel",
embed=embed, embed=embed,
) )
return
else: else:
logger.error( 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: 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 @check_reminders.before_loop
await self.db.delete_reminder_by_id(reminder.reminder_id) async def before_check_reminders(self):
await self.bot.wait_until_ready()
# 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))
@app_commands.command( @app_commands.command(
name="remindme", name="remindme",
description="Reminds you after a certain amount of time.", description="Reminds you after a certain amount of time.",
) )
async def remindme(self, interaction: discord.Interaction, time: str, *, reminder: str) -> None: 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) 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: if seconds == 0:
await interaction.response.send_message( await interaction.response.send_message(
"Invalid time format. Please use the format `[number][M/w/d/h/m/s]`.", "Invalid time format. Please use the format `[number][M/w/d/h/m/s]`.",
@ -169,13 +88,13 @@ class RemindMe(commands.Cog):
) )
return return
seconds = datetime.datetime.now(datetime.UTC) + datetime.timedelta(seconds=seconds) expires_at = datetime.datetime.now(datetime.UTC) + datetime.timedelta(seconds=seconds)
try: try:
await self.db.insert_reminder( await self.db.insert_reminder(
reminder_user_id=interaction.user.id, reminder_user_id=interaction.user.id,
reminder_content=reminder, reminder_content=reminder,
reminder_expires_at=seconds, reminder_expires_at=expires_at,
reminder_channel_id=interaction.channel_id or 0, reminder_channel_id=interaction.channel_id or 0,
guild_id=interaction.guild_id or 0, guild_id=interaction.guild_id or 0,
) )
@ -186,12 +105,13 @@ class RemindMe(commands.Cog):
user_name=interaction.user.name, user_name=interaction.user.name,
user_display_avatar=interaction.user.display_avatar.url, user_display_avatar=interaction.user.display_avatar.url,
title="Reminder Set", title="Reminder Set",
description=f"Reminder set for <t:{int(seconds.timestamp())}:f>.", description=f"Reminder set for <t:{int(expires_at.timestamp())}:f>.",
) )
embed.add_field( embed.add_field(
name="Note", 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: except Exception as e:
@ -207,9 +127,6 @@ class RemindMe(commands.Cog):
await interaction.response.send_message(embed=embed, ephemeral=True) 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: async def setup(bot: Tux) -> None:
await bot.add_cog(RemindMe(bot)) await bot.add_cog(RemindMe(bot))

View file

@ -46,7 +46,8 @@ class Run(commands.Cog):
self.run.usage = generate_usage(self.run) self.run.usage = generate_usage(self.run)
self.languages.usage = generate_usage(self.languages) 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. Converts ANSI encoded text into non-ANSI.
@ -63,7 +64,8 @@ class Run(commands.Cog):
return ansi_re.sub("", ansi) 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. Removes backticks from the provided string.
@ -277,7 +279,7 @@ class Run(commands.Cog):
code, code,
) )
await msg.delete() 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 return
await self.send_embedded_reply( await self.send_embedded_reply(
ctx, ctx,
@ -340,7 +342,7 @@ class Run(commands.Cog):
user_name=ctx.author.name, user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url, user_display_avatar=ctx.author.display_avatar.url,
title="Supported Languages", title="Supported Languages",
description=f"```{', '.join(compiler_map.keys())}```", description=f"```{", ".join(compiler_map.keys())}```",
) )
await ctx.send(embed=embed) await ctx.send(embed=embed)

View file

@ -152,7 +152,7 @@ class Snippets(commands.Cog):
text = "```\n" text = "```\n"
for i, snippet in enumerate(snippets[:10]): 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 += "```" text += "```"
# only show top 10, no pagination # 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="Name", value=snippet.snippet_name, inline=False)
embed.add_field( embed.add_field(
name="Author", 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, inline=False,
) )
embed.add_field(name="Content", value=f"> {snippet.snippet_content}", 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): if author := self.bot.get_user(snippet.snippet_user_id):
with contextlib.suppress(discord.Forbidden): with contextlib.suppress(discord.Forbidden):
await author.send( 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?** **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. If a snippet is locked, it cannot be edited by anyone other than moderators. This means that you can no longer edit this snippet.

View file

@ -135,7 +135,8 @@ class Tldr(commands.Cog):
return self._run_subprocess(["tldr", "--list"], "No TLDR pages found.").split("\n") 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. Helper method to run subprocesses for CLI interactions.

View file

@ -1,4 +1,4 @@
from datetime import datetime from datetime import UTC, datetime
from prisma.models import Guild, Reminder from prisma.models import Guild, Reminder
from tux.database.client import db from tux.database.client import db
@ -21,6 +21,10 @@ class ReminderController:
async def get_reminder_by_id(self, reminder_id: int) -> Reminder | None: async def get_reminder_by_id(self, reminder_id: int) -> Reminder | None:
return await self.table.find_first(where={"reminder_id": reminder_id}) 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( async def insert_reminder(
self, self,
reminder_user_id: int, reminder_user_id: int,
@ -38,6 +42,7 @@ class ReminderController:
"reminder_expires_at": reminder_expires_at, "reminder_expires_at": reminder_expires_at,
"reminder_channel_id": reminder_channel_id, "reminder_channel_id": reminder_channel_id,
"guild_id": guild_id, "guild_id": guild_id,
"reminder_sent": False,
}, },
) )
@ -53,3 +58,19 @@ class ReminderController:
where={"reminder_id": reminder_id}, where={"reminder_id": reminder_id},
data={"reminder_content": reminder_content}, 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},
)

View file

@ -13,7 +13,8 @@ class ActivityHandler(commands.Cog):
self.delay = delay self.delay = delay
self.activities = self.build_activity_list() 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 = [ activity_data = [
{"type": discord.ActivityType.watching, "name": "{member_count} members"}, {"type": discord.ActivityType.watching, "name": "{member_count} members"},
{"type": discord.ActivityType.watching, "name": "All Things Linux"}, {"type": discord.ActivityType.watching, "name": "All Things Linux"},

View file

@ -273,7 +273,8 @@ class ErrorHandler(commands.Cog):
return error_map.get(type(error), self.error_message).format(error=error) 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. Log the error traceback.

View file

@ -20,7 +20,8 @@ class EventHandler(commands.Cog):
async def on_guild_remove(self, guild: discord.Guild) -> None: async def on_guild_remove(self, guild: discord.Guild) -> None:
await self.db.guild.delete_guild_by_id(guild.id) 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: if message.author.bot:
return return

View file

@ -38,7 +38,8 @@ class TuxHelp(commands.HelpCommand):
return self.context.clean_prefix or CONST.DEFAULT_PREFIX 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. Creates a base embed with uniform styling.
@ -61,8 +62,8 @@ class TuxHelp(commands.HelpCommand):
color=CONST.EMBED_COLORS["DEFAULT"], color=CONST.EMBED_COLORS["DEFAULT"],
) )
@staticmethod
def _add_command_field( def _add_command_field(
self,
embed: discord.Embed, embed: discord.Embed,
command: commands.Command[Any, Any, Any], command: commands.Command[Any, Any, Any],
prefix: str, prefix: str,
@ -84,11 +85,12 @@ class TuxHelp(commands.HelpCommand):
embed.add_field( embed.add_field(
name=f"{prefix}{command.qualified_name} ({command_aliases})", 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, 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. Determines the type of a flag based on its annotation.
@ -111,7 +113,8 @@ class TuxHelp(commands.HelpCommand):
case _: case _:
return str(flag_annotation) 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. 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) flag_str = self._format_flag_name(flag)
if flag.aliases: if flag.aliases:
flag_str += f" ({', '.join(flag.aliases)})" flag_str += f" ({", ".join(flag.aliases)})"
# else: # else:
# flag_str += f" : {flag_type}" # 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: if flag.default is not discord.utils.MISSING:
flag_str += f"\n\tDefault: {flag.default}" flag_str += f"\n\tDefault: {flag.default}"
@ -289,12 +292,13 @@ class TuxHelp(commands.HelpCommand):
for command in mapping_commands: for command in mapping_commands:
cmd_name_and_aliases = f"`{command.name}`" cmd_name_and_aliases = f"`{command.name}`"
if command.aliases: 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 command_categories[cog_group][command.name] = cmd_name_and_aliases
return command_categories 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. Retrieves a list of cog groups from the 'cogs' folder.
@ -349,8 +353,8 @@ class TuxHelp(commands.HelpCommand):
return select_options return select_options
@staticmethod
def _add_navigation_and_selection( def _add_navigation_and_selection(
self,
menu: ViewMenu, menu: ViewMenu,
select_options: dict[discord.SelectOption, list[Page]], select_options: dict[discord.SelectOption, list[Page]],
) -> None: ) -> None:
@ -368,7 +372,8 @@ class TuxHelp(commands.HelpCommand):
menu.add_select(ViewSelect(title="Command Categories", options=select_options)) menu.add_select(ViewSelect(title="Command Categories", options=select_options))
menu.add_button(ViewButton.end_session()) 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. Extracts the cog group from a cog's string representation.
@ -424,7 +429,7 @@ class TuxHelp(commands.HelpCommand):
embed = self._embed_base( embed = self._embed_base(
title=f"{prefix}{command.qualified_name}", 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) await self._add_command_help_fields(embed, command)
@ -450,12 +455,12 @@ class TuxHelp(commands.HelpCommand):
embed.add_field( embed.add_field(
name="Usage", name="Usage",
value=f"`{prefix}{command.usage or 'No usage.'}`", value=f"`{prefix}{command.usage or "No usage."}`",
inline=False, inline=False,
) )
embed.add_field( embed.add_field(
name="Aliases", 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, inline=False,
) )
@ -471,7 +476,7 @@ class TuxHelp(commands.HelpCommand):
prefix = await self._get_prefix() 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) await self._add_command_help_fields(embed, group)
for command in group.commands: for command in group.commands:

View file

@ -37,7 +37,7 @@ def generate_usage(
flags: dict[str, commands.Flag] = flag_converter.get_flags() if flag_converter else {} flags: dict[str, commands.Flag] = flag_converter.get_flags() if flag_converter else {}
for param_name, param in parameters.items(): for param_name, param in parameters.items():
if param_name in ["ctx", "flags"]: if param_name in {"ctx", "flags"}:
continue continue
is_required = param.default == inspect.Parameter.empty is_required = param.default == inspect.Parameter.empty
matching_string = get_matching_string(param_name) matching_string = get_matching_string(param_name)
@ -60,7 +60,7 @@ def generate_usage(
usage += f" {flag}" usage += f" {flag}"
if optional_flags: if optional_flags:
usage += f" [{' | '.join(optional_flags)}]" usage += f" [{" | ".join(optional_flags)}]"
return usage return usage
@ -309,3 +309,33 @@ class SnippetUnbanFlags(commands.FlagConverter, case_insensitive=True, delimiter
aliases=["s", "quiet"], aliases=["s", "quiet"],
default=False, 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,
)