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

Merge branch 'main' into githubapi

This commit is contained in:
Kasen Engel 2024-04-14 06:56:36 -07:00 committed by GitHub
commit 5f7178b2f9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 1017 additions and 187 deletions

248
poetry.lock generated
View file

@ -255,6 +255,47 @@ files = [
[package.extras] [package.extras]
develop = ["aiomisc-pytest", "pytest", "pytest-cov"] develop = ["aiomisc-pytest", "pytest", "pytest-cov"]
[[package]]
name = "cairocffi"
version = "1.6.1"
description = "cffi-based cairo bindings for Python"
optional = false
python-versions = ">=3.7"
files = [
{file = "cairocffi-1.6.1-py3-none-any.whl", hash = "sha256:aa78ee52b9069d7475eeac457389b6275aa92111895d78fbaa2202a52dac112e"},
{file = "cairocffi-1.6.1.tar.gz", hash = "sha256:78e6bbe47357640c453d0be929fa49cd05cce2e1286f3d2a1ca9cbda7efdb8b7"},
]
[package.dependencies]
cffi = ">=1.1.0"
[package.extras]
doc = ["sphinx", "sphinx_rtd_theme"]
test = ["flake8", "isort", "numpy", "pikepdf", "pytest"]
xcb = ["xcffib (>=1.4.0)"]
[[package]]
name = "cairosvg"
version = "2.7.1"
description = "A Simple SVG Converter based on Cairo"
optional = false
python-versions = ">=3.5"
files = [
{file = "CairoSVG-2.7.1-py3-none-any.whl", hash = "sha256:8a5222d4e6c3f86f1f7046b63246877a63b49923a1cd202184c3a634ef546b3b"},
{file = "CairoSVG-2.7.1.tar.gz", hash = "sha256:432531d72347291b9a9ebfb6777026b607563fd8719c46ee742db0aef7271ba0"},
]
[package.dependencies]
cairocffi = "*"
cssselect2 = "*"
defusedxml = "*"
pillow = "*"
tinycss2 = "*"
[package.extras]
doc = ["sphinx", "sphinx-rtd-theme"]
test = ["flake8", "isort", "pytest"]
[[package]] [[package]]
name = "certifi" name = "certifi"
version = "2024.2.2" version = "2024.2.2"
@ -466,6 +507,7 @@ files = [
] ]
[[package]] [[package]]
name = "cryptography" name = "cryptography"
version = "42.0.5" version = "42.0.5"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
@ -519,6 +561,25 @@ ssh = ["bcrypt (>=3.1.5)"]
test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
test-randomorder = ["pytest-randomly"] test-randomorder = ["pytest-randomly"]
name = "cssselect2"
version = "0.7.0"
description = "CSS selectors for Python ElementTree"
optional = false
python-versions = ">=3.7"
files = [
{file = "cssselect2-0.7.0-py3-none-any.whl", hash = "sha256:fd23a65bfd444595913f02fc71f6b286c29261e354c41d722ca7a261a49b5969"},
{file = "cssselect2-0.7.0.tar.gz", hash = "sha256:1ccd984dab89fc68955043aca4e1b03e0cf29cad9880f6e28e3ba7a74b14aa5a"},
]
[package.dependencies]
tinycss2 = "*"
webencodings = "*"
[package.extras]
doc = ["sphinx", "sphinx_rtd_theme"]
test = ["flake8", "isort", "pytest"]
[[package]] [[package]]
name = "dateparser" name = "dateparser"
version = "1.2.0" version = "1.2.0"
@ -558,6 +619,18 @@ wrapt = ">=1.10,<2"
[package.extras] [package.extras]
dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"]
name = "defusedxml"
version = "0.7.1"
description = "XML bomb protection for Python stdlib modules"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
files = [
{file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"},
{file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"},
]
[[package]] [[package]]
name = "discord-py" name = "discord-py"
version = "2.3.2" version = "2.3.2"
@ -1046,6 +1119,92 @@ files = [
[package.dependencies] [package.dependencies]
setuptools = "*" setuptools = "*"
[[package]]
name = "pillow"
version = "10.3.0"
description = "Python Imaging Library (Fork)"
optional = false
python-versions = ">=3.8"
files = [
{file = "pillow-10.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45"},
{file = "pillow-10.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c"},
{file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78618cdbccaa74d3f88d0ad6cb8ac3007f1a6fa5c6f19af64b55ca170bfa1edf"},
{file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261ddb7ca91fcf71757979534fb4c128448b5b4c55cb6152d280312062f69599"},
{file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ce49c67f4ea0609933d01c0731b34b8695a7a748d6c8d186f95e7d085d2fe475"},
{file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b14f16f94cbc61215115b9b1236f9c18403c15dd3c52cf629072afa9d54c1cbf"},
{file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d33891be6df59d93df4d846640f0e46f1a807339f09e79a8040bc887bdcd7ed3"},
{file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b50811d664d392f02f7761621303eba9d1b056fb1868c8cdf4231279645c25f5"},
{file = "pillow-10.3.0-cp310-cp310-win32.whl", hash = "sha256:ca2870d5d10d8726a27396d3ca4cf7976cec0f3cb706debe88e3a5bd4610f7d2"},
{file = "pillow-10.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f0d0591a0aeaefdaf9a5e545e7485f89910c977087e7de2b6c388aec32011e9f"},
{file = "pillow-10.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:ccce24b7ad89adb5a1e34a6ba96ac2530046763912806ad4c247356a8f33a67b"},
{file = "pillow-10.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:5f77cf66e96ae734717d341c145c5949c63180842a545c47a0ce7ae52ca83795"},
{file = "pillow-10.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4b878386c4bf293578b48fc570b84ecfe477d3b77ba39a6e87150af77f40c57"},
{file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27"},
{file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9797a6c8fe16f25749b371c02e2ade0efb51155e767a971c61734b1bf6293994"},
{file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:9e91179a242bbc99be65e139e30690e081fe6cb91a8e77faf4c409653de39451"},
{file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1b87bd9d81d179bd8ab871603bd80d8645729939f90b71e62914e816a76fc6bd"},
{file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81d09caa7b27ef4e61cb7d8fbf1714f5aec1c6b6c5270ee53504981e6e9121ad"},
{file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c"},
{file = "pillow-10.3.0-cp311-cp311-win32.whl", hash = "sha256:7161ec49ef0800947dc5570f86568a7bb36fa97dd09e9827dc02b718c5643f09"},
{file = "pillow-10.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8eb0908e954d093b02a543dc963984d6e99ad2b5e36503d8a0aaf040505f747d"},
{file = "pillow-10.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:4e6f7d1c414191c1199f8996d3f2282b9ebea0945693fb67392c75a3a320941f"},
{file = "pillow-10.3.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:e46f38133e5a060d46bd630faa4d9fa0202377495df1f068a8299fd78c84de84"},
{file = "pillow-10.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:50b8eae8f7334ec826d6eeffaeeb00e36b5e24aa0b9df322c247539714c6df19"},
{file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d3bea1c75f8c53ee4d505c3e67d8c158ad4df0d83170605b50b64025917f338"},
{file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19aeb96d43902f0a783946a0a87dbdad5c84c936025b8419da0a0cd7724356b1"},
{file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74d28c17412d9caa1066f7a31df8403ec23d5268ba46cd0ad2c50fb82ae40462"},
{file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a"},
{file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d886f5d353333b4771d21267c7ecc75b710f1a73d72d03ca06df49b09015a9ef"},
{file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b5ec25d8b17217d635f8935dbc1b9aa5907962fae29dff220f2659487891cd3"},
{file = "pillow-10.3.0-cp312-cp312-win32.whl", hash = "sha256:51243f1ed5161b9945011a7360e997729776f6e5d7005ba0c6879267d4c5139d"},
{file = "pillow-10.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:412444afb8c4c7a6cc11a47dade32982439925537e483be7c0ae0cf96c4f6a0b"},
{file = "pillow-10.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:798232c92e7665fe82ac085f9d8e8ca98826f8e27859d9a96b41d519ecd2e49a"},
{file = "pillow-10.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:4eaa22f0d22b1a7e93ff0a596d57fdede2e550aecffb5a1ef1106aaece48e96b"},
{file = "pillow-10.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cd5e14fbf22a87321b24c88669aad3a51ec052eb145315b3da3b7e3cc105b9a2"},
{file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1530e8f3a4b965eb6a7785cf17a426c779333eb62c9a7d1bbcf3ffd5bf77a4aa"},
{file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d512aafa1d32efa014fa041d38868fda85028e3f930a96f85d49c7d8ddc0383"},
{file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:339894035d0ede518b16073bdc2feef4c991ee991a29774b33e515f1d308e08d"},
{file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:aa7e402ce11f0885305bfb6afb3434b3cd8f53b563ac065452d9d5654c7b86fd"},
{file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0ea2a783a2bdf2a561808fe4a7a12e9aa3799b701ba305de596bc48b8bdfce9d"},
{file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c78e1b00a87ce43bb37642c0812315b411e856a905d58d597750eb79802aaaa3"},
{file = "pillow-10.3.0-cp38-cp38-win32.whl", hash = "sha256:72d622d262e463dfb7595202d229f5f3ab4b852289a1cd09650362db23b9eb0b"},
{file = "pillow-10.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:2034f6759a722da3a3dbd91a81148cf884e91d1b747992ca288ab88c1de15999"},
{file = "pillow-10.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2ed854e716a89b1afcedea551cd85f2eb2a807613752ab997b9974aaa0d56936"},
{file = "pillow-10.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dc1a390a82755a8c26c9964d457d4c9cbec5405896cba94cf51f36ea0d855002"},
{file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4203efca580f0dd6f882ca211f923168548f7ba334c189e9eab1178ab840bf60"},
{file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3102045a10945173d38336f6e71a8dc71bcaeed55c3123ad4af82c52807b9375"},
{file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:6fb1b30043271ec92dc65f6d9f0b7a830c210b8a96423074b15c7bc999975f57"},
{file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:1dfc94946bc60ea375cc39cff0b8da6c7e5f8fcdc1d946beb8da5c216156ddd8"},
{file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b09b86b27a064c9624d0a6c54da01c1beaf5b6cadfa609cf63789b1d08a797b9"},
{file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d3b2348a78bc939b4fed6552abfd2e7988e0f81443ef3911a4b8498ca084f6eb"},
{file = "pillow-10.3.0-cp39-cp39-win32.whl", hash = "sha256:45ebc7b45406febf07fef35d856f0293a92e7417ae7933207e90bf9090b70572"},
{file = "pillow-10.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:0ba26351b137ca4e0db0342d5d00d2e355eb29372c05afd544ebf47c0956ffeb"},
{file = "pillow-10.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:50fd3f6b26e3441ae07b7c979309638b72abc1a25da31a81a7fbd9495713ef4f"},
{file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6b02471b72526ab8a18c39cb7967b72d194ec53c1fd0a70b050565a0f366d355"},
{file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8ab74c06ffdab957d7670c2a5a6e1a70181cd10b727cd788c4dd9005b6a8acd9"},
{file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2"},
{file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2ec1e921fd07c7cda7962bad283acc2f2a9ccc1b971ee4b216b75fad6f0463"},
{file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c8e73e99da7db1b4cad7f8d682cf6abad7844da39834c288fbfa394a47bbced"},
{file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:16563993329b79513f59142a6b02055e10514c1a8e86dca8b48a893e33cf91e3"},
{file = "pillow-10.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dd78700f5788ae180b5ee8902c6aea5a5726bac7c364b202b4b3e3ba2d293170"},
{file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aff76a55a8aa8364d25400a210a65ff59d0168e0b4285ba6bf2bd83cf675ba32"},
{file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b7bc2176354defba3edc2b9a777744462da2f8e921fbaf61e52acb95bafa9828"},
{file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:793b4e24db2e8742ca6423d3fde8396db336698c55cd34b660663ee9e45ed37f"},
{file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93480005693d247f8346bc8ee28c72a2191bdf1f6b5db469c096c0c867ac015"},
{file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83341b89884e2b2e55886e8fbbf37c3fa5efd6c8907124aeb72f285ae5696e5"},
{file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1a1d1915db1a4fdb2754b9de292642a39a7fb28f1736699527bb649484fb966a"},
{file = "pillow-10.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a0eaa93d054751ee9964afa21c06247779b90440ca41d184aeb5d410f20ff591"},
{file = "pillow-10.3.0.tar.gz", hash = "sha256:9d2455fbf44c914840c793e89aa82d0e1763a14253a000743719ae5946814b2d"},
]
[package.extras]
docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"]
fpx = ["olefile"]
mic = ["olefile"]
tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
typing = ["typing-extensions"]
xmp = ["defusedxml"]
[[package]] [[package]]
name = "platformdirs" name = "platformdirs"
version = "4.2.0" version = "4.2.0"
@ -1132,6 +1291,17 @@ files = [
[package.extras] [package.extras]
test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
[[package]]
name = "pyasn1"
version = "0.6.0"
description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
optional = false
python-versions = ">=3.8"
files = [
{file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"},
{file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"},
]
[[package]] [[package]]
name = "pycparser" name = "pycparser"
version = "2.22" version = "2.22"
@ -1557,31 +1727,43 @@ urllib3 = ">=1.21.1,<3"
[package.extras] [package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)"] socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
name = "rsa"
version = "4.9"
description = "Pure-Python RSA implementation"
optional = false
python-versions = ">=3.6,<4"
files = [
{file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"},
{file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"},
]
[package.dependencies]
pyasn1 = ">=0.1.3"
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.3.6" version = "0.3.7"
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.3.6-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:732ef99984275534f9466fbc01121523caf72aa8c2bdeb36fd2edf2bc294a992"}, {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0e8377cccb2f07abd25e84fc5b2cbe48eeb0fea9f1719cad7caedb061d70e5ce"},
{file = "ruff-0.3.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:93699d61116807edc5ca1cdf9d2d22cf8d93335d59e3ff0ca7aee62c1818a736"}, {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:15a4d1cc1e64e556fa0d67bfd388fed416b7f3b26d5d1c3e7d192c897e39ba4b"},
{file = "ruff-0.3.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc4006cbc6c11fefc25f122d2eb4731d7a3d815dc74d67c54991cc3f99c90177"}, {file = "ruff-0.3.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d28bdf3d7dc71dd46929fafeec98ba89b7c3550c3f0978e36389b5631b793663"},
{file = "ruff-0.3.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:878ef1a55ce931f3ca23b690b159cd0659f495a4c231a847b00ca55e4c688baf"}, {file = "ruff-0.3.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:379b67d4f49774ba679593b232dcd90d9e10f04d96e3c8ce4a28037ae473f7bb"},
{file = "ruff-0.3.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecb87788284af96725643eae9ab3ac746d8cc09aad140268523b019f7ac3cd98"}, {file = "ruff-0.3.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c060aea8ad5ef21cdfbbe05475ab5104ce7827b639a78dd55383a6e9895b7c51"},
{file = "ruff-0.3.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b2e79f8e1b6bd5411d7ddad3f2abff3f9d371beda29daef86400d416dedb7e02"}, {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ebf8f615dde968272d70502c083ebf963b6781aacd3079081e03b32adfe4d58a"},
{file = "ruff-0.3.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf48ec2c4bfae7837dc325c431a2932dc23a1485e71c59591c1df471ba234e0e"}, {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d48098bd8f5c38897b03604f5428901b65e3c97d40b3952e38637b5404b739a2"},
{file = "ruff-0.3.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c466a52c522e6a08df0af018f550902f154f5649ad09e7f0d43da766e7399ebc"}, {file = "ruff-0.3.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da8a4fda219bf9024692b1bc68c9cff4b80507879ada8769dc7e985755d662ea"},
{file = "ruff-0.3.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28ccf3fb6d1162a73cd286c63a5e4d885f46a1f99f0b392924bc95ccbd18ea8f"}, {file = "ruff-0.3.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c44e0149f1d8b48c4d5c33d88c677a4aa22fd09b1683d6a7ff55b816b5d074f"},
{file = "ruff-0.3.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b11e09439d9df6cc12d9f622065834654417c40216d271f639512d80e80e3e53"}, {file = "ruff-0.3.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3050ec0af72b709a62ecc2aca941b9cd479a7bf2b36cc4562f0033d688e44fa1"},
{file = "ruff-0.3.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:647f1fb5128a3e24ce68878b8050bb55044c45bb3f3ae4710d4da9ca96ede5cb"}, {file = "ruff-0.3.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a29cc38e4c1ab00da18a3f6777f8b50099d73326981bb7d182e54a9a21bb4ff7"},
{file = "ruff-0.3.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2b0c4c70578ef1871a9ac5c85ed7a8c33470e976c73ba9211a111d2771b5f787"}, {file = "ruff-0.3.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b15cc59c19edca917f51b1956637db47e200b0fc5e6e1878233d3a938384b0b"},
{file = "ruff-0.3.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e3da499ded004d0b956ab04248b2ae17e54a67ffc81353514ac583af5959a255"}, {file = "ruff-0.3.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e491045781b1e38b72c91247cf4634f040f8d0cb3e6d3d64d38dcf43616650b4"},
{file = "ruff-0.3.6-py3-none-win32.whl", hash = "sha256:4056480f5cf38ad278667c31b0ef334c29acdfcea617cb89c4ccbc7d96f1637f"}, {file = "ruff-0.3.7-py3-none-win32.whl", hash = "sha256:bc931de87593d64fad3a22e201e55ad76271f1d5bfc44e1a1887edd0903c7d9f"},
{file = "ruff-0.3.6-py3-none-win_amd64.whl", hash = "sha256:f1aa621beed533f46e9c7d6fe00e7f6e4570155b61d8f020387b72ace2b42e04"}, {file = "ruff-0.3.7-py3-none-win_amd64.whl", hash = "sha256:5ef0e501e1e39f35e03c2acb1d1238c595b8bb36cf7a170e7c1df1b73da00e74"},
{file = "ruff-0.3.6-py3-none-win_arm64.whl", hash = "sha256:7c8a2a0e0cab077a07465259ffe3b3c090e747ca8097c5dc4c36ca0fdaaac90d"}, {file = "ruff-0.3.7-py3-none-win_arm64.whl", hash = "sha256:789e144f6dc7019d1f92a812891c645274ed08af6037d11fc65fcbc183b7d59f"},
{file = "ruff-0.3.6.tar.gz", hash = "sha256:26071fb530038602b984e3bbe1443ef82a38450c4dcb1344a9caf67234ff9756"}, {file = "ruff-0.3.7.tar.gz", hash = "sha256:d5c1aebee5162c2226784800ae031f660c350e7a3402c4d1f8ea4e97e232e3ba"},
] ]
[[package]] [[package]]
@ -1669,6 +1851,24 @@ files = [
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
] ]
[[package]]
name = "tinycss2"
version = "1.2.1"
description = "A tiny CSS parser"
optional = false
python-versions = ">=3.7"
files = [
{file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"},
{file = "tinycss2-1.2.1.tar.gz", hash = "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627"},
]
[package.dependencies]
webencodings = ">=0.4"
[package.extras]
doc = ["sphinx", "sphinx_rtd_theme"]
test = ["flake8", "isort", "pytest"]
[[package]] [[package]]
name = "tomlkit" name = "tomlkit"
version = "0.12.4" version = "0.12.4"
@ -1767,6 +1967,17 @@ files = [
{file = "vulture-2.11.tar.gz", hash = "sha256:f0fbb60bce6511aad87ee0736c502456737490a82d919a44e6d92262cb35f1c2"}, {file = "vulture-2.11.tar.gz", hash = "sha256:f0fbb60bce6511aad87ee0736c502456737490a82d919a44e6d92262cb35f1c2"},
] ]
[[package]]
name = "webencodings"
version = "0.5.1"
description = "Character encoding aliases for legacy web content"
optional = false
python-versions = "*"
files = [
{file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},
{file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"},
]
[[package]] [[package]]
name = "win32-setctime" name = "win32-setctime"
version = "1.1.0" version = "1.1.0"
@ -1967,3 +2178,4 @@ multidict = ">=4.0"
lock-version = "2.0" lock-version = "2.0"
python-versions = ">=3.12,<4" python-versions = ">=3.12,<4"
content-hash = "95cd2b25b94d5eb7dd1dc10c590e3ad1b930c7e36775d6882146d84300a0a854" content-hash = "95cd2b25b94d5eb7dd1dc10c590e3ad1b930c7e36775d6882146d84300a0a854"
content-hash = "060fffe973c2ae0e030b0e24362608fa73db573ad19c0d65cf4e1695ea49121a"

View file

@ -35,7 +35,7 @@ model Users {
// Returns a string that allows you to mention the given user. // Returns a string that allows you to mention the given user.
mention String mention String
// Specifies if the user is a bot account. // Specifies if the user is a bot account.
bot Boolean bot Boolean @default(false)
// Returns the users creation time in UTC. This is when the users Discord account was created. // Returns the users creation time in UTC. This is when the users Discord account was created.
created_at DateTime? created_at DateTime?
// True if user is a member of a guild (not a discord.py attribute) // True if user is a member of a guild (not a discord.py attribute)
@ -45,7 +45,7 @@ model Users {
// An aware datetime object that specifies the date and time in UTC that the member joined the guild. If the member left and rejoined the guild, this will be the latest date. In certain cases, this can be None. // An aware datetime object that specifies the date and time in UTC that the member joined the guild. If the member left and rejoined the guild, this will be the latest date. In certain cases, this can be None.
joined_at DateTime? joined_at DateTime?
// This is a relation field and is a list of roles that the user has, linking to the `UserRoles` table. If you fetch a user from the database and include this field, you will get all the roles associated with that user. // This is a relation field and is a list of roles that the user has, linking to the `UserRoles` table. If you fetch a user from the database and include this field, you will get all the roles associated with that user.
roles UserRoles[] roles UserRoles[]
// This represents all the infractions that this user has given out when acting as a moderator. It has a `relation` annotation to make clear that for these infractions, this user is referred to in the `moderator` field of the `Infractions` table. // This represents all the infractions that this user has given out when acting as a moderator. It has a `relation` annotation to make clear that for these infractions, this user is referred to in the `moderator` field of the `Infractions` table.
@ -63,7 +63,8 @@ model Users {
notes_given Notes[] @relation("Moderator") notes_given Notes[] @relation("Moderator")
notes_received Notes[] @relation("User") notes_received Notes[] @relation("User")
reminders Reminders[] reminders Reminders[]
CommandStats CommandStats[]
} }
model Roles { model Roles {
@ -92,18 +93,13 @@ model Roles {
} }
model UserRoles { model UserRoles {
id BigInt @id @default(autoincrement())
// These refer to the `Users` model. `user_id` is the ID of a user from the `Users` table. The line `user Users @relation(fields: [user_id], references: [id])` implies that `user` establishes a relation with `Users` model based on the `user_id` in this `UserRoles` model and the `id` in the `Users` model.
user Users @relation(fields: [user_id], references: [id]) user Users @relation(fields: [user_id], references: [id])
user_id BigInt user_id BigInt
// These refer to the `Roles` model. `role_id` is the ID of a role from the `Roles` table. The line `role Roles @relation(fields: [role_id], references: [id])` implies that `role` establishes a relation with `Roles` model based on the `role_id` in this `UserRoles` model and the `id` in the `Roles` model.
role Roles @relation(fields: [role_id], references: [id]) role Roles @relation(fields: [role_id], references: [id])
role_id BigInt role_id BigInt
// This specifies a composite unique constraint on `user_id` and `role_id`, meaning each combination of a user id and role id must be unique. It prevents a single user from having the same role multiple times. This constraint helps to maintain data integrity. @@id([user_id, role_id])
@@unique([user_id, role_id])
} }
model Infractions { model Infractions {
@ -160,6 +156,24 @@ model Reminders {
user_id BigInt user_id BigInt
} }
model Commands {
id BigInt @id @default(autoincrement())
name String
content String
created_at DateTime? @default(now())
CommandStats CommandStats[]
}
model CommandStats {
id BigInt @id @default(autoincrement())
command_id BigInt
user_id BigInt
used_at DateTime? @default(now())
command Commands @relation(fields: [command_id], references: [id])
user Users @relation(fields: [user_id], references: [id])
}
// model Logs { // model Logs {
// id BigInt @id // id BigInt @id
// content String // content String

View file

@ -26,6 +26,10 @@ ruff = "^0.3.6"
sentry-sdk = "^1.45.0" sentry-sdk = "^1.45.0"
vulture = "^2.11" vulture = "^2.11"
pygithub = "^2.3.0" pygithub = "^2.3.0"
httpx = "^0.27.0"
cairosvg = "^2.7.1"
pillow = "^10.3.0"
rsa = "^4.9"
[tool.poetry.scripts] [tool.poetry.scripts]
pyright = "pyright:run" pyright = "pyright:run"

View file

@ -1,5 +1,6 @@
import asyncio import asyncio
from collections.abc import Sequence from collections.abc import Coroutine, Sequence
from typing import Any
import discord import discord
from discord import app_commands from discord import app_commands
@ -8,6 +9,9 @@ from loguru import logger
from tux.database.controllers import DatabaseController from tux.database.controllers import DatabaseController
# TODO: Move to a constants file or set a global check/error handler for all commands to avoid repetition.
GUILD_ONLY_MESSAGE = "This command can only be used in a guild."
class Db(commands.Cog): class Db(commands.Cog):
def __init__(self, bot: commands.Bot) -> None: def __init__(self, bot: commands.Bot) -> None:
@ -18,14 +22,14 @@ class Db(commands.Cog):
@app_commands.checks.has_role("Root") @app_commands.checks.has_role("Root")
@group.command(name="members", description="Seeds the database with all members.") @group.command(name="members", description="Seeds the database with all members.")
async def seed_members(self, interaction: discord.Interaction): async def seed_members(self, interaction: discord.Interaction) -> None:
await interaction.response.defer() await interaction.response.defer()
if interaction.guild is None: if interaction.guild is None:
await interaction.followup.send("This command can only be used in a guild.") await interaction.followup.send(GUILD_ONLY_MESSAGE)
return return
members = interaction.guild.members members: Sequence[discord.Member] = interaction.guild.members
batch_size = 10 batch_size = 10
@ -53,11 +57,11 @@ class Db(commands.Cog):
@app_commands.checks.has_role("Root") @app_commands.checks.has_role("Root")
@group.command(name="roles", description="Seeds the database with all roles.") @group.command(name="roles", description="Seeds the database with all roles.")
async def seed_roles(self, interaction: discord.Interaction): async def seed_roles(self, interaction: discord.Interaction) -> None:
await interaction.response.defer() await interaction.response.defer()
if interaction.guild is None: if interaction.guild is None:
await interaction.followup.send("This command can only be used in a guild.") await interaction.followup.send(GUILD_ONLY_MESSAGE)
return return
roles: Sequence[discord.Role] = interaction.guild.roles roles: Sequence[discord.Role] = interaction.guild.roles
@ -87,6 +91,40 @@ class Db(commands.Cog):
await interaction.followup.send("Seeded all roles.") await interaction.followup.send("Seeded all roles.")
logger.info(f"{interaction.user} seeded all roles.") logger.info(f"{interaction.user} seeded all roles.")
@app_commands.checks.has_role("Root")
@group.command(name="user_roles", description="Seeds the database with all user roles.")
async def seed_user_roles(self, interaction: discord.Interaction) -> None:
await interaction.response.defer()
if interaction.guild is None:
await interaction.followup.send(GUILD_ONLY_MESSAGE)
return
members: Sequence[discord.Member] = interaction.guild.members
batch_size = 10
for i in range(0, len(members), batch_size):
batch = members[i : i + batch_size]
tasks: list[Coroutine[Any, Any, None]] = []
for member in batch:
role_ids: list[int] = [role.id for role in member.roles]
tasks.append(
self.db_controller.user_roles.sync_user_roles(
user_id=member.id, role_ids=role_ids
)
)
await asyncio.gather(*tasks)
logger.info(f"Processed batch starting with member {batch[0].display_name}")
await asyncio.sleep(1)
await interaction.followup.send("Seeded all user roles.")
logger.info(f"{interaction.user} seeded all user roles.")
async def setup(bot: commands.Bot) -> None: async def setup(bot: commands.Bot) -> None:
await bot.add_cog(Db(bot)) await bot.add_cog(Db(bot))

View file

@ -70,7 +70,8 @@ class ErrorHandler(commands.Cog):
"""Handle traditional command errors.""" """Handle traditional command errors."""
if isinstance( if isinstance(
error, error,
commands.UnexpectedQuoteError commands.CommandNotFound
| commands.UnexpectedQuoteError
| commands.InvalidEndOfQuotedStringError | commands.InvalidEndOfQuotedStringError
| commands.CheckFailure, | commands.CheckFailure,
): ):

View file

@ -20,6 +20,47 @@ class GuildLogging(commands.Cog):
"""Audit logging - Channel""" """Audit logging - Channel"""
@commands.Cog.listener()
async def on_message(self, message: discord.Message):
# check if the message has no embeds, attachments, or content, stickers, or isnt a nitro gift/boost
# if so its probably a poll
poll_channel = self.bot.get_channel(1228717294788673656)
if message.channel == poll_channel:
# check if the message has content, stickers, or attachments
# if so delete the message as its not a poll
if message.content or message.stickers or message.attachments:
await message.delete()
embed = EmbedCreator.create_log_embed(
title="Non-Poll Deleted",
description=f"Message: {message.id}",
)
await self.send_to_audit_log(embed)
return
# make a thread for the poll
await message.create_thread(
name=f"Poll by {message.author.display_name}",
reason="Poll thread",
)
return
if (
not message.embeds
and not message.attachments
and not message.content
and not message.stickers
):
# check if the message is not a message
if message.type != discord.MessageType.default:
return
# delete the message and log it
await message.delete()
embed = EmbedCreator.create_log_embed(
title="Poll Deleted",
description=f"Message: {message.id}",
)
await self.send_to_audit_log(embed)
@commands.Cog.listener() @commands.Cog.listener()
async def on_guild_channel_create(self, channel: discord.abc.GuildChannel): async def on_guild_channel_create(self, channel: discord.abc.GuildChannel):
embed = EmbedCreator.create_log_embed( embed = EmbedCreator.create_log_embed(

View file

@ -3,49 +3,80 @@ from discord import app_commands
from discord.ext import commands from discord.ext import commands
from loguru import logger from loguru import logger
from tux.database.controllers import DatabaseController
from tux.utils.embeds import EmbedCreator
from tux.utils.enums import InfractionType
class Ban(commands.Cog): class Ban(commands.Cog):
def __init__(self, bot: commands.Bot) -> None: def __init__(self, bot: commands.Bot) -> None:
self.bot = bot self.bot = bot
self.db_controller = DatabaseController().infractions
@app_commands.checks.has_any_role("Admin", "Sr. Mod", "Mod") async def insert_infraction(
self,
user_id: int,
moderator_id: int,
infraction_type: InfractionType,
infraction_reason: str,
) -> None:
try:
await self.db_controller.create_infraction(
user_id=user_id,
moderator_id=moderator_id,
infraction_type=infraction_type,
infraction_reason=infraction_reason,
)
except Exception as error:
logger.error(f"Failed to create infraction. Error: {error}")
@app_commands.checks.has_any_role("Admin", "Sr. Mod", "Mod", "Jr. Mod")
@app_commands.command(name="ban", description="Bans a member from the server.") @app_commands.command(name="ban", description="Bans a member from the server.")
@app_commands.describe(member="Which member to ban", reason="Reason for ban") @app_commands.describe(member="Which member to ban", reason="Reason for ban")
async def ban( async def ban(
self, interaction: discord.Interaction, member: discord.Member, reason: str | None = None self, interaction: discord.Interaction, member: discord.Member, reason: str | None = None
) -> None: ) -> None:
logger.info(f"{interaction.user} banned {member.display_name} in {interaction.channel}")
response = await self.execute_ban(interaction, member, reason or "None provided")
await interaction.response.send_message(embed=response)
async def execute_ban(
self, interaction: discord.Interaction, member: discord.Member, reason: str | None = None
) -> discord.Embed:
try: try:
await member.ban(reason=reason) await member.ban(reason=reason)
embed = discord.Embed(
title=f"Banned {member.display_name}!", embed = EmbedCreator.create_infraction_embed(
color=discord.Colour.red(), title=f"{member.display_name} has been banned.",
description=f"Reason: `{reason}`", description=f"Reason: `{reason}`",
) interaction=interaction,
embed.set_footer(
text=f"Banned by {interaction.user.display_name}",
icon_url=interaction.user.display_avatar.url,
) )
logger.info(f"Successfully banned {member.display_name}.") embed.add_field(
name="Moderator",
value=f"{interaction.user.mention} ({interaction.user.id})",
inline=False,
)
except discord.errors.Forbidden as error: embed.add_field(
embed = discord.Embed( name="Member",
value=f"{member.mention} ({member.id})",
inline=False,
)
await self.insert_infraction(
user_id=member.id,
moderator_id=interaction.user.id,
infraction_type=InfractionType.BAN,
infraction_reason=reason or "None provided",
)
logger.info(f"Bannedd {member.display_name} for: {reason}")
except Exception as error:
embed = EmbedCreator.create_error_embed(
title=f"Failed to ban {member.display_name}", title=f"Failed to ban {member.display_name}",
color=discord.Colour.red(), description=f"Error Info: `{error}`",
description=f"Insufficient permissions. Error Info: `{error}`", interaction=interaction,
) )
logger.error(f"Failed to ban {member.display_name}. Error: {error}") logger.error(f"Failed to ban {member.display_name}. Error: {error}")
return embed await interaction.response.send_message(embed=embed)
async def setup(bot: commands.Bot) -> None: async def setup(bot: commands.Bot) -> None:

View file

@ -3,49 +3,90 @@ from discord import app_commands
from discord.ext import commands from discord.ext import commands
from loguru import logger from loguru import logger
from tux.database.controllers import DatabaseController
from tux.utils.embeds import EmbedCreator
from tux.utils.enums import InfractionType
class Kick(commands.Cog): class Kick(commands.Cog):
"""Cog for handling the kicking of members from a Discord server."""
def __init__(self, bot: commands.Bot) -> None: def __init__(self, bot: commands.Bot) -> None:
self.bot = bot self.bot = bot
self.db_controller = DatabaseController().infractions
async def insert_infraction(
self,
user_id: int,
moderator_id: int,
infraction_type: InfractionType,
infraction_reason: str,
) -> None:
"""Inserts an infraction record into the database."""
try:
await self.db_controller.create_infraction(
user_id=user_id,
moderator_id=moderator_id,
infraction_type=infraction_type,
infraction_reason=infraction_reason,
)
logger.info("Infraction recorded successfully.")
except Exception as error:
logger.error(f"Failed to create infraction. Error: {error}")
@app_commands.checks.has_any_role("Admin", "Sr. Mod", "Mod", "Jr. Mod") @app_commands.checks.has_any_role("Admin", "Sr. Mod", "Mod", "Jr. Mod")
@app_commands.command(name="kick", description="Kicks a member from the server.") @app_commands.command(name="kick", description="Kicks a member from the server.")
@app_commands.describe(member="Which member to kick", reason="Reason for kick") @app_commands.describe(member="Member to kick", reason="Reason for the kick")
async def kick( async def kick(
self, interaction: discord.Interaction, member: discord.Member, reason: str | None = None self, interaction: discord.Interaction, member: discord.Member, reason: str | None = None
) -> None: ) -> None:
logger.info(f"{interaction.user} kicked {member.display_name} in {interaction.channel}") """Kicks the specified member with an optional reason."""
if reason is None:
reason = "No reason provided"
response = await self.execute_kick(interaction, member, reason or "None provided")
await interaction.response.send_message(embed=response)
async def execute_kick(
self, interaction: discord.Interaction, member: discord.Member, reason: str | None = None
) -> discord.Embed:
try: try:
await member.kick(reason=reason) await member.kick(reason=reason)
embed = discord.Embed( await self.log_kick(interaction, member, reason)
title=f"Kicked {member.display_name}!", except Exception as error:
color=discord.Colour.gold(), await self.handle_kick_error(interaction, member, error)
description=f"Reason: `{reason}`",
)
embed.set_footer(
text=f"Kicked by {interaction.user.display_name}",
icon_url=interaction.user.display_avatar.url,
)
logger.info(f"Successfully kicked {member.display_name}.") async def log_kick(
self, interaction: discord.Interaction, member: discord.Member, reason: str
) -> None:
"""Sends a log message and informs about the kick operation."""
embed = EmbedCreator.create_infraction_embed(
title=f"{member.display_name} has been kicked.",
description=f"Reason: `{reason}`",
interaction=interaction,
)
embed.add_field(
name="Moderator",
value=f"{interaction.user.mention} ({interaction.user.id})",
inline=False,
)
embed.add_field(name="Member", value=f"{member.mention} ({member.id})", inline=False)
except discord.errors.Forbidden as error: await self.insert_infraction(
embed = discord.Embed( user_id=member.id,
title=f"Failed to kick {member.display_name}", moderator_id=interaction.user.id,
color=discord.Colour.red(), infraction_type=InfractionType.KICK,
description=f"Insufficient permissions. Error Info: `{error}`", infraction_reason=reason,
) )
logger.error(f"Failed to kick {member.display_name}. Error: {error}") logger.info(f"Kicked {member.display_name} for: {reason}")
await interaction.response.send_message(embed=embed)
return embed async def handle_kick_error(
self, interaction: discord.Interaction, member: discord.Member, error: Exception
) -> None:
"""Handles errors that occur during the kick operation."""
error_msg = f"Failed to kick {member.display_name}. Error: {error}"
logger.error(error_msg)
embed = EmbedCreator.create_error_embed(
title=f"Failed to kick {member.display_name}",
description=f"Error Info: `{error}`",
interaction=interaction,
)
await interaction.response.send_message(embed=embed)
async def setup(bot: commands.Bot) -> None: async def setup(bot: commands.Bot) -> None:

View file

@ -0,0 +1,68 @@
import discord
from discord import app_commands
from discord.ext import commands
from tux.utils.constants import Constants as CONST
from tux.utils.embeds import EmbedCreator
class ConfirmModal(discord.ui.Modal):
def __init__(self, *, title: str = "Submit an anonymous report", bot: commands.Bot) -> None:
super().__init__(title=title)
self.bot = bot
self.channel = CONST.LOG_CHANNELS["REPORT"]
short = discord.ui.TextInput( # type: ignore
style=discord.TextStyle.short,
label="Related user(s) or issue(s)",
required=True,
max_length=100,
placeholder="User IDs, usernames, or brief description",
)
long = discord.ui.TextInput( # type: ignore
style=discord.TextStyle.long,
label="Your report",
required=True,
max_length=4000,
placeholder="Please provide as much detail as possible",
)
async def on_submit(self, interaction: discord.Interaction) -> None:
embed = EmbedCreator.create_log_embed(
title=(f"Anonymous report for {self.short.value}"), # type: ignore
description=self.long.value, # type: ignore
)
channel = self.bot.get_channel(self.channel) or await self.bot.fetch_channel(self.channel)
webhook: discord.Webhook | None = None
if isinstance(channel, discord.TextChannel):
webhook = await channel.create_webhook(
name="Tux",
reason="Anonymous report webhook",
)
if webhook:
await webhook.send(embed=embed)
await webhook.delete(reason="Report sent")
await interaction.response.send_message(
"The report has been sent to the moderation team. Thank you for your help!",
ephemeral=True,
)
class Report(commands.Cog):
def __init__(self, bot: commands.Bot) -> None:
self.bot = bot
@app_commands.command(name="report", description="Report a user or issue anonymously")
async def report(self, interaction: discord.Interaction) -> None:
modal = ConfirmModal(bot=self.bot)
await interaction.response.send_modal(modal)
async def setup(bot: commands.Bot) -> None:
await bot.add_cog(Report(bot))

View file

@ -0,0 +1,76 @@
import datetime
import discord
from discord import app_commands
from discord.ext import commands
from loguru import logger
class TimeOut(commands.Cog):
def __init__(self, bot: commands.Bot) -> None:
self.bot = bot
@app_commands.command(name="timeout", description="Timeout a user")
@app_commands.describe(
member="Which member to timeout",
days="Days of timeout",
hours="Hours of timeout",
minutes="Minutes of timeout",
seconds="Seconds of timeout",
reason="Reason to timeout member",
)
async def timeout(
self,
interaction: discord.Interaction,
member: discord.Member,
days: int = 0,
hours: int = 0,
minutes: int = 0,
seconds: int = 0,
reason: str | None = None,
) -> None:
logger.info(
f"{interaction.user} used the timeout command to timeout {member}",
)
duration = datetime.timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds)
try:
await member.timeout(duration, reason=reason)
embed = discord.Embed(
color=discord.Color.red(),
title=f"User {member.display_name} timed out",
description=f"Reason: {reason if reason else '`None provided`'}",
timestamp=interaction.created_at,
)
embed.add_field(
name="User",
value=f"<@{member.id}>",
inline=True,
)
embed.add_field(
name="Duration",
value=duration,
inline=True,
)
embed.set_footer(
text=f"Requested by {interaction.user.display_name}",
icon_url=interaction.user.display_avatar,
)
await interaction.response.send_message(embed=embed)
except (discord.errors.Forbidden, discord.errors.HTTPException) as e:
logger.error("")
embed_error = discord.Embed(
colour=discord.Colour.red(),
title=f"Failed to timeout {member.display_name}",
description=f"`Error info: {e}`",
timestamp=interaction.created_at,
)
embed_error.set_footer(
text=f"Requested by {interaction.user.display_name}",
icon_url=interaction.user.display_avatar,
)
await interaction.response.send_message(embed=embed_error)
return
async def setup(bot: commands.Bot) -> None:
await bot.add_cog(TimeOut(bot))

View file

@ -3,64 +3,84 @@ 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.models import Infractions
from tux.database.controllers import DatabaseController
from tux.utils.embeds import EmbedCreator
from tux.utils.enums import InfractionType
class Warn(commands.Cog): class Warn(commands.Cog):
def __init__(self, bot: commands.Bot) -> None: def __init__(self, bot: commands.Bot) -> None:
self.bot = bot self.bot = bot
self.db_controller = DatabaseController().infractions
async def insert_infraction(
self,
user_id: int,
moderator_id: int,
infraction_type: InfractionType,
infraction_reason: str,
) -> Infractions | None:
try:
return await self.db_controller.create_infraction(
user_id=user_id,
moderator_id=moderator_id,
infraction_type=infraction_type,
infraction_reason=infraction_reason,
)
except Exception as error:
logger.error(f"Failed to create infraction. Error: {error}")
@app_commands.checks.has_any_role("Admin", "Sr. Mod", "Mod", "Jr. Mod") @app_commands.checks.has_any_role("Admin", "Sr. Mod", "Mod", "Jr. Mod")
@app_commands.command(name="warn", description="Warns a member in the server for a reason.") @app_commands.command(name="warn", description="Warns a member from the server.")
@app_commands.describe(member="Which member to warn", reason="Reason for warn") @app_commands.describe(member="Which member to warn", reason="Reason for warn")
async def warn( async def warn(
self, self, interaction: discord.Interaction, member: discord.Member, reason: str | None = None
interaction: discord.Interaction,
member: discord.Member,
reason: str | None = "No reason",
) -> None: ) -> None:
logger.info(f"{interaction.user} just warned user {member} for {reason}")
moderator: discord.Member | discord.User = interaction.user
if interaction.guild:
moderator = await interaction.guild.fetch_member(interaction.user.id)
response = await self.execute_warn(
interaction, moderator, member, reason or "None provided"
)
await interaction.response.send_message(embed=response)
async def execute_warn(
self,
interaction: discord.Interaction,
moderator: discord.Member | discord.User,
member: discord.Member,
reason: str,
) -> discord.Embed:
try: try:
# TODO Replace this with function that creates a warn new_warn: Infractions | None = await self.insert_infraction(
# await add_infraction(moderator.id, member.id, "warn", reason, datetime.now()) user_id=member.id,
embed = discord.Embed( moderator_id=interaction.user.id,
title=f"Warned {member.display_name}!", infraction_type=InfractionType.WARN,
color=discord.Colour.gold(), infraction_reason=reason or "None provided",
description=f"Reason: `{reason}`",
timestamp=interaction.created_at,
) )
embed.set_footer(
text=f"Warned by {moderator.display_name}", warn_id = new_warn.id if new_warn is not None else "Unknown"
icon_url=moderator.display_avatar.url,
embed = EmbedCreator.create_infraction_embed(
title=f"WARNING — ID: {warn_id}", interaction=interaction, description=""
) )
embed.add_field(
name="Reason",
value=f"`{reason}`" if reason else "No reason provided",
inline=False,
)
embed.add_field(
name="By",
value=f"{interaction.user.mention} `({interaction.user.id})`",
inline=True,
)
embed.add_field(
name="To",
value=f"{member.mention} `({member.id})`",
inline=True,
)
logger.info(f"Warned {member.display_name} for: {reason}")
except Exception as error: except Exception as error:
embed = discord.Embed( embed = EmbedCreator.create_error_embed(
title=f"Failed to warn {member.display_name}", title=f"Failed to warn {member.display_name}",
color=discord.Colour.red(), description=f"Error Info: `{error}`",
description=f"Unknown error. Error Info: `{error}`", interaction=interaction,
timestamp=interaction.created_at,
) )
logger.error(f"Failed to warn {member.display_name}. Error: {error}") logger.error(f"Failed to warn {member.display_name}. Error: {error}")
return embed await interaction.response.send_message(embed=embed)
async def setup(bot: commands.Bot) -> None: async def setup(bot: commands.Bot) -> None:

View file

@ -1,38 +0,0 @@
import traceback
import discord
from discord import app_commands
from discord.ext import commands
class ReportModal(discord.ui.Modal, title="Report"):
report = discord.ui.TextInput( # type: ignore
label="Submit your anonymous report",
style=discord.TextStyle.long,
placeholder="Type your feedback here...",
required=True,
max_length=300,
)
async def on_submit(self, interaction: discord.Interaction):
await interaction.response.send_message(
"Thanks for your report and helping keep our community safe!",
ephemeral=True,
)
async def on_error(self, interaction: discord.Interaction, error: Exception) -> None:
await interaction.response.send_message("Oops! Something went wrong.", ephemeral=True)
traceback.print_exception(type(error), error, error.__traceback__)
class Report(commands.Cog):
def __init__(self, bot: commands.Bot) -> None:
self.bot = bot
@app_commands.command(name="report", description="Make an anonymous report")
async def report(self, interaction: discord.Interaction) -> None:
await interaction.response.send_modal(ReportModal())
async def setup(bot: commands.Bot) -> None:
await bot.add_cog(Report(bot))

View file

@ -29,7 +29,7 @@ class Tldr(commands.Cog):
async def tldr(self, interaction: discord.Interaction, command: str) -> None: async def tldr(self, interaction: discord.Interaction, command: str) -> None:
logger.info(f"{interaction.user} used the /tldr to show info about {command}") logger.info(f"{interaction.user} used the /tldr to show info about {command}")
tldr_page = self.get_tldr_page(command) tldr_page = self.get_tldr_page(command)
embed = EmbedCreator.create_default_embed( embed = EmbedCreator.create_info_embed(
title=f"TLDR for {command}", description=tldr_page, interaction=interaction title=f"TLDR for {command}", description=tldr_page, interaction=interaction
) )
await interaction.response.send_message(embed=embed) await interaction.response.send_message(embed=embed)

178
tux/cogs/utility/tools.py Normal file
View file

@ -0,0 +1,178 @@
import io
from base64 import b64decode, b64encode
from typing import Any, cast
import cairosvg # type: ignore
import discord
import httpx
from discord import app_commands
from discord.ext import commands
from tux.utils.embeds import EmbedCreator
client = httpx.AsyncClient()
COLOR_FORMATS = {"HEX": "hex", "RGB": "rgb", "HSL": "hsl", "CMYK": "cmyk"}
class Tools(commands.Cog):
def __init__(self, bot: commands.Bot) -> None:
self.bot = bot
self.encodings = {
"base64": self.encode_base64,
# "md5": self.encode_md5,
# "sha256": self.encode_sha256,
# "sha512": self.encode_sha512,
}
self.decodings = {
"base64": self.decode_base64,
# "md5": self.decode_md5,
# "sha256": self.decode_sha256,
# "sha512": self.decode_sha512,
}
def encode_base64(self, input_string: str):
return b64encode(input_string.encode()).decode()
def decode_base64(self, input_string: str):
return b64decode(input_string.encode()).decode()
# def encode_md5(self, input_string: str):
# return hashlib.md5(input_string.encode()).hexdigest()
# def encode_sha256(self, input_string: str):
# return hashlib.sha256(input_string.encode()).hexdigest()
# def encode_sha512(self, input_string: str):
# return hashlib.sha512(input_string.encode()).hexdigest()
group = app_commands.Group(name="tools", description="Various tool commands.")
@group.command(name="colors", description="Converts a color to different formats.")
@app_commands.describe(color_format="Original color format to convert from")
@app_commands.choices(
color_format=[
app_commands.Choice[str](name=color_format, value=value)
for color_format, value in COLOR_FORMATS.items()
]
)
async def colors(
self,
interaction: discord.Interaction,
color_format: discord.app_commands.Choice[str],
color: str,
) -> None:
if color_format.value == "HEX" and color.startswith("#"):
color = color[1:]
api = f"https://www.thecolorapi.com/id?format=json&{color_format.value}={color}"
data: Any = await self.make_request(api)
content: bytes = await self.get_svg_content(data["image"]["named"])
png_bio: io.BytesIO = self.convert_svg_to_png(content)
embed = self.construct_embed(interaction, data)
await self.send_message(interaction, embed, png_bio)
async def make_request(self, api: str) -> Any:
return (await client.get(api)).json()
async def get_svg_content(self, svg_url: str) -> bytes:
return (await client.get(svg_url)).content
def convert_svg_to_png(self, content: bytes) -> io.BytesIO:
# Attempt conversion from SVG to PNG
png_content = cairosvg.svg2png(bytestring=content, dpi=96, scale=1, unsafe=False) # type: ignore
# Ensure the output is bytes; use cast to reassure type checkers
png_content = cast(bytes | None, png_content)
if png_content is None:
msg = "Failed to convert SVG to PNG"
raise ValueError(msg)
# Create BytesIO stream from the PNG content bytes
png_bio = io.BytesIO(png_content)
png_bio.seek(0)
return png_bio
def construct_embed(self, interaction: discord.Interaction, data: Any) -> discord.Embed:
embed = EmbedCreator.create_info_embed(
title="Color Converter",
description="Here is your color converted!",
interaction=interaction,
)
for color_format, value in COLOR_FORMATS.items():
embed.add_field(name=color_format, value=data[value]["value"])
embed.add_field(name="HSV", value=data["hsv"]["value"])
embed.add_field(name="XYZ", value=data["XYZ"]["value"])
embed.set_thumbnail(url="attachment://color.png")
return embed
async def send_message(
self, interaction: discord.Interaction, embed: discord.Embed, png_bio: io.BytesIO
) -> None:
await interaction.response.send_message(
embed=embed, file=discord.File(png_bio, "color.png")
)
@group.command(name="encode", description="Encodes a string to a specified format.")
@app_commands.describe(encoding="The encoding format to use", string="The string to encode")
@app_commands.choices(
encoding=[
app_commands.Choice[str](name="base64", value="base64"),
# app_commands.Choice[str](name="md5", value="md5"),
# app_commands.Choice[str](name="sha256", value="sha256"),
# app_commands.Choice[str](name="sha512", value="sha512"),
]
)
async def encode(
self,
interaction: discord.Interaction,
encoding: app_commands.Choice[str],
string: str,
) -> None:
title = f"{encoding.name.capitalize()} Encode"
try:
encode_func = self.encodings[encoding.value]
encoded_string = encode_func(string)
description = f"Encoded: {encoded_string}"
except KeyError:
description = "Invalid encoding selected!"
embed = EmbedCreator.create_info_embed(
title=title, description=description, interaction=interaction
)
await interaction.response.send_message(embed=embed)
@group.command(name="decode", description="Decodes a string from a specified format.")
@app_commands.describe(encoding="The decoding format to use", string="The string to decode")
@app_commands.choices(
encoding=[
app_commands.Choice[str](name="base64", value="base64"),
]
)
async def decode(
self,
interaction: discord.Interaction,
encoding: app_commands.Choice[str],
string: str,
) -> None:
title = f"{encoding.name.capitalize()} Decode"
try:
decode_func = self.decodings[encoding.value]
decoded_string = decode_func(string)
description = f"Decoded: {decoded_string}"
except KeyError:
description = "Invalid decoding selected!"
embed = EmbedCreator.create_info_embed(
title=title, description=description, interaction=interaction
)
await interaction.response.send_message(embed=embed)
async def setup(bot: commands.Bot) -> None:
await bot.add_cog(Tools(bot))

View file

@ -3,6 +3,7 @@ from .notes import NotesController
from .reminders import RemindersController from .reminders import RemindersController
from .roles import RolesController from .roles import RolesController
from .snippets import SnippetsController from .snippets import SnippetsController
from .user_roles import UserRolesController
from .users import UsersController from .users import UsersController
@ -14,3 +15,4 @@ class DatabaseController:
self.snippets = SnippetsController() self.snippets = SnippetsController()
self.reminders = RemindersController() self.reminders = RemindersController()
self.roles = RolesController() self.roles = RolesController()
self.user_roles = UserRolesController()

View file

@ -1,14 +1,6 @@
from enum import Enum
from prisma.models import Infractions from prisma.models import Infractions
from tux.database.client import db from tux.database.client import db
from tux.utils.enums import InfractionType
class InfractionType(Enum):
BAN = "ban"
WARN = "warn"
KICK = "kick"
TIMEOUT = "timeout"
class InfractionsController: class InfractionsController:

View file

@ -0,0 +1,89 @@
from prisma.models import UserRoles
from tux.database.client import db
class UserRolesController:
def __init__(self) -> None:
self.table = db.userroles
async def get_all_user_roles(self) -> list[UserRoles]:
"""
Retrieves all user roles from the database.
Returns:
list[UserRoles]: A list of all user roles.
"""
return await self.table.find_many()
async def get_user_role_by_ids(self, user_id: int, role_id: int) -> UserRoles | None:
"""
Retrieves a user role from the database based on the specified user ID and role ID.
Args:
user_id (int): The ID of the user.
role_id (int): The ID of the role.
Returns:
UserRoles | None: The user role if found, None if the user role does not exist.
"""
return await self.table.find_first(where={"user_id": user_id, "role_id": role_id})
async def create_user_role(self, user_id: int, role_id: int) -> UserRoles:
"""
Creates a new user role with the specified user ID and role ID.
Args:
user_id (int), role_id (int)
Returns:
UserRoles: The newly created user role.
"""
return await self.table.create(data={"user_id": user_id, "role_id": role_id})
async def delete_user_role(self, user_id: int, role_id: int) -> None:
"""
Deletes a user role based on specified user ID and role ID.
"""
await self.table.delete(where={"user_id_role_id": {"user_id": user_id, "role_id": role_id}})
async def delete_user_roles(self, user_id: int) -> None:
"""
Deletes all user roles from the database based on the specified user ID.
"""
await self.table.delete_many(where={"user_id": user_id})
async def delete_role_users(self, role_id: int) -> None:
"""
Deletes all user roles from the database based on the specified role ID.
"""
await self.table.delete_many(where={"role_id": role_id})
async def delete_all_user_roles(self) -> None:
"""
Deletes all user roles from the database.
"""
await self.table.delete_many()
async def get_user_roles_by_user_id(self, user_id: int) -> list[UserRoles]:
"""
Retrieves all user roles from the database based on the specified user ID.
"""
return await self.table.find_many(where={"user_id": user_id})
async def get_user_roles_by_role_id(self, role_id: int) -> list[UserRoles]:
"""
Retrieves all user roles from the database based on the specified role ID.
"""
return await self.table.find_many(where={"role_id": role_id})
async def sync_user_roles(self, user_id: int, role_ids: list[int]) -> None:
"""
Synchronizes user roles in the database based on the specified user ID and role IDs.
"""
user_roles = await self.get_user_roles_by_user_id(user_id)
user_role_ids = [user_role.role_id for user_role in user_roles]
for role_id in role_ids:
if role_id not in user_role_ids:
await self.create_user_role(user_id=user_id, role_id=role_id)
for user_role in user_roles:
if user_role.role_id not in role_ids:
await self.delete_user_role(user_id=user_id, role_id=user_role.role_id)

View file

@ -13,17 +13,54 @@ class ActivityChanger:
return [ return [
discord.Activity( discord.Activity(
type=discord.ActivityType.watching, name=f"{self.get_member_count()} members" type=discord.ActivityType.watching, name=f"{self.get_member_count()} members"
), ), # submitted by electron271
discord.Streaming(name="fortnite gamer hourz", url="https://twitch.tv/urmom"), discord.Activity(
discord.Activity(type=discord.ActivityType.watching, name="All Things Linux"), type=discord.ActivityType.watching, name="All Things Linux"
discord.Activity(type=discord.ActivityType.playing, name="with fire"), ), # submitted by electron271
discord.Activity(type=discord.ActivityType.watching, name="linux tech tips"), discord.Activity(
discord.Activity(type=discord.ActivityType.listening, name="mpd"), type=discord.ActivityType.playing, name="with fire"
discord.Activity(type=discord.ActivityType.watching, name="a vast field of grain"), ), # submitted by electron271
discord.Activity(
type=discord.ActivityType.watching, name="linux tech tips"
), # submitted by electron271
discord.Activity(
type=discord.ActivityType.listening, name="mpd"
), # submitted by electron271
discord.Activity(
type=discord.ActivityType.watching, name="a vast field of grain"
), # submitted by electron271
discord.Activity( discord.Activity(
type=discord.ActivityType.playing, type=discord.ActivityType.playing,
name="i am calling about your car's extended warranty", name="i am calling about your car's extended warranty",
), ), # submitted by electron271
discord.Activity(
type=discord.ActivityType.playing, name="SuperTuxKart"
), # submitted by electron271
discord.Activity(
type=discord.ActivityType.playing, name="supertux2"
), # submitted by lilliana
discord.Activity(
type=discord.ActivityType.watching, name="Linux install"
), # submitted by electron271
discord.Activity(
type=discord.ActivityType.watching, name="Brodie Robertson"
), # submitted by electron271
discord.Streaming(
name="SuperTuxKart", url="https://www.youtube.com/watch?v=dQw4w9WgXcQ"
), # submitted by electron271
discord.Activity(
type=discord.ActivityType.listening, name="Terry Davis on YouTube"
), # submitted by kaizen
discord.Activity(
type=discord.ActivityType.playing, name="with Puffy"
), # submitted by kaizen
discord.Activity(
type=discord.ActivityType.watching, name="the stars"
), # submitted by electron271
discord.Activity(
type=discord.ActivityType.playing,
name="To see who submitted these, check tux/utils/activities.py on the repo (/info tux)",
), # submitted by electron271
] ]
def get_member_count(self): def get_member_count(self):

View file

@ -34,8 +34,12 @@ class Constants:
# Channel constants # Channel constants
LOG_CHANNELS: Final[dict[str, int]] = { LOG_CHANNELS: Final[dict[str, int]] = {
# For general logging
"AUDIT": 1223690612822376529, "AUDIT": 1223690612822376529,
# For infractions, mod actions, etc.
"MOD": 1223690612822376529, "MOD": 1223690612822376529,
# For anonymous reports
"REPORT": 1223690612822376529,
} }
# User ID Constants # User ID Constants
@ -73,6 +77,8 @@ class Constants:
"WHITE": 0xFFFFFF, "WHITE": 0xFFFFFF,
# catppuccin yellow # catppuccin yellow
"POLL": 0xF9E2AF, "POLL": 0xF9E2AF,
# catppuccin crust
"INFRACTION": 0x11111B,
} }
EMBED_STATE_ICONS: Final[dict[str, str]] = { EMBED_STATE_ICONS: Final[dict[str, str]] = {
@ -83,6 +89,7 @@ class Constants:
"ERROR": "https://github.com/catppuccin/catppuccin/blob/main/assets/palette/circles/latte_red.png?raw=true", "ERROR": "https://github.com/catppuccin/catppuccin/blob/main/assets/palette/circles/latte_red.png?raw=true",
"SUCCESS": "https://github.com/catppuccin/catppuccin/blob/main/assets/palette/circles/mocha_green.png?raw=true", "SUCCESS": "https://github.com/catppuccin/catppuccin/blob/main/assets/palette/circles/mocha_green.png?raw=true",
"POLL": "https://github.com/catppuccin/catppuccin/raw/main/assets/palette/circles/mocha_yellow.png?raw=true", "POLL": "https://github.com/catppuccin/catppuccin/raw/main/assets/palette/circles/mocha_yellow.png?raw=true",
"INFRACTION": "https://github.com/catppuccin/catppuccin/raw/main/assets/palette/circles/mocha_crust.png?raw=true",
} }
EMBED_SPECIAL_CHARS: Final[dict[str, str]] = { EMBED_SPECIAL_CHARS: Final[dict[str, str]] = {

View file

@ -22,24 +22,23 @@ class EmbedCreator:
ctx: commands.Context[commands.Bot] | None, interaction: discord.Interaction | None ctx: commands.Context[commands.Bot] | None, interaction: discord.Interaction | None
) -> tuple[str, str | None]: ) -> tuple[str, str | None]:
user: discord.User | discord.Member | None = None user: discord.User | discord.Member | None = None
latency = None
if ctx: if ctx:
user = ctx.author user = ctx.author
latency = round(ctx.bot.latency * 1000, 2)
elif interaction: elif interaction:
user = interaction.user user = interaction.user
latency = round(interaction.client.latency * 1000, 2)
if isinstance(user, discord.User | discord.Member): if isinstance(user, discord.User | discord.Member):
return ( return (
f"Requested by {user.display_name}", f"{user.name}@atl $ tux {latency}ms", # noqa: RUF001
str(user.avatar.url) if user.avatar else None, str(user.avatar.url) if user.avatar else None,
) )
return ("", None) return ("", None)
# @staticmethod
# def shell_terminal_format(user: str) -> str:
# return f"[{user}@tux ~]$"
@staticmethod @staticmethod
def add_field(embed: discord.Embed, name: str, value: str, inline: bool = True) -> None: def add_field(embed: discord.Embed, name: str, value: str, inline: bool = True) -> None:
embed.add_field(name=name, value=value, inline=inline) embed.add_field(name=name, value=value, inline=inline)
@ -63,7 +62,7 @@ class EmbedCreator:
embed = discord.Embed() embed = discord.Embed()
embed.color = CONST.EMBED_STATE_COLORS[state] embed.color = discord.Colour(CONST.EMBED_STATE_COLORS[state])
embed.set_author( embed.set_author(
name=state.capitalize() if state else "Info", name=state.capitalize() if state else "Info",
@ -85,7 +84,7 @@ class EmbedCreator:
interaction: discord.Interaction | None, interaction: discord.Interaction | None,
state: str, state: str,
title: str, title: str,
description: str, description: str = "",
) -> discord.Embed: ) -> discord.Embed:
embed = cls.base_embed(ctx, interaction, state) embed = cls.base_embed(ctx, interaction, state)
embed.title = title embed.title = title
@ -162,3 +161,13 @@ class EmbedCreator:
interaction: discord.Interaction | None = None, interaction: discord.Interaction | None = None,
) -> discord.Embed: ) -> discord.Embed:
return cls.create_embed(ctx, interaction, "LOG", title, description) return cls.create_embed(ctx, interaction, "LOG", title, description)
@classmethod
def create_infraction_embed(
cls,
title: str,
description: str,
ctx: commands.Context[commands.Bot] | None = None,
interaction: discord.Interaction | None = None,
) -> discord.Embed:
return cls.create_embed(ctx, interaction, "INFRACTION", title, description)

8
tux/utils/enums.py Normal file
View file

@ -0,0 +1,8 @@
from enum import Enum
class InfractionType(Enum):
BAN = "ban"
WARN = "warn"
KICK = "kick"
TIMEOUT = "timeout"