From 4416c9fea8dd2376062abddce39531ade890d340 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:46:01 +0000 Subject: [PATCH 01/19] chore(deps): update dependency mkdocs-material to v9.5.33 --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index e6833db..c327586 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1127,13 +1127,13 @@ pyyaml = ">=5.1" [[package]] name = "mkdocs-material" -version = "9.5.32" +version = "9.5.33" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.5.32-py3-none-any.whl", hash = "sha256:f3704f46b63d31b3cd35c0055a72280bed825786eccaf19c655b44e0cd2c6b3f"}, - {file = "mkdocs_material-9.5.32.tar.gz", hash = "sha256:38ed66e6d6768dde4edde022554553e48b2db0d26d1320b19e2e2b9da0be1120"}, + {file = "mkdocs_material-9.5.33-py3-none-any.whl", hash = "sha256:dbc79cf0fdc6e2c366aa987de8b0c9d4e2bb9f156e7466786ba2fd0f9bf7ffca"}, + {file = "mkdocs_material-9.5.33.tar.gz", hash = "sha256:d23a8b5e3243c9b2f29cdfe83051104a8024b767312dc8fde05ebe91ad55d89d"}, ] [package.dependencies] From d2607f5774408bc7ecf5068e81c5545e855f05eb Mon Sep 17 00:00:00 2001 From: kzndotsh Date: Sat, 24 Aug 2024 19:53:39 +0000 Subject: [PATCH 02/19] feat: add pull request template to guide contributors on PR submission --- .github/PULL_REQUEST_TEMPLATE.md | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..de4ddeb --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,40 @@ +# Pull Request Template + +## Description + +Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change. + +Fixes # (issue) + +## Type of Change + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Documentation update + +## Checklist + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published in downstream modules + +## How Has This Been Tested? + +Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. + +- [ ] Test A +- [ ] Test B + +## Screenshots (if applicable) + +Please add screenshots to help explain your changes. + +## Additional Information + +Please add any other information that is important to this PR. From b99d04c2097818a9edbe29d325b0c7e6bbd32688 Mon Sep 17 00:00:00 2001 From: kzndotsh Date: Sat, 24 Aug 2024 15:54:13 -0400 Subject: [PATCH 03/19] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 38 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..dd84ea7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From fd19185f3f4796b20db654e73c4fe118aac7dbbd Mon Sep 17 00:00:00 2001 From: kzndotsh Date: Sat, 24 Aug 2024 19:56:07 +0000 Subject: [PATCH 04/19] docs: update bug report template for better issue reporting - Change the title and labels to be more descriptive and automatically label bugs - Improve the structure and clarity of the issue template - Add a section for code snippets or screenshots - Update the environment section to be more relevant to the project - Add a section for additional context to provide more information about the issue --- .github/ISSUE_TEMPLATE/bug_report.md | 40 +++++++++++++++------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dd84ea7..b01c646 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,38 +1,42 @@ --- -name: Bug report +name: Bug Report about: Create a report to help us improve -title: '' -labels: '' +title: "[BUG] - " +labels: type: bug assignees: '' --- -**Describe the bug** +## Describe the Bug + A clear and concise description of what the bug is. -**To Reproduce** +## To Reproduce + Steps to reproduce the behavior: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error -**Expected behavior** +Please include code snippets or screenshots where applicable. + +## Expected Behavior + A clear and concise description of what you expected to happen. -**Screenshots** +## Screenshots + If applicable, add screenshots to help explain your problem. -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] +## Environment (please complete the following information) -**Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] +- OS: [e.g. Ubuntu 20.04] +- Python version: [e.g. Python 3.12] +- Discord.py version: [e.g. 2.0.0] +- Tux version: [e.g. v1.0.0] -**Additional context** -Add any other context about the problem here. +## Additional Context + +Add any other context about the problem here, such as logs, configuration files, etc. From 8b7db8789989bb1cc702af39600d84c63c7fc290 Mon Sep 17 00:00:00 2001 From: kzndotsh Date: Sat, 24 Aug 2024 19:58:12 +0000 Subject: [PATCH 05/19] style(feature_request.md): improve readability and structure of feature request template feat(feature_request.md): add prefix "[FEATURE] - " to title for better issue identification feat(feature_request.md): add label "type: feature-request" for better issue categorization --- .github/ISSUE_TEMPLATE/feature_request.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bbcbbe7..2884381 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,20 +1,24 @@ --- -name: Feature request +name: Feature Request about: Suggest an idea for this project -title: '' -labels: '' +title: "[FEATURE] - " +labels: type: feature-request assignees: '' --- -**Is your feature request related to a problem? Please describe.** +## Is your feature request related to a problem? Please describe + A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -**Describe the solution you'd like** +## Describe the solution you'd like + A clear and concise description of what you want to happen. -**Describe alternatives you've considered** +## Describe alternatives you've considered + A clear and concise description of any alternative solutions or features you've considered. -**Additional context** +## Additional Context + Add any other context or screenshots about the feature request here. From 87b413fb187382202680c013489dc6418aaf371e Mon Sep 17 00:00:00 2001 From: kzndotsh Date: Sat, 24 Aug 2024 20:00:25 +0000 Subject: [PATCH 06/19] fix(.github/ISSUE_TEMPLATE): wrap labels in quotes for bug_report.md and feature_request.md to ensure correct label assignment --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index b01c646..60e8c36 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,7 +2,7 @@ name: Bug Report about: Create a report to help us improve title: "[BUG] - " -labels: type: bug +labels: "type: bug" assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 2884381..5e84edd 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -2,7 +2,7 @@ name: Feature Request about: Suggest an idea for this project title: "[FEATURE] - " -labels: type: feature-request +labels: "type: feature-request" assignees: '' --- From 109199eb7324890474f2ad4211ee7f72a42ade4c Mon Sep 17 00:00:00 2001 From: exvh <87952523+exvh@users.noreply.github.com> Date: Sun, 25 Aug 2024 00:08:12 +0200 Subject: [PATCH 07/19] Delete assets/roles/text-editors/ed.png --- assets/roles/text-editors/ed.png | Bin 14342 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 assets/roles/text-editors/ed.png diff --git a/assets/roles/text-editors/ed.png b/assets/roles/text-editors/ed.png deleted file mode 100644 index 2405d00f10eed594e47ca6a80d1cba070f92850a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14342 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 From 7805c7d064b08aab929d02bbec04ae51273b3be5 Mon Sep 17 00:00:00 2001 From: exvh <87952523+exvh@users.noreply.github.com> Date: Sun, 25 Aug 2024 00:08:25 +0200 Subject: [PATCH 08/19] Add files via upload --- assets/roles/text-editors/ed.png | Bin 0 -> 14978 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/roles/text-editors/ed.png diff --git a/assets/roles/text-editors/ed.png b/assets/roles/text-editors/ed.png new file mode 100644 index 0000000000000000000000000000000000000000..0f428c508bbfb1af2da26e8107467d355cefba76 GIT binary patch literal 14978 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 Date: Sat, 24 Aug 2024 22:48:53 +0000 Subject: [PATCH 09/19] chore(linting.yml): update Python version from 3.11 to 3.12 to keep up with the latest version style(linting.yml): change commit message format to follow conventional commit standards --- .github/workflows/linting.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 6c8c1a1..7fa2eda 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -19,7 +19,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v5 with: - python-version: 3.11 + python-version: 3.12 # Install Ruff - name: Install Ruff @@ -30,4 +30,4 @@ jobs: run: ruff format && ruff check . --fix - uses: stefanzweifel/git-auto-commit-action@v5 with: - commit_message: '[Fix] Linting and formatting via Ruff' + commit_message: 'fix: Linting and formatting via Ruff' From e82046de51b3b4d8243d1cc75584b1bc4a9c74a4 Mon Sep 17 00:00:00 2001 From: kzndotsh Date: Sat, 24 Aug 2024 23:00:35 +0000 Subject: [PATCH 10/19] chore(.pre-commit-config.yaml): exclude .archive/ directory from pre-commit checks to improve performance --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ac2deee..9c5385c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,4 +24,5 @@ repos: rev: v8.18.4 hooks: - id: gitleaks + exclude: ".archive/" From 857d8115db6de3bc2b2734ec28d7e50914c9ac22 Mon Sep 17 00:00:00 2001 From: kzndotsh Date: Sun, 25 Aug 2024 06:00:35 +0000 Subject: [PATCH 11/19] feat(.github/workflows/loc.yml): add new GitHub action to calculate and publish lines of code report feat(README.md): add placeholder for lines of code report to be updated by GitHub action --- .github/workflows/loc.yml | 58 +++++++++++++++++++++++++++++++++++++++ README.md | 4 +++ 2 files changed, 62 insertions(+) create mode 100644 .github/workflows/loc.yml diff --git a/.github/workflows/loc.yml b/.github/workflows/loc.yml new file mode 100644 index 0000000..d585774 --- /dev/null +++ b/.github/workflows/loc.yml @@ -0,0 +1,58 @@ +name: Calculate and Publish Lines of Code Report + +on: + push: + branches: + - main + release: + types: [published] + +jobs: + + loc-report: + runs-on: ubuntu-latest + + steps: + - name: Check out the repository + uses: actions/checkout@v2 + + - name: Calculate Lines of Code + id: loc + uses: PavanMudigonda/lines-of-code-reporter@v1.6 + with: + directory: "./tux, ./docs, ./prisma" # Directories to include + github_token: ${{ secrets.GITHUB_TOKEN }} # GitHub Token + exclude_lang: "JSON" # Exclude JSON files + exclude_dir: "assets" + + - name: Print Lines of Code Output + shell: pwsh + run: | + Write-Host 'Total Lines of Code...: ${{ steps.loc.outputs.total_lines_string }}' + Write-Host 'Lines of Code Markdown Report Path...: ${{ steps.loc.outputs.loc_report }}' + + - name: Add Lines of Code Summary + run: echo "${{ steps.loc.outputs.lines-of-code-summary }}" >> $GITHUB_STEP_SUMMARY + + - name: Update README with LOC + id: update-readme + run: | + # Define the placeholder and new content + LOC_PLACEHOLDER="" + LOC_CONTENT="${{ steps.loc.outputs.lines-of-code-summary }}" + + # Read the current README.md and replace the placeholder with the new content + README_CONTENT=$(cat README.md) + UPDATED_README_CONTENT="${README_CONTENT//$LOC_PLACEHOLDER/$LOC_CONTENT}" + + # Save the updated content back to README.md + echo "${UPDATED_README_CONTENT}" > README.md + + - name: Commit changes to README + if: ${{ github.ref == 'refs/heads/main' }} + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git add README.md + git commit -m "Update README with LOC report" + git push \ No newline at end of file diff --git a/README.md b/README.md index cbc7c20..f7a8970 100644 --- a/README.md +++ b/README.md @@ -150,3 +150,7 @@ See [LICENSE](LICENSE.md) for details. ## Metrics ![Alt](https://repobeats.axiom.co/api/embed/b988ba04401b7c68edf9def00f5132cd2a7f3735.svg "Repobeats analytics image") + +## Lines of Code + + From 30bd81584ab1465ce9bc48f137594d1aad7d6a36 Mon Sep 17 00:00:00 2001 From: kzndotsh Date: Sun, 25 Aug 2024 06:04:40 +0000 Subject: [PATCH 12/19] chore(loc.yml): add step to print workspace info for debugging purposes fix(loc.yml): correct directory paths in Calculate Lines of Code step to ensure correct directories are included in the calculation --- .github/workflows/loc.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/loc.yml b/.github/workflows/loc.yml index d585774..f957c2f 100644 --- a/.github/workflows/loc.yml +++ b/.github/workflows/loc.yml @@ -16,13 +16,18 @@ jobs: - name: Check out the repository uses: actions/checkout@v2 + - name: Print workspace info for debugging purposes + run: | + echo "Workspace directory: ${{ github.workspace }}" + ls -al ${{ github.workspace }} + - name: Calculate Lines of Code id: loc uses: PavanMudigonda/lines-of-code-reporter@v1.6 with: - directory: "./tux, ./docs, ./prisma" # Directories to include - github_token: ${{ secrets.GITHUB_TOKEN }} # GitHub Token - exclude_lang: "JSON" # Exclude JSON files + directory: "tux,docs,prisma" # Directories to include + github_token: ${{ secrets.GITHUB_TOKEN }} # GitHub Token + exclude_lang: "JSON" # Exclude JSON files exclude_dir: "assets" - name: Print Lines of Code Output From f7407086a55ca496976fc3e193628790d34be66e Mon Sep 17 00:00:00 2001 From: kzndotsh Date: Sun, 25 Aug 2024 06:05:50 +0000 Subject: [PATCH 13/19] chore(loc.yml): update directories path to absolute paths in lines of code calculation fix(loc.yml): change 'ls -al' to 'ls -alR' for recursive directory listing in debugging step refactor(loc.yml): remove 'exclude_lang' option as it's no longer needed --- .github/workflows/loc.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/loc.yml b/.github/workflows/loc.yml index f957c2f..8acc7e0 100644 --- a/.github/workflows/loc.yml +++ b/.github/workflows/loc.yml @@ -16,19 +16,19 @@ jobs: - name: Check out the repository uses: actions/checkout@v2 + # Debugging step to print the current directory and its contents - name: Print workspace info for debugging purposes run: | echo "Workspace directory: ${{ github.workspace }}" - ls -al ${{ github.workspace }} + ls -alR ${{ github.workspace }} - name: Calculate Lines of Code id: loc uses: PavanMudigonda/lines-of-code-reporter@v1.6 with: - directory: "tux,docs,prisma" # Directories to include - github_token: ${{ secrets.GITHUB_TOKEN }} # GitHub Token - exclude_lang: "JSON" # Exclude JSON files - exclude_dir: "assets" + directory: "${{ github.workspace }}/tux,${{ github.workspace }}/docs,${{ github.workspace }}/prisma" # Directories to include + github_token: ${{ secrets.GITHUB_TOKEN }} # GitHub Token + exclude_dir: "${{ github.workspace }}/assets" # Exclude assets directory - name: Print Lines of Code Output shell: pwsh From 84fc117a031fcb628e1d55aafc68efb05aca3e2e Mon Sep 17 00:00:00 2001 From: kzndotsh Date: Sun, 25 Aug 2024 02:07:25 -0400 Subject: [PATCH 14/19] Revert "chore(.pre-commit-config.yaml): exclude .archive/ directory from pre-commit checks to improve performance" This reverts commit e82046de51b3b4d8243d1cc75584b1bc4a9c74a4. --- .pre-commit-config.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9c5385c..ac2deee 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,5 +24,4 @@ repos: rev: v8.18.4 hooks: - id: gitleaks - exclude: ".archive/" From d1bd866ea5e21d06a2f14690f4231007e9674525 Mon Sep 17 00:00:00 2001 From: kzndotsh Date: Sun, 25 Aug 2024 02:09:46 -0400 Subject: [PATCH 15/19] Revert "feat(.github/workflows/loc.yml): add new GitHub action to calculate and publish lines of code report" This reverts commit 857d8115db6de3bc2b2734ec28d7e50914c9ac22. --- .github/workflows/loc.yml | 63 --------------------------------------- README.md | 4 --- 2 files changed, 67 deletions(-) delete mode 100644 .github/workflows/loc.yml diff --git a/.github/workflows/loc.yml b/.github/workflows/loc.yml deleted file mode 100644 index 8acc7e0..0000000 --- a/.github/workflows/loc.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Calculate and Publish Lines of Code Report - -on: - push: - branches: - - main - release: - types: [published] - -jobs: - - loc-report: - runs-on: ubuntu-latest - - steps: - - name: Check out the repository - uses: actions/checkout@v2 - - # Debugging step to print the current directory and its contents - - name: Print workspace info for debugging purposes - run: | - echo "Workspace directory: ${{ github.workspace }}" - ls -alR ${{ github.workspace }} - - - name: Calculate Lines of Code - id: loc - uses: PavanMudigonda/lines-of-code-reporter@v1.6 - with: - directory: "${{ github.workspace }}/tux,${{ github.workspace }}/docs,${{ github.workspace }}/prisma" # Directories to include - github_token: ${{ secrets.GITHUB_TOKEN }} # GitHub Token - exclude_dir: "${{ github.workspace }}/assets" # Exclude assets directory - - - name: Print Lines of Code Output - shell: pwsh - run: | - Write-Host 'Total Lines of Code...: ${{ steps.loc.outputs.total_lines_string }}' - Write-Host 'Lines of Code Markdown Report Path...: ${{ steps.loc.outputs.loc_report }}' - - - name: Add Lines of Code Summary - run: echo "${{ steps.loc.outputs.lines-of-code-summary }}" >> $GITHUB_STEP_SUMMARY - - - name: Update README with LOC - id: update-readme - run: | - # Define the placeholder and new content - LOC_PLACEHOLDER="" - LOC_CONTENT="${{ steps.loc.outputs.lines-of-code-summary }}" - - # Read the current README.md and replace the placeholder with the new content - README_CONTENT=$(cat README.md) - UPDATED_README_CONTENT="${README_CONTENT//$LOC_PLACEHOLDER/$LOC_CONTENT}" - - # Save the updated content back to README.md - echo "${UPDATED_README_CONTENT}" > README.md - - - name: Commit changes to README - if: ${{ github.ref == 'refs/heads/main' }} - run: | - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git add README.md - git commit -m "Update README with LOC report" - git push \ No newline at end of file diff --git a/README.md b/README.md index f7a8970..cbc7c20 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,3 @@ See [LICENSE](LICENSE.md) for details. ## Metrics ![Alt](https://repobeats.axiom.co/api/embed/b988ba04401b7c68edf9def00f5132cd2a7f3735.svg "Repobeats analytics image") - -## Lines of Code - - From 9e11b5fb675a8caad028133cea6ff578eaa3288e Mon Sep 17 00:00:00 2001 From: kzndotsh Date: Sun, 25 Aug 2024 06:15:22 +0000 Subject: [PATCH 16/19] refactor(.env.example): restructure and categorize environment variables for better readability and understanding feat(.env.example): add comments to clarify usage of each environment variable for better user guidance style(.env.example): remove unused SUPABASE_KEY variable to clean up the code --- .env.example | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/.env.example b/.env.example index b5134bd..ab715c7 100644 --- a/.env.example +++ b/.env.example @@ -1,20 +1,27 @@ +# +# Required +# + +# If in production mode: + +# DEV=False PROD_DATABASE_URL="" -# PROD_SUPABASE_DB_PASSWORD="" PROD_TOKEN="" -DEV=True +# If in development mode: + +# DEV=True DEV_DATABASE_URL="" -# DEV_SUPABASE_DB_PASSWORD="" DEV_TOKEN="" -TZ="UTC" -DISCORD_GUILD= +# +# Optional +# -SUPABASE_KEY="" SENTRY_URL="" -# PROD_COG_IGNORE_LIST="kick,ban" -# DEV_COG_IGNORE_LIST="mail, git" +PROD_COG_IGNORE_LIST= +DEV_COG_IGNORE_LIST= GITHUB_APP_ID= GITHUB_CLIENT_ID="" From 8e3d8cdc660f964c55330ef0ae73f315faa56ddb Mon Sep 17 00:00:00 2001 From: kzndotsh Date: Sun, 25 Aug 2024 08:53:50 +0000 Subject: [PATCH 17/19] feat(emojis): add new snippetban and snippetunban emojis feat(setup.py): add new Setup class for server setup refactor(moderation): move handle_case_response method to ModerationCogBase class for code reusability fix(moderation): replace async create_embed method with synchronous one feat(moderation): add support for case handling in ban command feat(cases.py): update snippetban and snippetunban emoji ids refactor(cases.py): replace async create_embed method with synchronous one refactor(jail.py, kick.py): remove redundant code and simplify jail and kick commands feat(jail.py, kick.py): improve error handling and logging for jail and kick commands style(jail.py, kick.py): improve code readability and maintainability by removing unnecessary imports and functions refactor(snippetban.py, snippetunban.py, timeout.py): simplify case handling logic and error handling feat(snippetban.py, snippetunban.py): add reason parameter to usage and make error messages ephemeral fix(timeout.py): move send_dm call after successful timeout to ensure user is notified only when action is successful refactor(unban.py, unjail.py): simplify unban and unjail commands by removing redundant code feat(unjail.py): add error handling for missing jail role or jail channel fix(unjail.py): ensure atomicity when removing jail role and adding previous roles to prevent partial role assignment refactor(untimeout.py, warn.py): remove redundant code and simplify case response handling fix(untimeout.py, warn.py): move send_dm call to after timeout/warn action to ensure it's sent after action is successful --- assets/emojis/snippetban.png | Bin 0 -> 10297 bytes assets/emojis/snippetunban.png | Bin 0 -> 8172 bytes tux/cogs/guild/setup.py | 85 ++++++++++++ tux/cogs/moderation/__init__.py | 45 ++++++- tux/cogs/moderation/ban.py | 46 +------ tux/cogs/moderation/cases.py | 6 +- tux/cogs/moderation/jail.py | 202 ++++++---------------------- tux/cogs/moderation/kick.py | 45 +------ tux/cogs/moderation/snippetban.py | 69 +++------- tux/cogs/moderation/snippetunban.py | 70 +++------- tux/cogs/moderation/timeout.py | 48 +------ tux/cogs/moderation/unban.py | 47 +------ tux/cogs/moderation/unjail.py | 152 ++++----------------- tux/cogs/moderation/untimeout.py | 48 +------ tux/cogs/moderation/warn.py | 45 +------ 15 files changed, 242 insertions(+), 666 deletions(-) create mode 100644 assets/emojis/snippetban.png create mode 100644 assets/emojis/snippetunban.png create mode 100644 tux/cogs/guild/setup.py diff --git a/assets/emojis/snippetban.png b/assets/emojis/snippetban.png new file mode 100644 index 0000000000000000000000000000000000000000..39f0b127652f12753b806832e88861a2df9f475b GIT binary patch literal 10297 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 None: + self.bot = bot + self.db = DatabaseController() + self.config = DatabaseController().guild_config + + setup = app_commands.Group(name="setup", description="Set up Tux for your server.") + + @setup.command(name="jail") + @commands.guild_only() + @checks.ac_has_pl(7) + async def setup_jail(self, interaction: discord.Interaction) -> None: + """ + Set up the jail role channel permissions for the server. + + Parameters + ---------- + interaction : discord.Interaction + The discord interaction object. + """ + + if interaction.guild is None: + return + + jail_role_id = await self.config.get_guild_config_field_value(interaction.guild.id, "jail_role_id") + if not jail_role_id: + await interaction.response.send_message("No jail role has been set up for this server.", ephemeral=True) + return + + jail_role = interaction.guild.get_role(jail_role_id) + if not jail_role: + await interaction.response.send_message("The jail role has been deleted.", ephemeral=True) + return + + jail_channel_id = await self.config.get_guild_config_field_value(interaction.guild.id, "jail_channel_id") + if not jail_channel_id: + await interaction.response.send_message("No jail channel has been set up for this server.", ephemeral=True) + return + + await interaction.response.defer(ephemeral=True) + + await self._set_permissions_for_channels(interaction, jail_role, jail_channel_id) + + await interaction.edit_original_response( + content="Permissions have been set up for the jail role.", + ) + + async def _set_permissions_for_channels( + self, + interaction: discord.Interaction, + jail_role: discord.Role, + jail_channel_id: int, + ) -> None: + if interaction.guild is None: + return + + for channel in interaction.guild.channels: + if not isinstance(channel, discord.TextChannel | discord.VoiceChannel | discord.ForumChannel): + continue + + if ( + jail_role in channel.overwrites + and channel.overwrites[jail_role].send_messages is False + and channel.overwrites[jail_role].read_messages is False + and channel.id != jail_channel_id + ): + continue + + await channel.set_permissions(jail_role, send_messages=False, read_messages=False) + if channel.id == jail_channel_id: + await channel.set_permissions(jail_role, send_messages=True, read_messages=True) + + await interaction.edit_original_response(content=f"Setting up permissions for {channel.name}.") + + +async def setup(bot: commands.Bot) -> None: + await bot.add_cog(Setup(bot)) diff --git a/tux/cogs/moderation/__init__.py b/tux/cogs/moderation/__init__.py index dff5908..e19932c 100644 --- a/tux/cogs/moderation/__init__.py +++ b/tux/cogs/moderation/__init__.py @@ -4,7 +4,9 @@ import discord from discord.ext import commands from loguru import logger +from prisma.enums import CaseType from tux.database.controllers import DatabaseController +from tux.utils.constants import Constants as CONST from tux.utils.embeds import create_embed_footer, create_error_embed @@ -14,7 +16,7 @@ class ModerationCogBase(commands.Cog): self.db = DatabaseController() self.config = DatabaseController().guild_config - async def create_embed( + def create_embed( self, ctx: commands.Context[commands.Bot], title: str, @@ -22,6 +24,7 @@ class ModerationCogBase(commands.Cog): color: int, icon_url: str, timestamp: datetime | None = None, + thumbnail_url: str | None = None, ) -> discord.Embed: """ Create an embed for moderation actions. @@ -49,6 +52,7 @@ class ModerationCogBase(commands.Cog): embed = discord.Embed(color=color, timestamp=timestamp or ctx.message.created_at) embed.set_author(name=title, icon_url=icon_url) + embed.set_thumbnail(url=thumbnail_url) footer_text, footer_icon_url = create_embed_footer(ctx) embed.set_footer(text=footer_text, icon_url=footer_icon_url) @@ -166,3 +170,42 @@ class ModerationCogBase(commands.Cog): return False return True + + async def handle_case_response( + self, + ctx: commands.Context[commands.Bot], + case_type: CaseType, + case_id: int | None, + reason: str, + target: discord.Member | discord.User, + duration: str | None = None, + ): + moderator = ctx.author + + fields = [ + ("Moderator", f"__{moderator}__\n`{moderator.id}`", True), + ("Target", f"__{target}__\n`{target.id}`", True), + ("Reason", f"> {reason}", False), + ] + + if case_id is not None: + embed = self.create_embed( + ctx, + title=f"Case #{case_id} ({duration} {case_type})" if duration else f"Case #{case_id} ({case_type})", + fields=fields, + color=CONST.EMBED_COLORS["CASE"], + icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], + ) + embed.set_thumbnail(url=target.avatar) + + else: + embed = self.create_embed( + ctx, + title=f"Case #0 ({duration} {case_type})" if duration else f"Case #0 ({case_type})", + fields=fields, + color=CONST.EMBED_COLORS["CASE"], + icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], + ) + + await self.send_embed(ctx, embed, log_type="mod") + await ctx.send(embed=embed, delete_after=30, ephemeral=True) diff --git a/tux/cogs/moderation/ban.py b/tux/cogs/moderation/ban.py index 747bba9..f1d0c5e 100644 --- a/tux/cogs/moderation/ban.py +++ b/tux/cogs/moderation/ban.py @@ -3,9 +3,7 @@ from discord.ext import commands from loguru import logger from prisma.enums import CaseType -from prisma.models import Case from tux.utils import checks -from tux.utils.constants import Constants as CONST from tux.utils.flags import BanFlags from . import ModerationCogBase @@ -48,6 +46,7 @@ class Ban(ModerationCogBase): discord.HTTPException If an error occurs while banning the user. """ + if ctx.guild is None: logger.warning("Ban command used outside of a guild context.") return @@ -74,48 +73,7 @@ class Ban(ModerationCogBase): guild_id=ctx.guild.id, ) - await self.handle_case_response(ctx, case, "created", flags.reason, target) - - async def handle_case_response( - self, - ctx: commands.Context[commands.Bot], - case: Case | None, - action: str, - reason: str, - target: discord.Member | discord.User, - previous_reason: str | None = None, - ) -> None: - moderator = ctx.author - - fields = [ - ("Moderator", f"__{moderator}__\n`{moderator.id}`", True), - ("Target", f"__{target}__\n`{target.id}`", True), - ("Reason", f"> {reason}", False), - ] - - if previous_reason: - fields.append(("Previous Reason", f"> {previous_reason}", False)) - - if case is not None: - embed = await self.create_embed( - ctx, - title=f"Case #{case.case_number} ({case.case_type}) {action}", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - embed.set_thumbnail(url=target.avatar) - else: - embed = await self.create_embed( - ctx, - title=f"Case {action} ({CaseType.BAN})", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - - await self.send_embed(ctx, embed, log_type="mod") - await ctx.send(embed=embed, delete_after=30, ephemeral=True) + await self.handle_case_response(ctx, CaseType.BAN, case.case_id, flags.reason, target) async def setup(bot: commands.Bot) -> None: diff --git a/tux/cogs/moderation/cases.py b/tux/cogs/moderation/cases.py index a53e89a..2f513a5 100644 --- a/tux/cogs/moderation/cases.py +++ b/tux/cogs/moderation/cases.py @@ -23,8 +23,8 @@ emojis: dict[str, int] = { "timeout": 1268115809083981886, "warn": 1268115764498399264, "jail": 1268115750392954880, - "snippetban": 1275782294363312172, # Placeholder - "snippetunban": 1275782294363312172, # Placeholder + "snippetban": 1277174953950576681, + "snippetunban": 1277174953292337222, } @@ -269,7 +269,7 @@ class Cases(ModerationCogBase): fields = self._create_case_fields(moderator, target, reason) - embed = await self.create_embed( + embed = self.create_embed( ctx, title=f"Case #{case.case_number} ({case.case_type}) {action}", fields=fields, diff --git a/tux/cogs/moderation/jail.py b/tux/cogs/moderation/jail.py index 6a4222a..ee48982 100644 --- a/tux/cogs/moderation/jail.py +++ b/tux/cogs/moderation/jail.py @@ -1,12 +1,9 @@ import discord -from discord import app_commands from discord.ext import commands from loguru import logger from prisma.enums import CaseType -from prisma.models import Case from tux.utils import checks -from tux.utils.constants import Constants as CONST from tux.utils.flags import JailFlags from . import ModerationCogBase @@ -16,74 +13,6 @@ class Jail(ModerationCogBase): def __init__(self, bot: commands.Bot) -> None: super().__init__(bot) - @app_commands.command( - name="setup_jail", - ) - @commands.guild_only() - @checks.ac_has_pl(7) - async def setup_jail(self, interaction: discord.Interaction) -> None: - """ - Set up the jail role channel permissions for the server. - - Parameters - ---------- - interaction : discord.Interaction - The discord interaction object. - """ - - if interaction.guild is None: - return - - jail_role_id = await self.config.get_guild_config_field_value(interaction.guild.id, "jail_role_id") - if not jail_role_id: - await interaction.response.send_message("No jail role has been set up for this server.", ephemeral=True) - return - - jail_role = interaction.guild.get_role(jail_role_id) - if not jail_role: - await interaction.response.send_message("The jail role has been deleted.", ephemeral=True) - return - - jail_channel_id = await self.config.get_guild_config_field_value(interaction.guild.id, "jail_channel_id") - if not jail_channel_id: - await interaction.response.send_message("No jail channel has been set up for this server.", ephemeral=True) - return - - await interaction.response.defer(ephemeral=True) - - await self._set_permissions_for_channels(interaction, jail_role, jail_channel_id) - - await interaction.edit_original_response( - content="Permissions have been set up for the jail role.", - ) - - async def _set_permissions_for_channels( - self, - interaction: discord.Interaction, - jail_role: discord.Role, - jail_channel_id: int, - ) -> None: - if interaction.guild is None: - return - - for channel in interaction.guild.channels: - if not isinstance(channel, discord.TextChannel | discord.VoiceChannel | discord.ForumChannel): - continue - - if ( - jail_role in channel.overwrites - and channel.overwrites[jail_role].send_messages is False - and channel.overwrites[jail_role].read_messages is False - and channel.id != jail_channel_id - ): - continue - - await channel.set_permissions(jail_role, send_messages=False, read_messages=False) - if channel.id == jail_channel_id: - await channel.set_permissions(jail_role, send_messages=True, read_messages=True) - - await interaction.edit_original_response(content=f"Setting up permissions for {channel.name}.") - @commands.hybrid_command( name="jail", aliases=["j"], @@ -91,7 +20,7 @@ class Jail(ModerationCogBase): ) @commands.guild_only() @checks.has_pl(2) - async def jail( + async def jail( # noqa: PLR0911 self, ctx: commands.Context[commands.Bot], target: discord.Member, @@ -137,38 +66,55 @@ class Jail(ModerationCogBase): case_target_roles = [role.id for role in target_roles] - await self._jail_user(ctx, target, flags, jail_role, target_roles) - - case = await self._insert_jail_case(ctx, target, flags.reason, case_target_roles) - - await self.handle_case_response(ctx, case, "created", flags.reason, target) - - async def _insert_jail_case( - self, - ctx: commands.Context[commands.Bot], - target: discord.Member, - reason: str, - case_target_roles: list[int] | None = None, - ) -> Case | None: - if not ctx.guild: - logger.warning("Jail command used outside of a guild context.") - return None - try: - return await self.db.case.insert_case( + case = await self.db.case.insert_case( case_target_id=target.id, case_moderator_id=ctx.author.id, case_type=CaseType.JAIL, - case_reason=reason, + case_reason=flags.reason, guild_id=ctx.guild.id, case_target_roles=case_target_roles, ) - except Exception as e: - logger.error(f"Failed to insert jail case for {target}. {e}") - await ctx.send(f"Failed to insert jail case for {target}. {e}", delete_after=30, ephemeral=True) - return None - def _get_manageable_roles(self, target: discord.Member, jail_role: discord.Role) -> list[discord.Role]: + except Exception as e: + logger.error(f"Failed to jail {target}. {e}") + await ctx.send(f"Failed to jail {target}. {e}", delete_after=30, ephemeral=True) + return + + try: + if target_roles: + await target.remove_roles(*target_roles, reason=flags.reason, atomic=False) + await target.add_roles(jail_role, reason=flags.reason) + + except (discord.Forbidden, discord.HTTPException) as e: + logger.error(f"Failed to jail {target}. {e}") + await ctx.send(f"Failed to jail {target}. {e}", delete_after=30, ephemeral=True) + return + + await self.send_dm(ctx, flags.silent, target, flags.reason, "jailed") + await self.handle_case_response(ctx, CaseType.JAIL, case.case_id, flags.reason, target) + + def _get_manageable_roles( + self, + target: discord.Member, + jail_role: discord.Role, + ) -> list[discord.Role]: + """ + Get the roles that can be managed by the bot. + + Parameters + ---------- + target : discord.Member + The member to jail. + jail_role : discord.Role + The jail role. + + Returns + ------- + list[discord.Role] + The roles that can be managed by the bot. + """ + return [ role for role in target.roles @@ -182,70 +128,6 @@ class Jail(ModerationCogBase): and role.is_assignable() ] - async def _jail_user( - self, - ctx: commands.Context[commands.Bot], - target: discord.Member, - flags: JailFlags, - jail_role: discord.Role, - target_roles: list[discord.Role], - ) -> None: - try: - await self.send_dm(ctx, flags.silent, target, flags.reason, "jailed") - - if target_roles: - await target.remove_roles(*target_roles, reason=flags.reason, atomic=False) - await target.add_roles(jail_role, reason=flags.reason) - - except (discord.Forbidden, discord.HTTPException) as e: - logger.error(f"Failed to jail {target}. {e}") - await ctx.send(f"Failed to jail {target}. {e}", delete_after=30, ephemeral=True) - return - - async def handle_case_response( - self, - ctx: commands.Context[commands.Bot], - case: Case | None, - action: str, - reason: str, - target: discord.Member | discord.User, - previous_reason: str | None = None, - ) -> None: - fields = [ - ("Moderator", f"__{ctx.author}__\n`{ctx.author.id}`", True), - ("Target", f"__{target}__\n`{target.id}`", True), - ("Reason", f"> {reason}", False), - ] - - if previous_reason: - fields.append(("Previous Reason", f"> {previous_reason}", False)) - - embed = await self._create_case_embed(ctx, case, action, fields, target) - - await self.send_embed(ctx, embed, log_type="mod") - await ctx.send(embed=embed, delete_after=30, ephemeral=True) - - async def _create_case_embed( - self, - ctx: commands.Context[commands.Bot], - case: Case | None, - action: str, - fields: list[tuple[str, str, bool]], - target: discord.Member | discord.User, - ) -> discord.Embed: - title = f"Case #{case.case_number} ({case.case_type}) {action}" if case else f"Case {action} ({CaseType.JAIL})" - - embed = await self.create_embed( - ctx, - title=title, - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - - embed.set_thumbnail(url=target.avatar) - return embed - async def setup(bot: commands.Bot) -> None: await bot.add_cog(Jail(bot)) diff --git a/tux/cogs/moderation/kick.py b/tux/cogs/moderation/kick.py index 697a51f..935b0b4 100644 --- a/tux/cogs/moderation/kick.py +++ b/tux/cogs/moderation/kick.py @@ -3,9 +3,7 @@ from discord.ext import commands from loguru import logger from prisma.enums import CaseType -from prisma.models import Case from tux.utils import checks -from tux.utils.constants import Constants as CONST from tux.utils.flags import KickFlags from . import ModerationCogBase @@ -75,48 +73,7 @@ class Kick(ModerationCogBase): guild_id=ctx.guild.id, ) - await self.handle_case_response(ctx, case, "created", flags.reason, target) - - async def handle_case_response( - self, - ctx: commands.Context[commands.Bot], - case: Case | None, - action: str, - reason: str, - target: discord.Member | discord.User, - previous_reason: str | None = None, - ) -> None: - moderator = ctx.author - - fields = [ - ("Moderator", f"__{moderator}__\n`{moderator.id}`", True), - ("Target", f"__{target}__\n`{target.id}`", True), - ("Reason", f"> {reason}", False), - ] - - if previous_reason: - fields.append(("Previous Reason", f"> {previous_reason}", False)) - - if case is not None: - embed = await self.create_embed( - ctx, - title=f"Case #{case.case_number} {action} ({case.case_type})", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - embed.set_thumbnail(url=target.avatar) - else: - embed = await self.create_embed( - ctx, - title=f"Case {action} ({CaseType.KICK})", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - - await self.send_embed(ctx, embed, log_type="mod") - await ctx.send(embed=embed, delete_after=30, ephemeral=True) + await self.handle_case_response(ctx, CaseType.KICK, case.case_id, flags.reason, target) async def setup(bot: commands.Bot) -> None: diff --git a/tux/cogs/moderation/snippetban.py b/tux/cogs/moderation/snippetban.py index 2555f4f..98f6c17 100644 --- a/tux/cogs/moderation/snippetban.py +++ b/tux/cogs/moderation/snippetban.py @@ -3,10 +3,8 @@ from discord.ext import commands from loguru import logger from prisma.enums import CaseType -from prisma.models import Case from tux.database.controllers.case import CaseController from tux.utils import checks -from tux.utils.constants import Constants as CONST from tux.utils.flags import SnippetBanFlags from . import ModerationCogBase @@ -20,7 +18,7 @@ class SnippetBan(ModerationCogBase): @commands.hybrid_command( name="snippetban", aliases=["sb"], - usage="snippetban [target]", + usage="snippetban [target] [reason] ", ) @commands.guild_only() @checks.has_pl(3) @@ -49,60 +47,25 @@ class SnippetBan(ModerationCogBase): return if await self.is_snippetbanned(ctx.guild.id, target.id): - await ctx.send("User is already snippet banned.", delete_after=30) + await ctx.send("User is already snippet banned.", delete_after=30, ephemeral=True) return - case = await self.db.case.insert_case( - case_target_id=target.id, - case_moderator_id=ctx.author.id, - case_type=CaseType.SNIPPETBAN, - case_reason=flags.reason, - guild_id=ctx.guild.id, - ) - - await self.send_dm(ctx, flags.silent, target, flags.reason, "Snippet banned") - await self.handle_case_response(ctx, case, "created", flags.reason, target) - - async def handle_case_response( - self, - ctx: commands.Context[commands.Bot], - case: Case | None, - action: str, - reason: str, - target: discord.Member | discord.User, - previous_reason: str | None = None, - ) -> None: - moderator = ctx.author - - fields = [ - ("Moderator", f"__{moderator}__\n`{moderator.id}`", True), - ("Target", f"__{target}__\n`{target.id}`", True), - ("Reason", f"> {reason}", False), - ] - - if previous_reason: - fields.append(("Previous Reason", f"> {previous_reason}", False)) - - if case is not None: - embed = await self.create_embed( - ctx, - title=f"Case #{case.case_number} ({case.case_type}) {action}", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - embed.set_thumbnail(url=target.avatar) - else: - embed = await self.create_embed( - ctx, - title=f"Case {action} ({CaseType.SNIPPETBAN})", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], + try: + case = await self.db.case.insert_case( + case_target_id=target.id, + case_moderator_id=ctx.author.id, + case_type=CaseType.SNIPPETBAN, + case_reason=flags.reason, + guild_id=ctx.guild.id, ) - await self.send_embed(ctx, embed, log_type="mod") - await ctx.send(embed=embed, delete_after=30, ephemeral=True) + except Exception as e: + logger.error(f"Failed to ban {target}. {e}") + await ctx.send(f"Failed to ban {target}. {e}", delete_after=30) + return + + await self.send_dm(ctx, flags.silent, target, flags.reason, "snippet banned") + await self.handle_case_response(ctx, CaseType.SNIPPETBAN, case.case_id, flags.reason, target) async def is_snippetbanned(self, guild_id: int, user_id: int) -> bool: """ diff --git a/tux/cogs/moderation/snippetunban.py b/tux/cogs/moderation/snippetunban.py index d44ee50..eac5dd5 100644 --- a/tux/cogs/moderation/snippetunban.py +++ b/tux/cogs/moderation/snippetunban.py @@ -3,10 +3,8 @@ from discord.ext import commands from loguru import logger from prisma.enums import CaseType -from prisma.models import Case from tux.database.controllers.case import CaseController from tux.utils import checks -from tux.utils.constants import Constants as CONST from tux.utils.flags import SnippetUnbanFlags from . import ModerationCogBase @@ -20,7 +18,7 @@ class SnippetUnban(ModerationCogBase): @commands.hybrid_command( name="snippetunban", aliases=["sub"], - usage="snippetunban [target]", + usage="snippetunban [target] [reason] ", ) @commands.guild_only() @checks.has_pl(3) @@ -48,62 +46,26 @@ class SnippetUnban(ModerationCogBase): logger.warning("Snippet ban command used outside of a guild context.") return - # Check if the user is already snippet banned if not await self.is_snippetbanned(ctx.guild.id, target.id): - await ctx.send("User is not snippet banned.", delete_after=30) + await ctx.send("User is not snippet banned.", delete_after=30, ephemeral=True) return - case = await self.db.case.insert_case( - case_target_id=target.id, - case_moderator_id=ctx.author.id, - case_type=CaseType.SNIPPETUNBAN, - case_reason=flags.reason, - guild_id=ctx.guild.id, - ) - - await self.send_dm(ctx, flags.silent, target, flags.reason, "Snippet unbanned") - await self.handle_case_response(ctx, case, "created", flags.reason, target) - - async def handle_case_response( - self, - ctx: commands.Context[commands.Bot], - case: Case | None, - action: str, - reason: str, - target: discord.Member | discord.User, - previous_reason: str | None = None, - ) -> None: - moderator = ctx.author - - fields = [ - ("Moderator", f"__{moderator}__\n`{moderator.id}`", True), - ("Target", f"__{target}__\n`{target.id}`", True), - ("Reason", f"> {reason}", False), - ] - - if previous_reason: - fields.append(("Previous Reason", f"> {previous_reason}", False)) - - if case is not None: - embed = await self.create_embed( - ctx, - title=f"Case #{case.case_number} ({case.case_type}) {action}", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - embed.set_thumbnail(url=target.avatar) - else: - embed = await self.create_embed( - ctx, - title=f"Case {action} ({CaseType.SNIPPETUNBAN})", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], + try: + case = await self.db.case.insert_case( + case_target_id=target.id, + case_moderator_id=ctx.author.id, + case_type=CaseType.SNIPPETUNBAN, + case_reason=flags.reason, + guild_id=ctx.guild.id, ) - await self.send_embed(ctx, embed, log_type="mod") - await ctx.send(embed=embed, delete_after=30, ephemeral=True) + except Exception as e: + logger.error(f"Failed to snippet unban {target}. {e}") + await ctx.send(f"Failed to snippet unban {target}. {e}", delete_after=30, ephemeral=True) + return + + await self.send_dm(ctx, flags.silent, target, flags.reason, "snippet unbanned") + await self.handle_case_response(ctx, CaseType.SNIPPETUNBAN, case.case_id, flags.reason, target) async def is_snippetbanned(self, guild_id: int, user_id: int) -> bool: """ diff --git a/tux/cogs/moderation/timeout.py b/tux/cogs/moderation/timeout.py index fd4620f..c96d67f 100644 --- a/tux/cogs/moderation/timeout.py +++ b/tux/cogs/moderation/timeout.py @@ -6,9 +6,7 @@ from discord.ext import commands from loguru import logger from prisma.enums import CaseType -from prisma.models import Case from tux.utils import checks -from tux.utils.constants import Constants as CONST from tux.utils.flags import TimeoutFlags from . import ModerationCogBase @@ -106,7 +104,6 @@ class Timeout(ModerationCogBase): duration = parse_time_string(flags.duration) try: - await self.send_dm(ctx, flags.silent, target, flags.reason, f"timed out for {flags.duration}") await target.timeout(duration, reason=flags.reason) except discord.DiscordException as e: @@ -122,49 +119,8 @@ class Timeout(ModerationCogBase): guild_id=ctx.guild.id, ) - await self.handle_case_response(ctx, flags, case, "created", flags.reason, target) - - async def handle_case_response( - self, - ctx: commands.Context[commands.Bot], - flags: TimeoutFlags, - case: Case | None, - action: str, - reason: str, - target: discord.Member | discord.User, - previous_reason: str | None = None, - ) -> None: - moderator = ctx.author - - fields = [ - ("Moderator", f"__{moderator}__\n`{moderator.id}`", True), - ("Target", f"__{target}__\n`{target.id}`", True), - ("Reason", f"> {reason}", False), - ] - - if previous_reason: - fields.append(("Previous Reason", f"> {previous_reason}", False)) - - if case is not None: - embed = await self.create_embed( - ctx, - title=f"Case #{case.case_number} {action} ({flags.duration} {case.case_type})", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - embed.set_thumbnail(url=target.avatar) - else: - embed = await self.create_embed( - ctx, - title=f"Case #0 {action} ({flags.duration} {CaseType.TIMEOUT})", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - - await self.send_embed(ctx, embed, log_type="mod") - await ctx.send(embed=embed, delete_after=30, ephemeral=True) + await self.send_dm(ctx, flags.silent, target, flags.reason, f"timed out for {flags.duration}") + await self.handle_case_response(ctx, CaseType.TIMEOUT, case.case_id, flags.reason, target, flags.duration) async def setup(bot: commands.Bot) -> None: diff --git a/tux/cogs/moderation/unban.py b/tux/cogs/moderation/unban.py index 0eadebb..52ccf94 100644 --- a/tux/cogs/moderation/unban.py +++ b/tux/cogs/moderation/unban.py @@ -3,9 +3,7 @@ from discord.ext import commands from loguru import logger from prisma.enums import CaseType -from prisma.models import Case from tux.utils import checks -from tux.utils.constants import Constants as CONST from tux.utils.flags import UnbanFlags from . import ModerationCogBase @@ -35,8 +33,6 @@ class Unban(ModerationCogBase): ---------- ctx : commands.Context[commands.Bot] The context object for the command. - target : discord.Member - The member to unban. flags : UnbanFlags The flags for the command (username_or_id: str, reason: str). @@ -76,48 +72,7 @@ class Unban(ModerationCogBase): case_reason=flags.reason, ) - await self.handle_case_response(ctx, case, "created", flags.reason, user) - - async def handle_case_response( - self, - ctx: commands.Context[commands.Bot], - case: Case | None, - action: str, - reason: str, - target: discord.Member | discord.User, - previous_reason: str | None = None, - ) -> None: - moderator = ctx.author - - fields = [ - ("Moderator", f"__{moderator}__\n`{moderator.id}`", True), - ("Target", f"__{target}__\n`{target.id}`", True), - ("Reason", f"> {reason}", False), - ] - - if previous_reason: - fields.append(("Previous Reason", f"> {previous_reason}", False)) - - if case is not None: - embed = await self.create_embed( - ctx, - title=f"Case #{case.case_number} ({case.case_type}) {action}", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - embed.set_thumbnail(url=target.avatar) - else: - embed = await self.create_embed( - ctx, - title=f"Case {action} ({CaseType.UNBAN})", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - - await self.send_embed(ctx, embed, log_type="mod") - await ctx.send(embed=embed, delete_after=30, ephemeral=True) + await self.handle_case_response(ctx, CaseType.UNBAN, case.case_id, flags.reason, user) async def setup(bot: commands.Bot) -> None: diff --git a/tux/cogs/moderation/unjail.py b/tux/cogs/moderation/unjail.py index ff4b191..5c10b5c 100644 --- a/tux/cogs/moderation/unjail.py +++ b/tux/cogs/moderation/unjail.py @@ -3,9 +3,7 @@ from discord.ext import commands from loguru import logger from prisma.enums import CaseType -from prisma.models import Case from tux.utils import checks -from tux.utils.constants import Constants as CONST from tux.utils.flags import UnjailFlags from . import ModerationCogBase @@ -22,7 +20,7 @@ class Unjail(ModerationCogBase): ) @commands.guild_only() @checks.has_pl(2) - async def unjail( + async def unjail( # noqa: PLR0911 self, ctx: commands.Context[commands.Bot], target: discord.Member, @@ -51,151 +49,55 @@ class Unjail(ModerationCogBase): if not await self.check_conditions(ctx, target, moderator, "unjail"): return - jail_role = await self._get_jail_role(ctx) - if not jail_role: + jail_role_id = await self.config.get_jail_role_id(ctx.guild.id) + jail_role = ctx.guild.get_role(jail_role_id) if jail_role_id else None + + jail_channel_id = await self.config.get_jail_channel_id(ctx.guild.id) + + if not all([jail_role_id, jail_role, jail_channel_id]): + error_msgs = { + not jail_role_id: "No jail role has been set up for this server.", + not jail_role: "The jail role has been deleted.", + not jail_channel_id: "No jail channel has been set up for this server.", + } + + for condition, msg in error_msgs.items(): + if condition: + await ctx.send(msg, delete_after=30, ephemeral=True) return if jail_role not in target.roles: await ctx.send("The member is not jailed.", delete_after=30, ephemeral=True) return - if not await self._check_jail_channel(ctx): - return - case = await self.db.case.get_last_jail_case_by_target_id(ctx.guild.id, target.id) if not case: await ctx.send("No jail case found for this member.", delete_after=30, ephemeral=True) return - await self._unjail_user(ctx, target, jail_role, case, flags.reason) - - unjail_case = await self._insert_unjail_case(ctx, target, flags.reason) - - await self.handle_case_response(ctx, unjail_case, "created", flags.reason, target) - - async def _get_jail_role(self, ctx: commands.Context[commands.Bot]) -> discord.Role | None: - if ctx.guild is None: - logger.warning("Unjail command used outside of a guild context.") - return None - - jail_role_id = await self.config.get_jail_role_id(ctx.guild.id) - if not jail_role_id: - await ctx.send("No jail role has been set up for this server.", delete_after=30, ephemeral=True) - return None - - jail_role = ctx.guild.get_role(jail_role_id) - if not jail_role: - await ctx.send("The jail role has been deleted.", delete_after=30, ephemeral=True) - return None - - return jail_role - - async def _check_jail_channel(self, ctx: commands.Context[commands.Bot]) -> bool: - if ctx.guild is None: - logger.warning("Unjail command used outside of a guild context.") - return False - - jail_channel_id = await self.config.get_jail_channel_id(ctx.guild.id) - if not jail_channel_id: - await ctx.send("No jail channel has been set up for this server.", delete_after=30, ephemeral=True) - return False - - return True - - async def _unjail_user( - self, - ctx: commands.Context[commands.Bot], - target: discord.Member, - jail_role: discord.Role, - case: Case, - reason: str, - ) -> None: - if ctx.guild is None: - logger.warning("Unjail command used outside of a guild context.") - return - try: - await target.remove_roles(jail_role, reason=reason) - previous_roles = [await commands.RoleConverter().convert(ctx, str(role)) for role in case.case_target_roles] - if previous_roles: - await target.add_roles(*previous_roles, reason=reason, atomic=False) + await target.remove_roles(jail_role, reason=flags.reason, atomic=True) + await target.add_roles(*previous_roles, reason=flags.reason, atomic=True) else: await ctx.send("No previous roles found for the member.", delete_after=30, ephemeral=True) + return except (discord.Forbidden, discord.HTTPException) as e: logger.error(f"Failed to unjail member {target}. {e}") await ctx.send(f"Failed to unjail member {target}. {e}", delete_after=30, ephemeral=True) + return - async def _insert_unjail_case( - self, - ctx: commands.Context[commands.Bot], - target: discord.Member, - reason: str, - ) -> Case | None: - if not ctx.guild: - logger.warning("Unjail command used outside of a guild context.") - return None - - try: - return await self.db.case.insert_case( - guild_id=ctx.guild.id, - case_target_id=target.id, - case_moderator_id=ctx.author.id, - case_type=CaseType.UNJAIL, - case_reason=reason, - ) - - except Exception as e: - logger.error(f"Failed to insert unjail case for {target}. {e}") - await ctx.send(f"Failed to insert unjail case for {target}. {e}", delete_after=30, ephemeral=True) - return None - - async def handle_case_response( - self, - ctx: commands.Context[commands.Bot], - case: Case | None, - action: str, - reason: str, - target: discord.Member | discord.User, - previous_reason: str | None = None, - ) -> None: - fields = [ - ("Moderator", f"__{ctx.author}__\n`{ctx.author.id}`", True), - ("Target", f"__{target}__\n`{target.id}`", True), - ("Reason", f"> {reason}", False), - ] - - if previous_reason: - fields.append(("Previous Reason", f"> {previous_reason}", False)) - - embed = await self._create_case_embed(ctx, case, action, fields, target) - await self.send_embed(ctx, embed, log_type="mod") - await ctx.send(embed=embed, delete_after=30, ephemeral=True) - - async def _create_case_embed( - self, - ctx: commands.Context[commands.Bot], - case: Case | None, - action: str, - fields: list[tuple[str, str, bool]], - target: discord.Member | discord.User, - ) -> discord.Embed: - title = ( - f"Case #{case.case_number} ({case.case_type}) {action}" if case else f"Case {action} ({CaseType.UNJAIL})" + unjail_case = await self.db.case.insert_case( + guild_id=ctx.guild.id, + case_target_id=target.id, + case_moderator_id=ctx.author.id, + case_type=CaseType.UNJAIL, + case_reason=flags.reason, ) - embed = await self.create_embed( - ctx, - title=title, - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - - embed.set_thumbnail(url=target.avatar) - return embed + await self.handle_case_response(ctx, CaseType.UNJAIL, unjail_case.case_id, flags.reason, target) async def setup(bot: commands.Bot) -> None: diff --git a/tux/cogs/moderation/untimeout.py b/tux/cogs/moderation/untimeout.py index 60587cf..9972528 100644 --- a/tux/cogs/moderation/untimeout.py +++ b/tux/cogs/moderation/untimeout.py @@ -3,9 +3,7 @@ from discord.ext import commands from loguru import logger from prisma.enums import CaseType -from prisma.models import Case from tux.utils import checks -from tux.utils.constants import Constants as CONST from tux.utils.flags import UntimeoutFlags from . import ModerationCogBase @@ -59,7 +57,6 @@ class Untimeout(ModerationCogBase): await ctx.send(f"{target} is not currently timed out.", delete_after=30, ephemeral=True) try: - await self.send_dm(ctx, flags.silent, target, flags.reason, "untimed out") await target.timeout(None, reason=flags.reason) except discord.DiscordException as e: await ctx.send(f"Failed to untimeout {target}. {e}", delete_after=30, ephemeral=True) @@ -74,49 +71,8 @@ class Untimeout(ModerationCogBase): guild_id=ctx.guild.id, ) - await self.handle_case_response(ctx, flags, case, "created", flags.reason, target) - - async def handle_case_response( - self, - ctx: commands.Context[commands.Bot], - flags: UntimeoutFlags, - case: Case | None, - action: str, - reason: str, - target: discord.Member | discord.User, - previous_reason: str | None = None, - ) -> None: - moderator = ctx.author - - fields = [ - ("Moderator", f"__{moderator}__\n`{moderator.id}`", True), - ("Target", f"__{target}__\n`{target.id}`", True), - ("Reason", f"> {reason}", False), - ] - - if previous_reason: - fields.append(("Previous Reason", f"> {previous_reason}", False)) - - if case is not None: - embed = await self.create_embed( - ctx, - title=f"Case #{case.case_number} {action} ({case.case_type})", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - embed.set_thumbnail(url=target.avatar) - else: - embed = await self.create_embed( - ctx, - title=f"Case #0 {action} ({CaseType.UNTIMEOUT})", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - - await self.send_embed(ctx, embed, log_type="mod") - await ctx.send(embed=embed, delete_after=30, ephemeral=True) + await self.send_dm(ctx, flags.silent, target, flags.reason, "untimed out") + await self.handle_case_response(ctx, CaseType.UNTIMEOUT, case.case_id, flags.reason, target) async def setup(bot: commands.Bot) -> None: diff --git a/tux/cogs/moderation/warn.py b/tux/cogs/moderation/warn.py index 975d86c..537a179 100644 --- a/tux/cogs/moderation/warn.py +++ b/tux/cogs/moderation/warn.py @@ -3,9 +3,7 @@ from discord.ext import commands from loguru import logger from prisma.enums import CaseType -from prisma.models import Case from tux.utils import checks -from tux.utils.constants import Constants as CONST from tux.utils.flags import WarnFlags from . import ModerationCogBase @@ -61,48 +59,7 @@ class Warn(ModerationCogBase): guild_id=ctx.guild.id, ) - await self.handle_case_response(ctx, case, "created", flags.reason, target) - - async def handle_case_response( - self, - ctx: commands.Context[commands.Bot], - case: Case | None, - action: str, - reason: str, - target: discord.Member | discord.User, - previous_reason: str | None = None, - ) -> None: - moderator = ctx.author - - fields = [ - ("Moderator", f"__{moderator}__\n`{moderator.id}`", True), - ("Target", f"__{target}__\n`{target.id}`", True), - ("Reason", f"> {reason}", False), - ] - - if previous_reason: - fields.append(("Previous Reason", f"> {previous_reason}", False)) - - if case is not None: - embed = await self.create_embed( - ctx, - title=f"Case #{case.case_number} ({case.case_type}) {action}", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - embed.set_thumbnail(url=target.avatar) - else: - embed = await self.create_embed( - ctx, - title=f"Case {action} ({CaseType.WARN})", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - - await self.send_embed(ctx, embed, log_type="mod") - await ctx.send(embed=embed, delete_after=30, ephemeral=True) + await self.handle_case_response(ctx, CaseType.WARN, case.case_id, flags.reason, target) async def setup(bot: commands.Bot) -> None: From df9c5f21f555d3c3c9f2eafc8acefcfab98c7705 Mon Sep 17 00:00:00 2001 From: kzndotsh Date: Sun, 25 Aug 2024 09:54:53 +0000 Subject: [PATCH 18/19] refactor(config.py): group config commands under a single command group for better organization feat(config.py): add get_roles and get_perms commands to fetch roles and permissions information style(config.py): add docstrings to get_roles and get_perms commands for better code readability --- tux/cogs/guild/config.py | 48 ++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/tux/cogs/guild/config.py b/tux/cogs/guild/config.py index 9ffa518..ebbf769 100644 --- a/tux/cogs/guild/config.py +++ b/tux/cogs/guild/config.py @@ -17,7 +17,9 @@ class Config(commands.Cog): self.bot = bot self.db = DatabaseController().guild_config - @app_commands.command(name="config_set_logs") + config = app_commands.Group(name="config", description="Configure Tux for your server.") + + @config.command(name="set_logs") @app_commands.guild_only() # @checks.ac_has_pl(7) @app_commands.checks.has_permissions(administrator=True) @@ -45,7 +47,7 @@ class Config(commands.Cog): await interaction.response.send_message(view=view, ephemeral=True) - @app_commands.command(name="config_set_channels") + @config.command(name="set_channels") @app_commands.guild_only() # @checks.ac_has_pl(7) @app_commands.checks.has_permissions(administrator=True) @@ -65,7 +67,7 @@ class Config(commands.Cog): view = ConfigSetChannels() await interaction.response.send_message(view=view, ephemeral=True) - @app_commands.command(name="config_set_perms") + @config.command(name="set_perms") @app_commands.describe(setting="Which permission level to configure") @app_commands.choices( setting=[ @@ -116,7 +118,7 @@ class Config(commands.Cog): delete_after=30, ) - @app_commands.command(name="config_set_roles") + @config.command(name="set_roles") @app_commands.guild_only() # @checks.ac_has_pl(7) @app_commands.describe(setting="Which role to configure") @@ -158,13 +160,45 @@ class Config(commands.Cog): delete_after=30, ) - @app_commands.command(name="config_get_roles") + @config.command(name="get_roles") @app_commands.guild_only() # @checks.ac_has_pl(7) @app_commands.checks.has_permissions(administrator=True) async def config_get_roles( self, interaction: discord.Interaction, + ) -> None: + """ + Get the basic roles for the guild. + + Parameters + ---------- + interaction : discord.Interaction + The discord interaction object. + """ + + if interaction.guild is None: + return + + embed = discord.Embed( + title="Config - Roles", + color=discord.Color.blue(), + timestamp=discord.utils.utcnow(), + ) + + jail_role_id = await self.db.get_jail_role_id(interaction.guild.id) + jail_role = f"<@&{jail_role_id}>" if jail_role_id else "Not set" + embed.add_field(name="Jail Role", value=jail_role, inline=False) + + await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=30) + + @config.command(name="get_perms") + @app_commands.guild_only() + # @checks.ac_has_pl(7) + @app_commands.checks.has_permissions(administrator=True) + async def config_get_perms( + self, + interaction: discord.Interaction, ) -> None: """ Get the roles for each permission level. @@ -192,7 +226,7 @@ class Config(commands.Cog): await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=30) - @app_commands.command(name="config_get_channels") + @config.command(name="get_channels") @app_commands.guild_only() # @checks.ac_has_pl(7) @app_commands.checks.has_permissions(administrator=True) @@ -232,7 +266,7 @@ class Config(commands.Cog): await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=30) - @app_commands.command(name="config_get_logs") + @config.command(name="get_logs") @app_commands.guild_only() # @checks.ac_has_pl(7) @app_commands.checks.has_permissions(administrator=True) From a190fe2d4322b9c6d9030382ea0e05342599fd3e Mon Sep 17 00:00:00 2001 From: kzndotsh Date: Sun, 25 Aug 2024 13:04:12 +0000 Subject: [PATCH 19/19] refactor(moderation cogs): use generate_usage function for command usage generation fix(unban.py): move username_or_id from flags to command arguments for better clarity refactor(timeout.py): remove parse_time_string function, use the one from utils.functions instead feat(error.py): add comprehensive list of DiscordException for better error handling refactor(error.py): reorganize error_map dictionary for better readability style(error.py): uncomment useful debug logs for error handlers feat(help.py): enhance flag_str formatting for better command help display feat(flags.py): add generate_usage function to generate usage string for a command with flags refactor(flags.py): simplify flag descriptions for better readability feat(functions.py): add parse_time_string function to convert string representation of time into timedelta object refactor(flags.py): remove username_or_id flag from UnbanFlags class as it's not used --- tux/cogs/moderation/ban.py | 9 +-- tux/cogs/moderation/jail.py | 4 +- tux/cogs/moderation/kick.py | 4 +- tux/cogs/moderation/snippetban.py | 4 +- tux/cogs/moderation/snippetunban.py | 4 +- tux/cogs/moderation/timeout.py | 48 +----------- tux/cogs/moderation/unban.py | 11 ++- tux/cogs/moderation/unjail.py | 4 +- tux/cogs/moderation/untimeout.py | 4 +- tux/cogs/moderation/warn.py | 4 +- tux/handlers/error.py | 109 ++++++++++++++++++++++++++-- tux/help.py | 6 +- tux/utils/flags.py | 103 +++++++++++++++++++------- tux/utils/functions.py | 45 +++++++++++- 14 files changed, 255 insertions(+), 104 deletions(-) diff --git a/tux/cogs/moderation/ban.py b/tux/cogs/moderation/ban.py index f1d0c5e..bcad88d 100644 --- a/tux/cogs/moderation/ban.py +++ b/tux/cogs/moderation/ban.py @@ -4,7 +4,7 @@ from loguru import logger from prisma.enums import CaseType from tux.utils import checks -from tux.utils.flags import BanFlags +from tux.utils.flags import BanFlags, generate_usage from . import ModerationCogBase @@ -12,12 +12,9 @@ from . import ModerationCogBase class Ban(ModerationCogBase): def __init__(self, bot: commands.Bot) -> None: super().__init__(bot) + self.ban.usage = generate_usage(self.ban, BanFlags) - @commands.hybrid_command( - name="ban", - aliases=["b"], - usage="ban [target] [reason] ", - ) + @commands.hybrid_command(name="ban", aliases=["b"]) @commands.guild_only() @checks.has_pl(3) async def ban( diff --git a/tux/cogs/moderation/jail.py b/tux/cogs/moderation/jail.py index ee48982..695369c 100644 --- a/tux/cogs/moderation/jail.py +++ b/tux/cogs/moderation/jail.py @@ -4,7 +4,7 @@ from loguru import logger from prisma.enums import CaseType from tux.utils import checks -from tux.utils.flags import JailFlags +from tux.utils.flags import JailFlags, generate_usage from . import ModerationCogBase @@ -12,11 +12,11 @@ from . import ModerationCogBase class Jail(ModerationCogBase): def __init__(self, bot: commands.Bot) -> None: super().__init__(bot) + self.jail.usage = generate_usage(self.jail, JailFlags) @commands.hybrid_command( name="jail", aliases=["j"], - usage="jail [target] [reason] ", ) @commands.guild_only() @checks.has_pl(2) diff --git a/tux/cogs/moderation/kick.py b/tux/cogs/moderation/kick.py index 935b0b4..b2d06c1 100644 --- a/tux/cogs/moderation/kick.py +++ b/tux/cogs/moderation/kick.py @@ -4,7 +4,7 @@ from loguru import logger from prisma.enums import CaseType from tux.utils import checks -from tux.utils.flags import KickFlags +from tux.utils.flags import KickFlags, generate_usage from . import ModerationCogBase @@ -12,11 +12,11 @@ from . import ModerationCogBase class Kick(ModerationCogBase): def __init__(self, bot: commands.Bot) -> None: super().__init__(bot) + self.kick.usage = generate_usage(self.kick, KickFlags) @commands.hybrid_command( name="kick", aliases=["k"], - usage="kick [target] [reason] ", ) @commands.guild_only() @checks.has_pl(2) diff --git a/tux/cogs/moderation/snippetban.py b/tux/cogs/moderation/snippetban.py index 98f6c17..e2d71f4 100644 --- a/tux/cogs/moderation/snippetban.py +++ b/tux/cogs/moderation/snippetban.py @@ -5,7 +5,7 @@ from loguru import logger from prisma.enums import CaseType from tux.database.controllers.case import CaseController from tux.utils import checks -from tux.utils.flags import SnippetBanFlags +from tux.utils.flags import SnippetBanFlags, generate_usage from . import ModerationCogBase @@ -14,11 +14,11 @@ class SnippetBan(ModerationCogBase): def __init__(self, bot: commands.Bot) -> None: super().__init__(bot) self.case_controller = CaseController() + self.snippet_ban.usage = generate_usage(self.snippet_ban, SnippetBanFlags) @commands.hybrid_command( name="snippetban", aliases=["sb"], - usage="snippetban [target] [reason] ", ) @commands.guild_only() @checks.has_pl(3) diff --git a/tux/cogs/moderation/snippetunban.py b/tux/cogs/moderation/snippetunban.py index eac5dd5..f553b5b 100644 --- a/tux/cogs/moderation/snippetunban.py +++ b/tux/cogs/moderation/snippetunban.py @@ -5,7 +5,7 @@ from loguru import logger from prisma.enums import CaseType from tux.database.controllers.case import CaseController from tux.utils import checks -from tux.utils.flags import SnippetUnbanFlags +from tux.utils.flags import SnippetUnbanFlags, generate_usage from . import ModerationCogBase @@ -14,11 +14,11 @@ class SnippetUnban(ModerationCogBase): def __init__(self, bot: commands.Bot) -> None: super().__init__(bot) self.case_controller = CaseController() + self.snippet_unban.usage = generate_usage(self.snippet_unban, SnippetUnbanFlags) @commands.hybrid_command( name="snippetunban", aliases=["sub"], - usage="snippetunban [target] [reason] ", ) @commands.guild_only() @checks.has_pl(3) diff --git a/tux/cogs/moderation/timeout.py b/tux/cogs/moderation/timeout.py index c96d67f..b2c686c 100644 --- a/tux/cogs/moderation/timeout.py +++ b/tux/cogs/moderation/timeout.py @@ -1,5 +1,4 @@ -import re -from datetime import UTC, datetime, timedelta +from datetime import UTC, datetime import discord from discord.ext import commands @@ -7,59 +6,20 @@ from loguru import logger from prisma.enums import CaseType from tux.utils import checks -from tux.utils.flags import TimeoutFlags +from tux.utils.flags import TimeoutFlags, generate_usage +from tux.utils.functions import parse_time_string from . import ModerationCogBase -def parse_time_string(time_str: str) -> timedelta: - """ - Convert a string representation of time (e.g., '60s', '1m', '2h', '10d') - into a datetime.timedelta object. - - Parameters - time_str (str): The string representation of time. - - Returns - timedelta: Corresponding timedelta object. - """ - - # Define regex pattern to parse time strings - time_pattern = re.compile(r"^(?P\d+)(?P[smhdw])$") - - # Match the input string with the pattern - match = time_pattern.match(time_str) - - if not match: - msg = f"Invalid time format: '{time_str}'" - raise ValueError(msg) - - # Extract the value and unit from the pattern match - value = int(match["value"]) - unit = match["unit"] - - # Define the mapping of units to keyword arguments for timedelta - unit_map = {"s": "seconds", "m": "minutes", "h": "hours", "d": "days", "w": "weeks"} - - # Check if the unit is in the map - if unit not in unit_map: - msg = f"Unknown time unit: '{unit}'" - raise ValueError(msg) - - # Create the timedelta with the appropriate keyword argument - kwargs = {unit_map[unit]: value} - - return timedelta(**kwargs) - - class Timeout(ModerationCogBase): def __init__(self, bot: commands.Bot) -> None: super().__init__(bot) + self.timeout.usage = generate_usage(self.timeout, TimeoutFlags) @commands.hybrid_command( name="timeout", aliases=["t", "to", "mute"], - usage="timeout [target] [duration] [reason]", ) @commands.guild_only() @checks.has_pl(2) diff --git a/tux/cogs/moderation/unban.py b/tux/cogs/moderation/unban.py index 52ccf94..f459b29 100644 --- a/tux/cogs/moderation/unban.py +++ b/tux/cogs/moderation/unban.py @@ -4,7 +4,7 @@ from loguru import logger from prisma.enums import CaseType from tux.utils import checks -from tux.utils.flags import UnbanFlags +from tux.utils.flags import UnbanFlags, generate_usage from . import ModerationCogBase @@ -12,17 +12,18 @@ from . import ModerationCogBase class Unban(ModerationCogBase): def __init__(self, bot: commands.Bot) -> None: super().__init__(bot) + self.unban.usage = generate_usage(self.unban, UnbanFlags) @commands.hybrid_command( name="unban", aliases=["ub"], - usage="unban [username_or_id] [reason]", ) @commands.guild_only() @checks.has_pl(3) async def unban( self, ctx: commands.Context[commands.Bot], + username_or_id: str, *, flags: UnbanFlags, ) -> None: @@ -33,8 +34,10 @@ class Unban(ModerationCogBase): ---------- ctx : commands.Context[commands.Bot] The context object for the command. + username_or_id : str + The username or ID of the user to unban. flags : UnbanFlags - The flags for the command (username_or_id: str, reason: str). + The flags for the command (reason: str). Raises ------ @@ -50,7 +53,7 @@ class Unban(ModerationCogBase): # Get the list of banned users in the guild banned_users = [ban.user async for ban in ctx.guild.bans()] - user = await commands.UserConverter().convert(ctx, flags.username_or_id) + user = await commands.UserConverter().convert(ctx, username_or_id) if user not in banned_users: await ctx.send(f"{user} was not found in the guild ban list.", delete_after=30, ephemeral=True) diff --git a/tux/cogs/moderation/unjail.py b/tux/cogs/moderation/unjail.py index 5c10b5c..0c87372 100644 --- a/tux/cogs/moderation/unjail.py +++ b/tux/cogs/moderation/unjail.py @@ -4,7 +4,7 @@ from loguru import logger from prisma.enums import CaseType from tux.utils import checks -from tux.utils.flags import UnjailFlags +from tux.utils.flags import UnjailFlags, generate_usage from . import ModerationCogBase @@ -12,11 +12,11 @@ from . import ModerationCogBase class Unjail(ModerationCogBase): def __init__(self, bot: commands.Bot) -> None: super().__init__(bot) + self.unjail.usage = generate_usage(self.unjail, UnjailFlags) @commands.hybrid_command( name="unjail", aliases=["uj"], - usage="unjail [target] [reason] ", ) @commands.guild_only() @checks.has_pl(2) diff --git a/tux/cogs/moderation/untimeout.py b/tux/cogs/moderation/untimeout.py index 9972528..8573e1d 100644 --- a/tux/cogs/moderation/untimeout.py +++ b/tux/cogs/moderation/untimeout.py @@ -4,7 +4,7 @@ from loguru import logger from prisma.enums import CaseType from tux.utils import checks -from tux.utils.flags import UntimeoutFlags +from tux.utils.flags import UntimeoutFlags, generate_usage from . import ModerationCogBase @@ -12,11 +12,11 @@ from . import ModerationCogBase class Untimeout(ModerationCogBase): def __init__(self, bot: commands.Bot) -> None: super().__init__(bot) + self.untimeout.usage = generate_usage(self.untimeout, UntimeoutFlags) @commands.hybrid_command( name="untimeout", aliases=["ut", "uto", "unmute"], - usage="untimeout [target] [reason] ", ) @commands.guild_only() @checks.has_pl(2) diff --git a/tux/cogs/moderation/warn.py b/tux/cogs/moderation/warn.py index 537a179..ce52840 100644 --- a/tux/cogs/moderation/warn.py +++ b/tux/cogs/moderation/warn.py @@ -4,7 +4,7 @@ from loguru import logger from prisma.enums import CaseType from tux.utils import checks -from tux.utils.flags import WarnFlags +from tux.utils.flags import WarnFlags, generate_usage from . import ModerationCogBase @@ -12,11 +12,11 @@ from . import ModerationCogBase class Warn(ModerationCogBase): def __init__(self, bot: commands.Bot) -> None: super().__init__(bot) + self.warn.usage = generate_usage(self.warn, WarnFlags) @commands.hybrid_command( name="warn", aliases=["w"], - usage="warn [target] [reason] ", ) @commands.guild_only() @checks.has_pl(2) diff --git a/tux/handlers/error.py b/tux/handlers/error.py index 089c64d..acdb722 100644 --- a/tux/handlers/error.py +++ b/tux/handlers/error.py @@ -9,6 +9,103 @@ from loguru import logger from tux.utils.embeds import create_error_embed from tux.utils.exceptions import AppCommandPermissionLevelError, PermissionLevelError +""" +Exception + DiscordException + GatewayNotFound + ClientException + CommandRegistrationError + InvalidData + LoginFailure + ConnectionClosed + PrivilegedIntentsRequired + InteractionResponded + HTTPException + Forbidden + NotFound + DiscordServerError + app_commands.CommandSyncFailure + RateLimited + CommandError + ConversionError + UserInputError + MissingRequiredArgument + MissingRequiredAttachment + TooManyArguments + BadArgument + MessageNotFound + MemberNotFound + GuildNotFound + UserNotFound + ChannelNotFound + ChannelNotReadable + BadColourArgument + RoleNotFound + BadInviteArgument + EmojiNotFound + GuildStickerNotFound + ScheduledEventNotFound + PartialEmojiConversionFailure + BadBoolArgument + RangeError + ThreadNotFound + FlagError + BadFlagArgument + MissingFlagArgument + TooManyFlags + MissingRequiredFlag + BadUnionArgument + BadLiteralArgument + ArgumentParsingError + UnexpectedQuoteError + InvalidEndOfQuotedStringError + ExpectedClosingQuoteError + CommandNotFound + CheckFailure + CheckAnyFailure + PrivateMessageOnly + NoPrivateMessage + NotOwner + MissingPermissions + BotMissingPermissions + MissingRole + BotMissingRole + MissingAnyRole + BotMissingAnyRole + NSFWChannelRequired + DisabledCommand + CommandInvokeError + CommandOnCooldown + MaxConcurrencyReached + HybridCommandError + ExtensionError + ExtensionAlreadyLoaded + ExtensionNotLoaded + NoEntryPointError + ExtensionFailed + ExtensionNotFound + AppCommandError + CommandInvokeError + TransformerError + TranslationError + CheckFailure + NoPrivateMessage + MissingRole + MissingAnyRole + MissingPermissions + BotMissingPermissions + CommandOnCooldown + CommandLimitReached + CommandAlreadyRegistered + CommandSignatureMismatch + CommandNotFound + MissingApplicationID + CommandSyncFailure + HTTPException + CommandSyncFailure +""" + + error_map: dict[type[Exception], str] = { # app_commands app_commands.AppCommandError: "An error occurred: {error}", @@ -29,12 +126,12 @@ error_map: dict[type[Exception], str] = { commands.MissingRole: "User not in sudoers file. This incident will be reported. (Missing Role)", commands.MissingAnyRole: "User not in sudoers file. This incident will be reported. (Missing Roles)", commands.MissingPermissions: "User not in sudoers file. This incident will be reported. (Missing Permissions)", + commands.FlagError: "An error occurred with the flags:\n`{error}`", + commands.MissingRequiredFlag: "Missing argument: {error}. Correct usage:\n`{ctx.prefix}{ctx.command.usage}`", commands.CheckFailure: "User not in sudoers file. This incident will be reported. (Permission Check Failed)", - commands.CommandNotFound: "This command was not found.", commands.CommandOnCooldown: "This command is on cooldown. Try again in {error.retry_after:.2f} seconds.", - commands.BadArgument: "Invalid argument passed. Correct usage: `{ctx.prefix}{ctx.command.usage}`", - commands.MissingRequiredArgument: "Missing required arg. Correct usage: `{ctx.prefix}{ctx.command.usage}`", - commands.MissingRequiredAttachment: "Missing required attachment.", + commands.MissingRequiredArgument: "Missing required argument. Correct usage:\n`{ctx.prefix}{ctx.command.usage}`", + commands.TooManyArguments: "Too many arguments passed. Correct usage:\n`{ctx.prefix}{ctx.command.usage}`", commands.NotOwner: "User not in sudoers file. This incident will be reported. (Not Owner)", commands.BotMissingPermissions: "User not in sudoers file. This incident will be reported. (Bot Missing Permissions)", # Custom errors @@ -101,12 +198,12 @@ class ErrorHandler(commands.Cog): The error that occurred. """ - # # If the command has its own error handler, return + # If the command has its own error handler, return # if hasattr(ctx.command, "on_error"): # logger.debug(f"Command {ctx.command} has its own error handler.") # return - # # If the cog has its own error handler, return + # If the cog has its own error handler, return # if ctx.cog and ctx.cog._get_overridden_method(ctx.cog.cog_command_error) is not None: # logger.debug(f"Cog {ctx.cog} has its own error handler.") # return diff --git a/tux/help.py b/tux/help.py index 84082c4..4ef90f9 100644 --- a/tux/help.py +++ b/tux/help.py @@ -158,10 +158,12 @@ class TuxHelp(commands.HelpCommand): flag_str = self._format_flag_name(flag) if flag.aliases: - flag_str += f" ({', '.join(flag.aliases)})" + flag_str += f" ({', '.join(flag.aliases)}) : {flag_type}" + else: + flag_str += f" : {flag_type}" flag_str += f"\n\t{flag.description or 'No description provided.'}" - flag_str += f"\n\tType: `{flag_type}`" + if flag.default is not discord.utils.MISSING: flag_str += f"\n\tDefault: {flag.default}" diff --git a/tux/utils/flags.py b/tux/utils/flags.py index eecac80..d0d2a74 100644 --- a/tux/utils/flags.py +++ b/tux/utils/flags.py @@ -1,3 +1,6 @@ +import inspect +from typing import Any + import discord from discord.ext import commands from discord.utils import MISSING @@ -6,16 +9,69 @@ from prisma.enums import CaseType from tux.utils.converters import CaseTypeConverter +def generate_usage( + command: commands.Command[Any, Any, Any], + flag_converter: type[commands.FlagConverter], +) -> str: + """ + Generate a usage string for a command with flags. + + Parameters + ---------- + command : commands.Command + The command for which to generate the usage string. + flag_converter : type[commands.FlagConverter] + The flag converter class for the command. + + Returns + ------- + str + The usage string for the command. Example: "ban [target] -[reason] -" + """ + + # Get the name of the command + command_name = command.qualified_name + + # Start the usage string with the command name + usage = f"{command_name}" + + # Get the parameters of the command (excluding the `ctx` and `flags` parameters) + parameters: dict[str, commands.Parameter] = command.clean_params + + flag_prefix = getattr(flag_converter, "__commands_flag_prefix__", "-") + flags: dict[str, commands.Flag] = flag_converter.get_flags() + + # Add non-flag arguments to the usage string + for param_name, param in parameters.items(): + # Ignore these parameters + if param_name in ["ctx", "flags"]: + continue + # Determine if the parameter is required + is_required = param.default == inspect.Parameter.empty + # Add the parameter to the usage string with required or optional wrapping + usage += f" [{param_name}]" if is_required else f" <{param_name}>" + + # Add flag arguments to the usage string + for flag_name, flag_obj in flags.items(): + # Determine if the flag is required or optional + if flag_obj.required: + usage += f" {flag_prefix}[{flag_name}]" + else: + usage += f" {flag_prefix}<{flag_name}>" + + return usage + + class BanFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"): reason: str = commands.flag( name="reason", - description="The reason for the ban.", + description="Reason for the ban.", aliases=["r"], default=MISSING, ) purge_days: int = commands.flag( name="purge_days", - description="The number of days (< 7) to purge in messages.", + description="Number of days in messages. (< 7)", aliases=["p", "purge"], default=0, ) @@ -30,18 +86,18 @@ class BanFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", pre class TempBanFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"): reason: str = commands.flag( name="reason", - description="The reason for the temp ban.", + description="Reason for the temp ban.", aliases=["r"], default=MISSING, ) expires_at: int = commands.flag( name="expires_at", - description="The time in days the ban will last for.", + description="Number of days the ban will last for.", aliases=["t", "d", "e", "duration", "expires", "time"], ) purge_days: int = commands.flag( name="purge_days", - description="The number of days (< 7) to purge in messages.", + description="Number of days in messages. (< 7)", aliases=["p"], default=0, ) @@ -56,7 +112,7 @@ class TempBanFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", class KickFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"): reason: str = commands.flag( name="reason", - description="The reason for the kick.", + description="Reason for the kick.", aliases=["r"], default=MISSING, ) @@ -71,13 +127,13 @@ class KickFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", pr class TimeoutFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"): duration: str = commands.flag( name="duration", - description="The duration of the timeout. (e.g. 1d, 1h, 1m)", + description="Duration of the timeout. (e.g. 1d, 1h, 1m)", aliases=["d"], default=MISSING, ) reason: str = commands.flag( name="reason", - description="The reason for the timeout.", + description="Reason for the timeout.", aliases=["r"], default=MISSING, ) @@ -92,7 +148,7 @@ class TimeoutFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", class UntimeoutFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"): reason: str = commands.flag( name="reason", - description="The reason for the untimeout.", + description="Reason for the untimeout.", aliases=["r"], default=MISSING, ) @@ -105,16 +161,9 @@ class UntimeoutFlags(commands.FlagConverter, case_insensitive=True, delimiter=" class UnbanFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"): - username_or_id: str = commands.flag( - name="username_or_id", - description="The username or ID of the user.", - aliases=["u"], - default=MISSING, - positional=True, - ) reason: str = commands.flag( name="reason", - description="The reason for the unban.", + description="Reason for the unban.", aliases=["r"], default=MISSING, ) @@ -123,7 +172,7 @@ class UnbanFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", p class JailFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"): reason: str = commands.flag( name="reason", - description="The reason for the jail.", + description="Reason for the jail.", aliases=["r"], default=MISSING, ) @@ -138,7 +187,7 @@ class JailFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", pr class UnjailFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"): reason: str = commands.flag( name="reason", - description="The reason for the unjail.", + description="Reason for the unjail.", aliases=["r"], default=MISSING, ) @@ -153,20 +202,20 @@ class UnjailFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", class CasesViewFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"): type: CaseType = commands.flag( name="case_type", - description="The case type to view.", + description="Type of case to view.", aliases=["t"], default=None, converter=CaseTypeConverter, ) target: discord.User = commands.flag( name="case_target", - description="The user to view cases for.", + description="User to view cases for.", aliases=["user", "u", "member", "memb", "m"], default=None, ) moderator: discord.User = commands.flag( name="case_moderator", - description="The moderator to view cases for.", + description="Moderator to view cases for.", aliases=["mod"], default=None, ) @@ -175,12 +224,12 @@ class CasesViewFlags(commands.FlagConverter, case_insensitive=True, delimiter=" class CaseModifyFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"): status: bool | None = commands.flag( name="case_status", - description="The status of the case.", + description="Status of the case.", aliases=["s"], ) reason: str | None = commands.flag( name="case_reason", - description="The modified reason.", + description="Modified reason.", aliases=["r"], ) @@ -188,7 +237,7 @@ class CaseModifyFlags(commands.FlagConverter, case_insensitive=True, delimiter=" class WarnFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"): reason: str = commands.flag( name="reason", - description="The reason for the warn.", + description="Reason for the warn.", aliases=["r"], default=MISSING, ) @@ -203,7 +252,7 @@ class WarnFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", pr class SnippetBanFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"): reason: str = commands.flag( name="reason", - description="The reason for the snippet ban.", + description="Reason for the snippet ban.", aliases=["r"], default=MISSING, ) @@ -218,7 +267,7 @@ class SnippetBanFlags(commands.FlagConverter, case_insensitive=True, delimiter=" class SnippetUnbanFlags(commands.FlagConverter, case_insensitive=True, delimiter=" ", prefix="-"): reason: str = commands.flag( name="reason", - description="The reason for the snippet unban.", + description="Reason for the snippet unban.", aliases=["r"], default=MISSING, ) diff --git a/tux/utils/functions.py b/tux/utils/functions.py index 2f32761..1245e99 100644 --- a/tux/utils/functions.py +++ b/tux/utils/functions.py @@ -1,5 +1,5 @@ import re -from datetime import UTC, datetime +from datetime import UTC, datetime, timedelta from typing import Any import discord @@ -26,6 +26,49 @@ def strip_formatting(content: str) -> str: return content.strip() +def parse_time_string(time_str: str) -> timedelta: + """ + Convert a string representation of time into a datetime.timedelta object. + + Parameters + ---------- + time_str : str + The string representation of time to convert. (e.g., '60s', '1m', '2h', '10d') + + Returns + ------- + timedelta + The timedelta object representing the time string. + """ + + # Define regex pattern to parse time strings + time_pattern = re.compile(r"^(?P\d+)(?P[smhdw])$") + + # Match the input string with the pattern + match = time_pattern.match(time_str) + + if not match: + msg = f"Invalid time format: '{time_str}'" + raise ValueError(msg) + + # Extract the value and unit from the pattern match + value = int(match["value"]) + unit = match["unit"] + + # Define the mapping of units to keyword arguments for timedelta + unit_map = {"s": "seconds", "m": "minutes", "h": "hours", "d": "days", "w": "weeks"} + + # Check if the unit is in the map + if unit not in unit_map: + msg = f"Unknown time unit: '{unit}'" + raise ValueError(msg) + + # Create the timedelta with the appropriate keyword argument + kwargs = {unit_map[unit]: value} + + return timedelta(**kwargs) + + def convert_to_seconds(time_str: str) -> int: """ Converts a formatted time string with the formats Mwdhms