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:
commit
5f7178b2f9
21 changed files with 1017 additions and 187 deletions
248
poetry.lock
generated
248
poetry.lock
generated
|
@ -255,6 +255,47 @@ files = [
|
|||
[package.extras]
|
||||
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]]
|
||||
name = "certifi"
|
||||
version = "2024.2.2"
|
||||
|
@ -466,6 +507,7 @@ files = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
name = "cryptography"
|
||||
version = "42.0.5"
|
||||
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-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]]
|
||||
name = "dateparser"
|
||||
version = "1.2.0"
|
||||
|
@ -558,6 +619,18 @@ wrapt = ">=1.10,<2"
|
|||
[package.extras]
|
||||
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]]
|
||||
name = "discord-py"
|
||||
version = "2.3.2"
|
||||
|
@ -1046,6 +1119,92 @@ files = [
|
|||
[package.dependencies]
|
||||
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]]
|
||||
name = "platformdirs"
|
||||
version = "4.2.0"
|
||||
|
@ -1132,6 +1291,17 @@ files = [
|
|||
[package.extras]
|
||||
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]]
|
||||
name = "pycparser"
|
||||
version = "2.22"
|
||||
|
@ -1557,31 +1727,43 @@ urllib3 = ">=1.21.1,<3"
|
|||
[package.extras]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
||||
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]]
|
||||
name = "ruff"
|
||||
version = "0.3.6"
|
||||
version = "0.3.7"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
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.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:93699d61116807edc5ca1cdf9d2d22cf8d93335d59e3ff0ca7aee62c1818a736"},
|
||||
{file = "ruff-0.3.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc4006cbc6c11fefc25f122d2eb4731d7a3d815dc74d67c54991cc3f99c90177"},
|
||||
{file = "ruff-0.3.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:878ef1a55ce931f3ca23b690b159cd0659f495a4c231a847b00ca55e4c688baf"},
|
||||
{file = "ruff-0.3.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecb87788284af96725643eae9ab3ac746d8cc09aad140268523b019f7ac3cd98"},
|
||||
{file = "ruff-0.3.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b2e79f8e1b6bd5411d7ddad3f2abff3f9d371beda29daef86400d416dedb7e02"},
|
||||
{file = "ruff-0.3.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf48ec2c4bfae7837dc325c431a2932dc23a1485e71c59591c1df471ba234e0e"},
|
||||
{file = "ruff-0.3.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c466a52c522e6a08df0af018f550902f154f5649ad09e7f0d43da766e7399ebc"},
|
||||
{file = "ruff-0.3.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28ccf3fb6d1162a73cd286c63a5e4d885f46a1f99f0b392924bc95ccbd18ea8f"},
|
||||
{file = "ruff-0.3.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b11e09439d9df6cc12d9f622065834654417c40216d271f639512d80e80e3e53"},
|
||||
{file = "ruff-0.3.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:647f1fb5128a3e24ce68878b8050bb55044c45bb3f3ae4710d4da9ca96ede5cb"},
|
||||
{file = "ruff-0.3.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2b0c4c70578ef1871a9ac5c85ed7a8c33470e976c73ba9211a111d2771b5f787"},
|
||||
{file = "ruff-0.3.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e3da499ded004d0b956ab04248b2ae17e54a67ffc81353514ac583af5959a255"},
|
||||
{file = "ruff-0.3.6-py3-none-win32.whl", hash = "sha256:4056480f5cf38ad278667c31b0ef334c29acdfcea617cb89c4ccbc7d96f1637f"},
|
||||
{file = "ruff-0.3.6-py3-none-win_amd64.whl", hash = "sha256:f1aa621beed533f46e9c7d6fe00e7f6e4570155b61d8f020387b72ace2b42e04"},
|
||||
{file = "ruff-0.3.6-py3-none-win_arm64.whl", hash = "sha256:7c8a2a0e0cab077a07465259ffe3b3c090e747ca8097c5dc4c36ca0fdaaac90d"},
|
||||
{file = "ruff-0.3.6.tar.gz", hash = "sha256:26071fb530038602b984e3bbe1443ef82a38450c4dcb1344a9caf67234ff9756"},
|
||||
{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.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:15a4d1cc1e64e556fa0d67bfd388fed416b7f3b26d5d1c3e7d192c897e39ba4b"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d28bdf3d7dc71dd46929fafeec98ba89b7c3550c3f0978e36389b5631b793663"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:379b67d4f49774ba679593b232dcd90d9e10f04d96e3c8ce4a28037ae473f7bb"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c060aea8ad5ef21cdfbbe05475ab5104ce7827b639a78dd55383a6e9895b7c51"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ebf8f615dde968272d70502c083ebf963b6781aacd3079081e03b32adfe4d58a"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d48098bd8f5c38897b03604f5428901b65e3c97d40b3952e38637b5404b739a2"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da8a4fda219bf9024692b1bc68c9cff4b80507879ada8769dc7e985755d662ea"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c44e0149f1d8b48c4d5c33d88c677a4aa22fd09b1683d6a7ff55b816b5d074f"},
|
||||
{file = "ruff-0.3.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3050ec0af72b709a62ecc2aca941b9cd479a7bf2b36cc4562f0033d688e44fa1"},
|
||||
{file = "ruff-0.3.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a29cc38e4c1ab00da18a3f6777f8b50099d73326981bb7d182e54a9a21bb4ff7"},
|
||||
{file = "ruff-0.3.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b15cc59c19edca917f51b1956637db47e200b0fc5e6e1878233d3a938384b0b"},
|
||||
{file = "ruff-0.3.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e491045781b1e38b72c91247cf4634f040f8d0cb3e6d3d64d38dcf43616650b4"},
|
||||
{file = "ruff-0.3.7-py3-none-win32.whl", hash = "sha256:bc931de87593d64fad3a22e201e55ad76271f1d5bfc44e1a1887edd0903c7d9f"},
|
||||
{file = "ruff-0.3.7-py3-none-win_amd64.whl", hash = "sha256:5ef0e501e1e39f35e03c2acb1d1238c595b8bb36cf7a170e7c1df1b73da00e74"},
|
||||
{file = "ruff-0.3.7-py3-none-win_arm64.whl", hash = "sha256:789e144f6dc7019d1f92a812891c645274ed08af6037d11fc65fcbc183b7d59f"},
|
||||
{file = "ruff-0.3.7.tar.gz", hash = "sha256:d5c1aebee5162c2226784800ae031f660c350e7a3402c4d1f8ea4e97e232e3ba"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1669,6 +1851,24 @@ files = [
|
|||
{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]]
|
||||
name = "tomlkit"
|
||||
version = "0.12.4"
|
||||
|
@ -1767,6 +1967,17 @@ files = [
|
|||
{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]]
|
||||
name = "win32-setctime"
|
||||
version = "1.1.0"
|
||||
|
@ -1967,3 +2178,4 @@ multidict = ">=4.0"
|
|||
lock-version = "2.0"
|
||||
python-versions = ">=3.12,<4"
|
||||
content-hash = "95cd2b25b94d5eb7dd1dc10c590e3ad1b930c7e36775d6882146d84300a0a854"
|
||||
content-hash = "060fffe973c2ae0e030b0e24362608fa73db573ad19c0d65cf4e1695ea49121a"
|
||||
|
|
|
@ -35,7 +35,7 @@ model Users {
|
|||
// Returns a string that allows you to mention the given user.
|
||||
mention String
|
||||
// Specifies if the user is a bot account.
|
||||
bot Boolean
|
||||
bot Boolean @default(false)
|
||||
// Returns the user’s creation time in UTC. This is when the user’s Discord account was created.
|
||||
created_at DateTime?
|
||||
// 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.
|
||||
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[]
|
||||
|
||||
// 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_received Notes[] @relation("User")
|
||||
|
||||
reminders Reminders[]
|
||||
reminders Reminders[]
|
||||
CommandStats CommandStats[]
|
||||
}
|
||||
|
||||
model Roles {
|
||||
|
@ -92,18 +93,13 @@ model Roles {
|
|||
}
|
||||
|
||||
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_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_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.
|
||||
@@unique([user_id, role_id])
|
||||
@@id([user_id, role_id])
|
||||
}
|
||||
|
||||
model Infractions {
|
||||
|
@ -160,6 +156,24 @@ model Reminders {
|
|||
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 {
|
||||
// id BigInt @id
|
||||
// content String
|
||||
|
|
|
@ -26,6 +26,10 @@ ruff = "^0.3.6"
|
|||
sentry-sdk = "^1.45.0"
|
||||
vulture = "^2.11"
|
||||
pygithub = "^2.3.0"
|
||||
httpx = "^0.27.0"
|
||||
cairosvg = "^2.7.1"
|
||||
pillow = "^10.3.0"
|
||||
rsa = "^4.9"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
pyright = "pyright:run"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import asyncio
|
||||
from collections.abc import Sequence
|
||||
from collections.abc import Coroutine, Sequence
|
||||
from typing import Any
|
||||
|
||||
import discord
|
||||
from discord import app_commands
|
||||
|
@ -8,6 +9,9 @@ from loguru import logger
|
|||
|
||||
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):
|
||||
def __init__(self, bot: commands.Bot) -> None:
|
||||
|
@ -18,14 +22,14 @@ class Db(commands.Cog):
|
|||
|
||||
@app_commands.checks.has_role("Root")
|
||||
@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()
|
||||
|
||||
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
|
||||
|
||||
members = interaction.guild.members
|
||||
members: Sequence[discord.Member] = interaction.guild.members
|
||||
|
||||
batch_size = 10
|
||||
|
||||
|
@ -53,11 +57,11 @@ class Db(commands.Cog):
|
|||
|
||||
@app_commands.checks.has_role("Root")
|
||||
@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()
|
||||
|
||||
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
|
||||
|
||||
roles: Sequence[discord.Role] = interaction.guild.roles
|
||||
|
@ -87,6 +91,40 @@ class Db(commands.Cog):
|
|||
await interaction.followup.send("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:
|
||||
await bot.add_cog(Db(bot))
|
||||
|
|
|
@ -70,7 +70,8 @@ class ErrorHandler(commands.Cog):
|
|||
"""Handle traditional command errors."""
|
||||
if isinstance(
|
||||
error,
|
||||
commands.UnexpectedQuoteError
|
||||
commands.CommandNotFound
|
||||
| commands.UnexpectedQuoteError
|
||||
| commands.InvalidEndOfQuotedStringError
|
||||
| commands.CheckFailure,
|
||||
):
|
||||
|
|
|
@ -20,6 +20,47 @@ class GuildLogging(commands.Cog):
|
|||
|
||||
"""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()
|
||||
async def on_guild_channel_create(self, channel: discord.abc.GuildChannel):
|
||||
embed = EmbedCreator.create_log_embed(
|
||||
|
|
|
@ -3,49 +3,80 @@ from discord import app_commands
|
|||
from discord.ext import commands
|
||||
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):
|
||||
def __init__(self, bot: commands.Bot) -> None:
|
||||
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.describe(member="Which member to ban", reason="Reason for ban")
|
||||
async def ban(
|
||||
self, interaction: discord.Interaction, member: discord.Member, reason: str | 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:
|
||||
await member.ban(reason=reason)
|
||||
embed = discord.Embed(
|
||||
title=f"Banned {member.display_name}!",
|
||||
color=discord.Colour.red(),
|
||||
|
||||
embed = EmbedCreator.create_infraction_embed(
|
||||
title=f"{member.display_name} has been banned.",
|
||||
description=f"Reason: `{reason}`",
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"Banned by {interaction.user.display_name}",
|
||||
icon_url=interaction.user.display_avatar.url,
|
||||
interaction=interaction,
|
||||
)
|
||||
|
||||
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 = discord.Embed(
|
||||
embed.add_field(
|
||||
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}",
|
||||
color=discord.Colour.red(),
|
||||
description=f"Insufficient permissions. Error Info: `{error}`",
|
||||
description=f"Error Info: `{error}`",
|
||||
interaction=interaction,
|
||||
)
|
||||
|
||||
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:
|
||||
|
|
|
@ -3,49 +3,90 @@ from discord import app_commands
|
|||
from discord.ext import commands
|
||||
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):
|
||||
"""Cog for handling the kicking of members from a Discord server."""
|
||||
|
||||
def __init__(self, bot: commands.Bot) -> None:
|
||||
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.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(
|
||||
self, interaction: discord.Interaction, member: discord.Member, reason: str | 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:
|
||||
await member.kick(reason=reason)
|
||||
embed = discord.Embed(
|
||||
title=f"Kicked {member.display_name}!",
|
||||
color=discord.Colour.gold(),
|
||||
description=f"Reason: `{reason}`",
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"Kicked by {interaction.user.display_name}",
|
||||
icon_url=interaction.user.display_avatar.url,
|
||||
)
|
||||
await self.log_kick(interaction, member, reason)
|
||||
except Exception as error:
|
||||
await self.handle_kick_error(interaction, member, error)
|
||||
|
||||
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:
|
||||
embed = discord.Embed(
|
||||
title=f"Failed to kick {member.display_name}",
|
||||
color=discord.Colour.red(),
|
||||
description=f"Insufficient permissions. Error Info: `{error}`",
|
||||
)
|
||||
logger.error(f"Failed to kick {member.display_name}. Error: {error}")
|
||||
await self.insert_infraction(
|
||||
user_id=member.id,
|
||||
moderator_id=interaction.user.id,
|
||||
infraction_type=InfractionType.KICK,
|
||||
infraction_reason=reason,
|
||||
)
|
||||
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:
|
||||
|
|
68
tux/cogs/moderation/report.py
Normal file
68
tux/cogs/moderation/report.py
Normal 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))
|
76
tux/cogs/moderation/timeout.py
Normal file
76
tux/cogs/moderation/timeout.py
Normal 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))
|
|
@ -3,64 +3,84 @@ from discord import app_commands
|
|||
from discord.ext import commands
|
||||
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):
|
||||
def __init__(self, bot: commands.Bot) -> None:
|
||||
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.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")
|
||||
async def warn(
|
||||
self,
|
||||
interaction: discord.Interaction,
|
||||
member: discord.Member,
|
||||
reason: str | None = "No reason",
|
||||
self, interaction: discord.Interaction, member: discord.Member, reason: str | None = 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:
|
||||
# TODO Replace this with function that creates a warn
|
||||
# await add_infraction(moderator.id, member.id, "warn", reason, datetime.now())
|
||||
embed = discord.Embed(
|
||||
title=f"Warned {member.display_name}!",
|
||||
color=discord.Colour.gold(),
|
||||
description=f"Reason: `{reason}`",
|
||||
timestamp=interaction.created_at,
|
||||
new_warn: Infractions | None = await self.insert_infraction(
|
||||
user_id=member.id,
|
||||
moderator_id=interaction.user.id,
|
||||
infraction_type=InfractionType.WARN,
|
||||
infraction_reason=reason or "None provided",
|
||||
)
|
||||
embed.set_footer(
|
||||
text=f"Warned by {moderator.display_name}",
|
||||
icon_url=moderator.display_avatar.url,
|
||||
|
||||
warn_id = new_warn.id if new_warn is not None else "Unknown"
|
||||
|
||||
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:
|
||||
embed = discord.Embed(
|
||||
embed = EmbedCreator.create_error_embed(
|
||||
title=f"Failed to warn {member.display_name}",
|
||||
color=discord.Colour.red(),
|
||||
description=f"Unknown error. Error Info: `{error}`",
|
||||
timestamp=interaction.created_at,
|
||||
description=f"Error Info: `{error}`",
|
||||
interaction=interaction,
|
||||
)
|
||||
|
||||
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:
|
||||
|
|
|
@ -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))
|
|
@ -29,7 +29,7 @@ class Tldr(commands.Cog):
|
|||
async def tldr(self, interaction: discord.Interaction, command: str) -> None:
|
||||
logger.info(f"{interaction.user} used the /tldr to show info about {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
|
||||
)
|
||||
await interaction.response.send_message(embed=embed)
|
||||
|
|
178
tux/cogs/utility/tools.py
Normal file
178
tux/cogs/utility/tools.py
Normal 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))
|
|
@ -3,6 +3,7 @@ from .notes import NotesController
|
|||
from .reminders import RemindersController
|
||||
from .roles import RolesController
|
||||
from .snippets import SnippetsController
|
||||
from .user_roles import UserRolesController
|
||||
from .users import UsersController
|
||||
|
||||
|
||||
|
@ -14,3 +15,4 @@ class DatabaseController:
|
|||
self.snippets = SnippetsController()
|
||||
self.reminders = RemindersController()
|
||||
self.roles = RolesController()
|
||||
self.user_roles = UserRolesController()
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
from enum import Enum
|
||||
|
||||
from prisma.models import Infractions
|
||||
from tux.database.client import db
|
||||
|
||||
|
||||
class InfractionType(Enum):
|
||||
BAN = "ban"
|
||||
WARN = "warn"
|
||||
KICK = "kick"
|
||||
TIMEOUT = "timeout"
|
||||
from tux.utils.enums import InfractionType
|
||||
|
||||
|
||||
class InfractionsController:
|
||||
|
|
89
tux/database/controllers/user_roles.py
Normal file
89
tux/database/controllers/user_roles.py
Normal 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)
|
|
@ -13,17 +13,54 @@ class ActivityChanger:
|
|||
return [
|
||||
discord.Activity(
|
||||
type=discord.ActivityType.watching, name=f"{self.get_member_count()} members"
|
||||
),
|
||||
discord.Streaming(name="fortnite gamer hourz", url="https://twitch.tv/urmom"),
|
||||
discord.Activity(type=discord.ActivityType.watching, name="All Things Linux"),
|
||||
discord.Activity(type=discord.ActivityType.playing, name="with fire"),
|
||||
discord.Activity(type=discord.ActivityType.watching, name="linux tech tips"),
|
||||
discord.Activity(type=discord.ActivityType.listening, name="mpd"),
|
||||
discord.Activity(type=discord.ActivityType.watching, name="a vast field of grain"),
|
||||
), # submitted by electron271
|
||||
discord.Activity(
|
||||
type=discord.ActivityType.watching, name="All Things Linux"
|
||||
), # submitted by electron271
|
||||
discord.Activity(
|
||||
type=discord.ActivityType.playing, name="with fire"
|
||||
), # 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(
|
||||
type=discord.ActivityType.playing,
|
||||
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):
|
||||
|
|
|
@ -34,8 +34,12 @@ class Constants:
|
|||
|
||||
# Channel constants
|
||||
LOG_CHANNELS: Final[dict[str, int]] = {
|
||||
# For general logging
|
||||
"AUDIT": 1223690612822376529,
|
||||
# For infractions, mod actions, etc.
|
||||
"MOD": 1223690612822376529,
|
||||
# For anonymous reports
|
||||
"REPORT": 1223690612822376529,
|
||||
}
|
||||
|
||||
# User ID Constants
|
||||
|
@ -73,6 +77,8 @@ class Constants:
|
|||
"WHITE": 0xFFFFFF,
|
||||
# catppuccin yellow
|
||||
"POLL": 0xF9E2AF,
|
||||
# catppuccin crust
|
||||
"INFRACTION": 0x11111B,
|
||||
}
|
||||
|
||||
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",
|
||||
"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",
|
||||
"INFRACTION": "https://github.com/catppuccin/catppuccin/raw/main/assets/palette/circles/mocha_crust.png?raw=true",
|
||||
}
|
||||
|
||||
EMBED_SPECIAL_CHARS: Final[dict[str, str]] = {
|
||||
|
|
|
@ -22,24 +22,23 @@ class EmbedCreator:
|
|||
ctx: commands.Context[commands.Bot] | None, interaction: discord.Interaction | None
|
||||
) -> tuple[str, str | None]:
|
||||
user: discord.User | discord.Member | None = None
|
||||
latency = None
|
||||
|
||||
if ctx:
|
||||
user = ctx.author
|
||||
latency = round(ctx.bot.latency * 1000, 2)
|
||||
elif interaction:
|
||||
user = interaction.user
|
||||
latency = round(interaction.client.latency * 1000, 2)
|
||||
|
||||
if isinstance(user, discord.User | discord.Member):
|
||||
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,
|
||||
)
|
||||
|
||||
return ("", None)
|
||||
|
||||
# @staticmethod
|
||||
# def shell_terminal_format(user: str) -> str:
|
||||
# return f"[{user}@tux ~]$"
|
||||
|
||||
@staticmethod
|
||||
def add_field(embed: discord.Embed, name: str, value: str, inline: bool = True) -> None:
|
||||
embed.add_field(name=name, value=value, inline=inline)
|
||||
|
@ -63,7 +62,7 @@ class EmbedCreator:
|
|||
|
||||
embed = discord.Embed()
|
||||
|
||||
embed.color = CONST.EMBED_STATE_COLORS[state]
|
||||
embed.color = discord.Colour(CONST.EMBED_STATE_COLORS[state])
|
||||
|
||||
embed.set_author(
|
||||
name=state.capitalize() if state else "Info",
|
||||
|
@ -85,7 +84,7 @@ class EmbedCreator:
|
|||
interaction: discord.Interaction | None,
|
||||
state: str,
|
||||
title: str,
|
||||
description: str,
|
||||
description: str = "",
|
||||
) -> discord.Embed:
|
||||
embed = cls.base_embed(ctx, interaction, state)
|
||||
embed.title = title
|
||||
|
@ -162,3 +161,13 @@ class EmbedCreator:
|
|||
interaction: discord.Interaction | None = None,
|
||||
) -> discord.Embed:
|
||||
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
8
tux/utils/enums.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from enum import Enum
|
||||
|
||||
|
||||
class InfractionType(Enum):
|
||||
BAN = "ban"
|
||||
WARN = "warn"
|
||||
KICK = "kick"
|
||||
TIMEOUT = "timeout"
|
Loading…
Reference in a new issue