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

Merge branch 'main' into prtemplateupdate

This commit is contained in:
Atmois 2024-09-06 20:35:52 +01:00 committed by GitHub
commit 61d6f0a0c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 694 additions and 933 deletions

227
poetry.lock generated
View file

@ -1551,122 +1551,123 @@ files = [
[[package]]
name = "pydantic"
version = "2.8.2"
version = "2.9.0"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.8"
files = [
{file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"},
{file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"},
{file = "pydantic-2.9.0-py3-none-any.whl", hash = "sha256:f66a7073abd93214a20c5f7b32d56843137a7a2e70d02111f3be287035c45370"},
{file = "pydantic-2.9.0.tar.gz", hash = "sha256:c7a8a9fdf7d100afa49647eae340e2d23efa382466a8d177efcd1381e9be5598"},
]
[package.dependencies]
annotated-types = ">=0.4.0"
pydantic-core = "2.20.1"
pydantic-core = "2.23.2"
typing-extensions = [
{version = ">=4.12.2", markers = "python_version >= \"3.13\""},
{version = ">=4.6.1", markers = "python_version < \"3.13\""},
]
tzdata = {version = "*", markers = "python_version >= \"3.9\""}
[package.extras]
email = ["email-validator (>=2.0.0)"]
[[package]]
name = "pydantic-core"
version = "2.20.1"
version = "2.23.2"
description = "Core functionality for Pydantic validation and serialization"
optional = false
python-versions = ">=3.8"
files = [
{file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"},
{file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"},
{file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"},
{file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"},
{file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"},
{file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"},
{file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"},
{file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"},
{file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"},
{file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"},
{file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"},
{file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"},
{file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"},
{file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"},
{file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"},
{file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"},
{file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"},
{file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"},
{file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"},
{file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"},
{file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"},
{file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"},
{file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"},
{file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"},
{file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"},
{file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"},
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"},
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"},
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"},
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"},
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"},
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"},
{file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"},
{file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"},
{file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"},
{file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"},
{file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"},
{file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"},
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"},
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"},
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"},
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"},
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"},
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"},
{file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"},
{file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"},
{file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"},
{file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"},
{file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"},
{file = "pydantic_core-2.23.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7d0324a35ab436c9d768753cbc3c47a865a2cbc0757066cb864747baa61f6ece"},
{file = "pydantic_core-2.23.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:276ae78153a94b664e700ac362587c73b84399bd1145e135287513442e7dfbc7"},
{file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:964c7aa318da542cdcc60d4a648377ffe1a2ef0eb1e996026c7f74507b720a78"},
{file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1cf842265a3a820ebc6388b963ead065f5ce8f2068ac4e1c713ef77a67b71f7c"},
{file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae90b9e50fe1bd115b24785e962b51130340408156d34d67b5f8f3fa6540938e"},
{file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ae65fdfb8a841556b52935dfd4c3f79132dc5253b12c0061b96415208f4d622"},
{file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c8aa40f6ca803f95b1c1c5aeaee6237b9e879e4dfb46ad713229a63651a95fb"},
{file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c53100c8ee5a1e102766abde2158077d8c374bee0639201f11d3032e3555dfbc"},
{file = "pydantic_core-2.23.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d6b9dd6aa03c812017411734e496c44fef29b43dba1e3dd1fa7361bbacfc1354"},
{file = "pydantic_core-2.23.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b18cf68255a476b927910c6873d9ed00da692bb293c5b10b282bd48a0afe3ae2"},
{file = "pydantic_core-2.23.2-cp310-none-win32.whl", hash = "sha256:e460475719721d59cd54a350c1f71c797c763212c836bf48585478c5514d2854"},
{file = "pydantic_core-2.23.2-cp310-none-win_amd64.whl", hash = "sha256:5f3cf3721eaf8741cffaf092487f1ca80831202ce91672776b02b875580e174a"},
{file = "pydantic_core-2.23.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7ce8e26b86a91e305858e018afc7a6e932f17428b1eaa60154bd1f7ee888b5f8"},
{file = "pydantic_core-2.23.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e9b24cca4037a561422bf5dc52b38d390fb61f7bfff64053ce1b72f6938e6b2"},
{file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:753294d42fb072aa1775bfe1a2ba1012427376718fa4c72de52005a3d2a22178"},
{file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:257d6a410a0d8aeb50b4283dea39bb79b14303e0fab0f2b9d617701331ed1515"},
{file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8319e0bd6a7b45ad76166cc3d5d6a36c97d0c82a196f478c3ee5346566eebfd"},
{file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a05c0240f6c711eb381ac392de987ee974fa9336071fb697768dfdb151345ce"},
{file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d5b0ff3218858859910295df6953d7bafac3a48d5cd18f4e3ed9999efd2245f"},
{file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:96ef39add33ff58cd4c112cbac076726b96b98bb8f1e7f7595288dcfb2f10b57"},
{file = "pydantic_core-2.23.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0102e49ac7d2df3379ef8d658d3bc59d3d769b0bdb17da189b75efa861fc07b4"},
{file = "pydantic_core-2.23.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a6612c2a844043e4d10a8324c54cdff0042c558eef30bd705770793d70b224aa"},
{file = "pydantic_core-2.23.2-cp311-none-win32.whl", hash = "sha256:caffda619099cfd4f63d48462f6aadbecee3ad9603b4b88b60cb821c1b258576"},
{file = "pydantic_core-2.23.2-cp311-none-win_amd64.whl", hash = "sha256:6f80fba4af0cb1d2344869d56430e304a51396b70d46b91a55ed4959993c0589"},
{file = "pydantic_core-2.23.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4c83c64d05ffbbe12d4e8498ab72bdb05bcc1026340a4a597dc647a13c1605ec"},
{file = "pydantic_core-2.23.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6294907eaaccf71c076abdd1c7954e272efa39bb043161b4b8aa1cd76a16ce43"},
{file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a801c5e1e13272e0909c520708122496647d1279d252c9e6e07dac216accc41"},
{file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc0c316fba3ce72ac3ab7902a888b9dc4979162d320823679da270c2d9ad0cad"},
{file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b06c5d4e8701ac2ba99a2ef835e4e1b187d41095a9c619c5b185c9068ed2a49"},
{file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82764c0bd697159fe9947ad59b6db6d7329e88505c8f98990eb07e84cc0a5d81"},
{file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b1a195efd347ede8bcf723e932300292eb13a9d2a3c1f84eb8f37cbbc905b7f"},
{file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7efb12e5071ad8d5b547487bdad489fbd4a5a35a0fc36a1941517a6ad7f23e0"},
{file = "pydantic_core-2.23.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5dd0ec5f514ed40e49bf961d49cf1bc2c72e9b50f29a163b2cc9030c6742aa73"},
{file = "pydantic_core-2.23.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:820f6ee5c06bc868335e3b6e42d7ef41f50dfb3ea32fbd523ab679d10d8741c0"},
{file = "pydantic_core-2.23.2-cp312-none-win32.whl", hash = "sha256:3713dc093d5048bfaedbba7a8dbc53e74c44a140d45ede020dc347dda18daf3f"},
{file = "pydantic_core-2.23.2-cp312-none-win_amd64.whl", hash = "sha256:e1895e949f8849bc2757c0dbac28422a04be031204df46a56ab34bcf98507342"},
{file = "pydantic_core-2.23.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:da43cbe593e3c87d07108d0ebd73771dc414488f1f91ed2e204b0370b94b37ac"},
{file = "pydantic_core-2.23.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:64d094ea1aa97c6ded4748d40886076a931a8bf6f61b6e43e4a1041769c39dd2"},
{file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:084414ffe9a85a52940b49631321d636dadf3576c30259607b75516d131fecd0"},
{file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:043ef8469f72609c4c3a5e06a07a1f713d53df4d53112c6d49207c0bd3c3bd9b"},
{file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3649bd3ae6a8ebea7dc381afb7f3c6db237fc7cebd05c8ac36ca8a4187b03b30"},
{file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6db09153d8438425e98cdc9a289c5fade04a5d2128faff8f227c459da21b9703"},
{file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5668b3173bb0b2e65020b60d83f5910a7224027232c9f5dc05a71a1deac9f960"},
{file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1c7b81beaf7c7ebde978377dc53679c6cba0e946426fc7ade54251dfe24a7604"},
{file = "pydantic_core-2.23.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:ae579143826c6f05a361d9546446c432a165ecf1c0b720bbfd81152645cb897d"},
{file = "pydantic_core-2.23.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:19f1352fe4b248cae22a89268720fc74e83f008057a652894f08fa931e77dced"},
{file = "pydantic_core-2.23.2-cp313-none-win32.whl", hash = "sha256:e1a79ad49f346aa1a2921f31e8dbbab4d64484823e813a002679eaa46cba39e1"},
{file = "pydantic_core-2.23.2-cp313-none-win_amd64.whl", hash = "sha256:582871902e1902b3c8e9b2c347f32a792a07094110c1bca6c2ea89b90150caac"},
{file = "pydantic_core-2.23.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:743e5811b0c377eb830150d675b0847a74a44d4ad5ab8845923d5b3a756d8100"},
{file = "pydantic_core-2.23.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6650a7bbe17a2717167e3e23c186849bae5cef35d38949549f1c116031b2b3aa"},
{file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56e6a12ec8d7679f41b3750ffa426d22b44ef97be226a9bab00a03365f217b2b"},
{file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:810ca06cca91de9107718dc83d9ac4d2e86efd6c02cba49a190abcaf33fb0472"},
{file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:785e7f517ebb9890813d31cb5d328fa5eda825bb205065cde760b3150e4de1f7"},
{file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ef71ec876fcc4d3bbf2ae81961959e8d62f8d74a83d116668409c224012e3af"},
{file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d50ac34835c6a4a0d456b5db559b82047403c4317b3bc73b3455fefdbdc54b0a"},
{file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16b25a4a120a2bb7dab51b81e3d9f3cde4f9a4456566c403ed29ac81bf49744f"},
{file = "pydantic_core-2.23.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:41ae8537ad371ec018e3c5da0eb3f3e40ee1011eb9be1da7f965357c4623c501"},
{file = "pydantic_core-2.23.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07049ec9306ec64e955b2e7c40c8d77dd78ea89adb97a2013d0b6e055c5ee4c5"},
{file = "pydantic_core-2.23.2-cp38-none-win32.whl", hash = "sha256:086c5db95157dc84c63ff9d96ebb8856f47ce113c86b61065a066f8efbe80acf"},
{file = "pydantic_core-2.23.2-cp38-none-win_amd64.whl", hash = "sha256:67b6655311b00581914aba481729971b88bb8bc7996206590700a3ac85e457b8"},
{file = "pydantic_core-2.23.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:358331e21a897151e54d58e08d0219acf98ebb14c567267a87e971f3d2a3be59"},
{file = "pydantic_core-2.23.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c4d9f15ffe68bcd3898b0ad7233af01b15c57d91cd1667f8d868e0eacbfe3f87"},
{file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0123655fedacf035ab10c23450163c2f65a4174f2bb034b188240a6cf06bb123"},
{file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e6e3ccebdbd6e53474b0bb7ab8b88e83c0cfe91484b25e058e581348ee5a01a5"},
{file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc535cb898ef88333cf317777ecdfe0faac1c2a3187ef7eb061b6f7ecf7e6bae"},
{file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aab9e522efff3993a9e98ab14263d4e20211e62da088298089a03056980a3e69"},
{file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05b366fb8fe3d8683b11ac35fa08947d7b92be78ec64e3277d03bd7f9b7cda79"},
{file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7568f682c06f10f30ef643a1e8eec4afeecdafde5c4af1b574c6df079e96f96c"},
{file = "pydantic_core-2.23.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cdd02a08205dc90238669f082747612cb3c82bd2c717adc60f9b9ecadb540f80"},
{file = "pydantic_core-2.23.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a2ab4f410f4b886de53b6bddf5dd6f337915a29dd9f22f20f3099659536b2f6"},
{file = "pydantic_core-2.23.2-cp39-none-win32.whl", hash = "sha256:0448b81c3dfcde439551bb04a9f41d7627f676b12701865c8a2574bcea034437"},
{file = "pydantic_core-2.23.2-cp39-none-win_amd64.whl", hash = "sha256:4cebb9794f67266d65e7e4cbe5dcf063e29fc7b81c79dc9475bd476d9534150e"},
{file = "pydantic_core-2.23.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e758d271ed0286d146cf7c04c539a5169a888dd0b57026be621547e756af55bc"},
{file = "pydantic_core-2.23.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f477d26183e94eaafc60b983ab25af2a809a1b48ce4debb57b343f671b7a90b6"},
{file = "pydantic_core-2.23.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da3131ef2b940b99106f29dfbc30d9505643f766704e14c5d5e504e6a480c35e"},
{file = "pydantic_core-2.23.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329a721253c7e4cbd7aad4a377745fbcc0607f9d72a3cc2102dd40519be75ed2"},
{file = "pydantic_core-2.23.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7706e15cdbf42f8fab1e6425247dfa98f4a6f8c63746c995d6a2017f78e619ae"},
{file = "pydantic_core-2.23.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e64ffaf8f6e17ca15eb48344d86a7a741454526f3a3fa56bc493ad9d7ec63936"},
{file = "pydantic_core-2.23.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dd59638025160056687d598b054b64a79183f8065eae0d3f5ca523cde9943940"},
{file = "pydantic_core-2.23.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:12625e69b1199e94b0ae1c9a95d000484ce9f0182f9965a26572f054b1537e44"},
{file = "pydantic_core-2.23.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5d813fd871b3d5c3005157622ee102e8908ad6011ec915a18bd8fde673c4360e"},
{file = "pydantic_core-2.23.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1eb37f7d6a8001c0f86dc8ff2ee8d08291a536d76e49e78cda8587bb54d8b329"},
{file = "pydantic_core-2.23.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ce7eaf9a98680b4312b7cebcdd9352531c43db00fca586115845df388f3c465"},
{file = "pydantic_core-2.23.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f087879f1ffde024dd2788a30d55acd67959dcf6c431e9d3682d1c491a0eb474"},
{file = "pydantic_core-2.23.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ce883906810b4c3bd90e0ada1f9e808d9ecf1c5f0b60c6b8831d6100bcc7dd6"},
{file = "pydantic_core-2.23.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:a8031074a397a5925d06b590121f8339d34a5a74cfe6970f8a1124eb8b83f4ac"},
{file = "pydantic_core-2.23.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:23af245b8f2f4ee9e2c99cb3f93d0e22fb5c16df3f2f643f5a8da5caff12a653"},
{file = "pydantic_core-2.23.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c57e493a0faea1e4c38f860d6862ba6832723396c884fbf938ff5e9b224200e2"},
{file = "pydantic_core-2.23.2.tar.gz", hash = "sha256:95d6bf449a1ac81de562d65d180af5d8c19672793c81877a2eda8fde5d08f2fd"},
]
[package.dependencies]
@ -2022,29 +2023,29 @@ pyasn1 = ">=0.1.3"
[[package]]
name = "ruff"
version = "0.6.3"
version = "0.6.4"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.6.3-py3-none-linux_armv6l.whl", hash = "sha256:97f58fda4e309382ad30ede7f30e2791d70dd29ea17f41970119f55bdb7a45c3"},
{file = "ruff-0.6.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3b061e49b5cf3a297b4d1c27ac5587954ccb4ff601160d3d6b2f70b1622194dc"},
{file = "ruff-0.6.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:34e2824a13bb8c668c71c1760a6ac7d795ccbd8d38ff4a0d8471fdb15de910b1"},
{file = "ruff-0.6.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bddfbb8d63c460f4b4128b6a506e7052bad4d6f3ff607ebbb41b0aa19c2770d1"},
{file = "ruff-0.6.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ced3eeb44df75353e08ab3b6a9e113b5f3f996bea48d4f7c027bc528ba87b672"},
{file = "ruff-0.6.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47021dff5445d549be954eb275156dfd7c37222acc1e8014311badcb9b4ec8c1"},
{file = "ruff-0.6.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d7bd20dc07cebd68cc8bc7b3f5ada6d637f42d947c85264f94b0d1cd9d87384"},
{file = "ruff-0.6.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:500f166d03fc6d0e61c8e40a3ff853fa8a43d938f5d14c183c612df1b0d6c58a"},
{file = "ruff-0.6.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42844ff678f9b976366b262fa2d1d1a3fe76f6e145bd92c84e27d172e3c34500"},
{file = "ruff-0.6.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70452a10eb2d66549de8e75f89ae82462159855e983ddff91bc0bce6511d0470"},
{file = "ruff-0.6.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:65a533235ed55f767d1fc62193a21cbf9e3329cf26d427b800fdeacfb77d296f"},
{file = "ruff-0.6.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2e2c23cef30dc3cbe9cc5d04f2899e7f5e478c40d2e0a633513ad081f7361b5"},
{file = "ruff-0.6.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d8a136aa7d228975a6aee3dd8bea9b28e2b43e9444aa678fb62aeb1956ff2351"},
{file = "ruff-0.6.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f92fe93bc72e262b7b3f2bba9879897e2d58a989b4714ba6a5a7273e842ad2f8"},
{file = "ruff-0.6.3-py3-none-win32.whl", hash = "sha256:7a62d3b5b0d7f9143d94893f8ba43aa5a5c51a0ffc4a401aa97a81ed76930521"},
{file = "ruff-0.6.3-py3-none-win_amd64.whl", hash = "sha256:746af39356fee2b89aada06c7376e1aa274a23493d7016059c3a72e3b296befb"},
{file = "ruff-0.6.3-py3-none-win_arm64.whl", hash = "sha256:14a9528a8b70ccc7a847637c29e56fd1f9183a9db743bbc5b8e0c4ad60592a82"},
{file = "ruff-0.6.3.tar.gz", hash = "sha256:183b99e9edd1ef63be34a3b51fee0a9f4ab95add123dbf89a71f7b1f0c991983"},
{file = "ruff-0.6.4-py3-none-linux_armv6l.whl", hash = "sha256:c4b153fc152af51855458e79e835fb6b933032921756cec9af7d0ba2aa01a258"},
{file = "ruff-0.6.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:bedff9e4f004dad5f7f76a9d39c4ca98af526c9b1695068198b3bda8c085ef60"},
{file = "ruff-0.6.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d02a4127a86de23002e694d7ff19f905c51e338c72d8e09b56bfb60e1681724f"},
{file = "ruff-0.6.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7862f42fc1a4aca1ea3ffe8a11f67819d183a5693b228f0bb3a531f5e40336fc"},
{file = "ruff-0.6.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eebe4ff1967c838a1a9618a5a59a3b0a00406f8d7eefee97c70411fefc353617"},
{file = "ruff-0.6.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:932063a03bac394866683e15710c25b8690ccdca1cf192b9a98260332ca93408"},
{file = "ruff-0.6.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:50e30b437cebef547bd5c3edf9ce81343e5dd7c737cb36ccb4fe83573f3d392e"},
{file = "ruff-0.6.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c44536df7b93a587de690e124b89bd47306fddd59398a0fb12afd6133c7b3818"},
{file = "ruff-0.6.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ea086601b22dc5e7693a78f3fcfc460cceabfdf3bdc36dc898792aba48fbad6"},
{file = "ruff-0.6.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b52387d3289ccd227b62102c24714ed75fbba0b16ecc69a923a37e3b5e0aaaa"},
{file = "ruff-0.6.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0308610470fcc82969082fc83c76c0d362f562e2f0cdab0586516f03a4e06ec6"},
{file = "ruff-0.6.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:803b96dea21795a6c9d5bfa9e96127cc9c31a1987802ca68f35e5c95aed3fc0d"},
{file = "ruff-0.6.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:66dbfea86b663baab8fcae56c59f190caba9398df1488164e2df53e216248baa"},
{file = "ruff-0.6.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:34d5efad480193c046c86608dbba2bccdc1c5fd11950fb271f8086e0c763a5d1"},
{file = "ruff-0.6.4-py3-none-win32.whl", hash = "sha256:f0f8968feea5ce3777c0d8365653d5e91c40c31a81d95824ba61d871a11b8523"},
{file = "ruff-0.6.4-py3-none-win_amd64.whl", hash = "sha256:549daccee5227282289390b0222d0fbee0275d1db6d514550d65420053021a58"},
{file = "ruff-0.6.4-py3-none-win_arm64.whl", hash = "sha256:ac4b75e898ed189b3708c9ab3fc70b79a433219e1e87193b4f2b77251d058d14"},
{file = "ruff-0.6.4.tar.gz", hash = "sha256:ac3b5bfbee99973f80aa1b7cbd1c9cbce200883bdd067300c22a6cc1c7fba212"},
]
[[package]]

View file

@ -6,7 +6,6 @@ from loguru import logger
from tux.cog_loader import CogLoader
from tux.database.client import db
from tux.help import TuxHelp
class Tux(commands.Bot):
@ -14,7 +13,6 @@ class Tux(commands.Bot):
super().__init__(*args, **kwargs)
self.setup_task = asyncio.create_task(self.setup())
self.is_shutting_down = False
self.help_command = TuxHelp()
async def setup(self) -> None:
"""

View file

@ -5,8 +5,8 @@ from discord.ext import commands
from loguru import logger
from tux.bot import Tux
from tux.ui.embeds import EmbedCreator
from tux.utils import checks
from tux.utils.embeds import EmbedCreator
def insert_returns(body: list[ast.stmt]) -> None:
@ -108,26 +108,27 @@ class Eval(commands.Cog):
# Evaluate the function
evaluated = await eval(f"{fn_name}()", env)
embed = EmbedCreator.create_success_embed(
title="Success!",
embed = EmbedCreator.create_embed(
EmbedCreator.SUCCESS,
bot=self.bot,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description=f"```py\n{evaluated}```",
ctx=ctx,
)
await ctx.reply(embed=embed, ephemeral=True, delete_after=30)
logger.info(f"{ctx.author} ran an expression: {cmd}")
except Exception as error:
embed = EmbedCreator.create_error_embed(
title="Error!",
embed = EmbedCreator.create_embed(
EmbedCreator.ERROR,
bot=self.bot,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description=f"```py\n{error}```",
ctx=ctx,
)
await ctx.reply(embed=embed, ephemeral=True, delete_after=30)
logger.error(f"An error occurred while running an expression: {error}")
else:
await ctx.send(embed=embed, ephemeral=True, delete_after=30)
async def setup(bot: Tux) -> None:
await bot.add_cog(Eval(bot))

View file

@ -1,400 +1,15 @@
# import discord
# from discord import app_commands
# from discord.ext import commands
# from githubkit.versions.latest.models import Issue
# from loguru import logger
# from tux.wrappers.github import GitHubService
# from tux.utils.constants import Constants as CONST
# from tux.utils.embeds import EmbedCreator
# class LinkButton(discord.ui.View):
# def __init__(self, url: str) -> None:
# super().__init__()
# self.add_item(
# discord.ui.Button(style=discord.ButtonStyle.link, label="View on Github", url=url),
# )
# class Git(commands.Cog):
# def __init__(self, bot: Tux) -> None:
# self.bot = bot
# self.github = GitHubService()
# self.repo_url = CONST.GITHUB_REPO_URL
# git = app_commands.Group(name="git", description="Github commands.")
# async def create_error_embed(self, interaction: discord.Interaction, error: str) -> None:
# """
# Create an error embed and send it as a followup message for all commands.
# Parameters
# ----------
# interaction : discord.Interaction
# The interaction object representing the command invocation
# error : str
# The error message to display.
# """
# embed = EmbedCreator.create_error_embed(
# title="Uh oh!",
# description=error,
# interaction=interaction,
# )
# await interaction.followup.send(embed=embed)
# @app_commands.checks.has_any_role("Contributor", "Root", "Admin")
# @git.command(name="get_repo")
# async def get_repo(self, interaction: discord.Interaction) -> None:
# """
# Get repository information.
# Parameters
# ----------
# interaction : discord.Interaction
# The interaction object representing the command invocation.
# """
# await interaction.response.defer()
# try:
# repo = await self.github.get_repo()
# embed = EmbedCreator.create_info_embed(
# title="Tux",
# description="",
# interaction=interaction,
# )
# embed.add_field(name="Description", value=repo.description, inline=False)
# embed.add_field(name="Stars", value=repo.stargazers_count)
# embed.add_field(name="Forks", value=repo.forks_count)
# embed.add_field(name="Open Issues", value=repo.open_issues_count)
# except Exception as e:
# await self.create_error_embed(interaction, f"Error fetching repository: {e}")
# logger.error(f"Error fetching repository: {e}")
# else:
# await interaction.followup.send(embed=embed, view=LinkButton(repo.html_url))
# logger.info(f"{interaction.user} fetched repository information.")
# @app_commands.checks.has_any_role("Contributor", "Root", "Admin")
# @git.command(name="create_issue")
# async def create_issue(self, interaction: discord.Interaction, title: str, body: str) -> None:
# """
# Create an issue.
# Parameters
# ----------
# interaction : discord.Interaction
# The interaction object representing the command invocation.
# title : str
# The title of the issue.
# body : str
# The body of the issue.
# """
# await interaction.response.defer()
# try:
# created_issue = await self.github.create_issue(title, body)
# embed = EmbedCreator.create_success_embed(
# title="Issue Created",
# description="The issue has been created successfully.",
# interaction=interaction,
# )
# embed.add_field(name="Issue Number", value=created_issue.number, inline=False)
# embed.add_field(name="Title", value=title)
# embed.add_field(name="Body", value=body)
# except Exception as e:
# await self.create_error_embed(interaction, f"Error creating issue: {e}")
# logger.error(f"Error creating issue: {e}")
# else:
# await interaction.followup.send(embed=embed, view=LinkButton(created_issue.html_url))
# logger.info(f"{interaction.user} created an issue.")
# @app_commands.checks.has_any_role("Contributor", "Root", "Admin")
# @git.command(name="close_issue", description="Close an issue.")
# async def close_issue(self, interaction: discord.Interaction, issue_number: int) -> None:
# """
# Close an issue.
# Parameters
# ----------
# interaction : discord.Interaction
# The interaction object representing the command invocation.
# issue_number : int
# The number of the issue to close.
# """
# await interaction.response.defer()
# try:
# closed_issue = await self.github.close_issue(issue_number)
# embed = EmbedCreator.create_success_embed(
# title="Issue Closed",
# description="The issue has been closed successfully.",
# interaction=interaction,
# )
# embed.add_field(name="Issue Number", value=issue_number)
# except Exception as e:
# await self.create_error_embed(interaction, f"Error closing issue: {e}")
# logger.error(f"Error closing issue: {e}")
# else:
# await interaction.followup.send(embed=embed, view=LinkButton(closed_issue.html_url))
# logger.info(f"{interaction.user} closed an issue.")
# @app_commands.checks.has_any_role("Contributor", "Root", "Admin")
# @git.command(name="get_issue", description="Get an issue.")
# async def get_issue(self, interaction: discord.Interaction, issue_number: int) -> None:
# """
# Get an issue.
# Parameters
# ----------
# interaction : discord.Interaction
# The interaction object representing the command invocation.
# issue_number : int
# The number of the issue to retrieve.
# """
# await interaction.response.defer()
# try:
# issue = await self.github.get_issue(issue_number)
# embed = EmbedCreator.create_info_embed(
# title=issue.title,
# description=str(issue.body) if issue.body is not None else "",
# interaction=interaction,
# )
# embed.add_field(name="State", value=issue.state)
# embed.add_field(name="Number", value=issue.number)
# embed.add_field(name="User", value=issue.user.login if issue.user else "Unknown")
# embed.add_field(name="Created At", value=issue.created_at)
# embed.add_field(name="Updated At", value=issue.updated_at)
# except Exception as e:
# await self.create_error_embed(interaction, f"Error fetching issue: {e}")
# logger.error(f"Error fetching issue: {e}")
# else:
# await interaction.followup.send(embed=embed, view=LinkButton(issue.html_url))
# logger.info(f"{interaction.user} fetched an issue.")
# @app_commands.checks.has_any_role("Contributor", "Root", "Admin")
# @git.command(name="get_open_issues", description="Get open issues.")
# async def get_open_issues(self, interaction: discord.Interaction) -> None:
# """
# Get open issues.
# Parameters
# ----------
# interaction : discord.Interaction
# The interaction object representing the command invocation.
# """
# await interaction.response.defer()
# try:
# open_issues: list[Issue] = await self.github.get_open_issues()
# embed = EmbedCreator.create_info_embed(
# title="Open Issues",
# description="Here are the open issues for the repository.",
# interaction=interaction,
# )
# for issue in open_issues[:25]:
# embed.add_field(name=issue.title, value=str(issue.number), inline=False)
# except Exception as e:
# await self.create_error_embed(interaction, f"Error fetching issues: {e}")
# logger.error(f"Error fetching issues: {e}")
# else:
# await interaction.followup.send(
# embed=embed,
# view=LinkButton(f"{self.repo_url}/issues?q=is%3Aissue+is%3Aopen"),
# )
# logger.info(f"{interaction.user} fetched open issues.")
# @app_commands.checks.has_any_role("Contributor", "Root", "Admin")
# @git.command(name="get_closed_issues", description="Get closed issues.")
# async def get_closed_issues(self, interaction: discord.Interaction) -> None:
# """
# Get closed issues.
# Parameters
# ----------
# interaction : discord.Interaction
# The interaction object representing the command invocation.
# """
# await interaction.response.defer()
# try:
# closed_issues = await self.github.get_closed_issues()
# embed = EmbedCreator.create_info_embed(
# title="Closed Issues",
# description="Here are the closed issues for the repository.",
# interaction=interaction,
# )
# for issue in closed_issues[:25]:
# embed.add_field(name=issue.title, value=str(issue.number), inline=False)
# except Exception as e:
# await self.create_error_embed(interaction, f"Error fetching issues: {e}")
# logger.error(f"Error fetching issues: {e}")
# else:
# await interaction.followup.send(
# embed=embed,
# view=LinkButton(
# "https://github.com/allthingslinux/tux/issues?q=is%3Aissue+is%3Aclosed",
# ),
# )
# logger.info(f"{interaction.user} fetched closed issues.")
# @app_commands.checks.has_any_role("Contributor", "Root", "Admin")
# @git.command(name="get_open_pulls", description="Get open pull requests.")
# async def get_open_pulls(self, interaction: discord.Interaction) -> None:
# """
# Get open pull requests.
# Parameters
# ----------
# interaction : discord.Interaction
# The interaction object representing the command invocation.
# """
# await interaction.response.defer()
# try:
# open_pulls = await self.github.get_open_pulls()
# embed = EmbedCreator.create_info_embed(
# title="Open Pull Requests",
# description="Here are the open pull requests for the repository.",
# interaction=interaction,
# )
# for pull in open_pulls[:25]:
# embed.add_field(name=pull.title, value=str(pull.number), inline=False)
# except Exception as e:
# await self.create_error_embed(interaction, f"Error fetching pull requests: {e}")
# logger.error(f"Error fetching pull requests: {e}")
# else:
# await interaction.followup.send(
# embed=embed,
# view=LinkButton("https://github.com/allthingslinux/tux/pulls?q=is%3Aopen+is%3Apr"),
# )
# logger.info(f"{interaction.user} fetched open pull requests.")
# @app_commands.checks.has_any_role("Contributor", "Root", "Admin")
# @git.command(name="get_closed_pulls", description="Get closed pull requests.")
# async def get_closed_pulls(self, interaction: discord.Interaction) -> None:
# """
# Get closed pull requests.
# Parameters
# ----------
# interaction : discord.Interaction
# The interaction object representing the command invocation.
# """
# await interaction.response.defer()
# try:
# closed_pulls = await self.github.get_closed_pulls()
# embed = EmbedCreator.create_info_embed(
# title="Closed Pull Requests",
# description="Here are the closed pull requests for the repository.",
# interaction=interaction,
# )
# for pull in closed_pulls[:25]:
# embed.add_field(name=pull.title, value=str(pull.number), inline=False)
# except Exception as e:
# await self.create_error_embed(interaction, f"Error fetching pull requests: {e}")
# logger.error(f"Error fetching pull requests: {e}")
# else:
# await interaction.followup.send(
# embed=embed,
# view=LinkButton("https://github.com/allthingslinux/tux/"),
# )
# logger.info(f"{interaction.user} fetched closed pull requests.")
# @app_commands.checks.has_any_role("Contributor", "Root", "Admin")
# @git.command(name="get_pull", description="Get a pull request.")
# async def get_pull(self, interaction: discord.Interaction, pull_number: int) -> None:
# """
# Get a pull request.
# Parameters
# ----------
# interaction : discord.Interaction
# The interaction object representing the command invocation.
# pull_number : int
# The number of the pull request to retrieve.
# """
# await interaction.response.defer()
# try:
# pull = await self.github.get_pull(pull_number)
# embed = EmbedCreator.create_info_embed(
# title=pull.title,
# description=str(pull.body) if pull.body is not None else "",
# interaction=interaction,
# )
# embed.add_field(name="State", value=pull.state)
# embed.add_field(name="Number", value=pull.number)
# embed.add_field(name="User", value=pull.user.login)
# embed.add_field(name="Created At", value=pull.created_at)
# embed.add_field(name="Updated At", value=pull.updated_at)
# except Exception as e:
# await self.create_error_embed(interaction, f"Error fetching pull request: {e}")
# logger.error(f"Error fetching pull request: {e}")
# else:
# await interaction.followup.send(embed=embed, view=LinkButton(pull.html_url))
# logger.info(f"{interaction.user} fetched a pull request.")
# async def setup(bot: Tux) -> None:
# await bot.add_cog(Git(bot))
# TODO: Rewrite this cog to use the new hybrid command system.
from discord.ext import commands
from loguru import logger
from tux.bot import Tux
from tux.ui.buttons import GithubButton
from tux.ui.embeds import EmbedCreator
from tux.utils import checks
from tux.utils.constants import Constants as CONST
from tux.utils.embeds import EmbedCreator
from tux.wrappers.github import GithubService
# TODO: Rewrite this cog to use the new hybrid command system.
class Git(commands.Cog):
def __init__(self, bot: Tux) -> None:
@ -442,10 +57,13 @@ class Git(commands.Cog):
try:
repo = await self.github.get_repo()
embed = EmbedCreator.create_info_embed(
embed = EmbedCreator.create_embed(
EmbedCreator.INFO,
bot=self.bot,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title="Tux",
description="",
ctx=ctx,
)
embed.add_field(name="Description", value=repo.description, inline=False)
embed.add_field(name="Stars", value=repo.stargazers_count)
@ -483,16 +101,20 @@ class Git(commands.Cog):
"""
try:
created_issue = await self.github.create_issue(title, body)
issue_body = body + "\n\nAuthor: " + str(ctx.author)
created_issue = await self.github.create_issue(title, issue_body)
embed = EmbedCreator.create_success_embed(
embed = EmbedCreator.create_embed(
EmbedCreator.SUCCESS,
bot=self.bot,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title="Issue Created",
description="The issue has been created successfully.",
ctx=ctx,
)
embed.add_field(name="Issue Number", value=created_issue.number, inline=False)
embed.add_field(name="Title", value=title, inline=False)
embed.add_field(name="Body", value=body, inline=False)
embed.add_field(name="Body", value=issue_body, inline=False)
except Exception as e:
await ctx.send(f"Error creating issue: {e}", delete_after=30, ephemeral=True)
@ -524,10 +146,13 @@ class Git(commands.Cog):
try:
issue = await self.github.get_issue(issue_number)
embed = EmbedCreator.create_info_embed(
embed = EmbedCreator.create_embed(
EmbedCreator.INFO,
bot=self.bot,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title=issue.title,
description=str(issue.body) if issue.body is not None else "",
ctx=ctx,
)
embed.add_field(name="State", value=issue.state, inline=False)
embed.add_field(name="Number", value=issue.number, inline=False)

View file

@ -3,7 +3,7 @@ import random
from discord.ext import commands
from tux.bot import Tux
from tux.utils.embeds import EmbedCreator
from tux.ui.embeds import EmbedCreator
class Fact(commands.Cog):
@ -46,10 +46,13 @@ class Fact(commands.Cog):
ctx : commands.Context[Tux]
The context object for the command.
"""
embed = EmbedCreator.create_info_embed(
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.INFO,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title="Fun Fact",
description=random.choice(self.facts),
ctx=ctx,
)
# set author

View file

@ -8,7 +8,7 @@ from loguru import logger
from PIL import Image, ImageEnhance, ImageOps
from tux.bot import Tux
from tux.utils.embeds import EmbedCreator
from tux.ui.embeds import EmbedCreator
class ImgEffect(commands.Cog):
@ -43,10 +43,13 @@ class ImgEffect(commands.Cog):
if image.content_type not in self.allowed_mimetypes:
logger.error("The file is not a permitted image.")
embed = EmbedCreator.create_error_embed(
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=interaction.user.name,
user_display_avatar=interaction.user.display_avatar.url,
title="Invalid File",
description="The file must be an image. Allowed types are PNG, JPEG, and JPG.",
interaction=interaction,
)
await interaction.response.send_message(embed=embed, ephemeral=True)

View file

@ -3,7 +3,7 @@ import random
from discord.ext import commands
from tux.bot import Tux
from tux.utils.embeds import EmbedCreator
from tux.ui.embeds import EmbedCreator
class Random(commands.Cog):
@ -174,10 +174,13 @@ class Random(commands.Cog):
await ctx.send(content="The dice must have at least 2 sides.", ephemeral=True, delete_after=30)
return
embed = EmbedCreator.create_info_embed(
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.INFO,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title=f"Dice Roll (D{sides})",
description=f"You rolled a {random.randint(1, sides)}!",
ctx=ctx,
)
await ctx.send(embed=embed)

View file

@ -4,7 +4,7 @@ from loguru import logger
from tux.bot import Tux
from tux.ui.buttons import XkcdButtons
from tux.utils.embeds import EmbedCreator
from tux.ui.embeds import EmbedCreator
from tux.wrappers import xkcd
@ -123,7 +123,9 @@ class Xkcd(commands.Cog):
else:
comic = self.client.get_random_comic(raw_comic_image=True)
embed = EmbedCreator.create_success_embed(
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.INFO,
title="",
description=f"\n\n> {comic.description.strip()}" if comic.description else "",
)
@ -134,8 +136,9 @@ class Xkcd(commands.Cog):
except xkcd.HttpError:
logger.error("HTTP error occurred while fetching xkcd comic")
embed = EmbedCreator.create_error_embed(
title="Error",
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
description="I couldn't find the xkcd comic. Please try again later.",
)
ephemeral = True
@ -143,8 +146,9 @@ class Xkcd(commands.Cog):
except Exception as e:
logger.error(f"Error getting xkcd comic: {e}")
embed = EmbedCreator.create_error_embed(
title="Error",
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
description="An error occurred while fetching the xkcd comic",
)
ephemeral = True

View file

@ -6,9 +6,9 @@ from discord.ext import commands
from tux.bot import Tux
from tux.database.controllers import DatabaseController
from tux.ui.embeds import EmbedCreator
from tux.ui.views.config import ConfigSetChannels, ConfigSetPrivateLogs, ConfigSetPublicLogs
from tux.utils.constants import CONST
from tux.utils.embeds import EmbedCreator
# TODO: Add onboarding setup to ensure all required channels, logs, and roles are set up
# TODO: Figure out how to handle using our custom checks because the current checks would result in a lock out
@ -339,10 +339,13 @@ class Config(commands.GroupCog, group_name="config"):
await self.db.update_guild_prefix(interaction.guild.id, prefix)
await interaction.response.send_message(
embed=EmbedCreator.create_success_embed(
embed=EmbedCreator.create_embed(
bot=self.bot,
user_name=interaction.user.name,
user_display_avatar=interaction.user.display_avatar.url,
embed_type=EmbedCreator.SUCCESS,
title="Guild Config",
description=f"The prefix was updated to `{prefix}`",
interaction=interaction,
),
)
@ -367,10 +370,13 @@ class Config(commands.GroupCog, group_name="config"):
await self.db.delete_guild_prefix(interaction.guild.id)
await interaction.response.send_message(
embed=EmbedCreator.create_success_embed(
embed=EmbedCreator.create_embed(
bot=self.bot,
user_name=interaction.user.name,
user_display_avatar=interaction.user.display_avatar.url,
embed_type=EmbedCreator.SUCCESS,
title="Guild Config",
description=f"The prefix was reset to `{CONST.DEFAULT_PREFIX}`",
interaction=interaction,
),
)

View file

@ -3,8 +3,8 @@ from discord import app_commands
from discord.ext import commands
from tux.bot import Tux
from tux.ui.embeds import EmbedCreator
from tux.utils import checks, exports
from tux.utils.embeds import EmbedCreator
class Export(commands.Cog):
@ -38,7 +38,11 @@ class Export(commands.Cog):
valid_flags = ["user", "display", "id", "reason", "mention", "created"]
if not bans:
embed = EmbedCreator.create_success_embed(
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.INFO,
user_name=interaction.user.name,
user_display_avatar=interaction.user.display_avatar.url,
title=f"{interaction.guild} Banned Users",
description="There are no banned users in this server.",
)

View file

@ -4,7 +4,7 @@ from discord.ext import commands
from reactionmenu import ViewButton, ViewMenu
from tux.bot import Tux
from tux.utils.embeds import EmbedCreator
from tux.ui.embeds import EmbedCreator
des_ids = [
[1175177565086953523, "_kde"],
@ -316,10 +316,13 @@ class RoleCount(commands.Cog):
The created embed.
"""
return EmbedCreator.create_info_embed(
return EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.INFO,
user_name=interaction.user.name,
user_display_avatar=interaction.user.display_avatar.url,
title=f"{which.name} Roles",
description="Number of users in each role",
interaction=interaction,
)
async def _send_response(

View file

@ -3,7 +3,7 @@ from discord import app_commands
from discord.ext import commands
from tux.bot import Tux
from tux.utils.embeds import EmbedCreator
from tux.ui.embeds import EmbedCreator
class MemberCount(commands.Cog):
@ -33,10 +33,13 @@ class MemberCount(commands.Cog):
staff_role = discord.utils.get(interaction.guild.roles, name="%wheel")
staff = len(staff_role.members) if staff_role else 0
embed = EmbedCreator.create_info_embed(
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.INFO,
user_name=interaction.user.name,
user_display_avatar=interaction.user.display_avatar.url,
title="Member Count",
description="Here is the member count for the server.",
interaction=interaction,
)
embed.add_field(name="Members", value=str(members), inline=False)

View file

@ -7,8 +7,8 @@ from loguru import logger
from prisma.enums import CaseType
from tux.bot import Tux
from tux.database.controllers import DatabaseController
from tux.ui.embeds import EmbedCreator
from tux.utils.constants import Constants as CONST
from tux.utils.embeds import create_embed_footer, create_error_embed
class ModerationCogBase(commands.Cog):
@ -55,7 +55,11 @@ class ModerationCogBase(commands.Cog):
embed.set_author(name=title, icon_url=icon_url)
embed.set_thumbnail(url=thumbnail_url)
footer_text, footer_icon_url = create_embed_footer(ctx)
footer_text, footer_icon_url = EmbedCreator.get_footer(
bot=self.bot,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
)
embed.set_footer(text=footer_text, icon_url=footer_icon_url)
for name, value, inline in fields:
@ -155,17 +159,38 @@ class ModerationCogBase(commands.Cog):
assert ctx.guild
if user == ctx.author:
embed = create_error_embed(f"You cannot {action} yourself.")
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title="You cannot self-moderate",
description=f"You cannot {action} yourself.",
)
await ctx.send(embed=embed, ephemeral=True, delete_after=30)
return False
if isinstance(moderator, discord.Member) and user.top_role >= moderator.top_role:
embed = create_error_embed(f"You cannot {action} a user with a higher or equal role.")
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title="You cannot self-moderate",
description=f"You cannot {action} a user with a higher or equal role.",
)
await ctx.send(embed=embed, ephemeral=True, delete_after=30)
return False
if user == ctx.guild.owner:
embed = create_error_embed(f"You cannot {action} the server owner.")
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title="You cannot self-moderate",
description=f"You cannot {action} the server owner.",
)
await ctx.send(embed=embed, ephemeral=True, delete_after=30)
return False

View file

@ -6,9 +6,9 @@ from prisma.enums import CaseType
from prisma.models import Case
from prisma.types import CaseWhereInput
from tux.bot import Tux
from tux.ui.embeds import EmbedCreator
from tux.utils import checks
from tux.utils.constants import Constants as CONST
from tux.utils.embeds import create_embed_footer
from tux.utils.flags import CaseModifyFlags, CasesViewFlags, generate_usage
from . import ModerationCogBase
@ -346,7 +346,11 @@ class Cases(ModerationCogBase):
if ctx.guild:
embed.set_author(name=ctx.guild.name, icon_url=ctx.guild.icon)
footer_text, footer_icon_url = create_embed_footer(ctx)
footer_text, footer_icon_url = EmbedCreator.get_footer(
bot=self.bot,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
)
embed.set_footer(text=footer_text, icon_url=footer_icon_url)
for case in cases:

View file

@ -5,8 +5,8 @@ from discord.ext import commands
from loguru import logger
from tux.bot import Tux
from tux.ui.embeds import EmbedCreator
from tux.utils.constants import Constants as CONST
from tux.utils.embeds import EmbedCreator
class Bookmarks(commands.Cog):
@ -54,7 +54,9 @@ class Bookmarks(commands.Cog):
if len(message.content) > CONST.EMBED_MAX_DESC_LENGTH:
message.content = f"{message.content[:CONST.EMBED_MAX_DESC_LENGTH - 3]}..."
embed = EmbedCreator.create_info_embed(
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.INFO,
title="Message Bookmarked",
description=f"> {message.content}",
)

View file

@ -6,8 +6,8 @@ from loguru import logger
from tux.bot import Tux
from tux.database.controllers.starboard import StarboardController, StarboardMessageController
from tux.ui.embeds import EmbedCreator
from tux.utils import checks
from tux.utils.embeds import EmbedCreator
class Starboard(commands.Cog):
@ -57,30 +57,39 @@ class Starboard(commands.Cog):
if len(emoji) != 1 or not emoji.isprintable():
await ctx.send(
embed=EmbedCreator.create_error_embed(
embed=EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title="Invalid Emoji",
description="Please use a single default Discord emoji.",
ctx=ctx,
),
)
return
if threshold < 1:
await ctx.send(
embed=EmbedCreator.create_error_embed(
embed=EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title="Invalid Threshold",
description="Threshold must be at least 1.",
ctx=ctx,
),
)
return
if not channel.permissions_for(ctx.guild.me).send_messages:
await ctx.send(
embed=EmbedCreator.create_error_embed(
embed=EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title="Permission Denied",
description=f"I don't have permission to send messages in {channel.mention}.",
ctx=ctx,
),
)
return
@ -88,10 +97,13 @@ class Starboard(commands.Cog):
try:
await self.starboard_controller.create_or_update_starboard(ctx.guild.id, channel.id, emoji, threshold)
embed = EmbedCreator.create_success_embed(
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.INFO,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title="Starboard Setup",
description="Starboard configured successfully.",
ctx=ctx,
)
embed.add_field(name="Channel", value=channel.mention)
embed.add_field(name="Emoji", value=emoji)
@ -120,16 +132,22 @@ class Starboard(commands.Cog):
result = await self.starboard_controller.delete_starboard_by_guild_id(ctx.guild.id)
embed = (
EmbedCreator.create_success_embed(
EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.INFO,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title="Starboard Removed",
description="Starboard configuration removed successfully.",
ctx=ctx,
)
if result
else EmbedCreator.create_error_embed(
else EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title="No Starboard Found",
description="No starboard configuration found for this server.",
ctx=ctx,
)
)

View file

@ -2,7 +2,7 @@ import psutil
from discord.ext import commands
from tux.bot import Tux
from tux.utils.embeds import EmbedCreator
from tux.ui.embeds import EmbedCreator
class Ping(commands.Cog):
@ -38,10 +38,13 @@ class Ping(commands.Cog):
else:
ram_amount_formatted = f"{round(ram_amount_in_mb)}MB"
embed = EmbedCreator.create_success_embed(
embed = EmbedCreator.create_embed(
embed_type=EmbedCreator.INFO,
bot=self.bot,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title="Pong!",
description="Here are some stats about the bot.",
ctx=ctx,
)
embed.add_field(name="API Latency", value=f"{discord_ping}ms", inline=True)

View file

@ -4,7 +4,7 @@ from discord.ext import commands
from loguru import logger
from tux.bot import Tux
from tux.utils.embeds import EmbedCreator
from tux.ui.embeds import EmbedCreator
# TODO: Create option inputs for the poll command instead of using a comma separated string
@ -74,10 +74,13 @@ class Poll(commands.Cog):
# Check if the options count is between 2-9
if len(options_list) < 2 or len(options_list) > 9:
embed = EmbedCreator.create_error_embed(
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=interaction.user.name,
user_display_avatar=interaction.user.display_avatar.url,
title="Invalid options count",
description=f"Poll options count needs to be between 2-9, you provided {len(options_list)} options.",
interaction=interaction,
)
await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=30)
@ -88,10 +91,13 @@ class Poll(commands.Cog):
[f"{num + 1}\u20e3 {option}" for num, option in enumerate(options_list)],
)
embed = EmbedCreator.create_poll_embed(
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.POLL,
user_name=interaction.user.name,
user_display_avatar=interaction.user.display_avatar.url,
title=title,
description=description,
interaction=interaction,
)
await interaction.response.send_message(embed=embed)

View file

@ -1,11 +1,10 @@
import discord
import httpx
from discord.ext import commands
from loguru import logger
from tux.bot import Tux
from tux.ui.embeds import EmbedCreator
from tux.utils.constants import Constants as CONST
from tux.utils.embeds import EmbedCreator
class Query(commands.Cog):
@ -42,10 +41,12 @@ class Query(commands.Cog):
# Check if the request was successful
if response.status_code != 200:
embed = EmbedCreator.create_error_embed(
title="Error",
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description="An error occurred while processing your request. (DDG Provided a non-200 status code)",
ctx=ctx,
)
return
@ -78,28 +79,34 @@ class Query(commands.Cog):
logger.info(f"GET request to http://api.duckduckgo.com/ with params {params}")
if response.status_code != 200:
embed = EmbedCreator.create_error_embed(
title="Error",
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description="An error occurred while processing your request. (DDG Provided a non-200 status code)",
ctx=ctx,
)
return
data = response.json()
else:
embed = EmbedCreator.create_error_embed(
title="Error",
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description="No results found for the search term.",
ctx=ctx,
)
await ctx.send(embed=embed, delete_after=30, ephemeral=True)
return
embed = discord.Embed(
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.INFO,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title=f'Answer to "{search_term}"',
description=f"{data['Abstract']}\n\nData from **{data['AbstractURL']}**",
color=CONST.EMBED_COLORS["INFO"],
timestamp=EmbedCreator.get_timestamp(ctx, None),
)
embed.set_author(

View file

@ -10,7 +10,7 @@ from loguru import logger
from prisma.models import Reminder
from tux.bot import Tux
from tux.database.controllers import DatabaseController
from tux.utils.embeds import EmbedCreator
from tux.ui.embeds import EmbedCreator
from tux.utils.functions import convert_to_seconds
@ -51,14 +51,13 @@ class RemindMe(commands.Cog):
user = self.bot.get_user(reminder.reminder_user_id)
if user is not None:
embed = EmbedCreator.custom_footer_embed(
ctx=None,
interaction=None,
state="SUCCESS",
user=user,
latency="N/A",
content=reminder.reminder_content,
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.INFO,
user_name=user.name,
user_display_avatar=user.display_avatar.url,
title="Reminder",
description=reminder.reminder_content,
)
try:
@ -181,10 +180,13 @@ class RemindMe(commands.Cog):
guild_id=interaction.guild_id or 0,
)
embed = EmbedCreator.create_success_embed(
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.SUCCESS,
user_name=interaction.user.name,
user_display_avatar=interaction.user.display_avatar.url,
title="Reminder Set",
description=f"Reminder set for <t:{int(seconds.timestamp())}:f>.",
interaction=interaction,
)
embed.add_field(
@ -193,10 +195,12 @@ class RemindMe(commands.Cog):
)
except Exception as e:
embed = EmbedCreator.create_error_embed(
title="Error",
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=interaction.user.name,
user_display_avatar=interaction.user.display_avatar.url,
description="There was an error creating the reminder.",
interaction=interaction,
)
logger.error(f"Error creating reminder: {e}")

View file

@ -3,7 +3,7 @@ import re
from discord.ext import commands
from tux.bot import Tux
from tux.utils.embeds import EmbedCreator
from tux.ui.embeds import EmbedCreator
from tux.wrappers import godbolt
ansi_re = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
@ -109,10 +109,13 @@ class Run(commands.Cog):
cleaned_code = "\n".join(cleaned_code.splitlines()[1:])
if normalized_lang not in compiler_map:
embed = EmbedCreator.create_error_embed(
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title="Fatal exception occurred!",
description="Bad Formatting",
ctx=ctx,
)
await ctx.send(embed=embed)
return ("", "", "")
@ -121,10 +124,13 @@ class Run(commands.Cog):
output = godbolt.getoutput(cleaned_code, compiler_id, options)
if output is None:
embed = EmbedCreator.create_error_embed(
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title="Fatal exception occurred!",
description="failed to get output from the compiler",
ctx=ctx,
)
await ctx.send(embed=embed, ephemeral=True, delete_after=30)
return ("", "", "")
@ -167,10 +173,13 @@ class Run(commands.Cog):
cleaned_code = "\n".join(cleaned_code.splitlines()[1:])
if normalized_lang not in compiler_map:
embed = EmbedCreator.create_error_embed(
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title="Fatal exception occurred!",
description="Bad Formatting",
ctx=ctx,
)
await ctx.send(embed=embed, ephemeral=True, delete_after=30)
return ("", "", "")
@ -179,10 +188,13 @@ class Run(commands.Cog):
output = godbolt.generateasm(cleaned_code, compiler_id, options)
if output is None:
embed = EmbedCreator.create_error_embed(
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title="Fatal exception occurred!",
description="failed to get output from the compiler",
ctx=ctx,
)
await ctx.send(embed=embed, ephemeral=True, delete_after=30)
return ("", "", "")
@ -222,10 +234,13 @@ class Run(commands.Cog):
The language of the code.
"""
embed = EmbedCreator.create_info_embed(
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.INFO,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title="Compilation provided by https://godbolt.org/",
description=f"```{lang}\n{output}\n```",
ctx=ctx,
)
await ctx.send(embed=embed)
@ -292,10 +307,13 @@ class Run(commands.Cog):
if isinstance(error, commands.MissingRequiredArgument):
desc = f"Missing required argument: `{error.param.name}`"
embed = EmbedCreator.create_error_embed(
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title="Fatal exception occurred!",
description=str(desc),
ctx=ctx,
)
await ctx.send(embed=embed, ephemeral=True, delete_after=30)
@ -315,10 +333,13 @@ class Run(commands.Cog):
The context in which the command is invoked.
"""
embed = EmbedCreator.create_info_embed(
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.INFO,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title="Supported Languages",
description=f"```{', '.join(compiler_map.keys())}```",
ctx=ctx,
)
await ctx.send(embed=embed)

View file

@ -12,9 +12,9 @@ from prisma.enums import CaseType
from prisma.models import Snippet
from tux.bot import Tux
from tux.database.controllers import CaseController, DatabaseController
from tux.ui.embeds import EmbedCreator
from tux.utils import checks
from tux.utils.constants import Constants as CONST
from tux.utils.embeds import EmbedCreator, create_embed_footer, create_error_embed
class Snippets(commands.Cog):
@ -58,10 +58,12 @@ class Snippets(commands.Cog):
# If there are no snippets, send an error message
if not snippets:
embed = EmbedCreator.create_error_embed(
title="Error",
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description="No snippets found.",
ctx=ctx,
)
await ctx.send(embed=embed, delete_after=30)
return
@ -96,7 +98,11 @@ class Snippets(commands.Cog):
if ctx.guild:
embed.set_author(name=ctx.guild.name, icon_url=ctx.guild.icon)
footer_text, footer_icon_url = create_embed_footer(ctx)
footer_text, footer_icon_url = EmbedCreator.get_footer(
bot=ctx.bot,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
)
embed.set_footer(text=footer_text, icon_url=footer_icon_url)
embed.timestamp = ctx.message.created_at
@ -134,10 +140,12 @@ class Snippets(commands.Cog):
# If there are no snippets, send an error message
if not snippets:
embed = EmbedCreator.create_error_embed(
title="Error",
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description="No snippets found.",
ctx=ctx,
)
await ctx.send(embed=embed, delete_after=30)
return
@ -188,14 +196,24 @@ class Snippets(commands.Cog):
snippet = await self.db.get_snippet_by_name_and_guild_id(name, ctx.guild.id)
if snippet is None:
embed = create_error_embed(error="Snippet not found.")
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description="Snippet not found.",
)
await ctx.send(embed=embed, delete_after=30, ephemeral=True)
return
# check if the snippet is locked
if snippet.locked:
embed = create_error_embed(
error="This snippet is locked and cannot be deleted. If you are a moderator you can use the `forcedeletesnippet` command.",
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description="This snippet is locked and cannot be deleted. If you are a moderator you can use the `forcedeletesnippet` command.",
)
await ctx.send(embed=embed, delete_after=30, ephemeral=True)
return
@ -203,7 +221,13 @@ class Snippets(commands.Cog):
# Check if the author of the snippet is the same as the user who wants to delete it and if theres no author don't allow deletion
author_id = snippet.snippet_user_id or 0
if author_id != ctx.author.id:
embed = create_error_embed(error="You can only delete your own snippets.")
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description="You can only delete your own snippets.",
)
await ctx.send(embed=embed, delete_after=30, ephemeral=True)
return
@ -236,7 +260,13 @@ class Snippets(commands.Cog):
snippet = await self.db.get_snippet_by_name_and_guild_id(name, ctx.guild.id)
if snippet is None:
embed = create_error_embed(error="Snippet not found.")
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description="Snippet not found.",
)
await ctx.send(embed=embed, delete_after=30, ephemeral=True)
return
@ -270,13 +300,23 @@ class Snippets(commands.Cog):
if "_" in name:
snippet = None # this is a bad fix, but it works for now
if snippet is None and "_" in name:
embed = create_error_embed(
error="Snippet not found. Did you mean to use `-` instead of `_`? Due to a recent change, `_` is no longer allowed in snippet names.",
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description="Snippet not found. Did you mean to use `-` instead of `_`? Due to a recent change, `_` is no longer allowed in snippet names.",
)
await ctx.send(embed=embed, delete_after=30, ephemeral=True)
return
if snippet is None:
embed = create_error_embed(error="Snippet not found.")
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description="Snippet not found.",
)
await ctx.send(embed=embed, delete_after=30, ephemeral=True)
return
@ -315,21 +355,24 @@ class Snippets(commands.Cog):
snippet = await self.db.get_snippet_by_name_and_guild_id(name, ctx.guild.id)
if snippet is None:
embed = create_error_embed(error="Snippet not found.")
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description="Snippet not found.",
)
await ctx.send(embed=embed, delete_after=30)
return
author = self.bot.get_user(snippet.snippet_user_id)
latency = round(int(ctx.bot.latency * 1000))
embed: discord.Embed = EmbedCreator.custom_footer_embed(
embed: discord.Embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.DEFAULT,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title="Snippet Information",
ctx=ctx,
latency=f"{latency}ms",
interaction=None,
state="DEFAULT",
user=author or ctx.author,
)
embed.add_field(name="Name", value=snippet.snippet_name, inline=False)
@ -375,7 +418,13 @@ class Snippets(commands.Cog):
args = arg.split(" ")
if len(args) < 2:
embed = create_error_embed(error="Please provide a name and content for the snippet.")
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description="Please provide a name and content for the snippet.",
)
await ctx.send(embed=embed, delete_after=30, ephemeral=True)
return
@ -387,7 +436,13 @@ class Snippets(commands.Cog):
# Check if the snippet already exists
if await self.db.get_snippet_by_name_and_guild_id(name, ctx.guild.id) is not None:
embed = create_error_embed(error="Snippet already exists.")
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description="Snippet already exists.",
)
await ctx.send(embed=embed, delete_after=30, ephemeral=True)
return
@ -395,10 +450,13 @@ class Snippets(commands.Cog):
rules = set(string.ascii_letters + string.digits + "-")
if len(name) > 20 or any(char not in rules for char in name):
embed = create_error_embed(
error="Snippet name must be alphanumeric (allows dashes and underscores) and less than 20 characters.",
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description="Snippet name must be alphanumeric (allows dashes and underscores) and less than 20 characters.",
)
await ctx.send(embed=embed)
return
@ -435,7 +493,13 @@ class Snippets(commands.Cog):
args = arg.split(" ")
if len(args) < 2:
embed = create_error_embed(error="Please provide a name and content for the snippet.")
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description="Please provide a name and content for the snippet.",
)
await ctx.send(embed=embed, delete_after=30, ephemeral=True)
return
@ -445,7 +509,13 @@ class Snippets(commands.Cog):
snippet = await self.db.get_snippet_by_name_and_guild_id(name, ctx.guild.id)
if snippet is None:
embed = create_error_embed(error="Snippet not found.")
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description="Snippet not found.",
)
await ctx.send(embed=embed, delete_after=30, ephemeral=True)
return
@ -462,8 +532,12 @@ class Snippets(commands.Cog):
try:
await checks.has_pl(2).predicate(ctx)
except commands.CheckFailure:
embed = create_error_embed(
error="This snippet is locked and cannot be edited. If you are a moderator you can use the `forcedeletesnippet` command.",
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description="This snippet is locked and cannot be edited. If you are a moderator you can use the `forcedeletesnippet` command.",
)
await ctx.send(embed=embed, delete_after=30, ephemeral=True)
return
@ -472,7 +546,13 @@ class Snippets(commands.Cog):
# Check if the author of the snippet is the same as the user who wants to edit it and if theres no author don't allow editing
author_id = snippet.snippet_user_id or 0
if author_id != ctx.author.id:
embed = create_error_embed(error="You can only edit your own snippets.")
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description="You can only edit your own snippets.",
)
await ctx.send(embed=embed, delete_after=30, ephemeral=True)
return
@ -508,14 +588,26 @@ class Snippets(commands.Cog):
snippet = await self.db.get_snippet_by_name_and_guild_id(name, ctx.guild.id)
if snippet is None:
embed = create_error_embed(error="Snippet not found.")
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description="Snippet not found.",
)
await ctx.send(embed=embed, delete_after=30, ephemeral=True)
return
status = await self.db.toggle_snippet_lock_by_id(snippet.snippet_id)
if status is None:
embed = create_error_embed(error="No return value from locking the snippet. It may still have been locked.")
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description="No return value from locking the snippet. It may still have been locked.",
)
await ctx.send(embed=embed, delete_after=30, ephemeral=True)
return

View file

@ -5,7 +5,7 @@ from discord import app_commands
from discord.ext import commands
from tux.bot import Tux
from tux.utils.embeds import EmbedCreator
from tux.ui.embeds import EmbedCreator
class Tldr(commands.Cog):
@ -60,10 +60,13 @@ class Tldr(commands.Cog):
tldr_page = self.get_tldr_page(command)
embed = EmbedCreator.create_info_embed(
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.INFO,
user_name=interaction.user.name,
user_display_avatar=interaction.user.display_avatar.url,
title=f"TLDR for {command}",
description=tldr_page,
interaction=interaction,
)
await interaction.response.send_message(embed=embed)
@ -88,10 +91,13 @@ class Tldr(commands.Cog):
tldr_page = self.get_tldr_page(command)
embed = EmbedCreator.create_info_embed(
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.INFO,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title=f"TLDR for {command}",
description=tldr_page,
ctx=ctx,
)
await ctx.send(embed=embed)

View file

@ -3,7 +3,7 @@ from discord.ext import commands
from loguru import logger
from tux.bot import Tux
from tux.utils.embeds import EmbedCreator
from tux.ui.embeds import EmbedCreator
class Wiki(commands.Cog):
@ -119,14 +119,23 @@ class Wiki(commands.Cog):
title: tuple[str, str] = self.query_arch_wiki(query)
if title[0] == "error":
embed = EmbedCreator.create_error_embed(
title="Error",
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description="No search results found.",
ctx=ctx,
)
else:
embed = EmbedCreator.create_info_embed(title=title[0], description=title[1], ctx=ctx)
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.INFO,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title=title[0],
description=title[1],
)
await ctx.send(embed=embed)
@ -149,14 +158,23 @@ class Wiki(commands.Cog):
title: tuple[str, str] = self.query_atl_wiki(query)
if title[0] == "error":
embed = EmbedCreator.create_error_embed(
title="Error",
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
description="No search results found.",
ctx=ctx,
)
else:
embed = EmbedCreator.create_info_embed(title=title[0], description=title[1], ctx=ctx)
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.INFO,
user_name=ctx.author.name,
user_display_avatar=ctx.author.display_avatar.url,
title=title[0],
description=title[1],
)
await ctx.send(embed=embed)

View file

@ -7,7 +7,7 @@ from discord.ext import commands
from loguru import logger
from tux.bot import Tux
from tux.utils.embeds import create_error_embed
from tux.ui.embeds import EmbedCreator
from tux.utils.exceptions import AppCommandPermissionLevelError, PermissionLevelError
"""
@ -170,7 +170,11 @@ class ErrorHandler(commands.Cog):
error_message = error_map.get(type(error), self.error_message).format(error=error)
embed = create_error_embed(error_message)
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
description=error_message,
)
if interaction.response.is_done():
await interaction.followup.send(embed=embed, ephemeral=True)
@ -212,7 +216,11 @@ class ErrorHandler(commands.Cog):
if isinstance(error, commands.CheckFailure):
message = error_map.get(type(error), self.error_message).format(error=error, ctx=ctx)
# await ctx.send(content=message, ephemeral=True, delete_after=30)
embed = create_error_embed(message)
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
description=message,
)
await ctx.send(embed=embed, ephemeral=True, delete_after=30)
sentry_sdk.capture_exception(error)
return
@ -232,7 +240,11 @@ class ErrorHandler(commands.Cog):
# await ctx.send(content=message, ephemeral=True, delete_after=30)
embed = create_error_embed(message)
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.ERROR,
description=message,
)
await ctx.send(embed=embed, ephemeral=True, delete_after=30)
# Log the error traceback if it's not in the error map

View file

@ -11,8 +11,8 @@ from reactionmenu import ViewButton, ViewMenu
from reactionmenu.abc import Page
from reactionmenu.views_menu import ViewSelect
from tux.ui.embeds import EmbedCreator
from tux.utils.constants import Constants as CONST
from tux.utils.embeds import EmbedCreator
class TuxHelp(commands.HelpCommand):
@ -491,8 +491,10 @@ class TuxHelp(commands.HelpCommand):
logger.error(f"An error occurred while sending a help message: {error}")
embed = EmbedCreator.create_error_embed(
title="An error occurred while sending help message.",
embed = EmbedCreator.create_embed(
EmbedCreator.ERROR,
user_name=self.context.author.name,
user_display_avatar=self.context.author.display_avatar.url,
description=error,
)

View file

@ -9,6 +9,7 @@ from sentry_sdk.integrations.loguru import LoguruIntegration
from tux.bot import Tux
from tux.database.controllers.guild_config import GuildConfigController
from tux.help import TuxHelp
# from tux.utils.console import Console
from tux.utils.constants import Constants as CONST
@ -48,6 +49,7 @@ async def main() -> None:
intents=discord.Intents.all(),
owner_ids=[*CONST.SYSADMIN_IDS, CONST.BOT_OWNER_ID],
allowed_mentions=discord.AllowedMentions(everyone=False),
help_command=TuxHelp(),
)
# Initialize the console and console task

128
tux/ui/embeds.py Normal file
View file

@ -0,0 +1,128 @@
from datetime import datetime
from enum import Enum
import discord
from loguru import logger
from tux.bot import Tux
from tux.utils.constants import Constants as CONST
class EmbedType(Enum):
DEFAULT = 1
INFO = 2
ERROR = 3
WARNING = 4
SUCCESS = 5
POLL = 6
CASE = 7
NOTE = 8
class EmbedCreator:
DEFAULT: EmbedType = EmbedType.DEFAULT
INFO: EmbedType = EmbedType.INFO
ERROR: EmbedType = EmbedType.ERROR
WARNING: EmbedType = EmbedType.WARNING
SUCCESS: EmbedType = EmbedType.SUCCESS
POLL: EmbedType = EmbedType.POLL
CASE: EmbedType = EmbedType.CASE
NOTE: EmbedType = EmbedType.NOTE
@staticmethod
def create_embed(
embed_type: EmbedType,
bot: Tux | None = None,
title: str | None = None,
description: str | None = None,
user_name: str | None = None,
user_display_avatar: str | None = None,
image_url: str | None = None,
thumbnail_url: str | None = None,
message_timestamp: datetime | None = None,
custom_footer_text: str | None = None,
custom_footer_icon_url: str | None = None,
custom_author_text: str | None = None,
custom_author_icon_url: str | None = None,
custom_color: int | None = None,
) -> discord.Embed:
"""
Create a customized Discord embed based on the specified type and parameters.
Args:
embed_type (EmbedType): Determines the default color and icon for the embed.
bot (Tux | None): If provided, used to display bot latency in the footer.
title (str | None): The embed's title. At least one of title or description should be provided.
description (str | None): The embed's main content. At least one of title or description should be provided.
user_name (str | None): Used in footer if provided, otherwise defaults to bot's username.
user_display_avatar (str | None): User's avatar URL for the footer icon.
image_url (str | None): URL for the embed's main image.
thumbnail_url (str | None): URL for the embed's thumbnail image.
message_timestamp (datetime | None): Custom timestamp for the embed.
custom_footer_text (str | None): Overrides default footer text if provided.
custom_footer_icon_url (str | None): Overrides default footer icon if provided.
custom_author_text (str | None): Overrides default author text if provided.
custom_author_icon_url (str | None): Overrides default author icon if provided.
custom_color (int | None): Overrides default color for the embed type if provided.
Note:
Custom parameters (prefixed with 'custom_') override default values.
"""
try:
embed: discord.Embed = discord.Embed(title=title, description=description)
type_settings: dict[EmbedType, tuple[int, str, str]] = {
EmbedType.DEFAULT: (CONST.EMBED_COLORS["DEFAULT"], CONST.EMBED_ICONS["DEFAULT"], "Default"),
EmbedType.INFO: (CONST.EMBED_COLORS["INFO"], CONST.EMBED_ICONS["INFO"], "Info"),
EmbedType.ERROR: (CONST.EMBED_COLORS["ERROR"], CONST.EMBED_ICONS["ERROR"], "Error"),
EmbedType.WARNING: (CONST.EMBED_COLORS["WARNING"], CONST.EMBED_ICONS["DEFAULT"], "Warning"),
EmbedType.SUCCESS: (CONST.EMBED_COLORS["SUCCESS"], CONST.EMBED_ICONS["SUCCESS"], "Success"),
EmbedType.POLL: (CONST.EMBED_COLORS["POLL"], CONST.EMBED_ICONS["POLL"], "Poll"),
EmbedType.CASE: (CONST.EMBED_COLORS["CASE"], CONST.EMBED_ICONS["CASE"], "Case"),
EmbedType.NOTE: (CONST.EMBED_COLORS["NOTE"], CONST.EMBED_ICONS["NOTE"], "Note"),
}
embed.color = custom_color or type_settings[embed_type][0]
embed.set_author(
name=custom_author_text or type_settings[embed_type][2],
icon_url=custom_author_icon_url or type_settings[embed_type][1],
)
if custom_footer_text:
embed.set_footer(text=custom_footer_text, icon_url=custom_footer_icon_url)
else:
footer: tuple[str, str | None] = EmbedCreator.get_footer(bot, user_name, user_display_avatar)
embed.set_footer(text=footer[0], icon_url=footer[1])
if image_url:
embed.set_image(url=image_url)
if thumbnail_url:
embed.set_thumbnail(url=thumbnail_url)
embed.timestamp = message_timestamp or discord.utils.utcnow()
except Exception as e:
logger.debug("Error in create_embed", exc_info=e)
raise
else:
return embed
@staticmethod
def get_footer(
bot: Tux | None = None,
user_name: str | None = None,
user_display_avatar: str | None = None,
) -> tuple[str, str | None]:
try:
text: str = f"{user_name}@atl $" if user_name else "tux@atl $"
text += f" {round(bot.latency * 1000)}ms" if bot else ""
except Exception as e:
logger.debug("Error in get_footer", exc_info=e)
raise
else:
return (text, user_display_avatar or "https://i.imgur.com/4sblrd0.png")

View file

@ -3,7 +3,7 @@ from loguru import logger
from tux.bot import Tux
from tux.database.controllers import DatabaseController
from tux.utils.embeds import EmbedCreator
from tux.ui.embeds import EmbedCreator
class ReportModal(discord.ui.Modal):
@ -42,10 +42,13 @@ class ReportModal(discord.ui.Modal):
logger.error("Guild is None")
return
embed = EmbedCreator.create_log_embed(
embed = EmbedCreator.create_embed(
bot=self.bot,
embed_type=EmbedCreator.INFO,
user_name=interaction.user.name,
user_display_avatar=interaction.user.display_avatar.url,
title=(f"Anonymous report for {self.short.value}"), # type: ignore
description=self.long.value, # type: ignore
interaction=None,
)
try:

View file

@ -1,247 +0,0 @@
from datetime import datetime
import discord
from discord.ext import commands
from tux.utils.constants import Constants as CONST
# TODO: Refactor this to reduce code duplication
def create_embed_footer(
ctx: commands.Context[commands.Bot] | None = None,
interaction: discord.Interaction | None = None,
fallback_text: str = "tux@atl $",
fallback_icon_url: str = "https://i.imgur.com/4sblrd0.png",
) -> tuple[str, str | None]:
user: discord.User | discord.Member | None = None
latency = None
if ctx:
user = ctx.author
latency = round(ctx.bot.latency * 1000)
elif interaction:
user = interaction.user
latency = round(interaction.client.latency * 1000)
if isinstance(user, discord.User | discord.Member):
return (
f"{user.name}@atl $ {latency}ms",
str(user.avatar.url) if user.avatar else fallback_icon_url,
)
return (fallback_text, fallback_icon_url)
@staticmethod
def create_error_embed(error: str) -> discord.Embed:
embed = discord.Embed()
embed.color = CONST.EMBED_COLORS["ERROR"]
embed.description = f"<:tux_error:1273494919897681930> {error}"
return embed
class EmbedCreator:
@staticmethod
def get_timestamp(
ctx: commands.Context[commands.Bot] | None,
interaction: discord.Interaction | None,
) -> datetime:
if ctx and ctx.message:
return ctx.message.created_at
return interaction.created_at if interaction else discord.utils.utcnow()
@staticmethod
def get_footer(
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)
elif interaction:
user = interaction.user
latency = round(interaction.client.latency * 1000)
if isinstance(user, discord.User | discord.Member):
return (
f"{user.name}@atl $ {latency}ms",
str(user.avatar.url) if user.avatar else None,
)
if ctx is None and interaction is None:
return ("tux@atl $", "https://i.imgur.com/4sblrd0.png")
return ("", None)
@staticmethod
def add_author(embed: discord.Embed, name: str, icon_url: str) -> None:
embed.set_author(name=name, icon_url=icon_url)
@staticmethod
def add_field(embed: discord.Embed, name: str, value: str, inline: bool = True) -> None:
embed.add_field(name=name, value=value, inline=inline)
@staticmethod
def base_embed(
ctx: commands.Context[commands.Bot] | None,
interaction: discord.Interaction | None,
state: str,
) -> discord.Embed:
footer: tuple[str, str | None] = EmbedCreator.get_footer(ctx, interaction)
timestamp: datetime = EmbedCreator.get_timestamp(ctx, interaction)
embed = discord.Embed()
embed.color = CONST.EMBED_COLORS[state]
embed.set_author(
name=state.capitalize() if state else "Info",
icon_url=CONST.EMBED_ICONS[state] if state else CONST.EMBED_ICONS["DEFAULT"],
)
embed.set_footer(text=footer[0], icon_url=footer[1])
embed.timestamp = timestamp
return embed
# requests a custom user and latency for the footer
@staticmethod
def custom_footer_embed(
ctx: commands.Context[commands.Bot] | None,
interaction: discord.Interaction | None,
state: str,
user: discord.User | discord.Member,
latency: str,
content: str = "",
title: str = "",
) -> discord.Embed:
timestamp: datetime = EmbedCreator.get_timestamp(ctx, interaction)
embed = discord.Embed()
embed.color = CONST.EMBED_COLORS[state]
embed.description = content
embed.title = title
embed.set_author(
name=state.capitalize() if state else "Info",
icon_url=CONST.EMBED_ICONS[state] if state else CONST.EMBED_ICONS["DEFAULT"],
)
embed.set_footer(
text=f"{user.name}@atl $ {latency}",
icon_url=str(user.avatar.url) if user.avatar else None,
)
embed.timestamp = timestamp
return embed
@classmethod
def create_embed(
cls,
ctx: commands.Context[commands.Bot] | None,
interaction: discord.Interaction | None,
state: str,
title: str,
description: str = "",
) -> discord.Embed:
embed = cls.base_embed(ctx, interaction, state)
embed.title = title
embed.description = description
return embed
@classmethod
def create_default_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, "DEFAULT", title, description)
@classmethod
def create_info_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, "INFO", title, description)
@classmethod
def create_error_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, "ERROR", title, description)
@classmethod
def create_warning_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, "WARNING", title, description)
@classmethod
def create_success_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, "SUCCESS", title, description)
@classmethod
def create_poll_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, "POLL", title, description)
@classmethod
def create_log_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, "DEFAULT", 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)
@classmethod
def create_note_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, "NOTE", title, description)

View file

@ -4,7 +4,7 @@ import io
import discord
from tux.utils.embeds import EmbedCreator
from tux.ui.embeds import EmbedCreator
_flags = {
"user": "User",
@ -66,7 +66,8 @@ async def get_help_embed(
"""
valid_flags.sort()
return EmbedCreator.create_info_embed(
return EmbedCreator.create_embed(
embed_type=EmbedCreator.INFO,
title=title,
description="Use any combination of the following flags to "
+ f"export a list of {data_description} to a CSV file:"