From 30e0de45c2685070bd66d495a7acbdc05f0f7804 Mon Sep 17 00:00:00 2001 From: BobDotCom <71356958+BobDotCom@users.noreply.github.com> Date: Tue, 4 Oct 2022 09:29:54 -0500 Subject: [PATCH] Implement pre-commit (#1664) * Add pre-commit * Fix docstring formatter * Fix docstring formatter even more * Add black configuration * Run pre-commit formatting --- .flake8 | 7 + .github/CODE_OF_CONDUCT.md | 138 +-- .github/CONTRIBUTING.md | 73 +- .github/DEVELOPER_CERTIFICATE_OF_ORIGIN.md | 1 + .github/ISSUE_TEMPLATE/bug_report.yml | 15 +- .github/ISSUE_TEMPLATE/feature_request.yml | 8 +- .github/PULL_REQUEST_TEMPLATE.md | 3 +- .github/SECURITY.md | 1 + .github/workflows/bandit.yml | 2 +- .github/workflows/codeql-analysis.yml | 54 +- .github/workflows/codespell.yml | 6 +- .github/workflows/docs.yml | 28 +- .github/workflows/mypy.yml | 4 +- .github/workflows/pr-deps.yml | 8 +- .github/workflows/python-app.yml | 8 +- .pre-commit-config.yaml | 90 ++ .prettierrc | 3 + .pylintrc | 7 +- CHANGELOG.md | 338 +++-- README.rst | 2 +- discord/__init__.py | 5 +- discord/__main__.py | 34 +- discord/abc.py | 438 ++++--- discord/activity.py | 160 ++- discord/appinfo.py | 45 +- discord/asset.py | 104 +- discord/audit_logs.py | 191 +-- discord/automod.py | 236 ++-- discord/backoff.py | 8 +- discord/bot.py | 336 +++-- discord/channel.py | 792 ++++++------ discord/client.py | 497 ++++---- discord/cog.py | 256 ++-- discord/colour.py | 10 +- discord/commands/context.py | 67 +- discord/commands/core.py | 363 ++++-- discord/commands/options.py | 147 ++- discord/commands/permissions.py | 12 +- discord/components.py | 86 +- discord/context_managers.py | 14 +- discord/embeds.py | 94 +- discord/emoji.py | 30 +- discord/enums.py | 52 +- discord/errors.py | 69 +- discord/ext/bridge/bot.py | 6 +- discord/ext/bridge/context.py | 34 +- discord/ext/bridge/core.py | 121 +- discord/ext/commands/_types.py | 4 +- discord/ext/commands/bot.py | 52 +- discord/ext/commands/cog.py | 6 +- discord/ext/commands/context.py | 68 +- discord/ext/commands/converter.py | 160 ++- discord/ext/commands/cooldowns.py | 50 +- discord/ext/commands/core.py | 455 +++---- discord/ext/commands/errors.py | 179 +-- discord/ext/commands/flags.py | 139 ++- discord/ext/commands/help.py | 141 ++- discord/ext/commands/view.py | 2 +- discord/ext/pages/pagination.py | 211 +++- discord/ext/tasks/__init__.py | 127 +- discord/file.py | 24 +- discord/flags.py | 84 +- discord/gateway.py | 42 +- discord/guild.py | 1237 ++++++++++--------- discord/http.py | 773 +++++++----- discord/integrations.py | 42 +- discord/interactions.py | 273 ++-- discord/invite.py | 132 +- discord/iterators.py | 69 +- discord/member.py | 207 ++-- discord/mentions.py | 20 +- discord/message.py | 445 ++++--- discord/object.py | 6 +- discord/oggparse.py | 8 +- discord/opus.py | 48 +- discord/partial_emoji.py | 32 +- discord/permissions.py | 171 ++- discord/player.py | 177 +-- discord/raw_models.py | 138 ++- discord/reaction.py | 66 +- discord/role.py | 80 +- discord/scheduled_events.py | 186 +-- discord/shard.py | 82 +- discord/sinks/core.py | 4 +- discord/sinks/errors.py | 2 - discord/sinks/m4a.py | 16 +- discord/sinks/mka.py | 8 +- discord/sinks/mkv.py | 8 +- discord/sinks/mp3.py | 8 +- discord/sinks/mp4.py | 16 +- discord/sinks/ogg.py | 8 +- discord/sinks/wave.py | 4 +- discord/stage_instance.py | 30 +- discord/state.py | 288 +++-- discord/sticker.py | 94 +- discord/team.py | 24 +- discord/template.py | 64 +- discord/threads.py | 199 +-- discord/types/activity.py | 18 +- discord/types/appinfo.py | 8 +- discord/types/audit_log.py | 30 +- discord/types/automod.py | 42 +- discord/types/components.py | 6 +- discord/types/embed.py | 4 +- discord/types/integration.py | 6 +- discord/types/interactions.py | 70 +- discord/types/invite.py | 6 +- discord/types/message.py | 36 +- discord/types/raw_models.py | 4 +- discord/types/scheduled_events.py | 10 +- discord/types/sticker.py | 6 +- discord/types/team.py | 8 +- discord/types/template.py | 8 +- discord/types/threads.py | 10 +- discord/types/voice.py | 4 +- discord/types/webhook.py | 8 +- discord/types/welcome_screen.py | 8 +- discord/ui/__init__.py | 1 - discord/ui/button.py | 56 +- discord/ui/input_text.py | 50 +- discord/ui/item.py | 38 +- discord/ui/modal.py | 70 +- discord/ui/select.py | 54 +- discord/ui/view.py | 152 +-- discord/user.py | 61 +- discord/utils.py | 216 ++-- discord/voice_client.py | 90 +- discord/webhook/__init__.py | 1 - discord/webhook/async_.py | 573 +++++---- discord/webhook/sync.py | 319 ++--- discord/welcome_screen.py | 66 +- discord/widget.py | 55 +- docs/_static/codeblocks.css | 603 ++++++--- docs/_static/copy.js | 4 +- docs/_static/custom.js | 47 +- docs/_static/icons.css | 10 +- docs/_static/scorer.js | 111 +- docs/_static/settings.js | 41 +- docs/_static/sidebar.js | 67 +- docs/_static/style.css | 179 ++- docs/_templates/genindex.html | 41 +- docs/_templates/layout.html | 441 ++++--- docs/api.rst | 34 +- docs/conf.py | 8 +- docs/ext/commands/api.rst | 2 +- docs/extensions/attributetable.py | 13 +- docs/extensions/builder.py | 7 +- docs/extensions/details.py | 8 +- docs/extensions/nitpick_file_ignorer.py | 5 +- docs/migrating_to_v2.rst | 2 +- examples/app_commands/context_menus.py | 8 +- examples/app_commands/info.py | 4 +- examples/app_commands/slash_autocomplete.py | 20 +- examples/app_commands/slash_basic.py | 8 +- examples/app_commands/slash_cog.py | 4 +- examples/app_commands/slash_cog_groups.py | 16 +- examples/app_commands/slash_groups.py | 4 +- examples/app_commands/slash_options.py | 6 +- examples/audio_recording.py | 4 +- examples/background_task.py | 4 +- examples/basic_bot.py | 6 +- examples/basic_voice.py | 17 +- examples/bridge_commands.py | 9 +- examples/converters.py | 20 +- examples/cooldown.py | 21 +- examples/create_private_emoji.py | 16 +- examples/custom_context.py | 16 +- examples/deleted.py | 7 +- examples/edits.py | 9 +- examples/modal_dialogs.py | 28 +- examples/new_member.py | 6 +- examples/reaction_roles.py | 19 +- examples/reply.py | 6 +- examples/secret.py | 26 +- examples/timeout.py | 4 +- examples/views/confirm.py | 12 +- examples/views/counter.py | 4 +- examples/views/dropdown.py | 16 +- examples/views/ephemeral.py | 8 +- examples/views/paginator.py | 70 +- examples/views/persistent.py | 18 +- examples/wait_for_event.py | 18 +- mypy.ini | 3 +- pyproject.toml | 2 + requirements-dev.txt | 1 + setup.py | 4 +- tests/helpers.py | 2 +- tests/test_utils.py | 25 +- 188 files changed, 9326 insertions(+), 6574 deletions(-) create mode 100644 .flake8 create mode 100644 .pre-commit-config.yaml create mode 100644 .prettierrc create mode 100644 pyproject.toml diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000000..6148720365 --- /dev/null +++ b/.flake8 @@ -0,0 +1,7 @@ +[flake8] +min_python_version = 3.8 +ignore = + E203, W503, # Incompatible with black see https://github.com/ambv/black/issues/315 + +max-line-length=120 +max-complexity=39 diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index 083a4f0651..4f0af8ed63 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -2,124 +2,116 @@ ## Our Pledge -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. +We as members, contributors, and leaders pledge to make participation in our community a +harassment-free experience for everyone, regardless of age, body size, visible or +invisible disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal appearance, +race, religion, or sexual identity and orientation. -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, +inclusive, and healthy community. ## Our Standards -Examples of behavior that contributes to a positive environment for our -community include: +Examples of behavior that contributes to a positive environment for our community +include: -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the - overall community +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, and + learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting +- The use of sexualized language or imagery, and sexual attention or advances of any + kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, without + their explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional + setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. +acceptable behavior and will take appropriate and fair corrective action in response to +any behavior that they deem inappropriate, threatening, offensive, or harmful. -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. +Community leaders have the right and responsibility to remove, edit, or reject comments, +commits, code, wiki edits, issues, and other contributions that are not aligned to this +Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. +This Code of Conduct applies within all community spaces, and also applies when an +individual is officially representing the community in public spaces. Examples of +representing our community include using an official e-mail address, posting via an +official social media account, or acting as an appointed representative at an online or +offline event. ## Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -Our Discord server. -All complaints will be reviewed and investigated promptly and fairly. +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to +the community leaders responsible for enforcement at Our Discord server. All complaints +will be reviewed and investigated promptly and fairly. -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. +All community leaders are obligated to respect the privacy and security of the reporter +of any incident. ## Enforcement Guidelines -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: +Community leaders will follow these Community Impact Guidelines in determining the +consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. +**Consequence**: A private, written warning from community leaders, providing clarity +around the nature of the violation and an explanation of why the behavior was +inappropriate. A public apology may be requested. ### 2. Warning -**Community Impact**: A violation through a single incident or series -of actions. +**Community Impact**: A violation through a single incident or series of actions. -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. +**Consequence**: A warning with consequences for continued behavior. No interaction with +the people involved, including unsolicited interaction with those enforcing the Code of +Conduct, for a specified period of time. This includes avoiding interactions in +community spaces as well as external channels like social media. Violating these terms +may lead to a temporary or permanent ban. ### 3. Temporary Ban -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. +**Community Impact**: A serious violation of community standards, including sustained +inappropriate behavior. -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. +**Consequence**: A temporary ban from any sort of interaction or public communication +with the community for a specified period of time. No public or private interaction with +the people involved, including unsolicited interaction with those enforcing the Code of +Conduct, is allowed during this period. Violating these terms may lead to a permanent +ban. ### 4. Permanent Ban -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. +**Community Impact**: Demonstrating a pattern of violation of community standards, +including sustained inappropriate behavior, harassment of an individual, or aggression +toward or disparagement of classes of individuals. -**Consequence**: A permanent ban from any sort of public interaction within -the community. +**Consequence**: A permanent ban from any sort of public interaction within the +community. ## Attribution -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, +available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 1c0ddeae61..5925c3bc8c 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,8 +1,10 @@ ## Contributing to Pycord -First off, thanks for taking the time to contribute. It makes the library substantially better. :+1: +First off, thanks for taking the time to contribute. It makes the library substantially +better. :+1: -The following is a set of guidelines for contributing to the repository. These are guidelines, not hard rules. +The following is a set of guidelines for contributing to the repository. These are +guidelines, not hard rules. ## This is too much to read! I want to ask a question! @@ -12,44 +14,71 @@ Generally speaking questions are better suited in our resources below. - [The FAQ in the documentation](https://docs.pycord.dev/en/master/faq.html) - [StackOverflow's `pycord` tag](https://stackoverflow.com/questions/tagged/pycord) -Please try your best not to ask questions in our issue tracker. Most of them don't belong there unless they provide value to a larger audience. +Please try your best not to ask questions in our issue tracker. Most of them don't +belong there unless they provide value to a larger audience. ## Good Bug Reports Please be aware of the following things when filing bug reports. -1. Don't open duplicate issues. Please search your issue to see if it has been asked already. Duplicate issues will be closed. -2. When filing a bug about exceptions or tracebacks, please include the *complete* traceback. Without the complete traceback the issue might be **unsolvable** and you will be asked to provide more information. -3. Make sure to provide enough information to make the issue workable. The issue template will generally walk you through the process but they are enumerated here as well: - - A **summary** of your bug report. This is generally a quick sentence or two to describe the issue in human terms. - - Guidance on **how to reproduce the issue**. Ideally, this should have a small code sample that allows us to run and see the issue for ourselves to debug. **Please make sure that the token is not displayed**. If you cannot provide a code snippet, then let us know what the steps were, how often it happens, etc. - - Tell us **what you expected to happen**. That way we can meet that expectation. - - Tell us **what actually happens**. What ends up happening in reality? It's not helpful to say "it fails" or "it doesn't work". Say *how* it failed, do you get an exception? Does it hang? How are the expectations different from reality? - - Tell us **information about your environment**. What version of Pycord are you using? How was it installed? What operating system are you running on? These are valuable questions and information that we use. - -If the bug report is missing this information then it'll take us longer to fix the issue. We will probably ask for clarification, and barring that if no response was given then the issue will be closed. +1. Don't open duplicate issues. Please search your issue to see if it has been asked + already. Duplicate issues will be closed. +2. When filing a bug about exceptions or tracebacks, please include the _complete_ + traceback. Without the complete traceback the issue might be **unsolvable** and you + will be asked to provide more information. +3. Make sure to provide enough information to make the issue workable. The issue + template will generally walk you through the process but they are enumerated here as + well: + - A **summary** of your bug report. This is generally a quick sentence or two to + describe the issue in human terms. + - Guidance on **how to reproduce the issue**. Ideally, this should have a small code + sample that allows us to run and see the issue for ourselves to debug. **Please + make sure that the token is not displayed**. If you cannot provide a code snippet, + then let us know what the steps were, how often it happens, etc. + - Tell us **what you expected to happen**. That way we can meet that expectation. + - Tell us **what actually happens**. What ends up happening in reality? It's not + helpful to say "it fails" or "it doesn't work". Say _how_ it failed, do you get an + exception? Does it hang? How are the expectations different from reality? + - Tell us **information about your environment**. What version of Pycord are you + using? How was it installed? What operating system are you running on? These are + valuable questions and information that we use. + +If the bug report is missing this information then it'll take us longer to fix the +issue. We will probably ask for clarification, and barring that if no response was given +then the issue will be closed. ## Submitting a Pull Request -Submitting a pull request is fairly simple, just make sure it focuses on a single aspect and doesn't manage to have scope creep and it's probably good to go. It would be incredibly lovely if the style is consistent to that found in the project. This project follows PEP-8 guidelines (mostly) with a column limit of 120. +Submitting a pull request is fairly simple, just make sure it focuses on a single aspect +and doesn't manage to have scope creep and it's probably good to go. It would be +incredibly lovely if the style is consistent to that found in the project. This project +follows PEP-8 guidelines (mostly) with a column limit of 120. ## Use of "type: ignore" comments -In some cases, it might be necessary to ignore type checker warnings for one reason or another. -If that is that case, it is **required** that a comment is left explaining why you are -deciding to ignore type checking warnings. + +In some cases, it might be necessary to ignore type checker warnings for one reason or +another. If that is that case, it is **required** that a comment is left explaining why +you are deciding to ignore type checking warnings. ### Licensing -By submitting a pull request, you agree that; 1) You hold the copyright on all submitted code inside said pull request; 2) You agree to transfer all rights to the owner of this repository, and; 3) If you are found to be in fault with any of the above, we shall not be held responsible in any way after the pull request has been merged. +By submitting a pull request, you agree that; 1) You hold the copyright on all submitted +code inside said pull request; 2) You agree to transfer all rights to the owner of this +repository, and; 3) If you are found to be in fault with any of the above, we shall not +be held responsible in any way after the pull request has been merged. ## Git Commit Styling -Not following this guideline could lead to your pull being squashed for a cleaner commit history +Not following this guideline could lead to your pull being squashed for a cleaner commit +history Some style guides we would recommend using in your pulls: -The [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) style is a very widely used style and a good style to start with. +The [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) style is a +very widely used style and a good style to start with. -The [gitmoji](https://gitmoji.dev) style guide would make your pull look more lively and different to others. +The [gitmoji](https://gitmoji.dev) style guide would make your pull look more lively and +different to others. -We don't limit nor deny your pulls when you're using another style although, please make sure it is appropriate and makes sense in this library. +We don't limit nor deny your pulls when you're using another style although, please make +sure it is appropriate and makes sense in this library. diff --git a/.github/DEVELOPER_CERTIFICATE_OF_ORIGIN.md b/.github/DEVELOPER_CERTIFICATE_OF_ORIGIN.md index 3679a30804..e003b3bd6d 100644 --- a/.github/DEVELOPER_CERTIFICATE_OF_ORIGIN.md +++ b/.github/DEVELOPER_CERTIFICATE_OF_ORIGIN.md @@ -1,4 +1,5 @@ # Developer Certificate of Origin (DCO) + ``` Version 1.1 diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 9a00277cb5..ac8bf3c65e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -5,8 +5,8 @@ body: - type: markdown attributes: value: > - Thanks for taking the time to fill out a bug. - If you want real-time support, consider joining our Discord at https://pycord.dev/discord instead. + Thanks for taking the time to fill out a bug. If you want real-time support, + consider joining our Discord at https://pycord.dev/discord instead. Please note that this form is for bugs only! - type: input @@ -19,7 +19,7 @@ body: attributes: label: Reproduction Steps description: > - What you did to make it happen. + What you did to make it happen. validations: required: true - type: textarea @@ -46,8 +46,8 @@ body: attributes: label: Intents description: > - What intents are you using for your bot? - This is the `discord.Intents` class you pass to the client. + What intents are you using for your bot? This is the `discord.Intents` class you + pass to the client. validations: required: true - type: textarea @@ -56,8 +56,9 @@ body: description: > Run `python -m discord -v` and paste this information below. - This command required v1.1.0 or higher of the library. If this errors out then show some basic - information involving your system such as operating system and Python version. + This command required v1.1.0 or higher of the library. If this errors out then + show some basic information involving your system such as operating system and + Python version. validations: required: true - type: checkboxes diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index c2edbb1b77..67572c9bea 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -26,16 +26,16 @@ body: attributes: label: The Problem description: > - What problem is your feature trying to solve? - What becomes easier or possible when this feature is implemented? + What problem is your feature trying to solve? What becomes easier or possible + when this feature is implemented? validations: required: true - type: textarea attributes: label: The Ideal Solution description: > - What is your ideal solution to the problem? - What would you like this feature to do? + What is your ideal solution to the problem? What would you like this feature to + do? validations: required: true - type: textarea diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 4607c4fa7e..febe190ff6 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -9,7 +9,8 @@ - [ ] This PR fixes an issue. - [ ] This PR adds something new (e.g. new method or parameters). - [ ] This PR is a breaking change (e.g. methods or parameters removed/renamed). -- [ ] This PR is **not** a code change (e.g. documentation, README, typehinting, examples, ...). +- [ ] This PR is **not** a code change (e.g. documentation, README, typehinting, + examples, ...). ## Checklist diff --git a/.github/SECURITY.md b/.github/SECURITY.md index d1401a94b7..8cf63b4949 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -10,5 +10,6 @@ ## Reporting a Vulnerability If you find a vulnerability you have two ways to report it: + - Write a dm to one of our core developers on https://pycord.dev/discord - Write an email to admin@pycord.dev diff --git a/.github/workflows/bandit.yml b/.github/workflows/bandit.yml index 73ce63c2a7..1ca5dad136 100644 --- a/.github/workflows/bandit.yml +++ b/.github/workflows/bandit.yml @@ -2,7 +2,7 @@ name: bandit on: [pull_request, push] jobs: bandit: -# if: github.event.pull_request.user.type != 'Bot' && !contains(github.event.pull_request.labels.*.name, 'skip-ci') + # if: github.event.pull_request.user.type != 'Bot' && !contains(github.event.pull_request.labels.*.name, 'skip-ci') runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 0517eeba2c..970be47be4 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -15,7 +15,7 @@ on: push: null pull_request: null schedule: - - cron: '26 6 * * 6' + - cron: "26 6 * * 6" jobs: analyze: @@ -30,40 +30,40 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'python' ] + language: ["python"] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # Learn more: # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed steps: - - name: Checkout repository - uses: actions/checkout@v2 + - name: Checkout repository + uses: actions/checkout@v2 - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 - # ℹī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl - # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language + # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language - #- run: | - # make bootstrap - # make release + #- run: | + # make bootstrap + # make release - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 6321a51781..31955631c8 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -2,10 +2,12 @@ name: codespell on: [pull_request, push] jobs: codespell: -# if: github.event.pull_request.user.type != 'Bot' && !contains(github.event.pull_request.labels.*.name, 'skip-ci') + # if: github.event.pull_request.user.type != 'Bot' && !contains(github.event.pull_request.labels.*.name, 'skip-ci') runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 - run: pip install codespell==2.1.0 - - run: codespell --ignore-words-list="groupt,nd,ot,ro,falsy,BU" --exclude-file=".github/workflows/codespell.yml" + - run: + codespell --ignore-words-list="groupt,nd,ot,ro,falsy,BU" \ + --exclude-file=".github/workflows/codespell.yml" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 034e3fba66..3ae086301f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -7,18 +7,18 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ 3.8 ] + python-version: [3.8] steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install -U pip - pip install -U sphinx sphinxcontrib-trio aiohttp sphinxcontrib-websupport myst-parser - - name: Compile to html - run: | - cd docs - make html + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install -U pip + pip install -U sphinx sphinxcontrib-trio aiohttp sphinxcontrib-websupport myst-parser + - name: Compile to html + run: | + cd docs + make html diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index f385ee3135..20fb2023f2 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -1,5 +1,5 @@ name: mypy -on: [ pull_request, push ] +on: [pull_request, push] jobs: mypy: # if: github.event.pull_request.user.type != 'Bot' && !contains(github.event.pull_request.labels.*.name, 'skip-ci') @@ -9,4 +9,4 @@ jobs: - uses: actions/setup-python@v2 - run: pip install -r requirements-dev.txt - run: mkdir --parents --verbose .mypy_cache - - run: mypy --non-interactive . \ No newline at end of file + - run: mypy --non-interactive discord/ diff --git a/.github/workflows/pr-deps.yml b/.github/workflows/pr-deps.yml index fc6976dcd8..9d8d09317d 100644 --- a/.github/workflows/pr-deps.yml +++ b/.github/workflows/pr-deps.yml @@ -1,6 +1,6 @@ name: PR Dependencies -on: +on: pull_request: types: [opened, synchronize, reopened, edited] @@ -9,6 +9,6 @@ jobs: runs-on: ubuntu-latest name: Check Dependencies steps: - - uses: gregsdennis/dependencies-action@main - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: gregsdennis/dependencies-action@main + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index e7cc6564ba..4defe80eea 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -3,15 +3,15 @@ name: Python application -on: [ push, pull_request ] +on: [push, pull_request] jobs: test: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ ubuntu-latest, macos-latest, windows-latest ] - python-version: [ 3.8, 3.9, "3.10" ] + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: [3.8, 3.9, "3.10"] env: OS: ${{ matrix.os }} PYTHON: ${{ matrix.python-version }} @@ -44,5 +44,5 @@ jobs: files: ./coverage.xml flags: pytest # optional name: codecov-umbrella # optional - fail_ci_if_error: false # Enable once we have good coverage + fail_ci_if_error: false # Enable once we have good coverage verbose: true # optional (default = false) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000..14d1663988 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,90 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - repo: https://github.com/PyCQA/autoflake + rev: v1.6.1 + hooks: + - id: autoflake + # args: + # - --in-place + # - --remove-all-unused-imports + # - --expand-star-imports + # - --remove-duplicate-keys + # - --remove-unused-variables + - repo: https://github.com/asottile/pyupgrade + rev: v2.38.2 + hooks: + - id: pyupgrade + args: [--py38-plus] + - repo: https://github.com/PyCQA/isort + rev: 5.10.1 + hooks: + - id: isort + - repo: https://github.com/psf/black + rev: 22.8.0 + hooks: + - id: black + args: [--safe, --quiet] + # - repo: https://github.com/Pierre-Sassoulas/black-disable-checker + # rev: 1.0.1 + # hooks: + # - id: black-disable-checker + # - repo: https://github.com/PyCQA/flake8 + # rev: 4.0.1 + # hooks: + # - id: flake8 + # additional_dependencies: [flake8-typing-imports==1.12.0] + # - repo: local + # hooks: + # - id: pylint + # name: pylint + # entry: pylint + # language: system + # types: [python] + # args: ["-rn", "-sn", "--rcfile=.pylintrc", "--fail-on=I"] + # # We define an additional manual step to allow running pylint with a spelling + # # checker in CI. + # - id: pylint + # alias: pylint-with-spelling + # name: pylint + # entry: pylint + # language: system + # types: [python] + # args: ["-rn", "-sn", "--rcfile=.pylintrc", "--fail-on=I", "--spelling-dict=en"] + # stages: [manual] + # - id: mypy + # name: mypy + # entry: mypy + # language: system + # types: [python] + # args: ["--non-interactive"] + # - repo: https://github.com/myint/rstcheck + # rev: "v5.0.0" + # hooks: + # - id: rstcheck + # args: ["--ignore-roles=func,class,mod", "--report=warning"] + # types: [text] + # - repo: https://github.com/pre-commit/mirrors-mypy + # rev: v0.950 + # hooks: + # - id: mypy + + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v3.0.0-alpha.0 + hooks: + - id: prettier + args: [--prose-wrap=always, --print-width=88] + - repo: https://github.com/DanielNoord/pydocstringformatter + rev: 93b15ca # TODO: Change this when v8.0 is released + hooks: + - id: pydocstringformatter + args: + [ + --style=numpydoc, + --no-numpydoc-name-type-spacing, + --no-final-period, + --no-capitalize-first-letter, + ] diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000..beda5ba4da --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +proseWrap: always +printWidth: 88 +endOfLine: auto diff --git a/.pylintrc b/.pylintrc index 837651426d..ccb1249f6b 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,8 +1,11 @@ -[MESSAGES CONTROL] +[MASTER] +extension-pkg-whitelist=pydantic,ujson +py-version=3.8 -disable=protected-access,fixme +[MESSAGES CONTROL] enable=bad-indentation,line-too-long +disable=protected-access,fixme [FORMAT] diff --git a/CHANGELOG.md b/CHANGELOG.md index dc00612f58..94525a67eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,277 +1,381 @@ # Changelog + All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) when possible (see our -[Version Guarantees](https://docs.pycord.dev/en/stable/version_guarantees.html) for more info). +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and +this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) when +possible (see our +[Version Guarantees](https://docs.pycord.dev/en/stable/version_guarantees.html) for more +info). ## [Unreleased] ## [2.2.0] - 2022-10-02 + ### Added -- New Guild Feature `INVITES_DISABLED` ([#1613](https://github.com/Pycord-Development/pycord/pull/1613)) -- `suppress` kwarg to `Messageable.send()` ([#1587](https://github.com/Pycord-Development/pycord/pull/1587)) + +- New Guild Feature `INVITES_DISABLED` + ([#1613](https://github.com/Pycord-Development/pycord/pull/1613)) +- `suppress` kwarg to `Messageable.send()` + ([#1587](https://github.com/Pycord-Development/pycord/pull/1587)) - `proxy` and `proxy_auth` params to many Webhook related methods. ([#1655](https://github.com/Pycord-Development/pycord/pull/1655)) -- `delete_message_seconds` parameter in ban methods. ([#1557](https://github.com/Pycord-Development/pycord/pull/1557)) -- New `View.get_item()` method. ([#1659](https://github.com/Pycord-Development/pycord/pull/1659)) -- Permissions support for bridge commands. ([#1642](https://github.com/Pycord-Development/pycord/pull/1642)) -- New `BridgeCommand.invoke()` method. ([#1642](https://github.com/Pycord-Development/pycord/pull/1642)) -- New `raw_mentions`, `raw_role_mentions` and `raw_channel_mentions` functions in `discord.utils`. - ([#1658](https://github.com/Pycord-Development/pycord/pull/1658)) -- New methods `original_response`, `edit_original_response` & `delete_original_response` for `Interaction` objects. +- `delete_message_seconds` parameter in ban methods. + ([#1557](https://github.com/Pycord-Development/pycord/pull/1557)) +- New `View.get_item()` method. + ([#1659](https://github.com/Pycord-Development/pycord/pull/1659)) +- Permissions support for bridge commands. + ([#1642](https://github.com/Pycord-Development/pycord/pull/1642)) +- New `BridgeCommand.invoke()` method. + ([#1642](https://github.com/Pycord-Development/pycord/pull/1642)) +- New `raw_mentions`, `raw_role_mentions` and `raw_channel_mentions` functions in + `discord.utils`. ([#1658](https://github.com/Pycord-Development/pycord/pull/1658)) +- New methods `original_response`, `edit_original_response` & `delete_original_response` + for `Interaction` objects. ([#1609](https://github.com/Pycord-Development/pycord/pull/1609)) - ### Deprecated -- The `delete_message_days` parameter in ban methods is now deprecated. Please use `delete_message_seconds` instead. + +- The `delete_message_days` parameter in ban methods is now deprecated. Please use + `delete_message_seconds` instead. ([#1557](https://github.com/Pycord-Development/pycord/pull/1557)) -- The `original_message`, `edit_original_message` & `delete_original_message` methods for `Interaction` are now deprecated. Please use the respective `original_response`, `edit_original_response` & `delete_original_response` methods instead. +- The `original_message`, `edit_original_message` & `delete_original_message` methods + for `Interaction` are now deprecated. Please use the respective `original_response`, + `edit_original_response` & `delete_original_response` methods instead. ([#1609](https://github.com/Pycord-Development/pycord/pull/1609)) ### Fixed -- Various fixes to ext.bridge groups ([#1633](https://github.com/Pycord-Development/pycord/pull/1633) & + +- Various fixes to ext.bridge groups + ([#1633](https://github.com/Pycord-Development/pycord/pull/1633) & [#1631](https://github.com/Pycord-Development/pycord/pull/1631)) -- Fix `VOICE_SERVER_UPDATE` error ([#1624](https://github.com/Pycord-Development/pycord/pull/1624)) -- Removed unnecessary instance check in autocomplete. ([#1643](https://github.com/Pycord-Development/pycord/pull/1643)) -- Interaction responses are now passed the respective `proxy` and `proxy_auth` params as defined in `Client`. - ([#1655](https://github.com/Pycord-Development/pycord/pull/1655)) +- Fix `VOICE_SERVER_UPDATE` error + ([#1624](https://github.com/Pycord-Development/pycord/pull/1624)) +- Removed unnecessary instance check in autocomplete. + ([#1643](https://github.com/Pycord-Development/pycord/pull/1643)) +- Interaction responses are now passed the respective `proxy` and `proxy_auth` params as + defined in `Client`. ([#1655](https://github.com/Pycord-Development/pycord/pull/1655)) ## [2.1.3] - 2022-09-06 + ### Fixed -- Fix TypeError in `process_application_commands`. ([#1622](https://github.com/Pycord-Development/pycord/pull/1622)) + +- Fix TypeError in `process_application_commands`. + ([#1622](https://github.com/Pycord-Development/pycord/pull/1622)) ## [2.1.2] - 2022-09-06 + ### Fixed -- Fix subcommands having MISSING cog attribute. ([#1594](https://github.com/Pycord-Development/pycord/pull/1594) & + +- Fix subcommands having MISSING cog attribute. + ([#1594](https://github.com/Pycord-Development/pycord/pull/1594) & [#1605](https://github.com/Pycord-Development/pycord/pull/1605)) ## [2.1.1] - 2022-08-25 + ### Fixed -- Bridge command detection in cogs. ([#1592](https://github.com/Pycord-Development/pycord/pull/1592)) + +- Bridge command detection in cogs. + ([#1592](https://github.com/Pycord-Development/pycord/pull/1592)) ## [2.1.0] - 2022-08-25 + ### Added -- Support for add, sub, union, intersect, and inverse operations on classes inheriting from `BaseFlags`. - ([#1486](https://github.com/Pycord-Development/pycord/pull/1486)) + +- Support for add, sub, union, intersect, and inverse operations on classes inheriting + from `BaseFlags`. ([#1486](https://github.com/Pycord-Development/pycord/pull/1486)) - A `disable_on_timeout` kwarg in the `View` constructor. ([#1492](https://github.com/Pycord-Development/pycord/pull/1492)) -- New `mention` property for `SlashCommand` objects, allowing a shortcut for the new command markdown syntax. +- New `mention` property for `SlashCommand` objects, allowing a shortcut for the new + command markdown syntax. ([#1523](https://github.com/Pycord-Development/pycord/pull/1523)) -- An `app_commands_badge` value on `ApplicationFlags`. ([#1535](https://github.com/Pycord-Development/pycord/pull/1535) - and [#1553](https://github.com/Pycord-Development/pycord/pull/1553)) +- An `app_commands_badge` value on `ApplicationFlags`. + ([#1535](https://github.com/Pycord-Development/pycord/pull/1535) and + [#1553](https://github.com/Pycord-Development/pycord/pull/1553)) - A new `fetch_application` method in the `Client` object. ([#1536](https://github.com/Pycord-Development/pycord/pull/1536)) - New `on_check_failure` event method for the `View` class. ([#799](https://github.com/Pycord-Development/pycord/pull/799)) -- A `set_mfa_required` method to `Guild`. ([#1552](https://github.com/Pycord-Development/pycord/pull/1552)) -- Support for command groups with bridge commands. ([#1496](https://github.com/Pycord-Development/pycord/pull/1496)) +- A `set_mfa_required` method to `Guild`. + ([#1552](https://github.com/Pycord-Development/pycord/pull/1552)) +- Support for command groups with bridge commands. + ([#1496](https://github.com/Pycord-Development/pycord/pull/1496)) - Support for `Attachment` type options for bridge commands. ([#1496](https://github.com/Pycord-Development/pycord/pull/1496)) - `is_app` property for `BridgeContext` to better differentiate context types. ([#1496](https://github.com/Pycord-Development/pycord/pull/1496)) -- Support for localization on bridge commands. ([#1496](https://github.com/Pycord-Development/pycord/pull/1496)) +- Support for localization on bridge commands. + ([#1496](https://github.com/Pycord-Development/pycord/pull/1496)) - A `filter_params` helper function in `discord.utils`. ([#1496](https://github.com/Pycord-Development/pycord/pull/1496)) - Support for `InteractionMessage` via the `message` property of `View`. ([#1492](https://github.com/Pycord-Development/pycord/pull/1492)) ### Changed -- Use `slash_variant` and `ext_variant` attributes instead of `get_application_command()` and `get_ext_command()` - methods on `BridgeCommand`. ([#1496](https://github.com/Pycord-Development/pycord/pull/1496)) + +- Use `slash_variant` and `ext_variant` attributes instead of + `get_application_command()` and `get_ext_command()` methods on `BridgeCommand`. + ([#1496](https://github.com/Pycord-Development/pycord/pull/1496)) - Set `store` kwarg default to `False` in load_extension(s) method. ([#1520](https://github.com/Pycord-Development/pycord/pull/1520)) - `commands.has_permissions()` check now returns `True` in DM channels. ([#1577](https://github.com/Pycord-Development/pycord/pull/1577)) ### Fixed + - Fix `VoiceChannel`/`CategoryChannel` data being invalidated on `Option._invoke`. ([#1490](https://github.com/Pycord-Development/pycord/pull/1490)) -- Fix type issues in options.py ([#1473](https://github.com/Pycord-Development/pycord/pull/1473)) +- Fix type issues in options.py + ([#1473](https://github.com/Pycord-Development/pycord/pull/1473)) - Fix KeyError on AutoModActionExecution when the bot lacks the Message Content Intent. ([#1521](https://github.com/Pycord-Development/pycord/pull/1521)) -- Large code/documentation cleanup & minor bug fixes. ([#1476](https://github.com/Pycord-Development/pycord/pull/1476)) -- Fix `Option` with type `str` raising AttributeError when `min_length` or `max_length` kwargs are passed. - ([#1527](https://github.com/Pycord-Development/pycord/pull/1527)) +- Large code/documentation cleanup & minor bug fixes. + ([#1476](https://github.com/Pycord-Development/pycord/pull/1476)) +- Fix `Option` with type `str` raising AttributeError when `min_length` or `max_length` + kwargs are passed. ([#1527](https://github.com/Pycord-Development/pycord/pull/1527)) - Fix `load_extensions` parameters not being passed through correctly. ([#1537](https://github.com/Pycord-Development/pycord/pull/1537)) - Fix `SlashCommandGroup` descriptions to use the correct default string. ([#1539](https://github.com/Pycord-Development/pycord/pull/1539) and [#1586](https://github.com/Pycord-Development/pycord/pull/1586)) -- Fix Enum type options breaking due to `from_datatype()` method & Fix minor typing import. - ([#1541](https://github.com/Pycord-Development/pycord/pull/1541)) +- Fix Enum type options breaking due to `from_datatype()` method & Fix minor typing + import. ([#1541](https://github.com/Pycord-Development/pycord/pull/1541)) - Adjust category and guild `_channels` attributes to work with NoneType positions. ([#1530](https://github.com/Pycord-Development/pycord/pull/1530)) -- Make `SelectOption.emoji` a property. ([#1550](https://github.com/Pycord-Development/pycord/pull/1550)) -- Improve sticker creation by checking for minimum and maximum length on `name` and `description`. - ([#1546](https://github.com/Pycord-Development/pycord/pull/1546)) +- Make `SelectOption.emoji` a property. + ([#1550](https://github.com/Pycord-Development/pycord/pull/1550)) +- Improve sticker creation by checking for minimum and maximum length on `name` and + `description`. ([#1546](https://github.com/Pycord-Development/pycord/pull/1546)) - Fix threads created with a base message being set to the wrong `message_reference`. ([#1551](https://github.com/Pycord-Development/pycord/pull/1551)) - Avoid unnecessary call to `sync_commands` during runtime. ([#1563](https://github.com/Pycord-Development/pycord/pull/1563)) - Fix bug in `Modal.on_timeout()` by using `custom_id` to create timeout task. ([#1562](https://github.com/Pycord-Development/pycord/pull/1562)) -- Respect limit argument in `Guild.bans()`. ([#1573](https://github.com/Pycord-Development/pycord/pull/1573)) -- Fix `before` argument in `on_scheduled_event_update` event always set to `None` by converting ID to `int`. +- Respect limit argument in `Guild.bans()`. + ([#1573](https://github.com/Pycord-Development/pycord/pull/1573)) +- Fix `before` argument in `on_scheduled_event_update` event always set to `None` by + converting ID to `int`. ([#1580](https://github.com/Pycord-Development/pycord/pull/1580)) - Fix `__eq__` method `ApplicationCommand` accidentally comparing to self. ([#1585](https://github.com/Pycord-Development/pycord/pull/1585)) - Apply `cog_check` method to `ApplicationCommand` invocations. ([#1575](https://github.com/Pycord-Development/pycord/pull/1575)) -- Fix `Interaction.edit_original_message()` using `ConnectionState` instead of `InteractionMessageState`. +- Fix `Interaction.edit_original_message()` using `ConnectionState` instead of + `InteractionMessageState`. ([#1565](https://github.com/Pycord-Development/pycord/pull/1565)) -- Fix required parameters validation error. ([#1589](https://github.com/Pycord-Development/pycord/pull/1589)) +- Fix required parameters validation error. + ([#1589](https://github.com/Pycord-Development/pycord/pull/1589)) ### Security + - Improved fix for application-based bots without the bot scope ([#1584](https://github.com/Pycord-Development/pycord/pull/1584)) ## [2.0.1] - 2022-08-16 + ### Security -- Fix for application-based bots without the bot scope ([#1568](https://github.com/Pycord-Development/pycord/pull/1568)) + +- Fix for application-based bots without the bot scope + ([#1568](https://github.com/Pycord-Development/pycord/pull/1568)) ## [2.0.0] - 2022-07-08 + ### Added -- New `news` property on `TextChannel`. ([#1370](https://github.com/Pycord-Development/pycord/pull/1370)) -- New `invisible` kwarg to `defer()` method. ([#1379](https://github.com/Pycord-Development/pycord/pull/1379)) + +- New `news` property on `TextChannel`. + ([#1370](https://github.com/Pycord-Development/pycord/pull/1370)) +- New `invisible` kwarg to `defer()` method. + ([#1379](https://github.com/Pycord-Development/pycord/pull/1379)) - Support for audit log event type 121 `APPLICATION_COMMAND_PERMISSION_UPDATE`. ([#1424](https://github.com/Pycord-Development/pycord/pull/1424)) -- New `ForumChannelConverter`. ([#1440](https://github.com/Pycord-Development/pycord/pull/1440)) -- A shortcut `jump_url` property to users. ([#1444](https://github.com/Pycord-Development/pycord/pull/1444)) -- Ability for webhooks to create forum posts. ([#1405](https://github.com/Pycord-Development/pycord/pull/1405)) -- New `message` property to `View` ([#1446](https://github.com/Pycord-Development/pycord/pull/1446)) +- New `ForumChannelConverter`. + ([#1440](https://github.com/Pycord-Development/pycord/pull/1440)) +- A shortcut `jump_url` property to users. + ([#1444](https://github.com/Pycord-Development/pycord/pull/1444)) +- Ability for webhooks to create forum posts. + ([#1405](https://github.com/Pycord-Development/pycord/pull/1405)) +- New `message` property to `View` + ([#1446](https://github.com/Pycord-Development/pycord/pull/1446)) - Support for `error`, `before_invoke`, and `after_invoke` handlers on `BridgeCommand`. ([#1411](https://github.com/Pycord-Development/pycord/pull/1411)) -- New `thread` property to `Message`. ([#1447](https://github.com/Pycord-Development/pycord/pull/1447)) -- A `starting_message` property to `Thread`. ([#1447](https://github.com/Pycord-Development/pycord/pull/1447)) +- New `thread` property to `Message`. + ([#1447](https://github.com/Pycord-Development/pycord/pull/1447)) +- A `starting_message` property to `Thread`. + ([#1447](https://github.com/Pycord-Development/pycord/pull/1447)) - An `app_permissions` property to `Interaction` and `ApplicationContext`. ([#1460](https://github.com/Pycord-Development/pycord/pull/1460)) -- Support for loading folders in `load_extension`, and a new helper function `load_extensions`. - ([#1423](https://github.com/Pycord-Development/pycord/pull/1423)) +- Support for loading folders in `load_extension`, and a new helper function + `load_extensions`. ([#1423](https://github.com/Pycord-Development/pycord/pull/1423)) - Support for AutoMod ([#1316](https://github.com/Pycord-Development/pycord/pull/1316)) - Support for `min_length` and `max_length` kwargs in `Option`. ([#1463](https://github.com/Pycord-Development/pycord/pull/1463)) -- Native timeout support for `Modal`. ([#1434](https://github.com/Pycord-Development/pycord/pull/1434)) +- Native timeout support for `Modal`. + ([#1434](https://github.com/Pycord-Development/pycord/pull/1434)) ### Changed -- Updated to new sticker limit for premium guilds. ([#1420](https://github.com/Pycord-Development/pycord/pull/1420)) + +- Updated to new sticker limit for premium guilds. + ([#1420](https://github.com/Pycord-Development/pycord/pull/1420)) - Replace deprecated endpoint in `HTTPClient.change_my_nickname`. ([#1426](https://github.com/Pycord-Development/pycord/pull/1426)) - Updated deprecated IDENTIFY packet connection properties. ([#1430](https://github.com/Pycord-Development/pycord/pull/1430)) ### Removed -- `Guild.region` attribute (Deprecated on API, VoiceChannel.rtc_region should be used instead). - ([#1429](https://github.com/Pycord-Development/pycord/pull/1429)) + +- `Guild.region` attribute (Deprecated on API, VoiceChannel.rtc_region should be used + instead). ([#1429](https://github.com/Pycord-Development/pycord/pull/1429)) ### Fixed + - Change `guild_only` to `dm_permission` in application command `to_dict` method. ([#1368](https://github.com/Pycord-Development/pycord/pull/1368)) - Fix `repr(ScheduledEventLocation)` raising TypeError. ([#1369](https://github.com/Pycord-Development/pycord/pull/1369)) -- Fix `repr(TextChannel)` raising AttributeError. ([#1370](https://github.com/Pycord-Development/pycord/pull/1370)) -- Fix application command validation. ([#1372](https://github.com/Pycord-Development/pycord/pull/1372)) +- Fix `repr(TextChannel)` raising AttributeError. + ([#1370](https://github.com/Pycord-Development/pycord/pull/1370)) +- Fix application command validation. + ([#1372](https://github.com/Pycord-Development/pycord/pull/1372)) - Fix scheduled event `cover` property raising AttributeError. ([#1381](https://github.com/Pycord-Development/pycord/pull/1381)) - Fix `SlashCommandGroup` treating optional arguments as required. ([#1386](https://github.com/Pycord-Development/pycord/pull/1386)) - Fix `remove_application_command` not always removing commands. ([#1391](https://github.com/Pycord-Development/pycord/pull/1391)) -- Fix busy-loop in `DecodeManager` when decode queue is empty, causing 100% CPU consumption. - ([#1395](https://github.com/Pycord-Development/pycord/pull/1395)) +- Fix busy-loop in `DecodeManager` when decode queue is empty, causing 100% CPU + consumption. ([#1395](https://github.com/Pycord-Development/pycord/pull/1395)) - Fix incorrect activities and permissions on `Interaction` and `Option` objects. ([#1365](https://github.com/Pycord-Development/pycord/pull/1365)) -- Converted PartialMember `deaf` and `mute` from str annotation (incorrect) to bool annotation. - ([#1424](https://github.com/Pycord-Development/pycord/pull/1424)) +- Converted PartialMember `deaf` and `mute` from str annotation (incorrect) to bool + annotation. ([#1424](https://github.com/Pycord-Development/pycord/pull/1424)) - Use `PUT` instead of `POST` in `HTTPClient.join_thread`. ([#1426](https://github.com/Pycord-Development/pycord/pull/1426)) - Fix enum options not setting `input_type` to a SlashCommandOptionType. ([#1428](https://github.com/Pycord-Development/pycord/pull/1428)) -- Fixed TypeError when using thread options. ([#1427](https://github.com/Pycord-Development/pycord/pull/1427)) -- Allow voice channels in PartialMessage. ([#1441](https://github.com/Pycord-Development/pycord/pull/1441)) +- Fixed TypeError when using thread options. + ([#1427](https://github.com/Pycord-Development/pycord/pull/1427)) +- Allow voice channels in PartialMessage. + ([#1441](https://github.com/Pycord-Development/pycord/pull/1441)) - Fixed `AuditLogAction.target_type` for application command permission updates. ([#1445](https://github.com/Pycord-Development/pycord/pull/1445)) -- Fix bridge commands to ignore the ephemeral kwarg. ([#1453](https://github.com/Pycord-Development/pycord/pull/1453)) -- Update `thread.members` on `thread.fetch_members`. ([#1464](https://github.com/Pycord-Development/pycord/pull/1464)) +- Fix bridge commands to ignore the ephemeral kwarg. + ([#1453](https://github.com/Pycord-Development/pycord/pull/1453)) +- Update `thread.members` on `thread.fetch_members`. + ([#1464](https://github.com/Pycord-Development/pycord/pull/1464)) - Fix error when discord doesn't send the `app_permissions` data in `Interaction`. ([#1467](https://github.com/Pycord-Development/pycord/pull/1467)) - Fix AttributeError when voice client `play()` function isn't completed yet. ([#1360](https://github.com/Pycord-Development/pycord/pull/1360)) ## [2.0.0-rc.1] - 2022-05-17 + ### Added -- A `delete_after` kwarg to `Paginator.send`. ([#1245](https://github.com/Pycord-Development/pycord/pull/1245)) -- New `reason` kwarg to `Thread.delete_messages`. ([#1253](https://github.com/Pycord-Development/pycord/pull/1253)) + +- A `delete_after` kwarg to `Paginator.send`. + ([#1245](https://github.com/Pycord-Development/pycord/pull/1245)) +- New `reason` kwarg to `Thread.delete_messages`. + ([#1253](https://github.com/Pycord-Development/pycord/pull/1253)) - A new `jump_url` property to channel and thread objects. ([#1254](https://github.com/Pycord-Development/pycord/pull/1254) & [#1259](https://github.com/Pycord-Development/pycord/pull/1259)) -- New `Paginator.edit()` method. ([#1258](https://github.com/Pycord-Development/pycord/pull/1258)) -- An `EmbedField` object. ([#1181](https://github.com/Pycord-Development/pycord/pull/1181)) +- New `Paginator.edit()` method. + ([#1258](https://github.com/Pycord-Development/pycord/pull/1258)) +- An `EmbedField` object. + ([#1181](https://github.com/Pycord-Development/pycord/pull/1181)) - Option names and descriptions are now validated locally. ([#1271](https://github.com/Pycord-Development/pycord/pull/1271)) - Component field limits are now enforced at library-level ([#1065](https://github.com/Pycord-Development/pycord/pull/1065) & [#1289](https://github.com/Pycord-Development/pycord/pull/1289)) -- Support providing option channel types as list. ([#1000](https://github.com/Pycord-Development/pycord/pull/1000)) -- New `Guild.jump_url` property. ([#1282](https://github.com/Pycord-Development/pycord/pull/1282)) -- ext.pages now supports ext.bridge. ([#1288](https://github.com/Pycord-Development/pycord/pull/1288)) -- Implement `None` check for check_guilds. ([#1291](https://github.com/Pycord-Development/pycord/pull/1291)) +- Support providing option channel types as list. + ([#1000](https://github.com/Pycord-Development/pycord/pull/1000)) +- New `Guild.jump_url` property. + ([#1282](https://github.com/Pycord-Development/pycord/pull/1282)) +- ext.pages now supports ext.bridge. + ([#1288](https://github.com/Pycord-Development/pycord/pull/1288)) +- Implement `None` check for check_guilds. + ([#1291](https://github.com/Pycord-Development/pycord/pull/1291)) - A debug warning to catch deprecated perms v1 usage until v2 perms are implemented. ([#1301](https://github.com/Pycord-Development/pycord/pull/1301)) -- A new `files` parameter to `Page` object. ([#1300](https://github.com/Pycord-Development/pycord/pull/1300)) +- A new `files` parameter to `Page` object. + ([#1300](https://github.com/Pycord-Development/pycord/pull/1300)) - A `disable_all_items` and `enable_all_items` methods to `View` object. - ([#1199](https://github.com/Pycord-Development/pycord/pull/1199) & + ([#1199](https://github.com/Pycord-Development/pycord/pull/1199) & [#1319](https://github.com/Pycord-Development/pycord/pull/1319)) -- New `is_nsfw` attribute to voice channels. ([#1317](https://github.com/Pycord-Development/pycord/pull/1317)) -- Support for Permissions v2. ([#1328](https://github.com/Pycord-Development/pycord/pull/1328)) -- Allow using Enum to specify option choices. ([#1292](https://github.com/Pycord-Development/pycord/pull/1292)) +- New `is_nsfw` attribute to voice channels. + ([#1317](https://github.com/Pycord-Development/pycord/pull/1317)) +- Support for Permissions v2. + ([#1328](https://github.com/Pycord-Development/pycord/pull/1328)) +- Allow using Enum to specify option choices. + ([#1292](https://github.com/Pycord-Development/pycord/pull/1292)) - The `file` and `files` parameters to `InteractionResponse.edit_message()`. ([#1340](https://github.com/Pycord-Development/pycord/pull/1340)) -- A `BridgeExtContext.delete()` method. ([#1348](https://github.com/Pycord-Development/pycord/pull/1348)) -- Forum channels support. ([#1249](https://github.com/Pycord-Development/pycord/pull/1249)) -- Implemented `Interaction.to_dict`. ([#1274](https://github.com/Pycord-Development/pycord/pull/1274)) -- Support event covers for audit logs. ([#1355](https://github.com/Pycord-Development/pycord/pull/1355)) +- A `BridgeExtContext.delete()` method. + ([#1348](https://github.com/Pycord-Development/pycord/pull/1348)) +- Forum channels support. + ([#1249](https://github.com/Pycord-Development/pycord/pull/1249)) +- Implemented `Interaction.to_dict`. + ([#1274](https://github.com/Pycord-Development/pycord/pull/1274)) +- Support event covers for audit logs. + ([#1355](https://github.com/Pycord-Development/pycord/pull/1355)) ### Changed -- Removed implicit defer call in `View`. ([#1260](https://github.com/Pycord-Development/pycord/pull/1260)) -- `Option` class and usage was rewritten. ([#1251](https://github.com/Pycord-Development/pycord/pull/1251)) + +- Removed implicit defer call in `View`. + ([#1260](https://github.com/Pycord-Development/pycord/pull/1260)) +- `Option` class and usage was rewritten. + ([#1251](https://github.com/Pycord-Development/pycord/pull/1251)) - `description` argument of `PageGroup` is now optional. ([#1330](https://github.com/Pycord-Development/pycord/pull/1330)) -- Allow `Modal.children` to be set on initialization. ([#1311](https://github.com/Pycord-Development/pycord/pull/1311)) -- Renamed `delete_exiting` to `delete_existing` (typo). ([#1336](https://github.com/Pycord-Development/pycord/pull/1336)) +- Allow `Modal.children` to be set on initialization. + ([#1311](https://github.com/Pycord-Development/pycord/pull/1311)) +- Renamed `delete_exiting` to `delete_existing` (typo). + ([#1336](https://github.com/Pycord-Development/pycord/pull/1336)) ### Fixed + - Fix `PartialMessage.edit()` setting `view` as `None` when `view` kwarg is not passed. ([#1256](https://github.com/Pycord-Development/pycord/pull/1256)) -- Fix channel parsing in slash command invocations. ([#1257](https://github.com/Pycord-Development/pycord/pull/1257)) -- Make channel `position` attribute optional. ([#1257](https://github.com/Pycord-Development/pycord/pull/1257)) +- Fix channel parsing in slash command invocations. + ([#1257](https://github.com/Pycord-Development/pycord/pull/1257)) +- Make channel `position` attribute optional. + ([#1257](https://github.com/Pycord-Development/pycord/pull/1257)) - Fix `PaginatorMenu` to use interaction routes for updates. ([#1267](https://github.com/Pycord-Development/pycord/pull/1267)) - Fix `PartialMessage.edit()` behavior when `content` is `None`. ([#1268](https://github.com/Pycord-Development/pycord/pull/1268)) -- Fix `Paginator.add_menu()` and `Paginator.add_default_buttons()` passing `custom_id` to `PaginatorMenu`. - ([#1270](https://github.com/Pycord-Development/pycord/pull/1270)) +- Fix `Paginator.add_menu()` and `Paginator.add_default_buttons()` passing `custom_id` + to `PaginatorMenu`. ([#1270](https://github.com/Pycord-Development/pycord/pull/1270)) - Fix `process_application_commands` command not found fallback. ([#1262](https://github.com/Pycord-Development/pycord/pull/1262)) -- Fix interaction response race condition. ([#1039](https://github.com/Pycord-Development/pycord/pull/1039)) -- Remove voice client when bot disconnects. ([#1273](https://github.com/Pycord-Development/pycord/pull/1273)) -- Fix conversion exception in ext.bridge. ([#1250](https://github.com/Pycord-Development/pycord/pull/1250)) +- Fix interaction response race condition. + ([#1039](https://github.com/Pycord-Development/pycord/pull/1039)) +- Remove voice client when bot disconnects. + ([#1273](https://github.com/Pycord-Development/pycord/pull/1273)) +- Fix conversion exception in ext.bridge. + ([#1250](https://github.com/Pycord-Development/pycord/pull/1250)) - Context.me return ClientUser when guilds intent is absent. ([#1286](https://github.com/Pycord-Development/pycord/pull/1286)) -- Updated `Message.edit` type hinting overload and removed resulting redundant overloads. - ([#1299](https://github.com/Pycord-Development/pycord/pull/1299)) +- Updated `Message.edit` type hinting overload and removed resulting redundant + overloads. ([#1299](https://github.com/Pycord-Development/pycord/pull/1299)) - Improved validation regex for command names & options. ([#1309](https://github.com/Pycord-Development/pycord/pull/1309)) -- Correct `Guild.fetch_members()` type hints. ([#1323](https://github.com/Pycord-Development/pycord/pull/1323)) +- Correct `Guild.fetch_members()` type hints. + ([#1323](https://github.com/Pycord-Development/pycord/pull/1323)) - Multiple fixes and enhancements for `PageGroup` handling. ([#1350](https://github.com/Pycord-Development/pycord/pull/1350)) -- Make `TextChannel._get_channel` async. ([#1358](https://github.com/Pycord-Development/pycord/pull/1358)) +- Make `TextChannel._get_channel` async. + ([#1358](https://github.com/Pycord-Development/pycord/pull/1358)) ## [2.0.0-beta.7] - 2022-04-09 + ### Fixed -- Fix py3.10 UnionType checks issue. ([#1240](https://github.com/Pycord-Development/pycord/pull/1240)) -[Unreleased]: https://github.com/Pycord-Development/pycord/compare/v2.2.0...HEAD +- Fix py3.10 UnionType checks issue. + ([#1240](https://github.com/Pycord-Development/pycord/pull/1240)) + +[unreleased]: https://github.com/Pycord-Development/pycord/compare/v2.2.0...HEAD [2.2.0]: https://github.com/Pycord-Development/pycord/compare/v2.1.3...v2.2.0 [2.1.3]: https://github.com/Pycord-Development/pycord/compare/v2.1.2...v2.1.3 [2.1.2]: https://github.com/Pycord-Development/pycord/compare/v2.1.1...v2.1.2 @@ -279,11 +383,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [2.1.0]: https://github.com/Pycord-Development/pycord/compare/v2.0.1...v2.1.0 [2.0.1]: https://github.com/Pycord-Development/pycord/compare/v2.0.0...v2.0.1 [2.0.0]: https://github.com/Pycord-Development/pycord/compare/v2.0.0-rc.1...v2.0.0 -[2.0.0-rc.1]: https://github.com/Pycord-Development/pycord/compare/v2.0.0-beta.7...v2.0.0-rc.1 -[2.0.0-beta.7]: https://github.com/Pycord-Development/pycord/compare/v2.0.0-beta.6...v2.0.0-beta.7 -[2.0.0-beta.6]: https://github.com/Pycord-Development/pycord/compare/v2.0.0-beta.5...v2.0.0-beta.6 -[2.0.0-beta.5]: https://github.com/Pycord-Development/pycord/compare/v2.0.0-beta.4...v2.0.0-beta.5 -[2.0.0-beta.4]: https://github.com/Pycord-Development/pycord/compare/v2.0.0-beta.3...v2.0.0-beta.4 -[2.0.0-beta.3]: https://github.com/Pycord-Development/pycord/compare/v2.0.0-beta.2...v2.0.0-beta.3 -[2.0.0-beta.2]: https://github.com/Pycord-Development/pycord/compare/v2.0.0-beta.1...v2.0.0-beta.2 -[2.0.0-beta.1]: https://github.com/Pycord-Development/pycord/compare/v1.7.3...v2.0.0-beta.1 +[2.0.0-rc.1]: + https://github.com/Pycord-Development/pycord/compare/v2.0.0-beta.7...v2.0.0-rc.1 +[2.0.0-beta.7]: + https://github.com/Pycord-Development/pycord/compare/v2.0.0-beta.6...v2.0.0-beta.7 +[2.0.0-beta.6]: + https://github.com/Pycord-Development/pycord/compare/v2.0.0-beta.5...v2.0.0-beta.6 +[2.0.0-beta.5]: + https://github.com/Pycord-Development/pycord/compare/v2.0.0-beta.4...v2.0.0-beta.5 +[2.0.0-beta.4]: + https://github.com/Pycord-Development/pycord/compare/v2.0.0-beta.3...v2.0.0-beta.4 +[2.0.0-beta.3]: + https://github.com/Pycord-Development/pycord/compare/v2.0.0-beta.2...v2.0.0-beta.3 +[2.0.0-beta.2]: + https://github.com/Pycord-Development/pycord/compare/v2.0.0-beta.1...v2.0.0-beta.2 +[2.0.0-beta.1]: + https://github.com/Pycord-Development/pycord/compare/v1.7.3...v2.0.0-beta.1 diff --git a/README.rst b/README.rst index 18abc6fd81..d457c729e4 100644 --- a/README.rst +++ b/README.rst @@ -69,7 +69,7 @@ To install the development version, do the following: $ git clone https://github.com/Pycord-Development/pycord $ cd pycord $ python3 -m pip install -U .[voice] - + or if you do not want to clone the repository: .. code:: sh diff --git a/discord/__init__.py b/discord/__init__.py index 248db2694c..3929fc5e3e 100644 --- a/discord/__init__.py +++ b/discord/__init__.py @@ -6,7 +6,6 @@ :copyright: (c) 2015-2021 Rapptz & (c) 2021-present Pycord Development :license: MIT, see LICENSE for more details. - """ __title__ = "pycord" @@ -76,6 +75,8 @@ class VersionInfo(NamedTuple): serial: int -version_info: VersionInfo = VersionInfo(major=2, minor=2, micro=0, releaselevel="final", serial=0) +version_info: VersionInfo = VersionInfo( + major=2, minor=2, micro=0, releaselevel="final", serial=0 +) logging.getLogger(__name__).addHandler(logging.NullHandler()) diff --git a/discord/__main__.py b/discord/__main__.py index 07c00ef91d..12b5b5a1c3 100644 --- a/discord/__main__.py +++ b/discord/__main__.py @@ -36,10 +36,16 @@ def show_version() -> None: - entries = ["- Python v{0.major}.{0.minor}.{0.micro}-{0.releaselevel}".format(sys.version_info)] + entries = [ + "- Python v{0.major}.{0.minor}.{0.micro}-{0.releaselevel}".format( + sys.version_info + ) + ] version_info = discord.version_info - entries.append("- py-cord v{0.major}.{0.minor}.{0.micro}-{0.releaselevel}".format(version_info)) + entries.append( + "- py-cord v{0.major}.{0.minor}.{0.micro}-{0.releaselevel}".format(version_info) + ) if version_info.releaselevel != "final": pkg = pkg_resources.get_distribution("py-cord") if pkg: @@ -293,7 +299,9 @@ def newcog(parser, args) -> None: def add_newbot_args(subparser: argparse._SubParsersAction) -> None: - parser = subparser.add_parser("newbot", help="creates a command bot project quickly") + parser = subparser.add_parser( + "newbot", help="creates a command bot project quickly" + ) parser.set_defaults(func=newbot) parser.add_argument("name", help="the bot project name") @@ -303,8 +311,12 @@ def add_newbot_args(subparser: argparse._SubParsersAction) -> None: nargs="?", default=Path.cwd(), ) - parser.add_argument("--prefix", help="the bot prefix (default: $)", default="$", metavar="") - parser.add_argument("--sharded", help="whether to use AutoShardedBot", action="store_true") + parser.add_argument( + "--prefix", help="the bot prefix (default: $)", default="$", metavar="" + ) + parser.add_argument( + "--sharded", help="whether to use AutoShardedBot", action="store_true" + ) parser.add_argument( "--no-git", help="do not create a .gitignore file", @@ -335,12 +347,18 @@ def add_newcog_args(subparser: argparse._SubParsersAction) -> None: help="whether to hide all commands in the cog", action="store_true", ) - parser.add_argument("--full", help="add all special methods as well", action="store_true") + parser.add_argument( + "--full", help="add all special methods as well", action="store_true" + ) def parse_args() -> Tuple[argparse.ArgumentParser, argparse.Namespace]: - parser = argparse.ArgumentParser(prog="discord", description="Tools for helping with Pycord") - parser.add_argument("-v", "--version", action="store_true", help="shows the library version") + parser = argparse.ArgumentParser( + prog="discord", description="Tools for helping with Pycord" + ) + parser.add_argument( + "-v", "--version", action="store_true", help="shows the library version" + ) parser.set_defaults(func=core) subparser = parser.add_subparsers(dest="subcommand", title="subcommands") diff --git a/discord/abc.py b/discord/abc.py index b2dd364e80..ce0806bbab 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -32,13 +32,9 @@ TYPE_CHECKING, Any, Callable, - Dict, Iterable, - List, - Optional, Protocol, Sequence, - Tuple, TypeVar, Union, overload, @@ -100,30 +96,34 @@ from .ui.view import View from .user import ClientUser - PartialMessageableChannel = Union[TextChannel, VoiceChannel, Thread, DMChannel, PartialMessageable] + PartialMessageableChannel = Union[ + TextChannel, VoiceChannel, Thread, DMChannel, PartialMessageable + ] MessageableChannel = Union[PartialMessageableChannel, GroupChannel] SnowflakeTime = Union["Snowflake", datetime] MISSING = utils.MISSING -async def _single_delete_strategy(messages: Iterable[Message], *, reason: Optional[str] = None): +async def _single_delete_strategy( + messages: Iterable[Message], *, reason: str | None = None +): for m in messages: await m.delete(reason=reason) async def _purge_messages_helper( - channel: Union[TextChannel, Thread, VoiceChannel], + channel: TextChannel | Thread | VoiceChannel, *, - limit: Optional[int] = 100, + limit: int | None = 100, check: Callable[[Message], bool] = MISSING, - before: Optional[SnowflakeTime] = None, - after: Optional[SnowflakeTime] = None, - around: Optional[SnowflakeTime] = None, - oldest_first: Optional[bool] = False, + before: SnowflakeTime | None = None, + after: SnowflakeTime | None = None, + around: SnowflakeTime | None = None, + oldest_first: bool | None = False, bulk: bool = True, - reason: Optional[str] = None, -) -> List[Message]: + reason: str | None = None, +) -> list[Message]: if check is MISSING: check = lambda m: True @@ -134,7 +134,7 @@ async def _purge_messages_helper( oldest_first=oldest_first, around=around, ) - ret: List[Message] = [] + ret: list[Message] = [] count = 0 minimum_time = int((time.time() - 14 * 24 * 60 * 60) * 1000.0 - 1420070400000) << 22 @@ -187,7 +187,7 @@ class Snowflake(Protocol): :class:`.Object`. Attributes - ----------- + ---------- id: :class:`int` The model's unique ID. """ @@ -209,7 +209,7 @@ class User(Snowflake, Protocol): This ABC must also implement :class:`~discord.abc.Snowflake`. Attributes - ----------- + ---------- name: :class:`str` The user's username. discriminator: :class:`str` @@ -250,7 +250,7 @@ class PrivateChannel(Snowflake, Protocol): This ABC must also implement :class:`~discord.abc.Snowflake`. Attributes - ----------- + ---------- me: :class:`~discord.ClientUser` The user presenting yourself. """ @@ -304,7 +304,7 @@ class GuildChannel: This ABC must also implement :class:`~discord.abc.Snowflake`. Attributes - ----------- + ---------- name: :class:`str` The channel name. guild: :class:`~discord.Guild` @@ -321,14 +321,16 @@ class GuildChannel: guild: Guild type: ChannelType position: int - category_id: Optional[int] + category_id: int | None flags: ChannelFlags _state: ConnectionState - _overwrites: List[_Overwrites] + _overwrites: list[_Overwrites] if TYPE_CHECKING: - def __init__(self, *, state: ConnectionState, guild: Guild, data: Dict[str, Any]): + def __init__( + self, *, state: ConnectionState, guild: Guild, data: dict[str, Any] + ): ... def __str__(self) -> str: @@ -338,23 +340,25 @@ def __str__(self) -> str: def _sorting_bucket(self) -> int: raise NotImplementedError - def _update(self, guild: Guild, data: Dict[str, Any]) -> None: + def _update(self, guild: Guild, data: dict[str, Any]) -> None: raise NotImplementedError async def _move( self, position: int, - parent_id: Optional[Any] = None, + parent_id: Any | None = None, lock_permissions: bool = False, *, - reason: Optional[str], + reason: str | None, ) -> None: if position < 0: raise InvalidArgument("Channel position cannot be less than 0.") http = self._state.http bucket = self._sorting_bucket - channels: List[GuildChannel] = [c for c in self.guild.channels if c._sorting_bucket == bucket] + channels: list[GuildChannel] = [ + c for c in self.guild.channels if c._sorting_bucket == bucket + ] channels.sort(key=lambda c: c.position) @@ -374,14 +378,16 @@ async def _move( payload = [] for index, c in enumerate(channels): - d: Dict[str, Any] = {"id": c.id, "position": index} + d: dict[str, Any] = {"id": c.id, "position": index} if parent_id is not MISSING and c.id == self.id: d.update(parent_id=parent_id, lock_permissions=lock_permissions) payload.append(d) await http.bulk_channel_update(self.guild.id, payload, reason=reason) - async def _edit(self, options: Dict[str, Any], reason: Optional[str]) -> Optional[ChannelPayload]: + async def _edit( + self, options: dict[str, Any], reason: str | None + ) -> ChannelPayload | None: try: parent = options.pop("category") except KeyError: @@ -417,14 +423,18 @@ async def _edit(self, options: Dict[str, Any], reason: Optional[str]) -> Optiona if lock_permissions: category = self.guild.get_channel(parent_id) if category: - options["permission_overwrites"] = [c._asdict() for c in category._overwrites] + options["permission_overwrites"] = [ + c._asdict() for c in category._overwrites + ] options["parent_id"] = parent_id elif lock_permissions and self.category_id is not None: # if we're syncing permissions on a pre-existing channel category without changing it # we need to update the permissions to point to the pre-existing category category = self.guild.get_channel(self.category_id) if category: - options["permission_overwrites"] = [c._asdict() for c in category._overwrites] + options["permission_overwrites"] = [ + c._asdict() for c in category._overwrites + ] else: await self._move( position, @@ -438,14 +448,18 @@ async def _edit(self, options: Dict[str, Any], reason: Optional[str]) -> Optiona perms = [] for target, perm in overwrites.items(): if not isinstance(perm, PermissionOverwrite): - raise InvalidArgument(f"Expected PermissionOverwrite received {perm.__class__.__name__}") + raise InvalidArgument( + f"Expected PermissionOverwrite received {perm.__class__.__name__}" + ) allow, deny = perm.pair() payload = { "allow": allow.value, "deny": deny.value, "id": target.id, - "type": _Overwrites.ROLE if isinstance(target, Role) else _Overwrites.MEMBER, + "type": _Overwrites.ROLE + if isinstance(target, Role) + else _Overwrites.MEMBER, } perms.append(payload) @@ -461,7 +475,9 @@ async def _edit(self, options: Dict[str, Any], reason: Optional[str]) -> Optiona options["type"] = ch_type.value if options: - return await self._state.http.edit_channel(self.id, reason=reason, **options) + return await self._state.http.edit_channel( + self.id, reason=reason, **options + ) def _fill_overwrites(self, data: GuildChannelPayload) -> None: self._overwrites = [] @@ -489,9 +505,10 @@ def _fill_overwrites(self, data: GuildChannelPayload) -> None: tmp[everyone_index], tmp[0] = tmp[0], tmp[everyone_index] @property - def changed_roles(self) -> List[Role]: + def changed_roles(self) -> list[Role]: """List[:class:`~discord.Role`]: Returns a list of roles that have been overridden from - their default values in the :attr:`~discord.Guild.roles` attribute.""" + their default values in the :attr:`~discord.Guild.roles` attribute. + """ ret = [] g = self.guild for overwrite in filter(lambda o: o.is_role(), self._overwrites): @@ -522,17 +539,17 @@ def created_at(self) -> datetime: """:class:`datetime.datetime`: Returns the channel's creation time in UTC.""" return utils.snowflake_time(self.id) - def overwrites_for(self, obj: Union[Role, User]) -> PermissionOverwrite: + def overwrites_for(self, obj: Role | User) -> PermissionOverwrite: """Returns the channel-specific overwrites for a member or a role. Parameters - ----------- + ---------- obj: Union[:class:`~discord.Role`, :class:`~discord.abc.User`] The role or user denoting whose overwrite to get. Returns - --------- + ------- :class:`~discord.PermissionOverwrite` The permission overwrites for this object. """ @@ -553,7 +570,7 @@ def overwrites_for(self, obj: Union[Role, User]) -> PermissionOverwrite: return PermissionOverwrite() @property - def overwrites(self) -> Dict[Union[Role, Member], PermissionOverwrite]: + def overwrites(self) -> dict[Role | Member, PermissionOverwrite]: """Returns all of the channel's overwrites. This is returned as a dictionary where the key contains the target which @@ -561,7 +578,7 @@ def overwrites(self) -> Dict[Union[Role, Member], PermissionOverwrite]: overwrite as a :class:`~discord.PermissionOverwrite`. Returns - -------- + ------- Dict[Union[:class:`~discord.Role`, :class:`~discord.Member`], :class:`~discord.PermissionOverwrite`] The channel's permission overwrites. """ @@ -587,7 +604,7 @@ def overwrites(self) -> Dict[Union[Role, Member], PermissionOverwrite]: return ret @property - def category(self) -> Optional[CategoryChannel]: + def category(self) -> CategoryChannel | None: """Optional[:class:`~discord.CategoryChannel`]: The category this channel belongs to. If there is no category then this is ``None``. @@ -609,7 +626,7 @@ def permissions_synced(self) -> bool: category = self.guild.get_channel(self.category_id) return bool(category and category.overwrites == self.overwrites) - def permissions_for(self, obj: Union[Member, Role], /) -> Permissions: + def permissions_for(self, obj: Member | Role, /) -> Permissions: """Handles permission resolution for the :class:`~discord.Member` or :class:`~discord.Role`. @@ -675,7 +692,9 @@ def permissions_for(self, obj: Union[Member, Role], /) -> Permissions: try: maybe_everyone = self._overwrites[0] if maybe_everyone.id == self.guild.id: - base.handle_overwrite(allow=maybe_everyone.allow, deny=maybe_everyone.deny) + base.handle_overwrite( + allow=maybe_everyone.allow, deny=maybe_everyone.deny + ) except IndexError: pass @@ -706,7 +725,9 @@ def permissions_for(self, obj: Union[Member, Role], /) -> Permissions: try: maybe_everyone = self._overwrites[0] if maybe_everyone.id == self.guild.id: - base.handle_overwrite(allow=maybe_everyone.allow, deny=maybe_everyone.deny) + base.handle_overwrite( + allow=maybe_everyone.allow, deny=maybe_everyone.deny + ) remaining_overwrites = self._overwrites[1:] else: remaining_overwrites = self._overwrites @@ -745,7 +766,7 @@ def permissions_for(self, obj: Union[Member, Role], /) -> Permissions: return base - async def delete(self, *, reason: Optional[str] = None) -> None: + async def delete(self, *, reason: str | None = None) -> None: """|coro| Deletes the channel. @@ -753,13 +774,13 @@ async def delete(self, *, reason: Optional[str] = None) -> None: You must have :attr:`~discord.Permissions.manage_channels` permission to use this. Parameters - ----------- + ---------- reason: Optional[:class:`str`] The reason for deleting this channel. Shows up on the audit log. Raises - ------- + ------ ~discord.Forbidden You do not have proper permissions to delete the channel. ~discord.NotFound @@ -772,24 +793,26 @@ async def delete(self, *, reason: Optional[str] = None) -> None: @overload async def set_permissions( self, - target: Union[Member, Role], + target: Member | Role, *, - overwrite: Optional[PermissionOverwrite] = ..., - reason: Optional[str] = ..., + overwrite: PermissionOverwrite | None = ..., + reason: str | None = ..., ) -> None: ... @overload async def set_permissions( self, - target: Union[Member, Role], + target: Member | Role, *, - reason: Optional[str] = ..., + reason: str | None = ..., **permissions: bool, ) -> None: ... - async def set_permissions(self, target, *, overwrite=MISSING, reason=None, **permissions): + async def set_permissions( + self, target, *, overwrite=MISSING, reason=None, **permissions + ): r"""|coro| Sets the channel specific permission overwrites for a target in the @@ -883,30 +906,36 @@ async def set_permissions(self, target, *, overwrite=MISSING, reason=None, **per await http.delete_channel_permissions(self.id, target.id, reason=reason) elif isinstance(overwrite, PermissionOverwrite): (allow, deny) = overwrite.pair() - await http.edit_channel_permissions(self.id, target.id, allow.value, deny.value, perm_type, reason=reason) + await http.edit_channel_permissions( + self.id, target.id, allow.value, deny.value, perm_type, reason=reason + ) else: raise InvalidArgument("Invalid overwrite type provided.") async def _clone_impl( self: GCH, - base_attrs: Dict[str, Any], + base_attrs: dict[str, Any], *, - name: Optional[str] = None, - reason: Optional[str] = None, + name: str | None = None, + reason: str | None = None, ) -> GCH: base_attrs["permission_overwrites"] = [x._asdict() for x in self._overwrites] base_attrs["parent_id"] = self.category_id base_attrs["name"] = name or self.name guild_id = self.guild.id cls = self.__class__ - data = await self._state.http.create_channel(guild_id, self.type.value, reason=reason, **base_attrs) + data = await self._state.http.create_channel( + guild_id, self.type.value, reason=reason, **base_attrs + ) obj = cls(state=self._state, guild=self.guild, data=data) # temporarily add it to the cache self.guild._channels[obj.id] = obj # type: ignore return obj - async def clone(self: GCH, *, name: Optional[str] = None, reason: Optional[str] = None) -> GCH: + async def clone( + self: GCH, *, name: str | None = None, reason: str | None = None + ) -> GCH: """|coro| Clones this channel. This creates a channel with the same properties @@ -918,24 +947,24 @@ async def clone(self: GCH, *, name: Optional[str] = None, reason: Optional[str] .. versionadded:: 1.1 Parameters - ------------ + ---------- name: Optional[:class:`str`] The name of the new channel. If not provided, defaults to this channel name. reason: Optional[:class:`str`] The reason for cloning this channel. Shows up on the audit log. - Raises + Returns ------- + :class:`.abc.GuildChannel` + The channel that was created. + + Raises + ------ ~discord.Forbidden You do not have the proper permissions to create this channel. ~discord.HTTPException Creating the channel failed. - - Returns - -------- - :class:`.abc.GuildChannel` - The channel that was created. """ raise NotImplementedError @@ -945,9 +974,9 @@ async def move( *, beginning: bool, offset: int = MISSING, - category: Optional[Snowflake] = MISSING, + category: Snowflake | None = MISSING, sync_permissions: bool = MISSING, - reason: Optional[str] = MISSING, + reason: str | None = MISSING, ) -> None: ... @@ -957,7 +986,7 @@ async def move( *, end: bool, offset: int = MISSING, - category: Optional[Snowflake] = MISSING, + category: Snowflake | None = MISSING, sync_permissions: bool = MISSING, reason: str = MISSING, ) -> None: @@ -969,7 +998,7 @@ async def move( *, before: Snowflake, offset: int = MISSING, - category: Optional[Snowflake] = MISSING, + category: Snowflake | None = MISSING, sync_permissions: bool = MISSING, reason: str = MISSING, ) -> None: @@ -981,7 +1010,7 @@ async def move( *, after: Snowflake, offset: int = MISSING, - category: Optional[Snowflake] = MISSING, + category: Snowflake | None = MISSING, sync_permissions: bool = MISSING, reason: str = MISSING, ) -> None: @@ -1005,7 +1034,7 @@ async def move(self, **kwargs) -> None: .. versionadded:: 1.7 Parameters - ------------ + ---------- beginning: :class:`bool` Whether to move the channel to the beginning of the channel list (or category if given). @@ -1037,7 +1066,7 @@ async def move(self, **kwargs) -> None: The reason for the move. Raises - ------- + ------ InvalidArgument An invalid position was given or a bad mix of arguments was passed. Forbidden @@ -1053,19 +1082,25 @@ async def move(self, **kwargs) -> None: before, after = kwargs.get("before"), kwargs.get("after") offset = kwargs.get("offset", 0) if sum(bool(a) for a in (beginning, end, before, after)) > 1: - raise InvalidArgument("Only one of [before, after, end, beginning] can be used.") + raise InvalidArgument( + "Only one of [before, after, end, beginning] can be used." + ) bucket = self._sorting_bucket parent_id = kwargs.get("category", MISSING) - channels: List[GuildChannel] + channels: list[GuildChannel] if parent_id not in (MISSING, None): parent_id = parent_id.id channels = [ - ch for ch in self.guild.channels if ch._sorting_bucket == bucket and ch.category_id == parent_id + ch + for ch in self.guild.channels + if ch._sorting_bucket == bucket and ch.category_id == parent_id ] else: channels = [ - ch for ch in self.guild.channels if ch._sorting_bucket == bucket and ch.category_id == self.category_id + ch + for ch in self.guild.channels + if ch._sorting_bucket == bucket and ch.category_id == self.category_id ] channels.sort(key=lambda c: (c.position, c.id)) @@ -1085,7 +1120,9 @@ async def move(self, **kwargs) -> None: elif before: index = next((i for i, c in enumerate(channels) if c.id == before.id), None) elif after: - index = next((i + 1 for i, c in enumerate(channels) if c.id == after.id), None) + index = next( + (i + 1 for i, c in enumerate(channels) if c.id == after.id), None + ) if index is None: raise InvalidArgument("Could not resolve appropriate move position") @@ -1100,20 +1137,22 @@ async def move(self, **kwargs) -> None: d.update(parent_id=parent_id, lock_permissions=lock_permissions) payload.append(d) - await self._state.http.bulk_channel_update(self.guild.id, payload, reason=reason) + await self._state.http.bulk_channel_update( + self.guild.id, payload, reason=reason + ) async def create_invite( self, *, - reason: Optional[str] = None, + reason: str | None = None, max_age: int = 0, max_uses: int = 0, temporary: bool = False, unique: bool = True, - target_event: Optional[ScheduledEvent] = None, - target_type: Optional[InviteTarget] = None, - target_user: Optional[User] = None, - target_application_id: Optional[int] = None, + target_event: ScheduledEvent | None = None, + target_type: InviteTarget | None = None, + target_user: User | None = None, + target_application_id: int | None = None, ) -> Invite: """|coro| @@ -1123,7 +1162,7 @@ async def create_invite( do this. Parameters - ------------ + ---------- max_age: :class:`int` How long the invite should last in seconds. If it's 0 then the invite doesn't expire. Defaults to ``0``. @@ -1165,18 +1204,18 @@ async def create_invite( .. versionadded:: 2.0 - Raises + Returns ------- + :class:`~discord.Invite` + The invite that was created. + + Raises + ------ ~discord.HTTPException Invite creation failed. ~discord.NotFound The channel that was passed is a category or an invalid channel. - - Returns - -------- - :class:`~discord.Invite` - The invite that was created. """ data = await self._state.http.create_invite( @@ -1195,30 +1234,33 @@ async def create_invite( invite.set_scheduled_event(target_event) return invite - async def invites(self) -> List[Invite]: + async def invites(self) -> list[Invite]: """|coro| Returns a list of all active instant invites from this channel. You must have :attr:`~discord.Permissions.manage_channels` to get this information. - Raises + Returns ------- + List[:class:`~discord.Invite`] + The list of invites that are currently active. + + Raises + ------ ~discord.Forbidden You do not have proper permissions to get the information. ~discord.HTTPException An error occurred while fetching the information. - - Returns - ------- - List[:class:`~discord.Invite`] - The list of invites that are currently active. """ state = self._state data = await state.http.invites_from_channel(self.id) guild = self.guild - return [Invite(state=state, data=invite, channel=self, guild=guild) for invite in data] + return [ + Invite(state=state, data=invite, channel=self, guild=guild) + for invite in data + ] class Messageable: @@ -1245,16 +1287,16 @@ async def _get_channel(self) -> MessageableChannel: @overload async def send( self, - content: Optional[str] = ..., + content: str | None = ..., *, tts: bool = ..., embed: Embed = ..., file: File = ..., - stickers: Sequence[Union[GuildSticker, StickerItem]] = ..., + stickers: Sequence[GuildSticker | StickerItem] = ..., delete_after: float = ..., - nonce: Union[str, int] = ..., + nonce: str | int = ..., allowed_mentions: AllowedMentions = ..., - reference: Union[Message, MessageReference, PartialMessage] = ..., + reference: Message | MessageReference | PartialMessage = ..., mention_author: bool = ..., view: View = ..., suppress: bool = ..., @@ -1264,16 +1306,16 @@ async def send( @overload async def send( self, - content: Optional[str] = ..., + content: str | None = ..., *, tts: bool = ..., embed: Embed = ..., - files: List[File] = ..., - stickers: Sequence[Union[GuildSticker, StickerItem]] = ..., + files: list[File] = ..., + stickers: Sequence[GuildSticker | StickerItem] = ..., delete_after: float = ..., - nonce: Union[str, int] = ..., + nonce: str | int = ..., allowed_mentions: AllowedMentions = ..., - reference: Union[Message, MessageReference, PartialMessage] = ..., + reference: Message | MessageReference | PartialMessage = ..., mention_author: bool = ..., view: View = ..., suppress: bool = ..., @@ -1283,16 +1325,16 @@ async def send( @overload async def send( self, - content: Optional[str] = ..., + content: str | None = ..., *, tts: bool = ..., - embeds: List[Embed] = ..., + embeds: list[Embed] = ..., file: File = ..., - stickers: Sequence[Union[GuildSticker, StickerItem]] = ..., + stickers: Sequence[GuildSticker | StickerItem] = ..., delete_after: float = ..., - nonce: Union[str, int] = ..., + nonce: str | int = ..., allowed_mentions: AllowedMentions = ..., - reference: Union[Message, MessageReference, PartialMessage] = ..., + reference: Message | MessageReference | PartialMessage = ..., mention_author: bool = ..., view: View = ..., suppress: bool = ..., @@ -1302,16 +1344,16 @@ async def send( @overload async def send( self, - content: Optional[str] = ..., + content: str | None = ..., *, tts: bool = ..., - embeds: List[Embed] = ..., - files: List[File] = ..., - stickers: Sequence[Union[GuildSticker, StickerItem]] = ..., + embeds: list[Embed] = ..., + files: list[File] = ..., + stickers: Sequence[GuildSticker | StickerItem] = ..., delete_after: float = ..., - nonce: Union[str, int] = ..., + nonce: str | int = ..., allowed_mentions: AllowedMentions = ..., - reference: Union[Message, MessageReference, PartialMessage] = ..., + reference: Message | MessageReference | PartialMessage = ..., mention_author: bool = ..., view: View = ..., suppress: bool = ..., @@ -1334,7 +1376,7 @@ async def send( reference=None, mention_author=None, view=None, - suppress=None + suppress=None, ): """|coro| @@ -1355,7 +1397,7 @@ async def send( **Specifying both parameters will lead to an exception**. Parameters - ------------ + ---------- content: Optional[:class:`str`] The content of the message to send. tts: :class:`bool` @@ -1409,8 +1451,13 @@ async def send( suppress: :class:`bool` Whether to suppress embeds for the message. + Returns + ------- + :class:`~discord.Message` + The message that was sent. + Raises - -------- + ------ ~discord.HTTPException Sending the message failed. ~discord.Forbidden @@ -1421,11 +1468,6 @@ async def send( or you specified both ``embed`` and ``embeds``, or the ``reference`` object is not a :class:`~discord.Message`, :class:`~discord.MessageReference` or :class:`~discord.PartialMessage`. - - Returns - --------- - :class:`~discord.Message` - The message that was sent. """ channel = await self._get_channel() @@ -1433,23 +1475,29 @@ async def send( content = str(content) if content is not None else None if embed is not None and embeds is not None: - raise InvalidArgument("cannot pass both embed and embeds parameter to send()") + raise InvalidArgument( + "cannot pass both embed and embeds parameter to send()" + ) if embed is not None: embed = embed.to_dict() elif embeds is not None: if len(embeds) > 10: - raise InvalidArgument("embeds parameter must be a list of up to 10 elements") + raise InvalidArgument( + "embeds parameter must be a list of up to 10 elements" + ) embeds = [embed.to_dict() for embed in embeds] - + flags = MessageFlags.suppress_embeds if suppress else MessageFlags.DEFAULT_VALUE if stickers is not None: stickers = [sticker.id for sticker in stickers] if allowed_mentions is None: - allowed_mentions = state.allowed_mentions and state.allowed_mentions.to_dict() + allowed_mentions = ( + state.allowed_mentions and state.allowed_mentions.to_dict() + ) elif state.allowed_mentions is not None: allowed_mentions = state.allowed_mentions.merge(allowed_mentions).to_dict() else: @@ -1469,7 +1517,9 @@ async def send( if view: if not hasattr(view, "__discord_ui_view__"): - raise InvalidArgument(f"view parameter must be View not {view.__class__!r}") + raise InvalidArgument( + f"view parameter must be View not {view.__class__!r}" + ) components = view.to_components() else: @@ -1502,7 +1552,9 @@ async def send( elif files is not None: if len(files) > 10: - raise InvalidArgument("files parameter must be a list of up to 10 elements") + raise InvalidArgument( + "files parameter must be a list of up to 10 elements" + ) elif not all(isinstance(file, File) for file in files): raise InvalidArgument("files parameter must be a list of File") @@ -1576,7 +1628,6 @@ def typing(self) -> Typing: await asyncio.sleep(10) await channel.send('done!') - """ return Typing(self) @@ -1586,30 +1637,30 @@ async def fetch_message(self, id: int, /) -> Message: Retrieves a single :class:`~discord.Message` from the destination. Parameters - ------------ + ---------- id: :class:`int` The message ID to look for. + Returns + ------- + :class:`~discord.Message` + The message asked for. + Raises - -------- + ------ ~discord.NotFound The specified message was not found. ~discord.Forbidden You do not have the permissions required to get a message. ~discord.HTTPException Retrieving the message failed. - - Returns - -------- - :class:`~discord.Message` - The message asked for. """ channel = await self._get_channel() data = await self._state.http.get_message(channel.id, id) return self._state.create_message(channel=channel, data=data) - async def pins(self) -> List[Message]: + async def pins(self) -> list[Message]: """|coro| Retrieves all messages that are currently pinned in the channel. @@ -1620,15 +1671,15 @@ async def pins(self) -> List[Message]: objects returned by this method do not contain complete :attr:`.Message.reactions` data. - Raises - ------- - ~discord.HTTPException - Retrieving the pinned messages failed. - Returns - -------- + ------- List[:class:`~discord.Message`] The messages that are currently pinned. + + Raises + ------ + ~discord.HTTPException + Retrieving the pinned messages failed. """ channel = await self._get_channel() @@ -1639,15 +1690,15 @@ async def pins(self) -> List[Message]: def can_send(self, *objects) -> bool: """Returns a :class:`bool` indicating whether you have the permissions to send the object(s). + Returns + ------- + :class:`bool` + Indicates whether you have the permissions to send the object(s). + Raises ------ TypeError An invalid type has been passed. - - Returns - -------- - :class:`bool` - Indicates whether you have the permissions to send the object(s). """ mapping = { "Message": "send_messages", @@ -1674,10 +1725,15 @@ def can_send(self, *objects) -> bool: if obj is None: permission = mapping["Message"] else: - permission = mapping.get(type(obj).__name__) or mapping[obj.__name__] + permission = ( + mapping.get(type(obj).__name__) or mapping[obj.__name__] + ) if type(obj).__name__ == "Emoji": - if obj._to_partial().is_unicode_emoji or obj.guild_id == channel.guild.id: + if ( + obj._to_partial().is_unicode_emoji + or obj.guild_id == channel.guild.id + ): continue elif type(obj).__name__ == "GuildSticker": if obj.guild_id == channel.guild.id: @@ -1694,35 +1750,18 @@ def can_send(self, *objects) -> bool: def history( self, *, - limit: Optional[int] = 100, - before: Optional[SnowflakeTime] = None, - after: Optional[SnowflakeTime] = None, - around: Optional[SnowflakeTime] = None, - oldest_first: Optional[bool] = None, + limit: int | None = 100, + before: SnowflakeTime | None = None, + after: SnowflakeTime | None = None, + around: SnowflakeTime | None = None, + oldest_first: bool | None = None, ) -> HistoryIterator: """Returns an :class:`~discord.AsyncIterator` that enables receiving the destination's message history. You must have :attr:`~discord.Permissions.read_message_history` permissions to use this. - Examples - --------- - - Usage :: - - counter = 0 - async for message in channel.history(limit=200): - if message.author == client.user: - counter += 1 - - Flattening into a list: :: - - messages = await channel.history(limit=123).flatten() - # messages is now a list of Message... - - All parameters are optional. - Parameters - ----------- + ---------- limit: Optional[:class:`int`] The number of messages to retrieve. If ``None``, retrieves every message in the channel. Note, however, @@ -1745,6 +1784,11 @@ def history( If set to ``True``, return messages in oldest->newest order. Defaults to ``True`` if ``after`` is specified, otherwise ``False``. + Yields + ------ + :class:`~discord.Message` + The message with the message data parsed. + Raises ------ ~discord.Forbidden @@ -1752,10 +1796,22 @@ def history( ~discord.HTTPException The request to get message history failed. - Yields - ------- - :class:`~discord.Message` - The message with the message data parsed. + Examples + -------- + + Usage :: + + counter = 0 + async for message in channel.history(limit=200): + if message.author == client.user: + counter += 1 + + Flattening into a list: :: + + messages = await channel.history(limit=123).flatten() + # messages is now a list of Message... + + All parameters are optional. """ return HistoryIterator( self, @@ -1785,10 +1841,10 @@ class Connectable(Protocol): __slots__ = () _state: ConnectionState - def _get_voice_client_key(self) -> Tuple[int, str]: + def _get_voice_client_key(self) -> tuple[int, str]: raise NotImplementedError - def _get_voice_state_pair(self) -> Tuple[int, int]: + def _get_voice_state_pair(self) -> tuple[int, int]: raise NotImplementedError async def connect( @@ -1806,7 +1862,7 @@ async def connect( This requires :attr:`Intents.voice_states`. Parameters - ----------- + ---------- timeout: :class:`float` The timeout in seconds to wait for the voice endpoint. reconnect: :class:`bool` @@ -1817,19 +1873,19 @@ async def connect( A type that subclasses :class:`~discord.VoiceProtocol` to connect with. Defaults to :class:`~discord.VoiceClient`. - Raises + Returns ------- + :class:`~discord.VoiceProtocol` + A voice client that is fully connected to the voice server. + + Raises + ------ asyncio.TimeoutError Could not connect to the voice channel in time. ~discord.ClientException You are already connected to a voice channel. ~discord.opus.OpusNotLoaded The opus library has not been loaded. - - Returns - -------- - :class:`~discord.VoiceProtocol` - A voice client that is fully connected to the voice server. """ key_id, _ = self._get_voice_client_key() diff --git a/discord/activity.py b/discord/activity.py index 0ad4482c8f..e6499c58b0 100644 --- a/discord/activity.py +++ b/discord/activity.py @@ -26,7 +26,7 @@ from __future__ import annotations import datetime -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, overload +from typing import TYPE_CHECKING, Any, Union, overload from .asset import Asset from .colour import Colour @@ -95,11 +95,7 @@ if TYPE_CHECKING: from .types.activity import Activity as ActivityPayload - from .types.activity import ( - ActivityAssets, - ActivityParty, - ActivityTimestamps, - ) + from .types.activity import ActivityAssets, ActivityParty, ActivityTimestamps class BaseActivity: @@ -124,16 +120,18 @@ class BaseActivity: __slots__ = ("_created_at",) def __init__(self, **kwargs): - self._created_at: Optional[float] = kwargs.pop("created_at", None) + self._created_at: float | None = kwargs.pop("created_at", None) @property - def created_at(self) -> Optional[datetime.datetime]: + def created_at(self) -> datetime.datetime | None: """Optional[:class:`datetime.datetime`]: When the user started doing this activity in UTC. .. versionadded:: 1.3 """ if self._created_at is not None: - return datetime.datetime.fromtimestamp(self._created_at / 1000, tz=datetime.timezone.utc) + return datetime.datetime.fromtimestamp( + self._created_at / 1000, tz=datetime.timezone.utc + ) def to_dict(self) -> ActivityPayload: raise NotImplementedError @@ -152,7 +150,7 @@ class Activity(BaseActivity): - :class:`Streaming` Attributes - ------------ + ---------- application_id: Optional[:class:`int`] The application ID of the game. name: Optional[:class:`str`] @@ -225,26 +223,30 @@ class Activity(BaseActivity): def __init__(self, **kwargs): super().__init__(**kwargs) - self.state: Optional[str] = kwargs.pop("state", None) - self.details: Optional[str] = kwargs.pop("details", None) + self.state: str | None = kwargs.pop("state", None) + self.details: str | None = kwargs.pop("details", None) self.timestamps: ActivityTimestamps = kwargs.pop("timestamps", {}) self.assets: ActivityAssets = kwargs.pop("assets", {}) self.party: ActivityParty = kwargs.pop("party", {}) - self.application_id: Optional[int] = _get_as_snowflake(kwargs, "application_id") - self.name: Optional[str] = kwargs.pop("name", None) - self.url: Optional[str] = kwargs.pop("url", None) + self.application_id: int | None = _get_as_snowflake(kwargs, "application_id") + self.name: str | None = kwargs.pop("name", None) + self.url: str | None = kwargs.pop("url", None) self.flags: int = kwargs.pop("flags", 0) - self.sync_id: Optional[str] = kwargs.pop("sync_id", None) - self.session_id: Optional[str] = kwargs.pop("session_id", None) - self.buttons: List[str] = kwargs.pop("buttons", []) + self.sync_id: str | None = kwargs.pop("sync_id", None) + self.session_id: str | None = kwargs.pop("session_id", None) + self.buttons: list[str] = kwargs.pop("buttons", []) activity_type = kwargs.pop("type", -1) self.type: ActivityType = ( - activity_type if isinstance(activity_type, ActivityType) else try_enum(ActivityType, activity_type) + activity_type + if isinstance(activity_type, ActivityType) + else try_enum(ActivityType, activity_type) ) emoji = kwargs.pop("emoji", None) - self.emoji: Optional[PartialEmoji] = PartialEmoji.from_dict(emoji) if emoji is not None else None + self.emoji: PartialEmoji | None = ( + PartialEmoji.from_dict(emoji) if emoji is not None else None + ) def __repr__(self) -> str: attrs = ( @@ -259,8 +261,8 @@ def __repr__(self) -> str: inner = " ".join("%s=%r" % t for t in attrs) return f"" - def to_dict(self) -> Dict[str, Any]: - ret: Dict[str, Any] = {} + def to_dict(self) -> dict[str, Any]: + ret: dict[str, Any] = {} for attr in self.__slots__: value = getattr(self, attr, None) if value is None: @@ -276,7 +278,7 @@ def to_dict(self) -> Dict[str, Any]: return ret @property - def start(self) -> Optional[datetime.datetime]: + def start(self) -> datetime.datetime | None: """Optional[:class:`datetime.datetime`]: When the user started doing this activity in UTC, if applicable.""" try: timestamp = self.timestamps["start"] / 1000 @@ -286,7 +288,7 @@ def start(self) -> Optional[datetime.datetime]: return datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc) @property - def end(self) -> Optional[datetime.datetime]: + def end(self) -> datetime.datetime | None: """Optional[:class:`datetime.datetime`]: When the user will stop doing this activity in UTC, if applicable.""" try: timestamp = self.timestamps["end"] / 1000 @@ -296,7 +298,7 @@ def end(self) -> Optional[datetime.datetime]: return datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc) @property - def large_image_url(self) -> Optional[str]: + def large_image_url(self) -> str | None: """Optional[:class:`str`]: Returns a URL pointing to the large image asset of this activity if applicable.""" if self.application_id is None: return None @@ -309,7 +311,7 @@ def large_image_url(self) -> Optional[str]: return f"{Asset.BASE}/app-assets/{self.application_id}/{large_image}.png" @property - def small_image_url(self) -> Optional[str]: + def small_image_url(self) -> str | None: """Optional[:class:`str`]: Returns a URL pointing to the small image asset of this activity if applicable.""" if self.application_id is None: return None @@ -322,12 +324,12 @@ def small_image_url(self) -> Optional[str]: return f"{Asset.BASE}/app-assets/{self.application_id}/{small_image}.png" @property - def large_image_text(self) -> Optional[str]: + def large_image_text(self) -> str | None: """Optional[:class:`str`]: Returns the large image asset hover text of this activity if applicable.""" return self.assets.get("large_text", None) @property - def small_image_text(self) -> Optional[str]: + def small_image_text(self) -> str | None: """Optional[:class:`str`]: Returns the small image asset hover text of this activity if applicable.""" return self.assets.get("small_text", None) @@ -356,12 +358,12 @@ class Game(BaseActivity): Returns the game's name. Parameters - ----------- + ---------- name: :class:`str` The game's name. Attributes - ----------- + ---------- name: :class:`str` The game's name. """ @@ -390,17 +392,21 @@ def type(self) -> ActivityType: return ActivityType.playing @property - def start(self) -> Optional[datetime.datetime]: + def start(self) -> datetime.datetime | None: """Optional[:class:`datetime.datetime`]: When the user started playing this game in UTC, if applicable.""" if self._start: - return datetime.datetime.fromtimestamp(self._start / 1000, tz=datetime.timezone.utc) + return datetime.datetime.fromtimestamp( + self._start / 1000, tz=datetime.timezone.utc + ) return None @property - def end(self) -> Optional[datetime.datetime]: + def end(self) -> datetime.datetime | None: """Optional[:class:`datetime.datetime`]: When the user will stop playing this game in UTC, if applicable.""" if self._end: - return datetime.datetime.fromtimestamp(self._end / 1000, tz=datetime.timezone.utc) + return datetime.datetime.fromtimestamp( + self._end / 1000, tz=datetime.timezone.utc + ) return None def __str__(self) -> str: @@ -409,8 +415,8 @@ def __str__(self) -> str: def __repr__(self) -> str: return f"" - def to_dict(self) -> Dict[str, Any]: - timestamps: Dict[str, Any] = {} + def to_dict(self) -> dict[str, Any]: + timestamps: dict[str, Any] = {} if self._start: timestamps["start"] = self._start @@ -457,7 +463,7 @@ class Streaming(BaseActivity): Returns the stream's name. Attributes - ----------- + ---------- platform: Optional[:class:`str`] Where the user is streaming from (ie. YouTube, Twitch). @@ -480,13 +486,13 @@ class Streaming(BaseActivity): __slots__ = ("platform", "name", "game", "url", "details", "assets") - def __init__(self, *, name: Optional[str], url: str, **extra: Any): + def __init__(self, *, name: str | None, url: str, **extra: Any): super().__init__(**extra) - self.platform: Optional[str] = name - self.name: Optional[str] = extra.pop("details", name) - self.game: Optional[str] = extra.pop("state", None) + self.platform: str | None = name + self.name: str | None = extra.pop("details", name) + self.game: str | None = extra.pop("state", None) self.url: str = url - self.details: Optional[str] = extra.pop("details", self.name) # compatibility + self.details: str | None = extra.pop("details", self.name) # compatibility self.assets: ActivityAssets = extra.pop("assets", {}) @property @@ -518,8 +524,8 @@ def twitch_name(self): else: return name[7:] if name[:7] == "twitch:" else None - def to_dict(self) -> Dict[str, Any]: - ret: Dict[str, Any] = { + def to_dict(self) -> dict[str, Any]: + ret: dict[str, Any] = { "type": ActivityType.streaming.value, "name": str(self.name), "url": str(self.url), @@ -530,7 +536,11 @@ def to_dict(self) -> Dict[str, Any]: return ret def __eq__(self, other: Any) -> bool: - return isinstance(other, Streaming) and other.name == self.name and other.url == self.url + return ( + isinstance(other, Streaming) + and other.name == self.name + and other.url == self.url + ) def __ne__(self, other: Any) -> bool: return not self.__eq__(other) @@ -576,12 +586,12 @@ class Spotify: def __init__(self, **data): self._state: str = data.pop("state", "") self._details: str = data.pop("details", "") - self._timestamps: Dict[str, int] = data.pop("timestamps", {}) + self._timestamps: dict[str, int] = data.pop("timestamps", {}) self._assets: ActivityAssets = data.pop("assets", {}) self._party: ActivityParty = data.pop("party", {}) self._sync_id: str = data.pop("sync_id") self._session_id: str = data.pop("session_id") - self._created_at: Optional[float] = data.pop("created_at", None) + self._created_at: float | None = data.pop("created_at", None) @property def type(self) -> ActivityType: @@ -592,29 +602,33 @@ def type(self) -> ActivityType: return ActivityType.listening @property - def created_at(self) -> Optional[datetime.datetime]: + def created_at(self) -> datetime.datetime | None: """Optional[:class:`datetime.datetime`]: When the user started listening in UTC. .. versionadded:: 1.3 """ if self._created_at is not None: - return datetime.datetime.fromtimestamp(self._created_at / 1000, tz=datetime.timezone.utc) + return datetime.datetime.fromtimestamp( + self._created_at / 1000, tz=datetime.timezone.utc + ) @property def colour(self) -> Colour: """:class:`Colour`: Returns the Spotify integration colour, as a :class:`Colour`. - There is an alias for this named :attr:`color`""" + There is an alias for this named :attr:`color` + """ return Colour(0x1DB954) @property def color(self) -> Colour: """:class:`Colour`: Returns the Spotify integration colour, as a :class:`Colour`. - There is an alias for this named :attr:`colour`""" + There is an alias for this named :attr:`colour` + """ return self.colour - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: return { "flags": 48, # SYNC | PLAY "name": "Spotify", @@ -658,7 +672,7 @@ def title(self) -> str: return self._details @property - def artists(self) -> List[str]: + def artists(self) -> list[str]: """List[:class:`str`]: The artists of the song being played.""" return self._state.split("; ") @@ -701,12 +715,16 @@ def track_url(self) -> str: @property def start(self) -> datetime.datetime: """:class:`datetime.datetime`: When the user started playing this song in UTC.""" - return datetime.datetime.fromtimestamp(self._timestamps["start"] / 1000, tz=datetime.timezone.utc) + return datetime.datetime.fromtimestamp( + self._timestamps["start"] / 1000, tz=datetime.timezone.utc + ) @property def end(self) -> datetime.datetime: """:class:`datetime.datetime`: When the user will stop playing this song in UTC.""" - return datetime.datetime.fromtimestamp(self._timestamps["end"] / 1000, tz=datetime.timezone.utc) + return datetime.datetime.fromtimestamp( + self._timestamps["end"] / 1000, tz=datetime.timezone.utc + ) @property def duration(self) -> datetime.timedelta: @@ -743,7 +761,7 @@ class CustomActivity(BaseActivity): .. versionadded:: 1.3 Attributes - ----------- + ---------- name: Optional[:class:`str`] The custom activity's name. emoji: Optional[:class:`PartialEmoji`] @@ -752,14 +770,16 @@ class CustomActivity(BaseActivity): __slots__ = ("name", "emoji", "state") - def __init__(self, name: Optional[str], *, emoji: Optional[PartialEmoji] = None, **extra: Any): + def __init__( + self, name: str | None, *, emoji: PartialEmoji | None = None, **extra: Any + ): super().__init__(**extra) - self.name: Optional[str] = name - self.state: Optional[str] = extra.pop("state", None) + self.name: str | None = name + self.state: str | None = extra.pop("state", None) if self.name == "Custom Status": self.name = self.state - self.emoji: Optional[PartialEmoji] + self.emoji: PartialEmoji | None if emoji is None: self.emoji = emoji elif isinstance(emoji, dict): @@ -769,7 +789,9 @@ def __init__(self, name: Optional[str], *, emoji: Optional[PartialEmoji] = None, elif isinstance(emoji, PartialEmoji): self.emoji = emoji else: - raise TypeError(f"Expected str, PartialEmoji, or None, received {type(emoji)!r} instead.") + raise TypeError( + f"Expected str, PartialEmoji, or None, received {type(emoji)!r} instead." + ) @property def type(self) -> ActivityType: @@ -779,7 +801,7 @@ def type(self) -> ActivityType: """ return ActivityType.custom - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: if self.name == self.state: o = { "type": ActivityType.custom.value, @@ -797,7 +819,11 @@ def to_dict(self) -> Dict[str, Any]: return o def __eq__(self, other: Any) -> bool: - return isinstance(other, CustomActivity) and other.name == self.name and other.emoji == self.emoji + return ( + isinstance(other, CustomActivity) + and other.name == self.name + and other.emoji == self.emoji + ) def __ne__(self, other: Any) -> bool: return not self.__eq__(other) @@ -829,7 +855,7 @@ def create_activity(data: None) -> None: ... -def create_activity(data: Optional[ActivityPayload]) -> Optional[ActivityTypes]: +def create_activity(data: ActivityPayload | None) -> ActivityTypes | None: if not data: return None @@ -851,6 +877,10 @@ def create_activity(data: Optional[ActivityPayload]) -> Optional[ActivityTypes]: # the url won't be None here return Streaming(**data) # type: ignore return Activity(**data) - elif game_type is ActivityType.listening and "sync_id" in data and "session_id" in data: + elif ( + game_type is ActivityType.listening + and "sync_id" in data + and "session_id" in data + ): return Spotify(**data) return Activity(**data) diff --git a/discord/appinfo.py b/discord/appinfo.py index 174ad773a0..9024b5e334 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List, Optional +from typing import TYPE_CHECKING from . import utils from .asset import Asset @@ -47,9 +47,8 @@ class AppInfo: """Represents the application info for the bot provided by Discord. - Attributes - ------------- + ---------- id: :class:`int` The application ID. name: :class:`str` @@ -141,25 +140,27 @@ def __init__(self, state: ConnectionState, data: AppInfoPayload): self.id: int = int(data["id"]) self.name: str = data["name"] self.description: str = data["description"] - self._icon: Optional[str] = data["icon"] - self.rpc_origins: List[str] = data["rpc_origins"] + self._icon: str | None = data["icon"] + self.rpc_origins: list[str] = data["rpc_origins"] self.bot_public: bool = data["bot_public"] self.bot_require_code_grant: bool = data["bot_require_code_grant"] self.owner: User = state.create_user(data["owner"]) - team: Optional[TeamPayload] = data.get("team") - self.team: Optional[Team] = Team(state, team) if team else None + team: TeamPayload | None = data.get("team") + self.team: Team | None = Team(state, team) if team else None self.summary: str = data["summary"] self.verify_key: str = data["verify_key"] - self.guild_id: Optional[int] = utils._get_as_snowflake(data, "guild_id") + self.guild_id: int | None = utils._get_as_snowflake(data, "guild_id") - self.primary_sku_id: Optional[int] = utils._get_as_snowflake(data, "primary_sku_id") - self.slug: Optional[str] = data.get("slug") - self._cover_image: Optional[str] = data.get("cover_image") - self.terms_of_service_url: Optional[str] = data.get("terms_of_service_url") - self.privacy_policy_url: Optional[str] = data.get("privacy_policy_url") + self.primary_sku_id: int | None = utils._get_as_snowflake( + data, "primary_sku_id" + ) + self.slug: str | None = data.get("slug") + self._cover_image: str | None = data.get("cover_image") + self.terms_of_service_url: str | None = data.get("terms_of_service_url") + self.privacy_policy_url: str | None = data.get("privacy_policy_url") def __repr__(self) -> str: return ( @@ -169,14 +170,14 @@ def __repr__(self) -> str: ) @property - def icon(self) -> Optional[Asset]: + def icon(self) -> Asset | None: """Optional[:class:`.Asset`]: Retrieves the application's icon asset, if any.""" if self._icon is None: return None return Asset._from_icon(self._state, self.id, self._icon, path="app") @property - def cover_image(self) -> Optional[Asset]: + def cover_image(self) -> Asset | None: """Optional[:class:`.Asset`]: Retrieves the cover image on a store embed, if any. This is only available if the application is a game sold on Discord. @@ -186,7 +187,7 @@ def cover_image(self) -> Optional[Asset]: return Asset._from_cover_image(self._state, self.id, self._cover_image) @property - def guild(self) -> Optional[Guild]: + def guild(self) -> Guild | None: """Optional[:class:`Guild`]: If this application is a game sold on Discord, this field will be the guild to which it has been linked. @@ -201,7 +202,7 @@ class PartialAppInfo: .. versionadded:: 2.0 Attributes - ------------- + ---------- id: :class:`int` The application ID. name: :class:`str` @@ -239,19 +240,19 @@ def __init__(self, *, state: ConnectionState, data: PartialAppInfoPayload): self._state: ConnectionState = state self.id: int = int(data["id"]) self.name: str = data["name"] - self._icon: Optional[str] = data.get("icon") + self._icon: str | None = data.get("icon") self.description: str = data["description"] - self.rpc_origins: Optional[List[str]] = data.get("rpc_origins") + self.rpc_origins: list[str] | None = data.get("rpc_origins") self.summary: str = data["summary"] self.verify_key: str = data["verify_key"] - self.terms_of_service_url: Optional[str] = data.get("terms_of_service_url") - self.privacy_policy_url: Optional[str] = data.get("privacy_policy_url") + self.terms_of_service_url: str | None = data.get("terms_of_service_url") + self.privacy_policy_url: str | None = data.get("privacy_policy_url") def __repr__(self) -> str: return f"<{self.__class__.__name__} id={self.id} name={self.name!r} description={self.description!r}>" @property - def icon(self) -> Optional[Asset]: + def icon(self) -> Asset | None: """Optional[:class:`.Asset`]: Retrieves the application's icon asset, if any.""" if self._icon is None: return None diff --git a/discord/asset.py b/discord/asset.py index 829340361a..9cfcb92897 100644 --- a/discord/asset.py +++ b/discord/asset.py @@ -27,7 +27,7 @@ import io import os -from typing import TYPE_CHECKING, Any, Literal, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any, Literal import yarl @@ -49,13 +49,18 @@ class AssetMixin: url: str - _state: Optional[Any] + _state: Any | None async def read(self) -> bytes: """|coro| Retrieves the content of this asset as a :class:`bytes` object. + Returns + ------- + :class:`bytes` + The content of the asset. + Raises ------ DiscordException @@ -64,11 +69,6 @@ async def read(self) -> bytes: Downloading the asset failed. NotFound The asset was deleted. - - Returns - ------- - :class:`bytes` - The content of the asset. """ if self._state is None: raise DiscordException("Invalid state (no ConnectionState provided)") @@ -77,7 +77,7 @@ async def read(self) -> bytes: async def save( self, - fp: Union[str, bytes, os.PathLike, io.BufferedIOBase], + fp: str | bytes | os.PathLike | io.BufferedIOBase, *, seek_begin: bool = True, ) -> int: @@ -95,6 +95,11 @@ async def save( Whether to seek to the beginning of the file after saving is successfully done. + Returns + ------- + :class:`int` + The number of bytes written. + Raises ------ DiscordException @@ -103,11 +108,6 @@ async def save( Downloading the asset failed. NotFound The asset was deleted. - - Returns - -------- - :class:`int` - The number of bytes written. """ data = await self.read() @@ -147,7 +147,7 @@ class Asset(AssetMixin): Returns the hash of the asset. """ - __slots__: Tuple[str, ...] = ( + __slots__: tuple[str, ...] = ( "_state", "_url", "_animated", @@ -183,7 +183,9 @@ def _from_avatar(cls, state, user_id: int, avatar: str) -> Asset: ) @classmethod - def _from_guild_avatar(cls, state, guild_id: int, member_id: int, avatar: str) -> Asset: + def _from_guild_avatar( + cls, state, guild_id: int, member_id: int, avatar: str + ) -> Asset: animated = avatar.startswith("a_") format = "gif" if animated else "png" return cls( @@ -258,7 +260,9 @@ def _from_user_banner(cls, state, user_id: int, banner_hash: str) -> Asset: ) @classmethod - def _from_scheduled_event_cover(cls, state, event_id: int, cover_hash: str) -> Asset: + def _from_scheduled_event_cover( + cls, state, event_id: int, cover_hash: str + ) -> Asset: return cls( state, url=f"{cls.BASE}/guild-events/{event_id}/{cover_hash}.png", @@ -306,7 +310,7 @@ def replace( """Returns a new asset with the passed components replaced. Parameters - ----------- + ---------- size: :class:`int` The new size of the asset. format: :class:`str` @@ -316,15 +320,15 @@ def replace( The new format to change it to if the asset isn't animated. Must be either 'webp', 'jpeg', 'jpg', or 'png'. - Raises - ------- - InvalidArgument - An invalid size or format was passed. - Returns - -------- + ------- :class:`Asset` The newly updated asset. + + Raises + ------ + InvalidArgument + An invalid size or format was passed. """ url = yarl.URL(self._url) path, _ = os.path.splitext(url.path) @@ -332,16 +336,22 @@ def replace( if format is not MISSING: if self._animated: if format not in VALID_ASSET_FORMATS: - raise InvalidArgument(f"format must be one of {VALID_ASSET_FORMATS}") + raise InvalidArgument( + f"format must be one of {VALID_ASSET_FORMATS}" + ) url = url.with_path(f"{path}.{format}") elif static_format is MISSING: if format not in VALID_STATIC_FORMATS: - raise InvalidArgument(f"format must be one of {VALID_STATIC_FORMATS}") + raise InvalidArgument( + f"format must be one of {VALID_STATIC_FORMATS}" + ) url = url.with_path(f"{path}.{format}") if static_format is not MISSING and not self._animated: if static_format not in VALID_STATIC_FORMATS: - raise InvalidArgument(f"static_format must be one of {VALID_STATIC_FORMATS}") + raise InvalidArgument( + f"static_format must be one of {VALID_STATIC_FORMATS}" + ) url = url.with_path(f"{path}.{static_format}") if size is not MISSING: @@ -358,19 +368,19 @@ def with_size(self, size: int, /) -> Asset: """Returns a new asset with the specified size. Parameters - ------------ + ---------- size: :class:`int` The new size of the asset. - Raises - ------- - InvalidArgument - The asset had an invalid size. - Returns - -------- + ------- :class:`Asset` The new updated asset. + + Raises + ------ + InvalidArgument + The asset had an invalid size. """ if not utils.valid_icon_size(size): raise InvalidArgument("size must be a power of 2 between 16 and 4096") @@ -382,19 +392,19 @@ def with_format(self, format: ValidAssetFormatTypes, /) -> Asset: """Returns a new asset with the specified format. Parameters - ------------ + ---------- format: :class:`str` The new format of the asset. - Raises - ------- - InvalidArgument - The asset has an invalid format. - Returns - -------- + ------- :class:`Asset` The new updated asset. + + Raises + ------ + InvalidArgument + The asset has an invalid format. """ if self._animated: @@ -415,19 +425,19 @@ def with_static_format(self, format: ValidStaticFormatTypes, /) -> Asset: not animated. Otherwise, the asset is not changed. Parameters - ------------ + ---------- format: :class:`str` The new static format of the asset. - Raises - ------- - InvalidArgument - The asset had an invalid format. - Returns - -------- + ------- :class:`Asset` The new updated asset. + + Raises + ------ + InvalidArgument + The asset had an invalid format. """ if self._animated: diff --git a/discord/audit_logs.py b/discord/audit_logs.py index 6868f99a8d..892c7e896e 100644 --- a/discord/audit_logs.py +++ b/discord/audit_logs.py @@ -25,20 +25,7 @@ from __future__ import annotations -from typing import ( - TYPE_CHECKING, - Any, - Callable, - ClassVar, - Dict, - Generator, - List, - Optional, - Tuple, - Type, - TypeVar, - Union, -) +from typing import TYPE_CHECKING, Any, Callable, ClassVar, Generator, TypeVar from . import enums, utils from .asset import Asset @@ -88,27 +75,31 @@ def _transform_snowflake(entry: AuditLogEntry, data: Snowflake) -> int: return int(data) -def _transform_channel(entry: AuditLogEntry, data: Optional[Snowflake]) -> Optional[Union[abc.GuildChannel, Object]]: +def _transform_channel( + entry: AuditLogEntry, data: Snowflake | None +) -> abc.GuildChannel | Object | None: if data is None: return None return entry.guild.get_channel(int(data)) or Object(id=data) -def _transform_member_id(entry: AuditLogEntry, data: Optional[Snowflake]) -> Union[Member, User, None]: +def _transform_member_id( + entry: AuditLogEntry, data: Snowflake | None +) -> Member | User | None: if data is None: return None return entry._get_member(int(data)) -def _transform_guild_id(entry: AuditLogEntry, data: Optional[Snowflake]) -> Optional[Guild]: +def _transform_guild_id(entry: AuditLogEntry, data: Snowflake | None) -> Guild | None: if data is None: return None return entry._state._get_guild(data) def _transform_overwrites( - entry: AuditLogEntry, data: List[PermissionOverwritePayload] -) -> List[Tuple[Object, PermissionOverwrite]]: + entry: AuditLogEntry, data: list[PermissionOverwritePayload] +) -> list[tuple[Object, PermissionOverwrite]]: overwrites = [] for elem in data: allow = Permissions(int(elem["allow"])) @@ -131,19 +122,21 @@ def _transform_overwrites( return overwrites -def _transform_icon(entry: AuditLogEntry, data: Optional[str]) -> Optional[Asset]: +def _transform_icon(entry: AuditLogEntry, data: str | None) -> Asset | None: if data is None: return None return Asset._from_guild_icon(entry._state, entry.guild.id, data) -def _transform_avatar(entry: AuditLogEntry, data: Optional[str]) -> Optional[Asset]: +def _transform_avatar(entry: AuditLogEntry, data: str | None) -> Asset | None: if data is None: return None return Asset._from_avatar(entry._state, entry._target_id, data) # type: ignore -def _transform_scheduled_event_cover(entry: AuditLogEntry, data: Optional[str]) -> Optional[Asset]: +def _transform_scheduled_event_cover( + entry: AuditLogEntry, data: str | None +) -> Asset | None: if data is None: return None return Asset._from_scheduled_event_cover(entry._state, entry._target_id, data) @@ -151,8 +144,8 @@ def _transform_scheduled_event_cover(entry: AuditLogEntry, data: Optional[str]) def _guild_hash_transformer( path: str, -) -> Callable[[AuditLogEntry, Optional[str]], Optional[Asset]]: - def _transform(entry: AuditLogEntry, data: Optional[str]) -> Optional[Asset]: +) -> Callable[[AuditLogEntry, str | None], Asset | None]: + def _transform(entry: AuditLogEntry, data: str | None) -> Asset | None: if data is None: return None return Asset._from_guild_image(entry._state, entry.guild.id, data, path=path) @@ -163,14 +156,16 @@ def _transform(entry: AuditLogEntry, data: Optional[str]) -> Optional[Asset]: T = TypeVar("T", bound=enums.Enum) -def _enum_transformer(enum: Type[T]) -> Callable[[AuditLogEntry, int], T]: +def _enum_transformer(enum: type[T]) -> Callable[[AuditLogEntry, int], T]: def _transform(entry: AuditLogEntry, data: int) -> T: return enums.try_enum(enum, data) return _transform -def _transform_type(entry: AuditLogEntry, data: int) -> Union[enums.ChannelType, enums.StickerType]: +def _transform_type( + entry: AuditLogEntry, data: int +) -> enums.ChannelType | enums.StickerType: if entry.action.name.startswith("sticker_"): return enums.try_enum(enums.StickerType, data) else: @@ -181,7 +176,7 @@ class AuditLogDiff: def __len__(self) -> int: return len(self.__dict__) - def __iter__(self) -> Generator[Tuple[str, Any], None, None]: + def __iter__(self) -> Generator[tuple[str, Any], None, None]: yield from self.__dict__.items() def __repr__(self) -> str: @@ -201,7 +196,7 @@ def __setattr__(self, key: str, value: Any) -> Any: class AuditLogChanges: - TRANSFORMERS: ClassVar[Dict[str, Tuple[Optional[str], Optional[Transformer]]]] = { + TRANSFORMERS: ClassVar[dict[str, tuple[str | None, Transformer | None]]] = { "verification_level": (None, _enum_transformer(enums.VerificationLevel)), "explicit_content_filter": (None, _enum_transformer(enums.ContentFilter)), "allow": (None, _transform_permissions), @@ -250,7 +245,7 @@ class AuditLogChanges: def __init__( self, entry: AuditLogEntry, - data: List[AuditLogChangePayload], + data: list[AuditLogChangePayload], *, state: ConnectionState, ): @@ -276,7 +271,7 @@ def __init__( if key: attr = key - transformer: Optional[Transformer] + transformer: Transformer | None try: before = elem["old_value"] @@ -289,10 +284,15 @@ def __init__( if attr == "location" and hasattr(self.before, "location_type"): from .scheduled_events import ScheduledEventLocation - if self.before.location_type is enums.ScheduledEventLocationType.external: + if ( + self.before.location_type + is enums.ScheduledEventLocationType.external + ): before = ScheduledEventLocation(state=state, value=before) elif hasattr(self.before, "channel"): - before = ScheduledEventLocation(state=state, value=self.before.channel) + before = ScheduledEventLocation( + state=state, value=self.before.channel + ) setattr(self.before, attr, before) @@ -307,10 +307,15 @@ def __init__( if attr == "location" and hasattr(self.after, "location_type"): from .scheduled_events import ScheduledEventLocation - if self.after.location_type is enums.ScheduledEventLocationType.external: + if ( + self.after.location_type + is enums.ScheduledEventLocationType.external + ): after = ScheduledEventLocation(state=state, value=after) elif hasattr(self.after, "channel"): - after = ScheduledEventLocation(state=state, value=self.after.channel) + after = ScheduledEventLocation( + state=state, value=self.after.channel + ) setattr(self.after, attr, after) @@ -330,7 +335,7 @@ def _handle_role( first: AuditLogDiff, second: AuditLogDiff, entry: AuditLogEntry, - elem: List[RolePayload], + elem: list[RolePayload], ) -> None: if not hasattr(first, "roles"): setattr(first, "roles", []) @@ -417,7 +422,9 @@ class AuditLogEntry(Hashable): which actions have this field filled out. """ - def __init__(self, *, users: Dict[int, User], data: AuditLogEntryPayload, guild: Guild): + def __init__( + self, *, users: dict[int, User], data: AuditLogEntryPayload, guild: Guild + ): self._state = guild._state self.guild = guild self._users = users @@ -437,27 +444,38 @@ def _from_data(self, data: AuditLogEntryPayload) -> None: self.extra: _AuditLogProxyMemberPrune = type( "_AuditLogProxy", (), {k: int(v) for k, v in self.extra.items()} )() - elif self.action is enums.AuditLogAction.member_move or self.action is enums.AuditLogAction.message_delete: + elif ( + self.action is enums.AuditLogAction.member_move + or self.action is enums.AuditLogAction.message_delete + ): channel_id = int(self.extra["channel_id"]) elems = { "count": int(self.extra["count"]), - "channel": self.guild.get_channel(channel_id) or Object(id=channel_id), + "channel": self.guild.get_channel(channel_id) + or Object(id=channel_id), } - self.extra: _AuditLogProxyMemberMoveOrMessageDelete = type("_AuditLogProxy", (), elems)() + self.extra: _AuditLogProxyMemberMoveOrMessageDelete = type( + "_AuditLogProxy", (), elems + )() elif self.action is enums.AuditLogAction.member_disconnect: # The member disconnect action has a dict with some information elems = { "count": int(self.extra["count"]), } - self.extra: _AuditLogProxyMemberDisconnect = type("_AuditLogProxy", (), elems)() + self.extra: _AuditLogProxyMemberDisconnect = type( + "_AuditLogProxy", (), elems + )() elif self.action.name.endswith("pin"): # the pin actions have a dict with some information channel_id = int(self.extra["channel_id"]) elems = { - "channel": self.guild.get_channel(channel_id) or Object(id=channel_id), + "channel": self.guild.get_channel(channel_id) + or Object(id=channel_id), "message_id": int(self.extra["message_id"]), } - self.extra: _AuditLogProxyPinAction = type("_AuditLogProxy", (), elems)() + self.extra: _AuditLogProxyPinAction = type( + "_AuditLogProxy", (), elems + )() elif self.action.name.startswith("overwrite_"): # the overwrite_ actions have a dict with some information instance_id = int(self.extra["id"]) @@ -472,20 +490,25 @@ def _from_data(self, data: AuditLogEntryPayload) -> None: self.extra: Role = role elif self.action.name.startswith("stage_instance"): channel_id = int(self.extra["channel_id"]) - elems = {"channel": self.guild.get_channel(channel_id) or Object(id=channel_id)} - self.extra: _AuditLogProxyStageInstanceAction = type("_AuditLogProxy", (), elems)() - - self.extra: Union[ - _AuditLogProxyMemberPrune, - _AuditLogProxyMemberMoveOrMessageDelete, - _AuditLogProxyMemberDisconnect, - _AuditLogProxyPinAction, - _AuditLogProxyStageInstanceAction, - Member, - User, - None, - Role, - ] + elems = { + "channel": self.guild.get_channel(channel_id) + or Object(id=channel_id) + } + self.extra: _AuditLogProxyStageInstanceAction = type( + "_AuditLogProxy", (), elems + )() + + self.extra: ( + _AuditLogProxyMemberPrune + | _AuditLogProxyMemberMoveOrMessageDelete + | _AuditLogProxyMemberDisconnect + | _AuditLogProxyPinAction + | _AuditLogProxyStageInstanceAction + | Member + | User + | None + | Role + ) # this key is not present when the above is present, typically. # It's a list of { new_value: a, old_value: b, key: c } @@ -497,7 +520,7 @@ def _from_data(self, data: AuditLogEntryPayload) -> None: self.user = self._get_member(utils._get_as_snowflake(data, "user_id")) # type: ignore self._target_id = utils._get_as_snowflake(data, "target_id") - def _get_member(self, user_id: int) -> Union[Member, User, None]: + def _get_member(self, user_id: int) -> Member | User | None: return self.guild.get_member(user_id) or self._users.get(user_id) def __repr__(self) -> str: @@ -511,20 +534,20 @@ def created_at(self) -> datetime.datetime: @utils.cached_property def target( self, - ) -> Union[ - Guild, - abc.GuildChannel, - Member, - User, - Role, - Invite, - Emoji, - StageInstance, - GuildSticker, - Thread, - Object, - None, - ]: + ) -> ( + Guild + | abc.GuildChannel + | Member + | User + | Role + | Invite + | Emoji + | StageInstance + | GuildSticker + | Thread + | Object + | None + ): try: converter = getattr(self, f"_convert_target_{self.action.target_type}") except AttributeError: @@ -557,19 +580,23 @@ def after(self) -> AuditLogDiff: def _convert_target_guild(self, target_id: int) -> Guild: return self.guild - def _convert_target_channel(self, target_id: int) -> Union[abc.GuildChannel, Object]: + def _convert_target_channel(self, target_id: int) -> abc.GuildChannel | Object: return self.guild.get_channel(target_id) or Object(id=target_id) - def _convert_target_user(self, target_id: int) -> Union[Member, User, None]: + def _convert_target_user(self, target_id: int) -> Member | User | None: return self._get_member(target_id) - def _convert_target_role(self, target_id: int) -> Union[Role, Object]: + def _convert_target_role(self, target_id: int) -> Role | Object: return self.guild.get_role(target_id) or Object(id=target_id) def _convert_target_invite(self, target_id: int) -> Invite: # invites have target_id set to null # so figure out which change has the full invite data - changeset = self.before if self.action is enums.AuditLogAction.invite_delete else self.after + changeset = ( + self.before + if self.action is enums.AuditLogAction.invite_delete + else self.after + ) fake_payload = { "max_age": changeset.max_age, @@ -586,20 +613,22 @@ def _convert_target_invite(self, target_id: int) -> Invite: pass return obj - def _convert_target_emoji(self, target_id: int) -> Union[Emoji, Object]: + def _convert_target_emoji(self, target_id: int) -> Emoji | Object: return self._state.get_emoji(target_id) or Object(id=target_id) - def _convert_target_message(self, target_id: int) -> Union[Member, User, None]: + def _convert_target_message(self, target_id: int) -> Member | User | None: return self._get_member(target_id) - def _convert_target_stage_instance(self, target_id: int) -> Union[StageInstance, Object]: + def _convert_target_stage_instance(self, target_id: int) -> StageInstance | Object: return self.guild.get_stage_instance(target_id) or Object(id=target_id) - def _convert_target_sticker(self, target_id: int) -> Union[GuildSticker, Object]: + def _convert_target_sticker(self, target_id: int) -> GuildSticker | Object: return self._state.get_sticker(target_id) or Object(id=target_id) - def _convert_target_thread(self, target_id: int) -> Union[Thread, Object]: + def _convert_target_thread(self, target_id: int) -> Thread | Object: return self.guild.get_thread(target_id) or Object(id=target_id) - def _convert_target_scheduled_event(self, target_id: int) -> Union[ScheduledEvent, Object]: + def _convert_target_scheduled_event( + self, target_id: int + ) -> ScheduledEvent | Object: return self.guild.get_scheduled_event(target_id) or Object(id=target_id) diff --git a/discord/automod.py b/discord/automod.py index 8d8b3bf495..02fbd4ab6e 100644 --- a/discord/automod.py +++ b/discord/automod.py @@ -26,7 +26,7 @@ from datetime import timedelta from functools import cached_property -from typing import TYPE_CHECKING, Dict, List, Optional, Union +from typing import TYPE_CHECKING from . import utils from .enums import ( @@ -39,9 +39,7 @@ from .mixins import Hashable from .object import Object -__all__ = ( - "AutoModRule", -) +__all__ = ("AutoModRule",) if TYPE_CHECKING: from .abc import Snowflake @@ -50,25 +48,23 @@ from .member import Member from .role import Role from .state import ConnectionState - from .types.automod import ( - AutoModAction as AutoModActionPayload, - AutoModActionMetadata as AutoModActionMetadataPayload, - AutoModRule as AutoModRulePayload, - AutoModTriggerMetadata as AutoModTriggerMetadataPayload, - ) + from .types.automod import AutoModAction as AutoModActionPayload + from .types.automod import AutoModActionMetadata as AutoModActionMetadataPayload + from .types.automod import AutoModRule as AutoModRulePayload + from .types.automod import AutoModTriggerMetadata as AutoModTriggerMetadataPayload MISSING = utils.MISSING class AutoModActionMetadata: """Represents an action's metadata. - + Depending on the action's type, different attributes will be used. - + .. versionadded:: 2.0 - + Attributes - ----------- + ---------- channel_id: :class:`int` The ID of the channel to send the message to. Only for actions of type :attr:`AutoModActionType.send_alert_message`. @@ -76,6 +72,7 @@ class AutoModActionMetadata: How long the member that triggered the action should be timed out for. Only for actions of type :attr:`AutoModActionType.timeout`. """ + # maybe add a table of action types and attributes? __slots__ = ( @@ -83,32 +80,34 @@ class AutoModActionMetadata: "timeout_duration", ) - def __init__(self, channel_id: int = MISSING, timeout_duration: timedelta = MISSING): + def __init__( + self, channel_id: int = MISSING, timeout_duration: timedelta = MISSING + ): self.channel_id: int = channel_id self.timeout_duration: timedelta = timeout_duration - def to_dict(self) -> Dict: + def to_dict(self) -> dict: data = {} - + if self.channel_id is not MISSING: data["channel_id"] = self.channel_id - + if self.timeout_duration is not MISSING: data["duration_seconds"] = self.timeout_duration.total_seconds() - + return data - + @classmethod def from_dict(cls, data: AutoModActionMetadataPayload): kwargs = {} - + if (channel_id := data.get("channel_id")) is not None: kwargs["channel_id"] = int(channel_id) - + if (duration_seconds := data.get("duration_seconds")) is not None: # might need an explicit int cast kwargs["timeout_duration"] = timedelta(seconds=duration_seconds) - + return cls(**kwargs) def __repr__(self) -> str: @@ -117,27 +116,28 @@ def __repr__(self) -> str: "timeout_duration", ) inner = [] - + for attr in repr_attrs: if (value := getattr(self, attr)) is not MISSING: inner.append(f"{attr}={value}") inner = " ".join(inner) - + return f"" class AutoModAction: """Represents an action for a guild's auto moderation rule. - + .. versionadded:: 2.0 - + Attributes - ----------- + ---------- type: :class:`AutoModActionType` The action's type. metadata: :class:`AutoModActionMetadata` The action's metadata. """ + # note that AutoModActionType.timeout is only valid for trigger type 1? __slots__ = ( @@ -149,15 +149,18 @@ def __init__(self, action_type: AutoModActionType, metadata: AutoModActionMetada self.type: AutoModActionType = action_type self.metadata: AutoModActionMetadata = metadata - def to_dict(self) -> Dict: + def to_dict(self) -> dict: return { "type": self.type.value, "metadata": self.metadata, } - + @classmethod def from_dict(cls, data: AutoModActionPayload): - return cls(try_enum(AutoModActionType, data["type"]), AutoModActionMetadata.from_dict(data["metadata"])) + return cls( + try_enum(AutoModActionType, data["type"]), + AutoModActionMetadata.from_dict(data["metadata"]), + ) def __repr__(self) -> str: return f"" @@ -165,18 +168,19 @@ def __repr__(self) -> str: class AutoModTriggerMetadata: """Represents a rule's trigger metadata. - + Depending on the trigger type, different attributes will be used. - + .. versionadded:: 2.0 - + Attributes - ----------- + ---------- keyword_filter: List[:class:`str`] A list of substrings to filter. Only for triggers of type :attr:`AutoModTriggerType.keyword`. presets: List[:class:`AutoModKeywordPresetType`] A list of keyword presets to filter. Only for triggers of type :attr:`AutoModTriggerType.keyword_preset`. """ + # maybe add a table of action types and attributes? # wording for presets could change @@ -185,31 +189,37 @@ class AutoModTriggerMetadata: "presets", ) - def __init__(self, keyword_filter: List[str] = MISSING, presets: List[AutoModKeywordPresetType] = MISSING): + def __init__( + self, + keyword_filter: list[str] = MISSING, + presets: list[AutoModKeywordPresetType] = MISSING, + ): self.keyword_filter = keyword_filter self.presets = presets - def to_dict(self) -> Dict: + def to_dict(self) -> dict: data = {} - + if self.keyword_filter is not MISSING: data["keyword_filter"] = self.keyword_filter - + if self.presets is not MISSING: data["presets"] = [wordset.value for wordset in self.presets] - + return data - + @classmethod def from_dict(cls, data: AutoModTriggerMetadataPayload): kwargs = {} - + if (keyword_filter := data.get("keyword_filter")) is not None: kwargs["keyword_filter"] = keyword_filter - + if (presets := data.get("presets")) is not None: - kwargs["presets"] = [try_enum(AutoModKeywordPresetType, wordset) for wordset in presets] - + kwargs["presets"] = [ + try_enum(AutoModKeywordPresetType, wordset) for wordset in presets + ] + return cls(**kwargs) def __repr__(self) -> str: @@ -218,12 +228,12 @@ def __repr__(self) -> str: "presets", ) inner = [] - + for attr in repr_attrs: if (value := getattr(self, attr)) is not MISSING: inner.append(f"{attr}={value}") inner = " ".join(inner) - + return f"" @@ -231,7 +241,7 @@ class AutoModRule(Hashable): """Represents a guild's auto moderation rule. .. versionadded:: 2.0 - + .. container:: operations .. describe:: x == y @@ -300,93 +310,111 @@ def __init__( self.guild_id: int = int(data["guild_id"]) self.name: str = data["name"] self.creator_id: int = int(data["creator_id"]) - self.event_type: AutoModEventType = try_enum(AutoModEventType, data["event_type"]) - self.trigger_type: AutoModTriggerType = try_enum(AutoModTriggerType, data["trigger_type"]) - self.trigger_metadata: AutoModTriggerMetadata = AutoModTriggerMetadata.from_dict(data["trigger_metadata"]) - self.actions: List[AutoModAction] = [AutoModAction.from_dict(d) for d in data["actions"]] + self.event_type: AutoModEventType = try_enum( + AutoModEventType, data["event_type"] + ) + self.trigger_type: AutoModTriggerType = try_enum( + AutoModTriggerType, data["trigger_type"] + ) + self.trigger_metadata: AutoModTriggerMetadata = ( + AutoModTriggerMetadata.from_dict(data["trigger_metadata"]) + ) + self.actions: list[AutoModAction] = [ + AutoModAction.from_dict(d) for d in data["actions"] + ] self.enabled: bool = data["enabled"] - self.exempt_role_ids: List[int] = [int(r) for r in data["exempt_roles"]] - self.exempt_channel_ids: List[int] = [int(c) for c in data["exempt_channels"]] + self.exempt_role_ids: list[int] = [int(r) for r in data["exempt_roles"]] + self.exempt_channel_ids: list[int] = [int(c) for c in data["exempt_channels"]] def __repr__(self) -> str: return f"" def __str__(self) -> str: return self.name - + @cached_property - def guild(self) -> Optional[Guild]: + def guild(self) -> Guild | None: """Optional[:class:`Guild`]: The guild this rule belongs to.""" return self._state._get_guild(self.guild_id) - + @cached_property - def creator(self) -> Optional[Member]: + def creator(self) -> Member | None: """Optional[:class:`Member`]: The member who created this rule.""" if self.guild is None: return None return self.guild.get_member(self.creator_id) - + @cached_property - def exempt_roles(self) -> List[Union[Role, Object]]: - """List[Union[:class:`Role`, :class:`Object`]]: The roles that are exempt + def exempt_roles(self) -> list[Role | Object]: + """List[Union[:class:`Role`, :class:`Object`]]: The roles that are exempt from this rule. - - If a role is not found in the guild's cache, + + If a role is not found in the guild's cache, then it will be returned as an :class:`Object`. """ if self.guild is None: return [Object(role_id) for role_id in self.exempt_role_ids] - return [self.guild.get_role(role_id) or Object(role_id) for role_id in self.exempt_role_ids] - + return [ + self.guild.get_role(role_id) or Object(role_id) + for role_id in self.exempt_role_ids + ] + @cached_property - def exempt_channels(self) -> List[Union[Union[TextChannel, ForumChannel, VoiceChannel], Object]]: + def exempt_channels( + self, + ) -> list[TextChannel | ForumChannel | VoiceChannel | Object]: """List[Union[Union[:class:`TextChannel`, :class:`ForumChannel`, :class:`VoiceChannel`], :class:`Object`]]: The channels that are exempt from this rule. - - If a channel is not found in the guild's cache, + + If a channel is not found in the guild's cache, then it will be returned as an :class:`Object`. """ if self.guild is None: return [Object(channel_id) for channel_id in self.exempt_channel_ids] - return [self.guild.get_channel(channel_id) or Object(channel_id) for channel_id in self.exempt_channel_ids] - - async def delete(self, reason: Optional[str] = None) -> None: + return [ + self.guild.get_channel(channel_id) or Object(channel_id) + for channel_id in self.exempt_channel_ids + ] + + async def delete(self, reason: str | None = None) -> None: """|coro| - + Deletes this rule. - + Parameters - ----------- + ---------- reason: Optional[:class:`str`] The reason for deleting this rule. Shows up in the audit log. - + Raises - ------- + ------ Forbidden You do not have the Manage Guild permission. HTTPException The operation failed. """ - await self._state.http.delete_auto_moderation_rule(self.guild_id, self.id, reason=reason) - + await self._state.http.delete_auto_moderation_rule( + self.guild_id, self.id, reason=reason + ) + async def edit( self, *, name: str = MISSING, event_type: AutoModEventType = MISSING, trigger_metadata: AutoModTriggerMetadata = MISSING, - actions: List[AutoModAction] = MISSING, + actions: list[AutoModAction] = MISSING, enabled: bool = MISSING, - exempt_roles: List[Snowflake] = MISSING, - exempt_channels: List[Snowflake] = MISSING, - reason: Optional[str] = None, - ) -> Optional[AutoModRule]: + exempt_roles: list[Snowflake] = MISSING, + exempt_channels: list[Snowflake] = MISSING, + reason: str | None = None, + ) -> AutoModRule | None: """|coro| - + Edits this rule. - + Parameters - ----------- + ---------- name: :class:`str` The rule's new name. event_type: :class:`AutoModEventType` @@ -403,45 +431,47 @@ async def edit( The channels that will be exempt from this rule. reason: Optional[:class:`str`] The reason for editing this rule. Shows up in the audit log. - - Raises + + Returns ------- + Optional[:class:`.AutoModRule`] + The newly updated rule, if applicable. This is only returned + when fields are updated. + + Raises + ------ Forbidden You do not have the Manage Guild permission. HTTPException The operation failed. - - Returns - -------- - Optional[:class:`.AutoModRule`] - The newly updated rule, if applicable. This is only returned - when fields are updated. """ http = self._state.http payload = {} - + if name is not MISSING: payload["name"] = name - + if event_type is not MISSING: payload["event_type"] = event_type.value - + if trigger_metadata is not MISSING: payload["trigger_metadata"] = trigger_metadata.to_dict() - + if actions is not MISSING: payload["actions"] = [a.to_dict() for a in actions] - + if enabled is not MISSING: payload["enabled"] = enabled - + # Maybe consider enforcing limits on the number of exempt roles/channels? if exempt_roles is not MISSING: payload["exempt_roles"] = [r.id for r in exempt_roles] - + if exempt_channels is not MISSING: payload["exempt_channels"] = [c.id for c in exempt_channels] - + if payload: - data = await http.edit_auto_moderation_rule(self.guild_id, self.id, payload, reason=reason) + data = await http.edit_auto_moderation_rule( + self.guild_id, self.id, payload, reason=reason + ) return AutoModRule(state=self._state, data=data) diff --git a/discord/backoff.py b/discord/backoff.py index 1ede214e86..009df69983 100644 --- a/discord/backoff.py +++ b/discord/backoff.py @@ -27,7 +27,7 @@ import random import time -from typing import Callable, Generic, Literal, TypeVar, Union, overload +from typing import Callable, Generic, Literal, TypeVar, overload T = TypeVar("T", bool, Literal[True], Literal[False]) @@ -68,7 +68,7 @@ def __init__(self, base: int = 1, *, integral: T = False): rand = random.Random() rand.seed() - self._randfunc: Callable[..., Union[int, float]] = rand.randrange if integral else rand.uniform # type: ignore + self._randfunc: Callable[..., int | float] = rand.randrange if integral else rand.uniform # type: ignore @overload def delay(self: ExponentialBackoff[Literal[False]]) -> float: @@ -79,10 +79,10 @@ def delay(self: ExponentialBackoff[Literal[True]]) -> int: ... @overload - def delay(self: ExponentialBackoff[bool]) -> Union[int, float]: + def delay(self: ExponentialBackoff[bool]) -> int | float: ... - def delay(self) -> Union[int, float]: + def delay(self) -> int | float: """Compute the next delay Returns the next delay to wait according to the exponential diff --git a/discord/bot.py b/discord/bot.py index 8a0608fa80..697a9be144 100644 --- a/discord/bot.py +++ b/discord/bot.py @@ -34,20 +34,7 @@ import sys import traceback from abc import ABC, abstractmethod -from typing import ( - Any, - Callable, - Coroutine, - Dict, - Generator, - List, - Literal, - Mapping, - Optional, - Type, - TypeVar, - Union, -) +from typing import Any, Callable, Coroutine, Generator, Literal, Mapping, TypeVar from .client import Client from .cog import CogMixin @@ -86,7 +73,7 @@ class ApplicationCommandMixin(ABC): application command compatibility. Attributes - ----------- + ---------- application_commands: :class:`dict` A mapping of command id string to :class:`.ApplicationCommand` objects. pending_application_commands: :class:`list` @@ -108,14 +95,16 @@ def pending_application_commands(self): return self._pending_application_commands @property - def commands(self) -> List[Union[ApplicationCommand, Any]]: + def commands(self) -> list[ApplicationCommand | Any]: commands = self.application_commands - if self._bot._supports_prefixed_commands and hasattr(self._bot, "prefixed_commands"): + if self._bot._supports_prefixed_commands and hasattr( + self._bot, "prefixed_commands" + ): commands += getattr(self._bot, "prefixed_commands") return commands @property - def application_commands(self) -> List[ApplicationCommand]: + def application_commands(self) -> list[ApplicationCommand]: return list(self._application_commands.values()) def add_application_command(self, command: ApplicationCommand) -> None: @@ -127,7 +116,7 @@ def add_application_command(self, command: ApplicationCommand) -> None: .. versionadded:: 2.0 Parameters - ----------- + ---------- command: :class:`.ApplicationCommand` The command to add. """ @@ -147,19 +136,21 @@ def add_application_command(self, command: ApplicationCommand) -> None: break self._pending_application_commands.append(command) - def remove_application_command(self, command: ApplicationCommand) -> Optional[ApplicationCommand]: + def remove_application_command( + self, command: ApplicationCommand + ) -> ApplicationCommand | None: """Remove a :class:`.ApplicationCommand` from the internal list of commands. .. versionadded:: 2.0 Parameters - ----------- + ---------- command: :class:`.ApplicationCommand` The command to remove. Returns - -------- + ------- Optional[:class:`.ApplicationCommand`] The command that was removed. If the name is not valid then ``None`` is returned instead. @@ -187,16 +178,16 @@ def get_command(self): def get_application_command( self, name: str, - guild_ids: Optional[List[int]] = None, - type: Type[ApplicationCommand] = SlashCommand, - ) -> Optional[ApplicationCommand]: + guild_ids: list[int] | None = None, + type: type[ApplicationCommand] = SlashCommand, + ) -> ApplicationCommand | None: """Get a :class:`.ApplicationCommand` from the internal list of commands. .. versionadded:: 2.0 Parameters - ----------- + ---------- name: :class:`str` The name of the command to get. guild_ids: List[:class:`int`] @@ -205,7 +196,7 @@ def get_application_command( The type of the command to get. Defaults to :class:`.SlashCommand`. Returns - -------- + ------- Optional[:class:`.ApplicationCommand`] The command that was requested. If not found, returns ``None``. """ @@ -218,9 +209,9 @@ def get_application_command( async def get_desynced_commands( self, - guild_id: Optional[int] = None, - prefetched: Optional[List[interactions.ApplicationCommand]] = None - ) -> List[Dict[str, Any]]: + guild_id: int | None = None, + prefetched: list[interactions.ApplicationCommand] | None = None, + ) -> list[dict[str, Any]]: """|coro| Gets the list of commands that are desynced from discord. If ``guild_id`` is specified, it will only return @@ -232,7 +223,6 @@ async def get_desynced_commands( .. versionadded:: 2.0 - Parameters ---------- guild_id: Optional[:class:`int`] @@ -256,7 +246,11 @@ def _check_command(cmd: ApplicationCommand, match: Mapping[str, Any]) -> bool: return True for i, subcommand in enumerate(cmd.subcommands): match_ = next( - (data for data in match["options"] if data["name"] == subcommand.name), + ( + data + for data in match["options"] + if data["name"] == subcommand.name + ), MISSING, ) if match_ is not MISSING and _check_command(subcommand, match_): @@ -286,18 +280,28 @@ def _check_command(cmd: ApplicationCommand, match: Mapping[str, Any]) -> bool: # The API considers False (autocomplete) and [] (choices) to be falsy values falsy_vals = (False, []) for opt in value: - cmd_vals = [val.get(opt, MISSING) for val in as_dict[check]] if check in as_dict else [] + cmd_vals = ( + [val.get(opt, MISSING) for val in as_dict[check]] + if check in as_dict + else [] + ) for i, val in enumerate(cmd_vals): if val in falsy_vals: cmd_vals[i] = MISSING - if match.get(check, MISSING) is not MISSING and cmd_vals != [ + if match.get( + check, MISSING + ) is not MISSING and cmd_vals != [ val.get(opt, MISSING) for val in match[check] ]: # We have a difference return True elif getattr(cmd, check, None) != match.get(check): # We have a difference - if check == "default_permission" and getattr(cmd, check) is True and match.get(check) is None: + if ( + check == "default_permission" + and getattr(cmd, check) is True + and match.get(check) is None + ): # This is a special case # TODO: Remove for perms v2 continue @@ -310,16 +314,24 @@ def _check_command(cmd: ApplicationCommand, match: Mapping[str, Any]) -> bool: if guild_id is None: pending = [cmd for cmd in cmds if cmd.guild_ids is None] else: - pending = [cmd for cmd in cmds if cmd.guild_ids is not None and guild_id in cmd.guild_ids] + pending = [ + cmd + for cmd in cmds + if cmd.guild_ids is not None and guild_id in cmd.guild_ids + ] - registered_commands: List[interactions.ApplicationCommand] = [] + registered_commands: list[interactions.ApplicationCommand] = [] if prefetched is not None: registered_commands = prefetched elif self._bot.user: if guild_id is None: - registered_commands = await self._bot.http.get_global_commands(self._bot.user.id) + registered_commands = await self._bot.http.get_global_commands( + self._bot.user.id + ) else: - registered_commands = await self._bot.http.get_guild_commands(self._bot.user.id, guild_id) + registered_commands = await self._bot.http.get_guild_commands( + self._bot.user.id, guild_id + ) registered_commands_dict = {cmd["name"]: cmd for cmd in registered_commands} # First let's check if the commands we have locally are the same as the ones on discord @@ -338,7 +350,9 @@ def _check_command(cmd: ApplicationCommand, match: Mapping[str, Any]) -> bool: ) else: # We have this command registered but it's the same - return_value.append({"command": cmd, "action": None, "id": int(match["id"])}) + return_value.append( + {"command": cmd, "action": None, "id": int(match["id"])} + ) # Now let's see if there are any commands on discord that we need to delete for cmd, value_ in registered_commands_dict.items(): @@ -361,7 +375,7 @@ async def register_command( self, command: ApplicationCommand, force: bool = True, - guild_ids: Optional[List[int]] = None, + guild_ids: list[int] | None = None, ) -> None: """|coro| @@ -389,12 +403,12 @@ async def register_command( async def register_commands( self, - commands: Optional[List[ApplicationCommand]] = None, - guild_id: Optional[int] = None, + commands: list[ApplicationCommand] | None = None, + guild_id: int | None = None, method: Literal["individual", "bulk", "auto"] = "bulk", force: bool = False, delete_existing: bool = True, - ) -> List[interactions.ApplicationCommand]: + ) -> list[interactions.ApplicationCommand]: """|coro| Register a list of commands. @@ -441,8 +455,12 @@ async def register_commands( "edit": self._bot.http.edit_global_command, } - def _register(method: Literal["bulk", "upsert", "delete", "edit"], *args, **kwargs): - return registration_methods[method](self._bot.user and self._bot.user.id, *args, **kwargs) + def _register( + method: Literal["bulk", "upsert", "delete", "edit"], *args, **kwargs + ): + return registration_methods[method]( + self._bot.user and self._bot.user.id, *args, **kwargs + ) else: pending = list( @@ -458,13 +476,21 @@ def _register(method: Literal["bulk", "upsert", "delete", "edit"], *args, **kwar "edit": self._bot.http.edit_guild_command, } - def _register(method: Literal["bulk", "upsert", "delete", "edit"], *args, **kwargs): - return registration_methods[method](self._bot.user and self._bot.user.id, guild_id, *args, **kwargs) + def _register( + method: Literal["bulk", "upsert", "delete", "edit"], *args, **kwargs + ): + return registration_methods[method]( + self._bot.user and self._bot.user.id, guild_id, *args, **kwargs + ) - def register(method: Literal["bulk", "upsert", "delete", "edit"], *args, **kwargs): + def register( + method: Literal["bulk", "upsert", "delete", "edit"], *args, **kwargs + ): if kwargs.pop("_log", True): if method == "bulk": - _log.debug(f"Bulk updating commands {[c['name'] for c in args[0]]} for guild {guild_id}") + _log.debug( + f"Bulk updating commands {[c['name'] for c in args[0]]} for guild {guild_id}" + ) # TODO: Find where "cmd" is defined elif method == "upsert": _log.debug(f"Creating command {cmd['name']} for guild {guild_id}") # type: ignore @@ -477,20 +503,28 @@ def register(method: Literal["bulk", "upsert", "delete", "edit"], *args, **kwarg pending_actions = [] if not force: - prefetched_commands: List[interactions.ApplicationCommand] = [] + prefetched_commands: list[interactions.ApplicationCommand] = [] if self._bot.user: if guild_id is None: - prefetched_commands = await self._bot.http.get_global_commands(self._bot.user.id) + prefetched_commands = await self._bot.http.get_global_commands( + self._bot.user.id + ) else: - prefetched_commands = await self._bot.http.get_guild_commands(self._bot.user.id, guild_id) - desynced = await self.get_desynced_commands(guild_id=guild_id, prefetched=prefetched_commands) + prefetched_commands = await self._bot.http.get_guild_commands( + self._bot.user.id, guild_id + ) + desynced = await self.get_desynced_commands( + guild_id=guild_id, prefetched=prefetched_commands + ) for cmd in desynced: if cmd["action"] == "delete": pending_actions.append( { "action": "delete" if delete_existing else None, - "command": collections.namedtuple("Command", ["name"])(name=cmd["command"]), + "command": collections.namedtuple("Command", ["name"])( + name=cmd["command"] + ), "id": cmd["id"], } ) @@ -523,9 +557,15 @@ def register(method: Literal["bulk", "upsert", "delete", "edit"], *args, **kwarg ) else: raise ValueError(f"Unknown action: {cmd['action']}") - filtered_no_action = list(filter(lambda c: c["action"] is not None, pending_actions)) - filtered_deleted = list(filter(lambda a: a["action"] != "delete", pending_actions)) - if method == "bulk" or (method == "auto" and len(filtered_deleted) == len(pending)): + filtered_no_action = list( + filter(lambda c: c["action"] is not None, pending_actions) + ) + filtered_deleted = list( + filter(lambda a: a["action"] != "delete", pending_actions) + ) + if method == "bulk" or ( + method == "auto" and len(filtered_deleted) == len(pending) + ): # Either the method is bulk or all the commands need to be modified, so we can just do a bulk upsert data = [cmd["command"].to_dict() for cmd in filtered_deleted] # If there's nothing to update, don't bother @@ -547,9 +587,13 @@ def register(method: Literal["bulk", "upsert", "delete", "edit"], *args, **kwarg await register("delete", cmd["command"]) continue if cmd["action"] == "edit": - registered.append(await register("edit", cmd["id"], cmd["command"].to_dict())) + registered.append( + await register("edit", cmd["id"], cmd["command"].to_dict()) + ) elif cmd["action"] == "upsert": - registered.append(await register("upsert", cmd["command"].to_dict())) + registered.append( + await register("upsert", cmd["command"].to_dict()) + ) else: raise ValueError(f"Unknown action: {cmd['action']}") @@ -557,9 +601,13 @@ def register(method: Literal["bulk", "upsert", "delete", "edit"], *args, **kwarg if method != "bulk": if self._bot.user: if guild_id is None: - registered = await self._bot.http.get_global_commands(self._bot.user.id) + registered = await self._bot.http.get_global_commands( + self._bot.user.id + ) else: - registered = await self._bot.http.get_guild_commands(self._bot.user.id, guild_id) + registered = await self._bot.http.get_guild_commands( + self._bot.user.id, guild_id + ) else: data = [cmd.to_dict() for cmd in pending] registered = await register("bulk", data) @@ -571,7 +619,9 @@ def register(method: Literal["bulk", "upsert", "delete", "edit"], *args, **kwarg type=i.get("type"), ) if not cmd: - raise ValueError(f"Registered command {i['name']}, type {i.get('type')} not found in pending commands") + raise ValueError( + f"Registered command {i['name']}, type {i.get('type')} not found in pending commands" + ) cmd.id = i["id"] self._application_commands[cmd.id] = cmd @@ -579,12 +629,12 @@ def register(method: Literal["bulk", "upsert", "delete", "edit"], *args, **kwarg async def sync_commands( self, - commands: Optional[List[ApplicationCommand]] = None, + commands: list[ApplicationCommand] | None = None, method: Literal["individual", "bulk", "auto"] = "bulk", force: bool = False, - guild_ids: Optional[List[int]] = None, + guild_ids: list[int] | None = None, register_guild_commands: bool = True, - check_guilds: Optional[List[int]] = [], + check_guilds: list[int] | None = [], delete_existing: bool = True, ) -> None: """|coro| @@ -643,19 +693,27 @@ async def sync_commands( global_commands, method=method, force=force, delete_existing=delete_existing ) - registered_guild_commands: Dict[int, List[interactions.ApplicationCommand]] = {} + registered_guild_commands: dict[int, list[interactions.ApplicationCommand]] = {} if register_guild_commands: - cmd_guild_ids: List[int] = [] + cmd_guild_ids: list[int] = [] for cmd in commands: if cmd.guild_ids is not None: cmd_guild_ids.extend(cmd.guild_ids) if check_guilds is not None: cmd_guild_ids.extend(check_guilds) for guild_id in set(cmd_guild_ids): - guild_commands = [cmd for cmd in commands if cmd.guild_ids is not None and guild_id in cmd.guild_ids] + guild_commands = [ + cmd + for cmd in commands + if cmd.guild_ids is not None and guild_id in cmd.guild_ids + ] app_cmds = await self.register_commands( - guild_commands, guild_id=guild_id, method=method, force=force, delete_existing=delete_existing + guild_commands, + guild_id=guild_id, + method=method, + force=force, + delete_existing=delete_existing, ) registered_guild_commands[guild_id] = app_cmds @@ -687,7 +745,9 @@ async def sync_commands( cmd.id = i["id"] self._application_commands[cmd.id] = cmd - async def process_application_commands(self, interaction: Interaction, auto_sync: Optional[bool] = None) -> None: + async def process_application_commands( + self, interaction: Interaction, auto_sync: bool | None = None + ) -> None: """|coro| This function processes the commands that have been registered @@ -699,13 +759,13 @@ async def process_application_commands(self, interaction: Interaction, auto_sync you should invoke this coroutine as well. This function finds a registered command matching the interaction id from - application commands and invokes it. If no matching command was + application commands and invokes it. If no matching command was found, it replies to the interaction with a default message. .. versionadded:: 2.0 Parameters - ----------- + ---------- interaction: :class:`discord.Interaction` The interaction to process auto_sync: Optional[:class:`bool`] @@ -722,10 +782,10 @@ async def process_application_commands(self, interaction: Interaction, auto_sync ): return - command: Optional[ApplicationCommand] = None + command: ApplicationCommand | None = None try: if interaction.data: - command = self._application_commands[interaction.data["id"]] # type: ignore + command = self._application_commands[interaction.data["id"]] # type: ignore except KeyError: for cmd in self.application_commands + self.pending_application_commands: if interaction.data: @@ -733,7 +793,11 @@ async def process_application_commands(self, interaction: Interaction, auto_sync if guild_id: guild_id = int(guild_id) if cmd.name == interaction.data["name"] and ( # type: ignore - guild_id == cmd.guild_ids or (isinstance(cmd.guild_ids, list) and guild_id in cmd.guild_ids) + guild_id == cmd.guild_ids + or ( + isinstance(cmd.guild_ids, list) + and guild_id in cmd.guild_ids + ) ): command = cmd break @@ -748,14 +812,18 @@ async def process_application_commands(self, interaction: Interaction, auto_sync return self._bot.dispatch("unknown_application_command", interaction) if interaction.type is InteractionType.auto_complete: - return self._bot.dispatch("application_command_auto_complete", interaction, command) + return self._bot.dispatch( + "application_command_auto_complete", interaction, command + ) ctx = await self.get_application_context(interaction) if command: ctx.command = command await self.invoke_application_command(ctx) - async def on_application_command_auto_complete(self, interaction: Interaction, command: ApplicationCommand) -> None: + async def on_application_command_auto_complete( + self, interaction: Interaction, command: ApplicationCommand + ) -> None: async def callback() -> None: ctx = await self.get_autocomplete_context(interaction) ctx.command = command @@ -763,7 +831,11 @@ async def callback() -> None: autocomplete_task = self._bot.loop.create_task(callback()) try: - await self._bot.wait_for("application_command_auto_complete", check=lambda i, c: c == command, timeout=3) + await self._bot.wait_for( + "application_command_auto_complete", + check=lambda i, c: c == command, + timeout=3, + ) except asyncio.TimeoutError: return else: @@ -778,7 +850,7 @@ def slash_command(self, **kwargs): .. versionadded:: 2.0 Returns - -------- + ------- Callable[..., :class:`SlashCommand`] A decorator that converts the provided method into a :class:`.SlashCommand`, adds it to the bot, then returns it. @@ -793,7 +865,7 @@ def user_command(self, **kwargs): .. versionadded:: 2.0 Returns - -------- + ------- Callable[..., :class:`UserCommand`] A decorator that converts the provided method into a :class:`.UserCommand`, adds it to the bot, then returns it. @@ -808,7 +880,7 @@ def message_command(self, **kwargs): .. versionadded:: 2.0 Returns - -------- + ------- Callable[..., :class:`MessageCommand`] A decorator that converts the provided method into a :class:`.MessageCommand`, adds it to the bot, then returns it. @@ -822,7 +894,7 @@ def application_command(self, **kwargs): .. versionadded:: 2.0 Returns - -------- + ------- Callable[..., :class:`ApplicationCommand`] A decorator that converts the provided method into an :class:`.ApplicationCommand`, adds it to the bot, then returns it. @@ -845,7 +917,7 @@ def command(self, **kwargs): .. versionadded:: 2.0 Returns - -------- + ------- Callable[..., :class:`ApplicationCommand`] A decorator that converts the provided method into an :class:`.ApplicationCommand`, adds it to the bot, then returns it. @@ -853,7 +925,11 @@ def command(self, **kwargs): return self.application_command(**kwargs) def create_group( - self, name: str, description: Optional[str] = None, guild_ids: Optional[List[int]] = None, **kwargs + self, + name: str, + description: str | None = None, + guild_ids: list[int] | None = None, + **kwargs, ) -> SlashCommandGroup: """A shortcut method that creates a slash command group with no subcommands and adds it to the internal command list via :meth:`add_application_command`. @@ -873,7 +949,7 @@ def create_group( Any additional keyword arguments to pass to :class:`.SlashCommandGroup`. Returns - -------- + ------- SlashCommandGroup The slash command group that was created. """ @@ -884,10 +960,10 @@ def create_group( def group( self, - name: Optional[str] = None, - description: Optional[str] = None, - guild_ids: Optional[List[int]] = None, - ) -> Callable[[Type[SlashCommandGroup]], SlashCommandGroup]: + name: str | None = None, + description: str | None = None, + guild_ids: list[int] | None = None, + ) -> Callable[[type[SlashCommandGroup]], SlashCommandGroup]: """A shortcut decorator that initializes the provided subclass of :class:`.SlashCommandGroup` and adds it to the internal command list via :meth:`add_application_command`. @@ -904,12 +980,12 @@ def group( This will be a global command if ``None`` is passed. Returns - -------- + ------- Callable[[Type[SlashCommandGroup]], SlashCommandGroup] The slash command group that was created. """ - def inner(cls: Type[SlashCommandGroup]) -> SlashCommandGroup: + def inner(cls: type[SlashCommandGroup]) -> SlashCommandGroup: group = cls( name or cls.__name__, ( @@ -939,7 +1015,9 @@ def walk_application_commands(self) -> Generator[ApplicationCommand, None, None] yield from command.walk_commands() yield command - async def get_application_context(self, interaction: Interaction, cls: Any = ApplicationContext) -> ApplicationContext: + async def get_application_context( + self, interaction: Interaction, cls: Any = ApplicationContext + ) -> ApplicationContext: r"""|coro| Returns the invocation context from the interaction. @@ -965,7 +1043,9 @@ class be provided, it must be similar enough to """ return cls(self, interaction) - async def get_autocomplete_context(self, interaction: Interaction, cls: Any = AutocompleteContext) -> AutocompleteContext: + async def get_autocomplete_context( + self, interaction: Interaction, cls: Any = AutocompleteContext + ) -> AutocompleteContext: r"""|coro| Returns the autocomplete context from the interaction. @@ -998,7 +1078,7 @@ async def invoke_application_command(self, ctx: ApplicationContext) -> None: context and handles all the internal event dispatch mechanisms. Parameters - ----------- + ---------- ctx: :class:`.ApplicationCommand` The invocation context to invoke. """ @@ -1015,7 +1095,7 @@ async def invoke_application_command(self, ctx: ApplicationContext) -> None: @property @abstractmethod - def _bot(self) -> Union["Bot", "AutoShardedBot"]: + def _bot(self) -> Bot | AutoShardedBot: ... @@ -1041,8 +1121,12 @@ def __init__(self, description=None, *args, **options): if self.owner_id and self.owner_ids: raise TypeError("Both owner_id and owner_ids are set.") - if self.owner_ids and not isinstance(self.owner_ids, collections.abc.Collection): - raise TypeError(f"owner_ids must be a collection not {self.owner_ids.__class__!r}") + if self.owner_ids and not isinstance( + self.owner_ids, collections.abc.Collection + ): + raise TypeError( + f"owner_ids must be a collection not {self.owner_ids.__class__!r}" + ) self._checks = [] self._check_once = [] @@ -1056,7 +1140,9 @@ async def on_connect(self): async def on_interaction(self, interaction): await self.process_application_commands(interaction) - async def on_application_command_error(self, context: ApplicationContext, exception: DiscordException) -> None: + async def on_application_command_error( + self, context: ApplicationContext, exception: DiscordException + ) -> None: """|coro| The default command error handler provided by the bot. @@ -1078,7 +1164,9 @@ async def on_application_command_error(self, context: ApplicationContext, except return print(f"Ignoring exception in command {context.command}:", file=sys.stderr) - traceback.print_exception(type(exception), exception, exception.__traceback__, file=sys.stderr) + traceback.print_exception( + type(exception), exception, exception.__traceback__, file=sys.stderr + ) # global check registration # TODO: Remove these from commands.Bot @@ -1095,13 +1183,12 @@ def check(self, func): :exc:`.ApplicationCommandError`. Example - --------- + ------- .. code-block:: python3 @bot.check def check_commands(ctx): return ctx.command.qualified_name in allowed_commands - """ # T was used instead of Check to ensure the type matches on return self.add_check(func) # type: ignore @@ -1112,12 +1199,11 @@ def add_check(self, func, *, call_once: bool = False) -> None: :meth:`.check_once`. Parameters - ----------- + ---------- func The function that was used as a global check. call_once: :class:`bool` If the function should only be called once per :meth:`.Bot.invoke` call. - """ if call_once: @@ -1131,13 +1217,12 @@ def remove_check(self, func, *, call_once: bool = False) -> None: if the function is not in the global checks. Parameters - ----------- + ---------- func The function to remove from the global checks. call_once: :class:`bool` If the function was added with ``call_once=True`` in the :meth:`.Bot.add_check` call or using :meth:`.check_once`. - """ checks = self._check_once if call_once else self._checks @@ -1164,18 +1249,19 @@ def check_once(self, func): :exc:`.ApplicationCommandError`. Example - --------- + ------- .. code-block:: python3 @bot.check_once def whitelist(ctx): return ctx.message.author.id in my_whitelist - """ self.add_check(func, call_once=True) return func - async def can_run(self, ctx: ApplicationContext, *, call_once: bool = False) -> bool: + async def can_run( + self, ctx: ApplicationContext, *, call_once: bool = False + ) -> bool: data = self._check_once if call_once else self._checks if not data: @@ -1190,14 +1276,14 @@ def add_listener(self, func: CoroFunc, name: str = MISSING) -> None: """The non decorator alternative to :meth:`.listen`. Parameters - ----------- + ---------- func: :ref:`coroutine ` The function to call. name: :class:`str` The name of the event to listen for. Defaults to ``func.__name__``. Example - -------- + ------- .. code-block:: python3 @@ -1221,7 +1307,7 @@ def remove_listener(self, func: CoroFunc, name: str = MISSING) -> None: """Removes a listener from the pool of listeners. Parameters - ----------- + ---------- func The function that was used as a listener to remove. name: :class:`str` @@ -1244,8 +1330,13 @@ def listen(self, name: str = MISSING) -> Callable[[CFT], CFT]: The functions being listened to must be a :ref:`coroutine `. + Raises + ------ + TypeError + The function being listened to is not a coroutine. + Example - -------- + ------- .. code-block:: python3 @@ -1260,11 +1351,6 @@ async def my_message(message): print('two') Would print one and two in an unspecified order. - - Raises - ------- - TypeError - The function being listened to is not a coroutine. """ def decorator(func: CFT) -> CFT: @@ -1295,12 +1381,12 @@ def before_invoke(self, coro): then the hooks are not called. Parameters - ----------- + ---------- coro: :ref:`coroutine ` The coroutine to register as the pre-invoke hook. Raises - ------- + ------ TypeError The coroutine passed is not actually a coroutine. """ @@ -1356,12 +1442,12 @@ async def is_owner(self, user: User) -> bool: :attr:`owner_ids` is not set. Parameters - ----------- + ---------- user: :class:`.abc.User` The user to check for. Returns - -------- + ------- :class:`bool` Whether the user is the owner. """ @@ -1393,7 +1479,7 @@ class Bot(BotBase, Client): .. versionadded:: 2.0 Attributes - ----------- + ---------- description: :class:`str` The content prefixed into the default help message. owner_id: Optional[:class:`int`] @@ -1421,7 +1507,7 @@ class Bot(BotBase, Client): """ @property - def _bot(self) -> "Bot": + def _bot(self) -> Bot: return self @@ -1433,5 +1519,5 @@ class AutoShardedBot(BotBase, AutoShardedClient): """ @property - def _bot(self) -> "AutoShardedBot": + def _bot(self) -> AutoShardedBot: return self diff --git a/discord/channel.py b/discord/channel.py index 8549523504..73ca1fe2c4 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -26,21 +26,7 @@ from __future__ import annotations import datetime -from typing import ( - TYPE_CHECKING, - Any, - Callable, - Dict, - Iterable, - List, - Mapping, - Optional, - Tuple, - Type, - TypeVar, - Union, - overload, -) +from typing import TYPE_CHECKING, Any, Callable, Iterable, Mapping, TypeVar, overload import discord.abc @@ -117,13 +103,19 @@ class _TextChannel(discord.abc.GuildChannel, Hashable): "flags", ) - def __init__(self, *, state: ConnectionState, guild: Guild, data: Union[TextChannelPayload, ForumChannelPayload]): + def __init__( + self, + *, + state: ConnectionState, + guild: Guild, + data: TextChannelPayload | ForumChannelPayload, + ): self._state: ConnectionState = state self.id: int = int(data["id"]) self._update(guild, data) @property - def _repr_attrs(self) -> Tuple[str, ...]: + def _repr_attrs(self) -> tuple[str, ...]: return "id", "name", "position", "nsfw", "category_id" def __repr__(self) -> str: @@ -131,22 +123,28 @@ def __repr__(self) -> str: joined = " ".join("%s=%r" % t for t in attrs) return f"<{self.__class__.__name__} {joined}>" - def _update(self, guild: Guild, data: Union[TextChannelPayload, ForumChannelPayload]) -> None: + def _update( + self, guild: Guild, data: TextChannelPayload | ForumChannelPayload + ) -> None: # This data will always exist self.guild: Guild = guild self.name: str = data["name"] - self.category_id: Optional[int] = utils._get_as_snowflake(data, "parent_id") + self.category_id: int | None = utils._get_as_snowflake(data, "parent_id") self._type: int = data["type"] # This data may be missing depending on how this object is being created/updated if not data.pop("_invoke_flag", False): - self.topic: Optional[str] = data.get("topic") + self.topic: str | None = data.get("topic") self.position: int = data.get("position") self.nsfw: bool = data.get("nsfw", False) # Does this need coercion into `int`? No idea yet. self.slowmode_delay: int = data.get("rate_limit_per_user", 0) - self.default_auto_archive_duration: ThreadArchiveDuration = data.get("default_auto_archive_duration", 1440) - self.last_message_id: Optional[int] = utils._get_as_snowflake(data, "last_message_id") + self.default_auto_archive_duration: ThreadArchiveDuration = data.get( + "default_auto_archive_duration", 1440 + ) + self.last_message_id: int | None = utils._get_as_snowflake( + data, "last_message_id" + ) self.flags: ChannelFlags = ChannelFlags._from_value(data.get("flags", 0)) self._fill_overwrites(data) @@ -160,7 +158,7 @@ def _sorting_bucket(self) -> int: return ChannelType.text.value @utils.copy_doc(discord.abc.GuildChannel.permissions_for) - def permissions_for(self, obj: Union[Member, Role], /) -> Permissions: + def permissions_for(self, obj: Member | Role, /) -> Permissions: base = super().permissions_for(obj) # text channels do not have voice related permissions @@ -169,24 +167,28 @@ def permissions_for(self, obj: Union[Member, Role], /) -> Permissions: return base @property - def members(self) -> List[Member]: + def members(self) -> list[Member]: """List[:class:`Member`]: Returns all members that can see this channel.""" return [m for m in self.guild.members if self.permissions_for(m).read_messages] @property - def threads(self) -> List[Thread]: + def threads(self) -> list[Thread]: """List[:class:`Thread`]: Returns all the threads that you can see. .. versionadded:: 2.0 """ - return [thread for thread in self.guild._threads.values() if thread.parent_id == self.id] + return [ + thread + for thread in self.guild._threads.values() + if thread.parent_id == self.id + ] def is_nsfw(self) -> bool: """:class:`bool`: Checks if the channel is NSFW.""" return self.nsfw @property - def last_message(self) -> Optional[Message]: + def last_message(self) -> Message | None: """Fetches the last message from this channel in cache. The message might not be valid or point to an existing message. @@ -200,32 +202,36 @@ def last_message(self) -> Optional[Message]: attribute. Returns - --------- + ------- Optional[:class:`Message`] The last message in this channel or ``None`` if not found. """ - return self._state._get_message(self.last_message_id) if self.last_message_id else None + return ( + self._state._get_message(self.last_message_id) + if self.last_message_id + else None + ) @overload async def edit( self, *, - reason: Optional[str] = ..., + reason: str | None = ..., name: str = ..., topic: str = ..., position: int = ..., nsfw: bool = ..., sync_permissions: bool = ..., - category: Optional[CategoryChannel] = ..., + category: CategoryChannel | None = ..., slowmode_delay: int = ..., default_auto_archive_duration: ThreadArchiveDuration = ..., type: ChannelType = ..., - overwrites: Mapping[Union[Role, Member, Snowflake], PermissionOverwrite] = ..., - ) -> Optional[TextChannel]: + overwrites: Mapping[Role | Member | Snowflake, PermissionOverwrite] = ..., + ) -> TextChannel | None: ... @overload - async def edit(self) -> Optional[TextChannel]: + async def edit(self) -> TextChannel | None: ... async def edit(self, *, reason=None, **options): @@ -276,6 +282,12 @@ async def edit(self, *, reason=None, **options): The new default auto archive duration in minutes for threads created in this channel. Must be one of ``60``, ``1440``, ``4320``, or ``10080``. + Returns + ------- + Optional[:class:`.TextChannel`] + The newly edited text channel. If the edit was only positional + then ``None`` is returned instead. + Raises ------ InvalidArgument @@ -285,12 +297,6 @@ async def edit(self, *, reason=None, **options): You do not have permissions to edit the channel. HTTPException Editing the channel failed. - - Returns - -------- - Optional[:class:`.TextChannel`] - The newly edited text channel. If the edit was only positional - then ``None`` is returned instead. """ payload = await self._edit(options, reason=reason) @@ -299,7 +305,9 @@ async def edit(self, *, reason=None, **options): return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore @utils.copy_doc(discord.abc.GuildChannel.clone) - async def clone(self, *, name: Optional[str] = None, reason: Optional[str] = None) -> TextChannel: + async def clone( + self, *, name: str | None = None, reason: str | None = None + ) -> TextChannel: return await self._clone_impl( { "topic": self.topic, @@ -310,7 +318,9 @@ async def clone(self, *, name: Optional[str] = None, reason: Optional[str] = Non reason=reason, ) - async def delete_messages(self, messages: Iterable[Snowflake], *, reason: Optional[str] = None) -> None: + async def delete_messages( + self, messages: Iterable[Snowflake], *, reason: str | None = None + ) -> None: """|coro| Deletes a list of messages. This is similar to :meth:`Message.delete` @@ -327,7 +337,7 @@ async def delete_messages(self, messages: Iterable[Snowflake], *, reason: Option use this. Parameters - ----------- + ---------- messages: Iterable[:class:`abc.Snowflake`] An iterable of messages denoting which ones to bulk delete. reason: Optional[:class:`str`] @@ -364,15 +374,15 @@ async def delete_messages(self, messages: Iterable[Snowflake], *, reason: Option async def purge( self, *, - limit: Optional[int] = 100, + limit: int | None = 100, check: Callable[[Message], bool] = MISSING, - before: Optional[SnowflakeTime] = None, - after: Optional[SnowflakeTime] = None, - around: Optional[SnowflakeTime] = None, - oldest_first: Optional[bool] = False, + before: SnowflakeTime | None = None, + after: SnowflakeTime | None = None, + around: SnowflakeTime | None = None, + oldest_first: bool | None = False, bulk: bool = True, - reason: Optional[str] = None, - ) -> List[Message]: + reason: str | None = None, + ) -> list[Message]: """|coro| Purges a list of messages that meet the criteria given by the predicate @@ -384,19 +394,8 @@ async def purge( The :attr:`~Permissions.read_message_history` permission is also needed to retrieve message history. - Examples - --------- - - Deleting bot's messages :: - - def is_me(m): - return m.author == client.user - - deleted = await channel.purge(limit=100, check=is_me) - await channel.send(f'Deleted {len(deleted)} message(s)') - Parameters - ----------- + ---------- limit: Optional[:class:`int`] The number of messages to search through. This is not the number of messages that will be deleted, though it can be. @@ -418,17 +417,28 @@ def is_me(m): reason: Optional[:class:`str`] The reason for deleting the messages. Shows up on the audit log. - Raises + Returns ------- + List[:class:`.Message`] + The list of messages that were deleted. + + Raises + ------ Forbidden You do not have proper permissions to do the actions required. HTTPException Purging the messages failed. - Returns + Examples -------- - List[:class:`.Message`] - The list of messages that were deleted. + + Deleting bot's messages :: + + def is_me(m): + return m.author == client.user + + deleted = await channel.purge(limit=100, check=is_me) + await channel.send(f'Deleted {len(deleted)} message(s)') """ return await discord.abc._purge_messages_helper( self, @@ -442,22 +452,22 @@ def is_me(m): reason=reason, ) - async def webhooks(self) -> List[Webhook]: + async def webhooks(self) -> list[Webhook]: """|coro| Gets the list of webhooks from this channel. Requires :attr:`~.Permissions.manage_webhooks` permissions. - Raises - ------- - Forbidden - You don't have permissions to get the webhooks. - Returns - -------- + ------- List[:class:`Webhook`] The webhooks for this channel. + + Raises + ------ + Forbidden + You don't have permissions to get the webhooks. """ from .webhook import Webhook @@ -466,7 +476,7 @@ async def webhooks(self) -> List[Webhook]: return [Webhook.from_state(d, state=self._state) for d in data] async def create_webhook( - self, *, name: str, avatar: Optional[bytes] = None, reason: Optional[str] = None + self, *, name: str, avatar: bytes | None = None, reason: str | None = None ) -> Webhook: """|coro| @@ -478,7 +488,7 @@ async def create_webhook( Added the ``reason`` keyword-only parameter. Parameters - ------------- + ---------- name: :class:`str` The webhook's name. avatar: Optional[:class:`bytes`] @@ -487,17 +497,17 @@ async def create_webhook( reason: Optional[:class:`str`] The reason for creating this webhook. Shows up in the audit logs. - Raises + Returns ------- + :class:`Webhook` + The created webhook. + + Raises + ------ HTTPException Creating the webhook failed. Forbidden You do not have permissions to create a webhook. - - Returns - -------- - :class:`Webhook` - The created webhook. """ from .webhook import Webhook @@ -505,10 +515,14 @@ async def create_webhook( if avatar is not None: avatar = utils._bytes_to_base64_data(avatar) # type: ignore - data = await self._state.http.create_webhook(self.id, name=str(name), avatar=avatar, reason=reason) + data = await self._state.http.create_webhook( + self.id, name=str(name), avatar=avatar, reason=reason + ) return Webhook.from_state(data, state=self._state) - async def follow(self, *, destination: TextChannel, reason: Optional[str] = None) -> Webhook: + async def follow( + self, *, destination: TextChannel, reason: str | None = None + ) -> Webhook: """ Follows a channel using a webhook. @@ -522,7 +536,7 @@ async def follow(self, *, destination: TextChannel, reason: Optional[str] = None .. versionadded:: 1.3 Parameters - ----------- + ---------- destination: :class:`TextChannel` The channel you would like to follow from. reason: Optional[:class:`str`] @@ -530,28 +544,32 @@ async def follow(self, *, destination: TextChannel, reason: Optional[str] = None .. versionadded:: 1.4 - Raises + Returns ------- + :class:`Webhook` + The created webhook. + + Raises + ------ HTTPException Following the channel failed. Forbidden You do not have the permissions to create a webhook. - - Returns - -------- - :class:`Webhook` - The created webhook. """ if not self.is_news(): raise ClientException("The channel must be a news channel.") if not isinstance(destination, TextChannel): - raise InvalidArgument(f"Expected TextChannel received {destination.__class__.__name__}") + raise InvalidArgument( + f"Expected TextChannel received {destination.__class__.__name__}" + ) from .webhook import Webhook - data = await self._state.http.follow_webhook(self.id, webhook_channel_id=destination.id, reason=reason) + data = await self._state.http.follow_webhook( + self.id, webhook_channel_id=destination.id, reason=reason + ) return Webhook._as_follower(data, channel=destination, user=self._state.user) def get_partial_message(self, message_id: int, /) -> PartialMessage: @@ -563,12 +581,12 @@ def get_partial_message(self, message_id: int, /) -> PartialMessage: .. versionadded:: 1.6 Parameters - ------------ + ---------- message_id: :class:`int` The message ID to create a partial message for. Returns - --------- + ------- :class:`PartialMessage` The partial message. """ @@ -577,18 +595,18 @@ def get_partial_message(self, message_id: int, /) -> PartialMessage: return PartialMessage(channel=self, id=message_id) - def get_thread(self, thread_id: int, /) -> Optional[Thread]: + def get_thread(self, thread_id: int, /) -> Thread | None: """Returns a thread with the given ID. .. versionadded:: 2.0 Parameters - ----------- + ---------- thread_id: :class:`int` The ID to search for. Returns - -------- + ------- Optional[:class:`Thread`] The returned thread or ``None`` if not found. """ @@ -599,8 +617,8 @@ def archived_threads( *, private: bool = False, joined: bool = False, - limit: Optional[int] = 50, - before: Optional[Union[Snowflake, datetime.datetime]] = None, + limit: int | None = 50, + before: Snowflake | datetime.datetime | None = None, ) -> ArchivedThreadIterator: """Returns an :class:`~discord.AsyncIterator` that iterates over all archived threads in the guild. @@ -610,7 +628,7 @@ def archived_threads( .. versionadded:: 2.0 Parameters - ----------- + ---------- limit: Optional[:class:`bool`] The number of threads to retrieve. If ``None``, retrieves every archived thread in the channel. Note, however, @@ -623,17 +641,17 @@ def archived_threads( Whether to retrieve private archived threads that you've joined. You cannot set ``joined`` to ``True`` and ``private`` to ``False``. + Yields + ------ + :class:`Thread` + The archived threads. + Raises ------ Forbidden You do not have permissions to get archived threads. HTTPException The request to get the archived threads failed. - - Yields - ------- - :class:`Thread` - The archived threads. """ return ArchivedThreadIterator( self.id, @@ -648,68 +666,70 @@ def archived_threads( class TextChannel(discord.abc.Messageable, _TextChannel): """Represents a Discord text channel. - .. container:: operations + .. container:: operations - .. describe:: x == y + .. describe:: x == y - Checks if two channels are equal. + Checks if two channels are equal. - .. describe:: x != y + .. describe:: x != y - Checks if two channels are not equal. + Checks if two channels are not equal. - .. describe:: hash(x) + .. describe:: hash(x) - Returns the channel's hash. + Returns the channel's hash. - .. describe:: str(x) + .. describe:: str(x) - Returns the channel's name. + Returns the channel's name. - Attributes - ----------- - name: :class:`str` - The channel name. - guild: :class:`Guild` - The guild the channel belongs to. - id: :class:`int` - The channel ID. - category_id: Optional[:class:`int`] - The category channel ID this channel belongs to, if applicable. - topic: Optional[:class:`str`] - The channel's topic. ``None`` if it doesn't exist. - position: Optional[:class:`int`] - The position in the channel list. This is a number that starts at 0. e.g. the - top channel is position 0. Can be ``None`` if the channel was received in an interaction. - last_message_id: Optional[:class:`int`] - The last message ID of the message sent to this channel. It may - *not* point to an existing or valid message. - slowmode_delay: :class:`int` - The number of seconds a member must wait between sending messages - in this channel. A value of `0` denotes that it is disabled. - Bots and users with :attr:`~Permissions.manage_channels` or - :attr:`~Permissions.manage_messages` bypass slowmode. - nsfw: :class:`bool` - If the channel is marked as "not safe for work". + Attributes + ---------- + name: :class:`str` + The channel name. + guild: :class:`Guild` + The guild the channel belongs to. + id: :class:`int` + The channel ID. + category_id: Optional[:class:`int`] + The category channel ID this channel belongs to, if applicable. + topic: Optional[:class:`str`] + The channel's topic. ``None`` if it doesn't exist. + position: Optional[:class:`int`] + The position in the channel list. This is a number that starts at 0. e.g. the + top channel is position 0. Can be ``None`` if the channel was received in an interaction. + last_message_id: Optional[:class:`int`] + The last message ID of the message sent to this channel. It may + *not* point to an existing or valid message. + slowmode_delay: :class:`int` + The number of seconds a member must wait between sending messages + in this channel. A value of `0` denotes that it is disabled. + Bots and users with :attr:`~Permissions.manage_channels` or + :attr:`~Permissions.manage_messages` bypass slowmode. + nsfw: :class:`bool` + If the channel is marked as "not safe for work". - .. note:: + .. note:: - To check if the channel or the guild of that channel are marked as NSFW, consider :meth:`is_nsfw` instead. - default_auto_archive_duration: :class:`int` - The default auto archive duration in minutes for threads created in this channel. + To check if the channel or the guild of that channel are marked as NSFW, consider :meth:`is_nsfw` instead. + default_auto_archive_duration: :class:`int` + The default auto archive duration in minutes for threads created in this channel. - .. versionadded:: 2.0 - flags: :class:`ChannelFlags` - Extra features of the channel. + .. versionadded:: 2.0 + flags: :class:`ChannelFlags` + Extra features of the channel. - .. versionadded:: 2.0 - """ + .. versionadded:: 2.0 + """ - def __init__(self, *, state: ConnectionState, guild: Guild, data: TextChannelPayload): + def __init__( + self, *, state: ConnectionState, guild: Guild, data: TextChannelPayload + ): super().__init__(state=state, guild=guild, data=data) @property - def _repr_attrs(self) -> Tuple[str, ...]: + def _repr_attrs(self) -> tuple[str, ...]: return super()._repr_attrs + ("news",) def _update(self, guild: Guild, data: TextChannelPayload) -> None: @@ -731,10 +751,10 @@ async def create_thread( self, *, name: str, - message: Optional[Snowflake] = None, + message: Snowflake | None = None, auto_archive_duration: ThreadArchiveDuration = MISSING, - type: Optional[ChannelType] = None, - reason: Optional[str] = None, + type: ChannelType | None = None, + reason: str | None = None, ) -> Thread: """|coro| @@ -746,7 +766,7 @@ async def create_thread( .. versionadded:: 2.0 Parameters - ----------- + ---------- name: :class:`str` The name of the thread. message: Optional[:class:`abc.Snowflake`] @@ -763,17 +783,17 @@ async def create_thread( reason: :class:`str` The reason for creating a new thread. Shows up on the audit log. - Raises + Returns ------- + :class:`Thread` + The created thread + + Raises + ------ Forbidden You do not have permissions to create a thread. HTTPException Starting the thread failed. - - Returns - -------- - :class:`Thread` - The created thread """ if type is None: @@ -783,7 +803,8 @@ async def create_thread( data = await self._state.http.start_thread_without_message( self.id, name=name, - auto_archive_duration=auto_archive_duration or self.default_auto_archive_duration, + auto_archive_duration=auto_archive_duration + or self.default_auto_archive_duration, type=type.value, reason=reason, ) @@ -792,7 +813,8 @@ async def create_thread( self.id, message.id, name=name, - auto_archive_duration=auto_archive_duration or self.default_auto_archive_duration, + auto_archive_duration=auto_archive_duration + or self.default_auto_archive_duration, reason=reason, ) @@ -821,7 +843,7 @@ class ForumChannel(_TextChannel): Returns the channel's name. Attributes - ----------- + ---------- name: :class:`str` The channel name. guild: :class:`Guild` @@ -863,14 +885,16 @@ class ForumChannel(_TextChannel): .. versionadded:: 2.0 """ - def __init__(self, *, state: ConnectionState, guild: Guild, data: ForumChannelPayload): + def __init__( + self, *, state: ConnectionState, guild: Guild, data: ForumChannelPayload + ): super().__init__(state=state, guild=guild, data=data) def _update(self, guild: Guild, data: ForumChannelPayload) -> None: super()._update(guild, data) @property - def guidelines(self) -> Optional[str]: + def guidelines(self) -> str | None: """Optional[:class:`str`]: The channel's guidelines. An alias of :attr:`topic`.""" return self.topic @@ -890,7 +914,7 @@ async def create_thread( view=None, auto_archive_duration: ThreadArchiveDuration = MISSING, slowmode_delay: int = MISSING, - reason: Optional[str] = None, + reason: str | None = None, ) -> Thread: """|coro| @@ -902,7 +926,7 @@ async def create_thread( .. versionadded:: 2.0 Parameters - ----------- + ---------- name: :class:`str` The name of the thread. content: :class:`str` @@ -943,37 +967,43 @@ async def create_thread( reason: :class:`str` The reason for creating a new thread. Shows up on the audit log. - Raises + Returns ------- + :class:`Thread` + The created thread + + Raises + ------ Forbidden You do not have permissions to create a thread. HTTPException Starting the thread failed. - - Returns - -------- - :class:`Thread` - The created thread """ state = self._state message_content = str(content) if content is not None else None if embed is not None and embeds is not None: - raise InvalidArgument("cannot pass both embed and embeds parameter to create_thread()") + raise InvalidArgument( + "cannot pass both embed and embeds parameter to create_thread()" + ) if embed is not None: embed = embed.to_dict() elif embeds is not None: if len(embeds) > 10: - raise InvalidArgument("embeds parameter must be a list of up to 10 elements") + raise InvalidArgument( + "embeds parameter must be a list of up to 10 elements" + ) embeds = [embed.to_dict() for embed in embeds] if stickers is not None: stickers = [sticker.id for sticker in stickers] if allowed_mentions is None: - allowed_mentions = state.allowed_mentions and state.allowed_mentions.to_dict() + allowed_mentions = ( + state.allowed_mentions and state.allowed_mentions.to_dict() + ) elif state.allowed_mentions is not None: allowed_mentions = state.allowed_mentions.merge(allowed_mentions).to_dict() else: @@ -981,7 +1011,9 @@ async def create_thread( if view: if not hasattr(view, "__discord_ui_view__"): - raise InvalidArgument(f"view parameter must be View not {view.__class__!r}") + raise InvalidArgument( + f"view parameter must be View not {view.__class__!r}" + ) components = view.to_components() else: @@ -1011,7 +1043,9 @@ async def create_thread( elif files is not None: if len(files) > 10: - raise InvalidArgument("files parameter must be a list of up to 10 elements") + raise InvalidArgument( + "files parameter must be a list of up to 10 elements" + ) elif not all(isinstance(file, File) for file in files): raise InvalidArgument("files parameter must be a list of File") @@ -1041,7 +1075,8 @@ async def create_thread( allowed_mentions=allowed_mentions, stickers=stickers, components=components, - auto_archive_duration=auto_archive_duration or self.default_auto_archive_duration, + auto_archive_duration=auto_archive_duration + or self.default_auto_archive_duration, rate_limit_per_user=slowmode_delay or self.slowmode_delay, reason=reason, ) @@ -1077,30 +1112,38 @@ def __init__( *, state: ConnectionState, guild: Guild, - data: Union[VoiceChannelPayload, StageChannelPayload], + data: VoiceChannelPayload | StageChannelPayload, ): self._state: ConnectionState = state self.id: int = int(data["id"]) self._update(guild, data) - def _get_voice_client_key(self) -> Tuple[int, str]: + def _get_voice_client_key(self) -> tuple[int, str]: return self.guild.id, "guild_id" - def _get_voice_state_pair(self) -> Tuple[int, int]: + def _get_voice_state_pair(self) -> tuple[int, int]: return self.guild.id, self.id - def _update(self, guild: Guild, data: Union[VoiceChannelPayload, StageChannelPayload]) -> None: + def _update( + self, guild: Guild, data: VoiceChannelPayload | StageChannelPayload + ) -> None: # This data will always exist self.guild = guild self.name: str = data["name"] - self.category_id: Optional[int] = utils._get_as_snowflake(data, "parent_id") + self.category_id: int | None = utils._get_as_snowflake(data, "parent_id") # This data may be missing depending on how this object is being created/updated if not data.pop("_invoke_flag", False): rtc = data.get("rtc_region") - self.rtc_region: Optional[VoiceRegion] = try_enum(VoiceRegion, rtc) if rtc is not None else None - self.video_quality_mode: VideoQualityMode = try_enum(VideoQualityMode, data.get("video_quality_mode", 1)) - self.last_message_id: Optional[int] = utils._get_as_snowflake(data, "last_message_id") + self.rtc_region: VoiceRegion | None = ( + try_enum(VoiceRegion, rtc) if rtc is not None else None + ) + self.video_quality_mode: VideoQualityMode = try_enum( + VideoQualityMode, data.get("video_quality_mode", 1) + ) + self.last_message_id: int | None = utils._get_as_snowflake( + data, "last_message_id" + ) self.position: int = data.get("position") self.bitrate: int = data.get("bitrate") self.user_limit: int = data.get("user_limit") @@ -1112,7 +1155,7 @@ def _sorting_bucket(self) -> int: return ChannelType.voice.value @property - def members(self) -> List[Member]: + def members(self) -> list[Member]: """List[:class:`Member`]: Returns all members that are currently inside this voice channel.""" ret = [] for user_id, state in self.guild._voice_states.items(): @@ -1123,7 +1166,7 @@ def members(self) -> List[Member]: return ret @property - def voice_states(self) -> Dict[int, VoiceState]: + def voice_states(self) -> dict[int, VoiceState]: """Returns a mapping of member IDs who have voice states in this channel. .. versionadded:: 1.3 @@ -1134,7 +1177,7 @@ def voice_states(self) -> Dict[int, VoiceState]: when the member cache is unavailable. Returns - -------- + ------- Mapping[:class:`int`, :class:`VoiceState`] The mapping of member ID to a voice state. """ @@ -1145,7 +1188,7 @@ def voice_states(self) -> Dict[int, VoiceState]: } @utils.copy_doc(discord.abc.GuildChannel.permissions_for) - def permissions_for(self, obj: Union[Member, Role], /) -> Permissions: + def permissions_for(self, obj: Member | Role, /) -> Permissions: base = super().permissions_for(obj) # Voice channels cannot be edited by people who can't connect to them. @@ -1179,7 +1222,7 @@ class VoiceChannel(discord.abc.Messageable, VocalGuildChannel): Returns the channel's name. Attributes - ----------- + ---------- name: :class:`str` The channel name. guild: :class:`Guild` @@ -1206,7 +1249,7 @@ class VoiceChannel(discord.abc.Messageable, VocalGuildChannel): .. versionadded:: 2.0 last_message_id: Optional[:class:`int`] The ID of the last message sent to this channel. It may not always point to an existing or valid message. - + .. versionadded:: 2.0 flags: :class:`ChannelFlags` Extra features of the channel. @@ -1242,7 +1285,7 @@ def is_nsfw(self) -> bool: return self.nsfw @property - def last_message(self) -> Optional[Message]: + def last_message(self) -> Message | None: """Fetches the last message from this channel in cache. The message might not be valid or point to an existing message. @@ -1256,11 +1299,15 @@ def last_message(self) -> Optional[Message]: attribute. Returns - --------- + ------- Optional[:class:`Message`] The last message in this channel or ``None`` if not found. """ - return self._state._get_message(self.last_message_id) if self.last_message_id else None + return ( + self._state._get_message(self.last_message_id) + if self.last_message_id + else None + ) def get_partial_message(self, message_id: int, /) -> PartialMessage: """Creates a :class:`PartialMessage` from the message ID. @@ -1271,12 +1318,12 @@ def get_partial_message(self, message_id: int, /) -> PartialMessage: .. versionadded:: 1.6 Parameters - ------------ + ---------- message_id: :class:`int` The message ID to create a partial message for. Returns - --------- + ------- :class:`PartialMessage` The partial message. """ @@ -1285,7 +1332,9 @@ def get_partial_message(self, message_id: int, /) -> PartialMessage: return PartialMessage(channel=self, id=message_id) - async def delete_messages(self, messages: Iterable[Snowflake], *, reason: Optional[str] = None) -> None: + async def delete_messages( + self, messages: Iterable[Snowflake], *, reason: str | None = None + ) -> None: """|coro| Deletes a list of messages. This is similar to :meth:`Message.delete` @@ -1302,7 +1351,7 @@ async def delete_messages(self, messages: Iterable[Snowflake], *, reason: Option use this. Parameters - ----------- + ---------- messages: Iterable[:class:`abc.Snowflake`] An iterable of messages denoting which ones to bulk delete. reason: Optional[:class:`str`] @@ -1339,15 +1388,15 @@ async def delete_messages(self, messages: Iterable[Snowflake], *, reason: Option async def purge( self, *, - limit: Optional[int] = 100, + limit: int | None = 100, check: Callable[[Message], bool] = MISSING, - before: Optional[SnowflakeTime] = None, - after: Optional[SnowflakeTime] = None, - around: Optional[SnowflakeTime] = None, - oldest_first: Optional[bool] = False, + before: SnowflakeTime | None = None, + after: SnowflakeTime | None = None, + around: SnowflakeTime | None = None, + oldest_first: bool | None = False, bulk: bool = True, - reason: Optional[str] = None, - ) -> List[Message]: + reason: str | None = None, + ) -> list[Message]: """|coro| Purges a list of messages that meet the criteria given by the predicate @@ -1359,19 +1408,8 @@ async def purge( The :attr:`~Permissions.read_message_history` permission is also needed to retrieve message history. - Examples - --------- - - Deleting bot's messages :: - - def is_me(m): - return m.author == client.user - - deleted = await channel.purge(limit=100, check=is_me) - await channel.send(f'Deleted {len(deleted)} message(s)') - Parameters - ----------- + ---------- limit: Optional[:class:`int`] The number of messages to search through. This is not the number of messages that will be deleted, though it can be. @@ -1393,17 +1431,28 @@ def is_me(m): reason: Optional[:class:`str`] The reason for deleting the messages. Shows up on the audit log. - Raises + Returns ------- + List[:class:`.Message`] + The list of messages that were deleted. + + Raises + ------ Forbidden You do not have proper permissions to do the actions required. HTTPException Purging the messages failed. - Returns + Examples -------- - List[:class:`.Message`] - The list of messages that were deleted. + + Deleting bot's messages :: + + def is_me(m): + return m.author == client.user + + deleted = await channel.purge(limit=100, check=is_me) + await channel.send(f'Deleted {len(deleted)} message(s)') """ return await discord.abc._purge_messages_helper( self, @@ -1417,22 +1466,22 @@ def is_me(m): reason=reason, ) - async def webhooks(self) -> List[Webhook]: + async def webhooks(self) -> list[Webhook]: """|coro| Gets the list of webhooks from this channel. Requires :attr:`~.Permissions.manage_webhooks` permissions. - Raises - ------- - Forbidden - You don't have permissions to get the webhooks. - Returns - -------- + ------- List[:class:`Webhook`] The webhooks for this channel. + + Raises + ------ + Forbidden + You don't have permissions to get the webhooks. """ from .webhook import Webhook @@ -1441,7 +1490,7 @@ async def webhooks(self) -> List[Webhook]: return [Webhook.from_state(d, state=self._state) for d in data] async def create_webhook( - self, *, name: str, avatar: Optional[bytes] = None, reason: Optional[str] = None + self, *, name: str, avatar: bytes | None = None, reason: str | None = None ) -> Webhook: """|coro| @@ -1453,7 +1502,7 @@ async def create_webhook( Added the ``reason`` keyword-only parameter. Parameters - ------------- + ---------- name: :class:`str` The webhook's name. avatar: Optional[:class:`bytes`] @@ -1462,17 +1511,17 @@ async def create_webhook( reason: Optional[:class:`str`] The reason for creating this webhook. Shows up in the audit logs. - Raises + Returns ------- + :class:`Webhook` + The created webhook. + + Raises + ------ HTTPException Creating the webhook failed. Forbidden You do not have permissions to create a webhook. - - Returns - -------- - :class:`Webhook` - The created webhook. """ from .webhook import Webhook @@ -1480,7 +1529,9 @@ async def create_webhook( if avatar is not None: avatar = utils._bytes_to_base64_data(avatar) # type: ignore - data = await self._state.http.create_webhook(self.id, name=str(name), avatar=avatar, reason=reason) + data = await self._state.http.create_webhook( + self.id, name=str(name), avatar=avatar, reason=reason + ) return Webhook.from_state(data, state=self._state) @property @@ -1489,7 +1540,9 @@ def type(self) -> ChannelType: return ChannelType.voice @utils.copy_doc(discord.abc.GuildChannel.clone) - async def clone(self, *, name: Optional[str] = None, reason: Optional[str] = None) -> VoiceChannel: + async def clone( + self, *, name: str | None = None, reason: str | None = None + ) -> VoiceChannel: return await self._clone_impl( {"bitrate": self.bitrate, "user_limit": self.user_limit}, name=name, @@ -1505,16 +1558,16 @@ async def edit( user_limit: int = ..., position: int = ..., sync_permissions: int = ..., - category: Optional[CategoryChannel] = ..., - overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ..., - rtc_region: Optional[VoiceRegion] = ..., + category: CategoryChannel | None = ..., + overwrites: Mapping[Role | Member, PermissionOverwrite] = ..., + rtc_region: VoiceRegion | None = ..., video_quality_mode: VideoQualityMode = ..., - reason: Optional[str] = ..., - ) -> Optional[VoiceChannel]: + reason: str | None = ..., + ) -> VoiceChannel | None: ... @overload - async def edit(self) -> Optional[VoiceChannel]: + async def edit(self) -> VoiceChannel | None: ... async def edit(self, *, reason=None, **options): @@ -1561,6 +1614,12 @@ async def edit(self, *, reason=None, **options): .. versionadded:: 2.0 + Returns + ------- + Optional[:class:`.VoiceChannel`] + The newly edited voice channel. If the edit was only positional + then ``None`` is returned instead. + Raises ------ InvalidArgument @@ -1569,12 +1628,6 @@ async def edit(self, *, reason=None, **options): You do not have permissions to edit the channel. HTTPException Editing the channel failed. - - Returns - -------- - Optional[:class:`.VoiceChannel`] - The newly edited voice channel. If the edit was only positional - then ``None`` is returned instead. """ payload = await self._edit(options, reason=reason) @@ -1582,7 +1635,9 @@ async def edit(self, *, reason=None, **options): # the payload will always be the proper channel payload return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore - async def create_activity_invite(self, activity: Union[EmbeddedActivity, int], **kwargs) -> Invite: + async def create_activity_invite( + self, activity: EmbeddedActivity | int, **kwargs + ) -> Invite: """|coro| A shortcut method that creates an instant activity invite. @@ -1591,7 +1646,7 @@ async def create_activity_invite(self, activity: Union[EmbeddedActivity, int], * do this. Parameters - ------------ + ---------- activity: Union[:class:`discord.EmbeddedActivity`, :class:`int`] The activity to create an invite for which can be an application id as well. max_age: :class:`int` @@ -1610,18 +1665,17 @@ async def create_activity_invite(self, activity: Union[EmbeddedActivity, int], * reason: Optional[:class:`str`] The reason for creating this invite. Shows up on the audit log. + Returns + ------- + :class:`~discord.Invite` + The invite that was created. Raises - ------- + ------ TypeError If the activity is not a valid activity or application id. ~discord.HTTPException Invite creation failed. - - Returns - -------- - :class:`~discord.Invite` - The invite that was created. """ if isinstance(activity, EmbeddedActivity): @@ -1661,7 +1715,7 @@ class StageChannel(VocalGuildChannel): Returns the channel's name. Attributes - ----------- + ---------- name: :class:`str` The channel name. guild: :class:`Guild` @@ -1714,12 +1768,16 @@ def _update(self, guild: Guild, data: StageChannelPayload) -> None: self.topic = data.get("topic") @property - def requesting_to_speak(self) -> List[Member]: + def requesting_to_speak(self) -> list[Member]: """List[:class:`Member`]: A list of members who are requesting to speak in the stage channel.""" - return [member for member in self.members if member.voice and member.voice.requested_to_speak_at is not None] + return [ + member + for member in self.members + if member.voice and member.voice.requested_to_speak_at is not None + ] @property - def speakers(self) -> List[Member]: + def speakers(self) -> list[Member]: """List[:class:`Member`]: A list of members who have been permitted to speak in the stage channel. .. versionadded:: 2.0 @@ -1727,25 +1785,33 @@ def speakers(self) -> List[Member]: return [ member for member in self.members - if member.voice and not member.voice.suppress and member.voice.requested_to_speak_at is None + if member.voice + and not member.voice.suppress + and member.voice.requested_to_speak_at is None ] @property - def listeners(self) -> List[Member]: + def listeners(self) -> list[Member]: """List[:class:`Member`]: A list of members who are listening in the stage channel. .. versionadded:: 2.0 """ - return [member for member in self.members if member.voice and member.voice.suppress] + return [ + member for member in self.members if member.voice and member.voice.suppress + ] @property - def moderators(self) -> List[Member]: + def moderators(self) -> list[Member]: """List[:class:`Member`]: A list of members who are moderating the stage channel. .. versionadded:: 2.0 """ required_permissions = Permissions.stage_moderator() - return [member for member in self.members if self.permissions_for(member) >= required_permissions] + return [ + member + for member in self.members + if self.permissions_for(member) >= required_permissions + ] @property def type(self) -> ChannelType: @@ -1753,11 +1819,13 @@ def type(self) -> ChannelType: return ChannelType.stage_voice @utils.copy_doc(discord.abc.GuildChannel.clone) - async def clone(self, *, name: Optional[str] = None, reason: Optional[str] = None) -> StageChannel: + async def clone( + self, *, name: str | None = None, reason: str | None = None + ) -> StageChannel: return await self._clone_impl({}, name=name, reason=reason) @property - def instance(self) -> Optional[StageInstance]: + def instance(self) -> StageInstance | None: """Optional[:class:`StageInstance`]: The running stage instance of the stage channel. .. versionadded:: 2.0 @@ -1769,8 +1837,8 @@ async def create_instance( *, topic: str, privacy_level: StagePrivacyLevel = MISSING, - reason: Optional[str] = None, - send_notification: Optional[bool] = False, + reason: str | None = None, + send_notification: bool | None = False, ) -> StageInstance: """|coro| @@ -1782,7 +1850,7 @@ async def create_instance( .. versionadded:: 2.0 Parameters - ----------- + ---------- topic: :class:`str` The stage instance's topic. privacy_level: :class:`StagePrivacyLevel` @@ -1793,6 +1861,11 @@ async def create_instance( Send a notification to everyone in the server that the stage instance has started. Defaults to ``False``. Requires the ``mention_everyone`` permission. + Returns + ------- + :class:`StageInstance` + The newly created stage instance. + Raises ------ InvalidArgument @@ -1801,18 +1874,19 @@ async def create_instance( You do not have permissions to create a stage instance. HTTPException Creating a stage instance failed. - - Returns - -------- - :class:`StageInstance` - The newly created stage instance. """ - payload: Dict[str, Any] = {"channel_id": self.id, "topic": topic, "send_start_notification": send_notification} + payload: dict[str, Any] = { + "channel_id": self.id, + "topic": topic, + "send_start_notification": send_notification, + } if privacy_level is not MISSING: if not isinstance(privacy_level, StagePrivacyLevel): - raise InvalidArgument("privacy_level field must be of type PrivacyLevel") + raise InvalidArgument( + "privacy_level field must be of type PrivacyLevel" + ) payload["privacy_level"] = privacy_level.value @@ -1826,17 +1900,17 @@ async def fetch_instance(self) -> StageInstance: .. versionadded:: 2.0 - Raises + Returns ------- + :class:`StageInstance` + The stage instance. + + Raises + ------ NotFound The stage instance or channel could not be found. HTTPException Getting the stage instance failed. - - Returns - -------- - :class:`StageInstance` - The stage instance. """ data = await self._state.http.get_stage_instance(self.id) return StageInstance(guild=self.guild, state=self._state, data=data) @@ -1846,19 +1920,19 @@ async def edit( self, *, name: str = ..., - topic: Optional[str] = ..., + topic: str | None = ..., position: int = ..., sync_permissions: int = ..., - category: Optional[CategoryChannel] = ..., - overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ..., - rtc_region: Optional[VoiceRegion] = ..., + category: CategoryChannel | None = ..., + overwrites: Mapping[Role | Member, PermissionOverwrite] = ..., + rtc_region: VoiceRegion | None = ..., video_quality_mode: VideoQualityMode = ..., - reason: Optional[str] = ..., - ) -> Optional[StageChannel]: + reason: str | None = ..., + ) -> StageChannel | None: ... @overload - async def edit(self) -> Optional[StageChannel]: + async def edit(self) -> StageChannel | None: ... async def edit(self, *, reason=None, **options): @@ -1899,6 +1973,12 @@ async def edit(self, *, reason=None, **options): .. versionadded:: 2.0 + Returns + ------- + Optional[:class:`.StageChannel`] + The newly edited stage channel. If the edit was only positional + then ``None`` is returned instead. + Raises ------ InvalidArgument @@ -1907,12 +1987,6 @@ async def edit(self, *, reason=None, **options): You do not have permissions to edit the channel. HTTPException Editing the channel failed. - - Returns - -------- - Optional[:class:`.StageChannel`] - The newly edited stage channel. If the edit was only positional - then ``None`` is returned instead. """ payload = await self._edit(options, reason=reason) @@ -1945,7 +2019,7 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable): Returns the category's name. Attributes - ----------- + ---------- name: :class:`str` The category name. guild: :class:`Guild` @@ -1979,7 +2053,9 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable): "flags", ) - def __init__(self, *, state: ConnectionState, guild: Guild, data: CategoryChannelPayload): + def __init__( + self, *, state: ConnectionState, guild: Guild, data: CategoryChannelPayload + ): self._state: ConnectionState = state self.id: int = int(data["id"]) self._update(guild, data) @@ -1991,7 +2067,7 @@ def _update(self, guild: Guild, data: CategoryChannelPayload) -> None: # This data will always exist self.guild: Guild = guild self.name: str = data["name"] - self.category_id: Optional[int] = utils._get_as_snowflake(data, "parent_id") + self.category_id: int | None = utils._get_as_snowflake(data, "parent_id") # This data may be missing depending on how this object is being created/updated if not data.pop("_invoke_flag", False): @@ -2014,7 +2090,9 @@ def is_nsfw(self) -> bool: return self.nsfw @utils.copy_doc(discord.abc.GuildChannel.clone) - async def clone(self, *, name: Optional[str] = None, reason: Optional[str] = None) -> CategoryChannel: + async def clone( + self, *, name: str | None = None, reason: str | None = None + ) -> CategoryChannel: return await self._clone_impl({"nsfw": self.nsfw}, name=name, reason=reason) @overload @@ -2024,13 +2102,13 @@ async def edit( name: str = ..., position: int = ..., nsfw: bool = ..., - overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ..., - reason: Optional[str] = ..., - ) -> Optional[CategoryChannel]: + overwrites: Mapping[Role | Member, PermissionOverwrite] = ..., + reason: str | None = ..., + ) -> CategoryChannel | None: ... @overload - async def edit(self) -> Optional[CategoryChannel]: + async def edit(self) -> CategoryChannel | None: ... async def edit(self, *, reason=None, **options): @@ -2060,6 +2138,12 @@ async def edit(self, *, reason=None, **options): overwrites: Dict[Union[:class:`Role`, :class:`Member`, :class:`~discord.abc.Snowflake`], :class:`PermissionOverwrite`] The overwrites to apply to channel permissions. Useful for creating secret channels. + Returns + ------- + Optional[:class:`.CategoryChannel`] + The newly edited category channel. If the edit was only positional + then ``None`` is returned instead. + Raises ------ InvalidArgument @@ -2068,12 +2152,6 @@ async def edit(self, *, reason=None, **options): You do not have permissions to edit the category. HTTPException Editing the category failed. - - Returns - -------- - Optional[:class:`.CategoryChannel`] - The newly edited category channel. If the edit was only positional - then ``None`` is returned instead. """ payload = await self._edit(options, reason=reason) @@ -2087,7 +2165,7 @@ async def move(self, **kwargs): await super().move(**kwargs) @property - def channels(self) -> List[GuildChannelType]: + def channels(self) -> list[GuildChannelType]: """List[:class:`abc.GuildChannel`]: Returns the channels that are under this category. These are sorted by the official Discord UI, which places voice channels below the text channels. @@ -2101,36 +2179,52 @@ def comparator(channel): return ret @property - def text_channels(self) -> List[TextChannel]: + def text_channels(self) -> list[TextChannel]: """List[:class:`TextChannel`]: Returns the text channels that are under this category.""" - ret = [c for c in self.guild.channels if c.category_id == self.id and isinstance(c, TextChannel)] + ret = [ + c + for c in self.guild.channels + if c.category_id == self.id and isinstance(c, TextChannel) + ] ret.sort(key=lambda c: (c.position or -1, c.id)) return ret @property - def voice_channels(self) -> List[VoiceChannel]: + def voice_channels(self) -> list[VoiceChannel]: """List[:class:`VoiceChannel`]: Returns the voice channels that are under this category.""" - ret = [c for c in self.guild.channels if c.category_id == self.id and isinstance(c, VoiceChannel)] + ret = [ + c + for c in self.guild.channels + if c.category_id == self.id and isinstance(c, VoiceChannel) + ] ret.sort(key=lambda c: (c.position or -1, c.id)) return ret @property - def stage_channels(self) -> List[StageChannel]: + def stage_channels(self) -> list[StageChannel]: """List[:class:`StageChannel`]: Returns the stage channels that are under this category. .. versionadded:: 1.7 """ - ret = [c for c in self.guild.channels if c.category_id == self.id and isinstance(c, StageChannel)] + ret = [ + c + for c in self.guild.channels + if c.category_id == self.id and isinstance(c, StageChannel) + ] ret.sort(key=lambda c: (c.position or -1, c.id)) return ret @property - def forum_channels(self) -> List[ForumChannel]: + def forum_channels(self) -> list[ForumChannel]: """List[:class:`ForumChannel`]: Returns the forum channels that are under this category. .. versionadded:: 2.0 """ - ret = [c for c in self.guild.channels if c.category_id == self.id and isinstance(c, ForumChannel)] + ret = [ + c + for c in self.guild.channels + if c.category_id == self.id and isinstance(c, ForumChannel) + ] ret.sort(key=lambda c: (c.position or -1, c.id)) return ret @@ -2225,9 +2319,11 @@ class DMChannel(discord.abc.Messageable, Hashable): __slots__ = ("id", "recipient", "me", "_state") - def __init__(self, *, me: ClientUser, state: ConnectionState, data: DMChannelPayload): + def __init__( + self, *, me: ClientUser, state: ConnectionState, data: DMChannelPayload + ): self._state: ConnectionState = state - self.recipient: Optional[User] = state.store_user(data["recipients"][0]) + self.recipient: User | None = state.store_user(data["recipients"][0]) self.me: ClientUser = me self.id: int = int(data["id"]) @@ -2243,7 +2339,7 @@ def __repr__(self) -> str: return f"" @classmethod - def _from_message(cls: Type[DMC], state: ConnectionState, channel_id: int) -> DMC: + def _from_message(cls: type[DMC], state: ConnectionState, channel_id: int) -> DMC: self: DMC = cls.__new__(cls) self._state = state self.id = channel_id @@ -2283,13 +2379,13 @@ def permissions_for(self, obj: Any = None, /) -> Permissions: - :attr:`~Permissions.manage_messages`: You cannot delete others messages in a DM. Parameters - ----------- + ---------- obj: :class:`User` The user to check permissions for. This parameter is ignored but kept for compatibility with other ``permissions_for`` methods. Returns - -------- + ------- :class:`Permissions` The resolved permissions. """ @@ -2309,12 +2405,12 @@ def get_partial_message(self, message_id: int, /) -> PartialMessage: .. versionadded:: 1.6 Parameters - ------------ + ---------- message_id: :class:`int` The message ID to create a partial message for. Returns - --------- + ------- :class:`PartialMessage` The partial message. """ @@ -2374,19 +2470,23 @@ class GroupChannel(discord.abc.Messageable, Hashable): "_state", ) - def __init__(self, *, me: ClientUser, state: ConnectionState, data: GroupChannelPayload): + def __init__( + self, *, me: ClientUser, state: ConnectionState, data: GroupChannelPayload + ): self._state: ConnectionState = state self.id: int = int(data["id"]) self.me: ClientUser = me self._update_group(data) def _update_group(self, data: GroupChannelPayload) -> None: - self.owner_id: Optional[int] = utils._get_as_snowflake(data, "owner_id") - self._icon: Optional[str] = data.get("icon") - self.name: Optional[str] = data.get("name") - self.recipients: List[User] = [self._state.store_user(u) for u in data.get("recipients", [])] + self.owner_id: int | None = utils._get_as_snowflake(data, "owner_id") + self._icon: str | None = data.get("icon") + self.name: str | None = data.get("name") + self.recipients: list[User] = [ + self._state.store_user(u) for u in data.get("recipients", []) + ] - self.owner: Optional[BaseUser] + self.owner: BaseUser | None if self.owner_id == self.me.id: self.owner = self.me else: @@ -2413,7 +2513,7 @@ def type(self) -> ChannelType: return ChannelType.group @property - def icon(self) -> Optional[Asset]: + def icon(self) -> Asset | None: """Optional[:class:`Asset`]: Returns the channel's icon asset if available.""" if self._icon is None: return None @@ -2447,12 +2547,12 @@ def permissions_for(self, obj: Snowflake, /) -> Permissions: This also checks the kick_members permission if the user is the owner. Parameters - ----------- + ---------- obj: :class:`~discord.abc.Snowflake` The user to check permissions for. Returns - -------- + ------- :class:`Permissions` The resolved permissions for the user. """ @@ -2476,7 +2576,7 @@ async def leave(self) -> None: If you are the only one in the group, this deletes it as well. Raises - ------- + ------ HTTPException Leaving the group failed. """ @@ -2509,18 +2609,20 @@ class PartialMessageable(discord.abc.Messageable, Hashable): Returns the partial messageable's hash. Attributes - ----------- + ---------- id: :class:`int` The channel ID associated with this partial messageable. type: Optional[:class:`ChannelType`] The channel type associated with this partial messageable, if given. """ - def __init__(self, state: ConnectionState, id: int, type: Optional[ChannelType] = None): + def __init__( + self, state: ConnectionState, id: int, type: ChannelType | None = None + ): self._state: ConnectionState = state self._channel: Object = Object(id=id) self.id: int = id - self.type: Optional[ChannelType] = type + self.type: ChannelType | None = type async def _get_channel(self) -> Object: return self._channel @@ -2532,12 +2634,12 @@ def get_partial_message(self, message_id: int, /) -> PartialMessage: doing an unnecessary API call. Parameters - ------------ + ---------- message_id: :class:`int` The message ID to create a partial message for. Returns - --------- + ------- :class:`PartialMessage` The partial message. """ diff --git a/discord/client.py b/discord/client.py index 71a43d56bf..e6fa165c7d 100644 --- a/discord/client.py +++ b/discord/client.py @@ -30,20 +30,7 @@ import signal import sys import traceback -from typing import ( - TYPE_CHECKING, - Any, - Callable, - Coroutine, - Dict, - Generator, - List, - Optional, - Sequence, - Tuple, - TypeVar, - Union, -) +from typing import TYPE_CHECKING, Any, Callable, Coroutine, Generator, Sequence, TypeVar import aiohttp @@ -220,19 +207,23 @@ class Client: def __init__( self, *, - loop: Optional[asyncio.AbstractEventLoop] = None, + loop: asyncio.AbstractEventLoop | None = None, **options: Any, ): # self.ws is set in the connect method self.ws: DiscordWebSocket = None # type: ignore - self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() if loop is None else loop - self._listeners: Dict[str, List[Tuple[asyncio.Future, Callable[..., bool]]]] = {} - self.shard_id: Optional[int] = options.get("shard_id") - self.shard_count: Optional[int] = options.get("shard_count") - - connector: Optional[aiohttp.BaseConnector] = options.pop("connector", None) - proxy: Optional[str] = options.pop("proxy", None) - proxy_auth: Optional[aiohttp.BasicAuth] = options.pop("proxy_auth", None) + self.loop: asyncio.AbstractEventLoop = ( + asyncio.get_event_loop() if loop is None else loop + ) + self._listeners: dict[ + str, list[tuple[asyncio.Future, Callable[..., bool]]] + ] = {} + self.shard_id: int | None = options.get("shard_id") + self.shard_count: int | None = options.get("shard_count") + + connector: aiohttp.BaseConnector | None = options.pop("connector", None) + proxy: str | None = options.pop("proxy", None) + proxy_auth: aiohttp.BasicAuth | None = options.pop("proxy_auth", None) unsync_clock: bool = options.pop("assume_unsync_clock", True) self.http: HTTPClient = HTTPClient( connector, @@ -242,9 +233,11 @@ def __init__( loop=self.loop, ) - self._handlers: Dict[str, Callable] = {"ready": self._handle_ready} + self._handlers: dict[str, Callable] = {"ready": self._handle_ready} - self._hooks: Dict[str, Callable] = {"before_identify": self._call_before_identify_hook} + self._hooks: dict[str, Callable] = { + "before_identify": self._call_before_identify_hook + } self._enable_debug_events: bool = options.pop("enable_debug_events", False) self._connection: ConnectionState = self._get_state(**options) @@ -260,7 +253,9 @@ def __init__( # internals - def _get_websocket(self, guild_id: Optional[int] = None, *, shard_id: Optional[int] = None) -> DiscordWebSocket: + def _get_websocket( + self, guild_id: int | None = None, *, shard_id: int | None = None + ) -> DiscordWebSocket: return self.ws def _get_state(self, **options: Any) -> ConnectionState: @@ -298,22 +293,22 @@ def is_ws_ratelimited(self) -> bool: return False @property - def user(self) -> Optional[ClientUser]: + def user(self) -> ClientUser | None: """Optional[:class:`.ClientUser`]: Represents the connected client. ``None`` if not logged in.""" return self._connection.user @property - def guilds(self) -> List[Guild]: + def guilds(self) -> list[Guild]: """List[:class:`.Guild`]: The guilds that the connected client is a member of.""" return self._connection.guilds @property - def emojis(self) -> List[Emoji]: + def emojis(self) -> list[Emoji]: """List[:class:`.Emoji`]: The emojis that the connected client has.""" return self._connection.emojis @property - def stickers(self) -> List[GuildSticker]: + def stickers(self) -> list[GuildSticker]: """List[:class:`.GuildSticker`]: The stickers that the connected client has. .. versionadded:: 2.0 @@ -329,7 +324,7 @@ def cached_messages(self) -> Sequence[Message]: return utils.SequenceProxy(self._connection._messages or []) @property - def private_channels(self) -> List[PrivateChannel]: + def private_channels(self) -> list[PrivateChannel]: """List[:class:`.abc.PrivateChannel`]: The private channels that the connected client is participating on. .. note:: @@ -340,7 +335,7 @@ def private_channels(self) -> List[PrivateChannel]: return self._connection.private_channels @property - def voice_clients(self) -> List[VoiceProtocol]: + def voice_clients(self) -> list[VoiceProtocol]: """List[:class:`.VoiceProtocol`]: Represents a list of voice connections. These are usually :class:`.VoiceClient` instances. @@ -348,7 +343,7 @@ def voice_clients(self) -> List[VoiceProtocol]: return self._connection.voice_clients @property - def application_id(self) -> Optional[int]: + def application_id(self) -> int | None: """Optional[:class:`int`]: The client's application ID. If this is not passed via ``__init__`` then this is retrieved @@ -453,13 +448,17 @@ async def on_error(self, event_method: str, *args: Any, **kwargs: Any) -> None: # hooks - async def _call_before_identify_hook(self, shard_id: Optional[int], *, initial: bool = False) -> None: + async def _call_before_identify_hook( + self, shard_id: int | None, *, initial: bool = False + ) -> None: # This hook is an internal hook that actually calls the public one. # It allows the library to have its own hook without stepping on the # toes of those who need to override their own hook. await self.before_identify_hook(shard_id, initial=initial) - async def before_identify_hook(self, shard_id: Optional[int], *, initial: bool = False) -> None: + async def before_identify_hook( + self, shard_id: int | None, *, initial: bool = False + ) -> None: """|coro| A hook that is called before IDENTIFYing a session. This is useful @@ -471,7 +470,7 @@ async def before_identify_hook(self, shard_id: Optional[int], *, initial: bool = .. versionadded:: 1.4 Parameters - ------------ + ---------- shard_id: :class:`int` The shard ID that requested being IDENTIFY'd initial: :class:`bool` @@ -488,9 +487,8 @@ async def login(self, token: str) -> None: Logs in the client with the specified credentials. - Parameters - ----------- + ---------- token: :class:`str` The authentication token. Do not prefix this token with anything as the library will do it for you. @@ -507,7 +505,9 @@ async def login(self, token: str) -> None: passing status code. """ if not isinstance(token, str): - raise TypeError(f"token must be of type str, not {token.__class__.__name__}") + raise TypeError( + f"token must be of type str, not {token.__class__.__name__}" + ) _log.info("logging in using static token") @@ -523,7 +523,7 @@ async def connect(self, *, reconnect: bool = True) -> None: is not resumed until the WebSocket connection is terminated. Parameters - ----------- + ---------- reconnect: :class:`bool` If we should attempt reconnecting, either due to internet failure or a specific failure on Discord's part. Certain @@ -531,7 +531,7 @@ async def connect(self, *, reconnect: bool = True) -> None: invalid sharding payloads or bad tokens). Raises - ------- + ------ :exc:`GatewayNotFound` The gateway to connect to Discord is not found. Usually if this is thrown then there is a Discord API outage. @@ -607,7 +607,9 @@ async def connect(self, *, reconnect: bool = True) -> None: # Always try to RESUME the connection # If the connection is not RESUME-able then the gateway will invalidate the session. # This is apparently what the official Discord client does. - ws_params.update(sequence=self.ws.sequence, resume=True, session=self.ws.session_id) + ws_params.update( + sequence=self.ws.sequence, resume=True, session=self.ws.session_id + ) async def close(self) -> None: """|coro| @@ -650,7 +652,7 @@ async def start(self, token: str, *, reconnect: bool = True) -> None: A shorthand coroutine for :meth:`login` + :meth:`connect`. Raises - ------- + ------ TypeError An unexpected keyword argument was received. """ @@ -724,14 +726,14 @@ def is_closed(self) -> bool: return self._closed @property - def activity(self) -> Optional[ActivityTypes]: + def activity(self) -> ActivityTypes | None: """Optional[:class:`.BaseActivity`]: The activity being used upon logging in. """ return create_activity(self._connection._activity) @activity.setter - def activity(self, value: Optional[ActivityTypes]) -> None: + def activity(self, value: ActivityTypes | None) -> None: if value is None: self._connection._activity = None elif isinstance(value, BaseActivity): @@ -761,7 +763,7 @@ def status(self, value): raise TypeError("status must derive from Status.") @property - def allowed_mentions(self) -> Optional[AllowedMentions]: + def allowed_mentions(self) -> AllowedMentions | None: """Optional[:class:`~discord.AllowedMentions`]: The allowed mention configuration. .. versionadded:: 1.4 @@ -769,11 +771,13 @@ def allowed_mentions(self) -> Optional[AllowedMentions]: return self._connection.allowed_mentions @allowed_mentions.setter - def allowed_mentions(self, value: Optional[AllowedMentions]) -> None: + def allowed_mentions(self, value: AllowedMentions | None) -> None: if value is None or isinstance(value, AllowedMentions): self._connection.allowed_mentions = value else: - raise TypeError(f"allowed_mentions must be AllowedMentions not {value.__class__!r}") + raise TypeError( + f"allowed_mentions must be AllowedMentions not {value.__class__!r}" + ) @property def intents(self) -> Intents: @@ -786,7 +790,7 @@ def intents(self) -> Intents: # helpers/getters @property - def users(self) -> List[User]: + def users(self) -> list[User]: """List[:class:`~discord.User`]: Returns a list of all the users the bot can see.""" return list(self._connection._users.values()) @@ -795,59 +799,61 @@ async def fetch_application(self, application_id: int, /) -> PartialAppInfo: Retrieves a :class:`.PartialAppInfo` from an application ID. Parameters - ----------- + ---------- application_id: :class:`int` The application ID to retrieve information from. - Raises + Returns ------- + :class:`.PartialAppInfo` + The application information. + + Raises + ------ NotFound An application with this ID does not exist. HTTPException Retrieving the application failed. - - Returns - -------- - :class:`.PartialAppInfo` - The application information. """ data = await self.http.get_application(application_id) return PartialAppInfo(state=self._connection, data=data) - def get_channel(self, id: int, /) -> Optional[Union[GuildChannel, Thread, PrivateChannel]]: + def get_channel(self, id: int, /) -> GuildChannel | Thread | PrivateChannel | None: """Returns a channel or thread with the given ID. Parameters - ----------- + ---------- id: :class:`int` The ID to search for. Returns - -------- + ------- Optional[Union[:class:`.abc.GuildChannel`, :class:`.Thread`, :class:`.abc.PrivateChannel`]] The returned channel or ``None`` if not found. """ return self._connection.get_channel(id) - def get_message(self, id: int, /) -> Optional[Message]: + def get_message(self, id: int, /) -> Message | None: """Returns a message the given ID. This is useful if you have a message_id but don't want to do an API call to access the message. Parameters - ----------- + ---------- id: :class:`int` The ID to search for. Returns - -------- + ------- Optional[:class:`.Message`] The returned message or ``None`` if not found. """ return self._connection._get_message(id) - def get_partial_messageable(self, id: int, *, type: Optional[ChannelType] = None) -> PartialMessageable: + def get_partial_messageable( + self, id: int, *, type: ChannelType | None = None + ) -> PartialMessageable: """Returns a partial messageable with the given channel ID. This is useful if you have a channel_id but don't want to do an API call @@ -856,31 +862,31 @@ def get_partial_messageable(self, id: int, *, type: Optional[ChannelType] = None .. versionadded:: 2.0 Parameters - ----------- + ---------- id: :class:`int` The channel ID to create a partial messageable for. type: Optional[:class:`.ChannelType`] The underlying channel type for the partial messageable. Returns - -------- + ------- :class:`.PartialMessageable` The partial messageable """ return PartialMessageable(state=self._connection, id=id, type=type) - def get_stage_instance(self, id: int, /) -> Optional[StageInstance]: + def get_stage_instance(self, id: int, /) -> StageInstance | None: """Returns a stage instance with the given stage channel ID. .. versionadded:: 2.0 Parameters - ----------- + ---------- id: :class:`int` The ID to search for. Returns - -------- + ------- Optional[:class:`.StageInstance`] The stage instance or ``None`` if not found. """ @@ -891,52 +897,52 @@ def get_stage_instance(self, id: int, /) -> Optional[StageInstance]: if isinstance(channel, StageChannel): return channel.instance - def get_guild(self, id: int, /) -> Optional[Guild]: + def get_guild(self, id: int, /) -> Guild | None: """Returns a guild with the given ID. Parameters - ----------- + ---------- id: :class:`int` The ID to search for. Returns - -------- + ------- Optional[:class:`.Guild`] The guild or ``None`` if not found. """ return self._connection._get_guild(id) - def get_user(self, id: int, /) -> Optional[User]: + def get_user(self, id: int, /) -> User | None: """Returns a user with the given ID. Parameters - ----------- + ---------- id: :class:`int` The ID to search for. Returns - -------- + ------- Optional[:class:`~discord.User`] The user or ``None`` if not found. """ return self._connection.get_user(id) - def get_emoji(self, id: int, /) -> Optional[Emoji]: + def get_emoji(self, id: int, /) -> Emoji | None: """Returns an emoji with the given ID. Parameters - ----------- + ---------- id: :class:`int` The ID to search for. Returns - -------- + ------- Optional[:class:`.Emoji`] The custom emoji or ``None`` if not found. """ return self._connection.get_emoji(id) - def get_sticker(self, id: int, /) -> Optional[GuildSticker]: + def get_sticker(self, id: int, /) -> GuildSticker | None: """Returns a guild sticker with the given ID. .. versionadded:: 2.0 @@ -947,7 +953,7 @@ def get_sticker(self, id: int, /) -> Optional[GuildSticker]: or :meth:`.fetch_premium_sticker_packs`. Returns - -------- + ------- Optional[:class:`.GuildSticker`] The sticker or ``None`` if not found. """ @@ -994,16 +1000,16 @@ def get_all_members(self) -> Generator[Member, None, None]: for guild in self.guilds: yield from guild.members - async def get_or_fetch_user(self, id: int, /) -> Optional[User]: + async def get_or_fetch_user(self, id: int, /) -> User | None: """Looks up a user in the user cache or fetches if not found. Parameters - ----------- + ---------- id: :class:`int` The ID to search for. Returns - --------- + ------- Optional[:class:`~discord.User`] The user or ``None`` if not found. """ @@ -1023,8 +1029,8 @@ def wait_for( self, event: str, *, - check: Optional[Callable[..., bool]] = None, - timeout: Optional[float] = None, + check: Callable[..., bool] | None = None, + timeout: float | None = None, ) -> Any: """|coro| @@ -1046,8 +1052,32 @@ def wait_for( This function returns the **first event that meets the requirements**. + Parameters + ---------- + event: :class:`str` + The event name, similar to the :ref:`event reference `, + but without the ``on_`` prefix, to wait for. + check: Optional[Callable[..., :class:`bool`]] + A predicate to check what to wait for. The arguments must meet the + parameters of the event being waited for. + timeout: Optional[:class:`float`] + The number of seconds to wait before timing out and raising + :exc:`asyncio.TimeoutError`. + + Returns + ------- + Any + Returns no arguments, a single argument, or a :class:`tuple` of multiple + arguments that mirrors the parameters passed in the + :ref:`event reference `. + + Raises + ------ + asyncio.TimeoutError + Raised if a timeout is provided and reached. + Examples - --------- + -------- Waiting for a user reply: :: @@ -1080,31 +1110,6 @@ def check(reaction, user): await channel.send('\N{THUMBS DOWN SIGN}') else: await channel.send('\N{THUMBS UP SIGN}') - - - Parameters - ------------ - event: :class:`str` - The event name, similar to the :ref:`event reference `, - but without the ``on_`` prefix, to wait for. - check: Optional[Callable[..., :class:`bool`]] - A predicate to check what to wait for. The arguments must meet the - parameters of the event being waited for. - timeout: Optional[:class:`float`] - The number of seconds to wait before timing out and raising - :exc:`asyncio.TimeoutError`. - - Raises - ------- - asyncio.TimeoutError - Raised if a timeout is provided and reached. - - Returns - -------- - Any - Returns no arguments, a single argument, or a :class:`tuple` of multiple - arguments that mirrors the parameters passed in the - :ref:`event reference `. """ future = self.loop.create_future() @@ -1134,19 +1139,19 @@ def event(self, coro: Coro) -> Coro: The events must be a :ref:`coroutine `, if not, :exc:`TypeError` is raised. + Raises + ------ + TypeError + The coroutine passed is not actually a coroutine. + Example - --------- + ------- .. code-block:: python3 @client.event async def on_ready(): print('Ready!') - - Raises - -------- - TypeError - The coroutine passed is not actually a coroutine. """ if not asyncio.iscoroutinefunction(coro): @@ -1159,24 +1164,13 @@ async def on_ready(): async def change_presence( self, *, - activity: Optional[BaseActivity] = None, - status: Optional[Status] = None, + activity: BaseActivity | None = None, + status: Status | None = None, ): """|coro| Changes the client's presence. - Example - --------- - - .. code-block:: python3 - - game = discord.Game("with the API") - await client.change_presence(status=discord.Status.idle, activity=game) - - .. versionchanged:: 2.0 - Removed the ``afk`` keyword-only parameter. - Parameters ---------- activity: Optional[:class:`.BaseActivity`] @@ -1189,6 +1183,17 @@ async def change_presence( ------ :exc:`InvalidArgument` If the ``activity`` parameter is not the proper type. + + Example + ------- + + .. code-block:: python3 + + game = discord.Game("with the API") + await client.change_presence(status=discord.Status.idle, activity=game) + + .. versionchanged:: 2.0 + Removed the ``afk`` keyword-only parameter. """ if status is None: @@ -1215,7 +1220,7 @@ async def change_presence( def fetch_guilds( self, *, - limit: Optional[int] = 100, + limit: int | None = 100, before: SnowflakeTime = None, after: SnowflakeTime = None, ) -> GuildIterator: @@ -1230,23 +1235,8 @@ def fetch_guilds( This method is an API call. For general usage, consider :attr:`guilds` instead. - Examples - --------- - - Usage :: - - async for guild in client.fetch_guilds(limit=150): - print(guild.name) - - Flattening into a list :: - - guilds = await client.fetch_guilds(limit=150).flatten() - # guilds is now a list of Guild... - - All parameters are optional. - Parameters - ----------- + ---------- limit: Optional[:class:`int`] The number of guilds to retrieve. If ``None``, it retrieves every guild you have access to. Note, however, @@ -1261,39 +1251,54 @@ def fetch_guilds( If a datetime is provided, it is recommended to use a UTC aware datetime. If the datetime is naive, it is assumed to be local time. + Yields + ------ + :class:`.Guild` + The guild with the guild data parsed. + Raises ------ :exc:`HTTPException` Getting the guilds failed. - Yields + Examples -------- - :class:`.Guild` - The guild with the guild data parsed. + + Usage :: + + async for guild in client.fetch_guilds(limit=150): + print(guild.name) + + Flattening into a list :: + + guilds = await client.fetch_guilds(limit=150).flatten() + # guilds is now a list of Guild... + + All parameters are optional. """ return GuildIterator(self, limit=limit, before=before, after=after) - async def fetch_template(self, code: Union[Template, str]) -> Template: + async def fetch_template(self, code: Template | str) -> Template: """|coro| Gets a :class:`.Template` from a discord.new URL or code. Parameters - ----------- + ---------- code: Union[:class:`.Template`, :class:`str`] The Discord Template Code or URL (must be a discord.new URL). - Raises + Returns ------- + :class:`.Template` + The template from the URL/code. + + Raises + ------ :exc:`NotFound` The template is invalid. :exc:`HTTPException` Getting the template failed. - - Returns - -------- - :class:`.Template` - The template from the URL/code. """ code = utils.resolve_template(code) data = await self.http.get_template(code) @@ -1314,7 +1319,7 @@ async def fetch_guild(self, guild_id: int, /, *, with_counts=True) -> Guild: This method is an API call. For general usage, consider :meth:`get_guild` instead. Parameters - ----------- + ---------- guild_id: :class:`int` The guild's ID to fetch from. @@ -1324,17 +1329,18 @@ async def fetch_guild(self, guild_id: int, /, *, with_counts=True) -> Guild: fields. .. versionadded:: 2.0 + + Returns + ------- + :class:`.Guild` + The guild from the ID. + Raises ------ :exc:`Forbidden` You do not have access to the guild. :exc:`HTTPException` Getting the guild failed. - - Returns - -------- - :class:`.Guild` - The guild from the ID. """ data = await self.http.get_guild(guild_id, with_counts=with_counts) return Guild(data=data, state=self._connection) @@ -1364,18 +1370,18 @@ async def create_guild( .. versionadded:: 1.4 + Returns + ------- + :class:`.Guild` + The guild created. This is not the same guild that is + added to cache. + Raises ------ :exc:`HTTPException` Guild creation failed. :exc:`InvalidArgument` Invalid icon image format given. Must be PNG or JPG. - - Returns - ------- - :class:`.Guild` - The guild created. This is not the same guild that is - added to cache. """ if icon is not MISSING: icon_base64 = utils._bytes_to_base64_data(icon) @@ -1396,21 +1402,21 @@ async def fetch_stage_instance(self, channel_id: int, /) -> StageInstance: .. versionadded:: 2.0 Parameters - ----------- + ---------- channel_id: :class:`int` The stage channel ID. - Raises + Returns ------- + :class:`.StageInstance` + The stage instance from the stage channel ID. + + Raises + ------ :exc:`NotFound` The stage instance or channel could not be found. :exc:`HTTPException` Getting the stage instance failed. - - Returns - -------- - :class:`.StageInstance` - The stage instance from the stage channel ID. """ data = await self.http.get_stage_instance(channel_id) guild = self.get_guild(int(data["guild_id"])) @@ -1420,11 +1426,11 @@ async def fetch_stage_instance(self, channel_id: int, /) -> StageInstance: async def fetch_invite( self, - url: Union[Invite, str], + url: Invite | str, *, with_counts: bool = True, with_expiration: bool = True, - event_id: Optional[int] = None, + event_id: int | None = None, ) -> Invite: """|coro| @@ -1437,7 +1443,7 @@ async def fetch_invite( :class:`.PartialInviteChannel` respectively. Parameters - ----------- + ---------- url: Union[:class:`.Invite`, :class:`str`] The Discord invite ID or URL (must be a discord.gg URL). with_counts: :class:`bool` @@ -1457,17 +1463,17 @@ async def fetch_invite( .. versionadded:: 2.0 - Raises + Returns ------- + :class:`.Invite` + The invite from the URL/ID. + + Raises + ------ :exc:`NotFound` The invite has expired or is invalid. :exc:`HTTPException` Getting the invite failed. - - Returns - -------- - :class:`.Invite` - The invite from the URL/ID. """ invite_id = utils.resolve_invite(url) @@ -1479,7 +1485,7 @@ async def fetch_invite( ) return Invite.from_incomplete(state=self._connection, data=data) - async def delete_invite(self, invite: Union[Invite, str]) -> None: + async def delete_invite(self, invite: Invite | str) -> None: """|coro| Revokes an :class:`.Invite`, URL, or ID to an invite. @@ -1493,7 +1499,7 @@ async def delete_invite(self, invite: Union[Invite, str]) -> None: The invite to revoke. Raises - ------- + ------ :exc:`Forbidden` You do not have permissions to revoke invites. :exc:`NotFound` @@ -1517,21 +1523,21 @@ async def fetch_widget(self, guild_id: int, /) -> Widget: The guild must have the widget enabled to get this information. Parameters - ----------- + ---------- guild_id: :class:`int` The ID of the guild. - Raises + Returns ------- + :class:`.Widget` + The guild's widget. + + Raises + ------ :exc:`Forbidden` The widget for this guild is disabled. :exc:`HTTPException` Retrieving the widget failed. - - Returns - -------- - :class:`.Widget` - The guild's widget. """ data = await self.http.get_widget(guild_id) @@ -1542,15 +1548,15 @@ async def application_info(self) -> AppInfo: Retrieves the bot's application information. - Raises - ------- - :exc:`HTTPException` - Retrieving the information failed somehow. - Returns - -------- + ------- :class:`.AppInfo` The bot's application information. + + Raises + ------ + :exc:`HTTPException` + Retrieving the information failed somehow. """ data = await self.http.application_info() if "rpc_origins" not in data: @@ -1570,26 +1576,28 @@ async def fetch_user(self, user_id: int, /) -> User: consider :meth:`get_user` instead. Parameters - ----------- + ---------- user_id: :class:`int` The user's ID to fetch from. - Raises + Returns ------- + :class:`~discord.User` + The user you requested. + + Raises + ------ :exc:`NotFound` A user with this ID does not exist. :exc:`HTTPException` Fetching the user failed. - - Returns - -------- - :class:`~discord.User` - The user you requested. """ data = await self.http.get_user(user_id) return User(state=self._connection, data=data) - async def fetch_channel(self, channel_id: int, /) -> Union[GuildChannel, PrivateChannel, Thread]: + async def fetch_channel( + self, channel_id: int, / + ) -> GuildChannel | PrivateChannel | Thread: """|coro| Retrieves a :class:`.abc.GuildChannel`, :class:`.abc.PrivateChannel`, or :class:`.Thread` with the specified ID. @@ -1600,8 +1608,13 @@ async def fetch_channel(self, channel_id: int, /) -> Union[GuildChannel, Private .. versionadded:: 1.2 - Raises + Returns ------- + Union[:class:`.abc.GuildChannel`, :class:`.abc.PrivateChannel`, :class:`.Thread`] + The channel from the ID. + + Raises + ------ :exc:`InvalidData` An unknown channel type was received from Discord. :exc:`HTTPException` @@ -1610,17 +1623,14 @@ async def fetch_channel(self, channel_id: int, /) -> Union[GuildChannel, Private Invalid Channel ID. :exc:`Forbidden` You do not have permission to fetch this channel. - - Returns - -------- - Union[:class:`.abc.GuildChannel`, :class:`.abc.PrivateChannel`, :class:`.Thread`] - The channel from the ID. """ data = await self.http.get_channel(channel_id) factory, ch_type = _threaded_channel_factory(data["type"]) if factory is None: - raise InvalidData("Unknown channel type {type} for channel ID {id}.".format_map(data)) + raise InvalidData( + "Unknown channel type {type} for channel ID {id}.".format_map(data) + ) if ch_type in (ChannelType.group, ChannelType.private): # the factory will be a DMChannel or GroupChannel here @@ -1636,65 +1646,68 @@ async def fetch_webhook(self, webhook_id: int, /) -> Webhook: Retrieves a :class:`.Webhook` with the specified ID. + Returns + ------- + :class:`.Webhook` + The webhook you requested. + Raises - -------- + ------ :exc:`HTTPException` Retrieving the webhook failed. :exc:`NotFound` Invalid webhook ID. :exc:`Forbidden` You do not have permission to fetch this webhook. - - Returns - --------- - :class:`.Webhook` - The webhook you requested. """ data = await self.http.get_webhook(webhook_id) return Webhook.from_state(data, state=self._connection) - async def fetch_sticker(self, sticker_id: int, /) -> Union[StandardSticker, GuildSticker]: + async def fetch_sticker(self, sticker_id: int, /) -> StandardSticker | GuildSticker: """|coro| Retrieves a :class:`.Sticker` with the specified ID. .. versionadded:: 2.0 + Returns + ------- + Union[:class:`.StandardSticker`, :class:`.GuildSticker`] + The sticker you requested. + Raises - -------- + ------ :exc:`HTTPException` Retrieving the sticker failed. :exc:`NotFound` Invalid sticker ID. - - Returns - -------- - Union[:class:`.StandardSticker`, :class:`.GuildSticker`] - The sticker you requested. """ data = await self.http.get_sticker(sticker_id) cls, _ = _sticker_factory(data["type"]) # type: ignore return cls(state=self._connection, data=data) # type: ignore - async def fetch_premium_sticker_packs(self) -> List[StickerPack]: + async def fetch_premium_sticker_packs(self) -> list[StickerPack]: """|coro| Retrieves all available premium sticker packs. .. versionadded:: 2.0 - Raises - ------- - :exc:`HTTPException` - Retrieving the sticker packs failed. - Returns - --------- + ------- List[:class:`.StickerPack`] All available premium sticker packs. + + Raises + ------ + :exc:`HTTPException` + Retrieving the sticker packs failed. """ data = await self.http.list_premium_sticker_packs() - return [StickerPack(state=self._connection, data=pack) for pack in data["sticker_packs"]] + return [ + StickerPack(state=self._connection, data=pack) + for pack in data["sticker_packs"] + ] async def create_dm(self, user: Snowflake) -> DMChannel: """|coro| @@ -1707,7 +1720,7 @@ async def create_dm(self, user: Snowflake) -> DMChannel: .. versionadded:: 2.0 Parameters - ----------- + ---------- user: :class:`~discord.abc.Snowflake` The user to create a DM with. @@ -1724,7 +1737,7 @@ async def create_dm(self, user: Snowflake) -> DMChannel: data = await state.http.start_private_message(user.id) return state.add_dm_channel(data) - def add_view(self, view: View, *, message_id: Optional[int] = None) -> None: + def add_view(self, view: View, *, message_id: int | None = None) -> None: """Registers a :class:`~discord.ui.View` for persistent listening. This method should be used for when a view is comprised of components @@ -1733,7 +1746,7 @@ def add_view(self, view: View, *, message_id: Optional[int] = None) -> None: .. versionadded:: 2.0 Parameters - ------------ + ---------- view: :class:`discord.ui.View` The view to register for dispatching. message_id: Optional[:class:`int`] @@ -1742,7 +1755,7 @@ def add_view(self, view: View, *, message_id: Optional[int] = None) -> None: then message update events are not propagated for the view. Raises - ------- + ------ TypeError A view was not passed. ValueError @@ -1754,7 +1767,9 @@ def add_view(self, view: View, *, message_id: Optional[int] = None) -> None: raise TypeError(f"expected an instance of View not {view.__class__!r}") if not view.is_persistent(): - raise ValueError("View is not persistent. Items need to have a custom_id set and View must have no timeout") + raise ValueError( + "View is not persistent. Items need to have a custom_id set and View must have no timeout" + ) self._connection.store_view(view, message_id) diff --git a/discord/cog.py b/discord/cog.py index 5643f71915..b6a3c9e289 100644 --- a/discord/cog.py +++ b/discord/cog.py @@ -30,21 +30,7 @@ import pathlib import sys import types -from typing import ( - Any, - Callable, - ClassVar, - Dict, - Generator, - List, - Mapping, - Optional, - Tuple, - Type, - TypeVar, - Union, - overload, -) +from typing import Any, Callable, ClassVar, Generator, Mapping, TypeVar, overload import discord.utils @@ -106,7 +92,7 @@ class MyCog(discord.Cog, name='My Cog'): pass Attributes - ----------- + ---------- name: :class:`str` The cog name. By default, it is the name of the class with no modification. description: :class:`str` @@ -139,12 +125,12 @@ async def bar(self, ctx): """ __cog_name__: str - __cog_settings__: Dict[str, Any] - __cog_commands__: List[ApplicationCommand] - __cog_listeners__: List[Tuple[str, str]] - __cog_guild_ids__: List[int] + __cog_settings__: dict[str, Any] + __cog_commands__: list[ApplicationCommand] + __cog_listeners__: list[tuple[str, str]] + __cog_guild_ids__: list[int] - def __new__(cls: Type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta: + def __new__(cls: type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta: name, bases, attrs = args attrs["__cog_name__"] = kwargs.pop("name", name) attrs["__cog_settings__"] = kwargs.pop("command_attrs", {}) @@ -162,7 +148,8 @@ def __new__(cls: Type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta: new_cls = super().__new__(cls, name, bases, attrs, **kwargs) valid_commands = [ - (c for i, c in j.__dict__.items() if isinstance(c, _BaseCommand)) for j in reversed(new_cls.__mro__) + (c for i, c in j.__dict__.items() if isinstance(c, _BaseCommand)) + for j in reversed(new_cls.__mro__) ] if any(isinstance(i, ApplicationCommand) for i in valid_commands) and any( not isinstance(i, _BaseCommand) for i in valid_commands @@ -178,7 +165,9 @@ def __new__(cls: Type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta: if elem in listeners: del listeners[elem] - if getattr(value, "parent", None) and isinstance(value, ApplicationCommand): + if getattr(value, "parent", None) and isinstance( + value, ApplicationCommand + ): # Skip commands if they are a part of a group continue @@ -187,7 +176,9 @@ def __new__(cls: Type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta: value = value.__func__ if isinstance(value, _filter): if is_static_method: - raise TypeError(f"Command in method {base}.{elem!r} must not be staticmethod.") + raise TypeError( + f"Command in method {base}.{elem!r} must not be staticmethod." + ) if elem.startswith(("cog_", "bot_")): raise TypeError(no_bot_cog.format(base, elem)) commands[elem] = value @@ -195,14 +186,18 @@ def __new__(cls: Type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta: # a test to see if this value is a BridgeCommand if hasattr(value, "add_to") and not getattr(value, "parent", None): if is_static_method: - raise TypeError(f"Command in method {base}.{elem!r} must not be staticmethod.") + raise TypeError( + f"Command in method {base}.{elem!r} must not be staticmethod." + ) if elem.startswith(("cog_", "bot_")): raise TypeError(no_bot_cog.format(base, elem)) commands[f"ext_{elem}"] = value.ext_variant commands[f"app_{elem}"] = value.slash_variant for cmd in getattr(value, "subcommands", []): - commands[f"ext_{cmd.ext_variant.qualified_name}"] = cmd.ext_variant + commands[ + f"ext_{cmd.ext_variant.qualified_name}" + ] = cmd.ext_variant if inspect.iscoroutinefunction(value): try: @@ -231,20 +226,31 @@ def __new__(cls: Type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta: # r.e type ignore, type-checker complains about overriding a ClassVar new_cls.__cog_commands__ = tuple(c._update_copy(cmd_attrs) for c in new_cls.__cog_commands__) # type: ignore - name_filter = lambda c: 'app' if isinstance(c, ApplicationCommand) else 'ext' + name_filter = lambda c: "app" if isinstance(c, ApplicationCommand) else "ext" - lookup = {f"{name_filter(cmd)}_{cmd.qualified_name}": cmd for cmd in new_cls.__cog_commands__} + lookup = { + f"{name_filter(cmd)}_{cmd.qualified_name}": cmd + for cmd in new_cls.__cog_commands__ + } # Update the Command instances dynamically as well for command in new_cls.__cog_commands__: - if isinstance(command, ApplicationCommand) and not command.guild_ids and new_cls.__cog_guild_ids__: + if ( + isinstance(command, ApplicationCommand) + and not command.guild_ids + and new_cls.__cog_guild_ids__ + ): command.guild_ids = new_cls.__cog_guild_ids__ if not isinstance(command, SlashCommandGroup): # ignore bridge commands cmd = getattr(new_cls, command.callback.__name__, None) if hasattr(cmd, "add_to"): - setattr(cmd, f"{name_filter(command).replace('app', 'slash')}_variant", command) + setattr( + cmd, + f"{name_filter(command).replace('app', 'slash')}_variant", + command, + ) else: setattr(new_cls, command.callback.__name__, command) @@ -284,18 +290,18 @@ class Cog(metaclass=CogMeta): """ __cog_name__: ClassVar[str] - __cog_settings__: ClassVar[Dict[str, Any]] - __cog_commands__: ClassVar[List[ApplicationCommand]] - __cog_listeners__: ClassVar[List[Tuple[str, str]]] - __cog_guild_ids__: ClassVar[List[int]] + __cog_settings__: ClassVar[dict[str, Any]] + __cog_commands__: ClassVar[list[ApplicationCommand]] + __cog_listeners__: ClassVar[list[tuple[str, str]]] + __cog_guild_ids__: ClassVar[list[int]] - def __new__(cls: Type[CogT], *args: Any, **kwargs: Any) -> CogT: + def __new__(cls: type[CogT], *args: Any, **kwargs: Any) -> CogT: # For issue 426, we need to store a copy of the command objects # since we modify them to inject `self` to them. # To do this, we need to interfere with the Cog creation process. return super().__new__(cls) - def get_commands(self) -> List[ApplicationCommand]: + def get_commands(self) -> list[ApplicationCommand]: r""" Returns -------- @@ -307,7 +313,11 @@ def get_commands(self) -> List[ApplicationCommand]: This does not include subcommands. """ - return [c for c in self.__cog_commands__ if isinstance(c, ApplicationCommand) and c.parent is None] + return [ + c + for c in self.__cog_commands__ + if isinstance(c, ApplicationCommand) and c.parent is None + ] @property def qualified_name(self) -> str: @@ -335,20 +345,25 @@ def walk_commands(self) -> Generator[ApplicationCommand, None, None]: if isinstance(command, SlashCommandGroup): yield from command.walk_commands() - def get_listeners(self) -> List[Tuple[str, Callable[..., Any]]]: + def get_listeners(self) -> list[tuple[str, Callable[..., Any]]]: """Returns a :class:`list` of (name, function) listener pairs that are defined in this cog. Returns - -------- + ------- List[Tuple[:class:`str`, :ref:`coroutine `]] The listeners defined in this cog. """ - return [(name, getattr(self, method_name)) for name, method_name in self.__cog_listeners__] + return [ + (name, getattr(self, method_name)) + for name, method_name in self.__cog_listeners__ + ] @classmethod - def _get_overridden_method(cls, method: FuncT) -> Optional[FuncT]: + def _get_overridden_method(cls, method: FuncT) -> FuncT | None: """Return None if the method is not overridden. Otherwise, returns the overridden method.""" - return getattr(getattr(method, "__func__", method), "__cog_special_method__", method) + return getattr( + getattr(method, "__func__", method), "__cog_special_method__", method + ) @classmethod def listener(cls, name: str = MISSING) -> Callable[[FuncT], FuncT]: @@ -357,20 +372,22 @@ def listener(cls, name: str = MISSING) -> Callable[[FuncT], FuncT]: This is the cog equivalent of :meth:`.Bot.listen`. Parameters - ------------ + ---------- name: :class:`str` The name of the event being listened to. If not provided, it defaults to the function's name. Raises - -------- + ------ TypeError The function is not a coroutine function or a string was not passed as the name. """ if name is not MISSING and not isinstance(name, str): - raise TypeError(f"Cog.listener expected str but received {name.__class__.__name__!r} instead.") + raise TypeError( + f"Cog.listener expected str but received {name.__class__.__name__!r} instead." + ) def decorator(func: FuncT) -> FuncT: actual = func @@ -408,7 +425,6 @@ def cog_unload(self) -> None: Subclasses must replace this if they want special unloading behaviour. """ - pass @_cog_special_method def bot_check_once(self, ctx: ApplicationContext) -> bool: @@ -419,7 +435,7 @@ def bot_check_once(self, ctx: ApplicationContext) -> bool: ``ctx``, to represent the :class:`.Context` or :class:`.ApplicationContext`. Parameters - ----------- + ---------- ctx: :class:`.Context` The invocation context. """ @@ -434,7 +450,7 @@ def bot_check(self, ctx: ApplicationContext) -> bool: ``ctx``, to represent the :class:`.Context` or :class:`.ApplicationContext`. Parameters - ----------- + ---------- ctx: :class:`.Context` The invocation context. """ @@ -449,14 +465,16 @@ def cog_check(self, ctx: ApplicationContext) -> bool: ``ctx``, to represent the :class:`.Context` or :class:`.ApplicationContext`. Parameters - ----------- + ---------- ctx: :class:`.Context` The invocation context. """ return True @_cog_special_method - async def cog_command_error(self, ctx: ApplicationContext, error: Exception) -> None: + async def cog_command_error( + self, ctx: ApplicationContext, error: Exception + ) -> None: """A special method that is called whenever an error is dispatched inside this cog. @@ -466,13 +484,12 @@ async def cog_command_error(self, ctx: ApplicationContext, error: Exception) -> This **must** be a coroutine. Parameters - ----------- + ---------- ctx: :class:`.ApplicationContext` The invocation context where the error happened. error: :class:`ApplicationCommandError` The error that happened. """ - pass @_cog_special_method async def cog_before_invoke(self, ctx: ApplicationContext) -> None: @@ -483,11 +500,10 @@ async def cog_before_invoke(self, ctx: ApplicationContext) -> None: This **must** be a coroutine. Parameters - ----------- + ---------- ctx: :class:`.ApplicationContext` The invocation context. """ - pass @_cog_special_method async def cog_after_invoke(self, ctx: ApplicationContext) -> None: @@ -498,11 +514,10 @@ async def cog_after_invoke(self, ctx: ApplicationContext) -> None: This **must** be a coroutine. Parameters - ----------- + ---------- ctx: :class:`.ApplicationContext` The invocation context. """ - pass def _inject(self: CogT, bot) -> CogT: cls = self.__class__ @@ -571,8 +586,8 @@ def _eject(self, bot) -> None: class CogMixin: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.__cogs: Dict[str, Cog] = {} - self.__extensions: Dict[str, types.ModuleType] = {} + self.__cogs: dict[str, Cog] = {} + self.__extensions: dict[str, types.ModuleType] = {} def add_cog(self, cog: Cog, *, override: bool = False) -> None: """Adds a "cog" to the bot. @@ -585,7 +600,7 @@ def add_cog(self, cog: Cog, *, override: bool = False) -> None: is already loaded. Parameters - ----------- + ---------- cog: :class:`.Cog` The cog to register to the bot. override: :class:`bool` @@ -595,7 +610,7 @@ def add_cog(self, cog: Cog, *, override: bool = False) -> None: .. versionadded:: 2.0 Raises - ------- + ------ TypeError The cog does not inherit from :class:`.Cog`. ApplicationCommandError @@ -618,26 +633,26 @@ def add_cog(self, cog: Cog, *, override: bool = False) -> None: cog = cog._inject(self) self.__cogs[cog_name] = cog - def get_cog(self, name: str) -> Optional[Cog]: + def get_cog(self, name: str) -> Cog | None: """Gets the cog instance requested. If the cog is not found, ``None`` is returned instead. Parameters - ----------- + ---------- name: :class:`str` The name of the cog you are requesting. This is equivalent to the name passed via keyword argument in class creation or the class name if unspecified. Returns - -------- + ------- Optional[:class:`Cog`] The cog that was requested. If not found, returns ``None``. """ return self.__cogs.get(name) - def remove_cog(self, name: str) -> Optional[Cog]: + def remove_cog(self, name: str) -> Cog | None: """Removes a cog from the bot and returns it. All registered commands and event listeners that the @@ -646,7 +661,7 @@ def remove_cog(self, name: str) -> Optional[Cog]: If no cog is found then this method has no effect. Parameters - ----------- + ---------- name: :class:`str` The name of the cog to remove. @@ -701,7 +716,8 @@ def _remove_module_references(self, name: str) -> None: remove = [ index for index, event in enumerate(event_list) - if event.__module__ is not None and _is_submodule(name, event.__module__) + if event.__module__ is not None + and _is_submodule(name, event.__module__) ] for index in reversed(remove): @@ -725,7 +741,9 @@ def _call_module_finalizers(self, lib: types.ModuleType, key: str) -> None: if _is_submodule(name, module): del sys.modules[module] - def _load_from_module_spec(self, spec: importlib.machinery.ModuleSpec, key: str) -> None: + def _load_from_module_spec( + self, spec: importlib.machinery.ModuleSpec, key: str + ) -> None: # precondition: key not in self.__extensions lib = importlib.util.module_from_spec(spec) sys.modules[key] = lib @@ -751,7 +769,7 @@ def _load_from_module_spec(self, spec: importlib.machinery.ModuleSpec, key: str) else: self.__extensions[key] = lib - def _resolve_name(self, name: str, package: Optional[str]) -> str: + def _resolve_name(self, name: str, package: str | None) -> str: try: return importlib.util.resolve_name(name, package) except ImportError: @@ -762,9 +780,9 @@ def load_extension( self, name: str, *, - package: Optional[str] = None, + package: str | None = None, recursive: bool = False, - ) -> List[str]: + ) -> list[str]: ... @overload @@ -772,15 +790,15 @@ def load_extension( self, name: str, *, - package: Optional[str] = None, + package: str | None = None, recursive: bool = False, store: bool = False, - ) -> Optional[Union[Dict[str, Union[Exception, bool]], List[str]]]: + ) -> dict[str, Exception | bool] | list[str] | None: ... def load_extension( - self, name, *, package = None, recursive = False, store = False - ) -> Optional[Union[Dict[str, Union[Exception, bool]], List[str]]]: + self, name, *, package=None, recursive=False, store=False + ) -> dict[str, Exception | bool] | list[str] | None: """Loads an extension. An extension is a python module that contains commands, cogs, or @@ -794,7 +812,7 @@ def load_extension( the current working directory or a folder that contains multiple extensions. Parameters - ----------- + ---------- name: :class:`str` The extension or folder name to load. It must be dot separated like regular Python imports if accessing a submodule. e.g. @@ -823,8 +841,18 @@ def load_extension( .. versionadded:: 2.0 - Raises + Returns ------- + Optional[Union[Dict[:class:`str`, Union[:exc:`errors.ExtensionError`, :class:`bool`]], List[:class:`str`]]] + If the store parameter is set to ``True``, a dictionary will be returned that + contains keys to represent the loaded extension names. The values bound to + each key can either be an exception that occurred when loading that extension + or a ``True`` boolean representing a successful load. If the store parameter + is set to ``False``, either a list containing a list of loaded extensions or + nothing due to an encountered exception. + + Raises + ------ ExtensionNotFound The extension could not be imported. This is also raised if the name of the extension could not @@ -835,16 +863,6 @@ def load_extension( The extension does not have a setup function. ExtensionFailed The extension or its setup function had an execution error. - - Returns - -------- - Optional[Union[Dict[:class:`str`, Union[:exc:`errors.ExtensionError`, :class:`bool`]], List[:class:`str`]]] - If the store parameter is set to ``True``, a dictionary will be returned that - contains keys to represent the loaded extension names. The values bound to - each key can either be an exception that occurred when loading that extension - or a ``True`` boolean representing a successful load. If the store parameter - is set to ``False``, either a list containing a list of loaded extensions or - nothing due to an encountered exception. """ name = self._resolve_name(name, package) @@ -881,7 +899,9 @@ def load_extension( parts = list(ext_file.parts[:-1]) # Gets the file name without the extension parts.append(ext_file.stem) - loaded = self.load_extension(".".join(parts), package=package, recursive=recursive, store=store) + loaded = self.load_extension( + ".".join(parts), package=package, recursive=recursive, store=store + ) final_out.update(loaded) if store else final_out.extend(loaded) if isinstance(final_out, Exception): @@ -893,31 +913,31 @@ def load_extension( def load_extensions( self, *names: str, - package: Optional[str] = None, + package: str | None = None, recursive: bool = False, - ) -> List[str]: + ) -> list[str]: ... @overload def load_extensions( self, *names: str, - package: Optional[str] = None, + package: str | None = None, recursive: bool = False, store: bool = False, - ) -> Optional[Union[Dict[str, Union[Exception, bool]], List[str]]]: + ) -> dict[str, Exception | bool] | list[str] | None: ... def load_extensions( - self, *names, package = None, recursive = False, store = False - ) -> Optional[Union[Dict[str, Union[Exception, bool]], List[str]]]: + self, *names, package=None, recursive=False, store=False + ) -> dict[str, Exception | bool] | list[str] | None: """Loads multiple extensions at once. This method simplifies the process of loading multiple extensions by handling the looping of ``load_extension``. Parameters - ----------- + ---------- names: :class:`str` The extension or folder names to load. It must be dot separated like regular Python imports if accessing a submodule. e.g. @@ -946,8 +966,18 @@ def load_extensions( .. versionadded:: 2.0 + Returns + ------- + Optional[Union[Dict[:class:`str`, Union[:exc:`errors.ExtensionError`, :class:`bool`]], List[:class:`str`]]] + If the store parameter is set to ``True``, a dictionary will be returned that + contains keys to represent the loaded extension names. The values bound to + each key can either be an exception that occurred when loading that extension + or a ``True`` boolean representing a successful load. If the store parameter + is set to ``False``, either a list containing names of loaded extensions or + nothing due to an encountered exception. + Raises - -------- + ------ ExtensionNotFound A given extension could not be imported. This is also raised if the name of the extension could not @@ -958,27 +988,21 @@ def load_extensions( A given extension does not have a setup function. ExtensionFailed A given extension or its setup function had an execution error. - - Returns - -------- - Optional[Union[Dict[:class:`str`, Union[:exc:`errors.ExtensionError`, :class:`bool`]], List[:class:`str`]]] - If the store parameter is set to ``True``, a dictionary will be returned that - contains keys to represent the loaded extension names. The values bound to - each key can either be an exception that occurred when loading that extension - or a ``True`` boolean representing a successful load. If the store parameter - is set to ``False``, either a list containing names of loaded extensions or - nothing due to an encountered exception. """ loaded_extensions = {} if store else [] for ext_path in names: - loaded = self.load_extension(ext_path, package=package, recursive=recursive, store=store) - loaded_extensions.update(loaded) if store else loaded_extensions.extend(loaded) + loaded = self.load_extension( + ext_path, package=package, recursive=recursive, store=store + ) + loaded_extensions.update(loaded) if store else loaded_extensions.extend( + loaded + ) return loaded_extensions - def unload_extension(self, name: str, *, package: Optional[str] = None) -> None: + def unload_extension(self, name: str, *, package: str | None = None) -> None: """Unloads an extension. When the extension is unloaded, all commands, listeners, and cogs are @@ -990,7 +1014,7 @@ def unload_extension(self, name: str, *, package: Optional[str] = None) -> None: :meth:`~.Bot.load_extension`. Parameters - ------------ + ---------- name: :class:`str` The extension name to unload. It must be dot separated like regular Python imports if accessing a submodule. e.g. @@ -1003,7 +1027,7 @@ def unload_extension(self, name: str, *, package: Optional[str] = None) -> None: .. versionadded:: 1.7 Raises - ------- + ------ ExtensionNotFound The name of the extension could not be resolved using the provided ``package`` parameter. @@ -1019,7 +1043,7 @@ def unload_extension(self, name: str, *, package: Optional[str] = None) -> None: self._remove_module_references(lib.__name__) self._call_module_finalizers(lib, name) - def reload_extension(self, name: str, *, package: Optional[str] = None) -> None: + def reload_extension(self, name: str, *, package: str | None = None) -> None: """Atomically reloads an extension. This replaces the extension with the same extension, only refreshed. This is @@ -1028,7 +1052,7 @@ def reload_extension(self, name: str, *, package: Optional[str] = None) -> None: the bot will roll back to the prior working state. Parameters - ------------ + ---------- name: :class:`str` The extension name to reload. It must be dot separated like regular Python imports if accessing a submodule. e.g. @@ -1041,7 +1065,7 @@ def reload_extension(self, name: str, *, package: Optional[str] = None) -> None: .. versionadded:: 1.7 Raises - ------- + ------ ExtensionNotLoaded The extension was not loaded. ExtensionNotFound @@ -1060,7 +1084,11 @@ def reload_extension(self, name: str, *, package: Optional[str] = None) -> None: raise errors.ExtensionNotLoaded(name) # get the previous module states from sys modules - modules = {name: module for name, module in sys.modules.items() if _is_submodule(lib.__name__, name)} + modules = { + name: module + for name, module in sys.modules.items() + if _is_submodule(lib.__name__, name) + } try: # Unload and then load the module... diff --git a/discord/colour.py b/discord/colour.py index 2c5a9f267f..eb4f139a55 100644 --- a/discord/colour.py +++ b/discord/colour.py @@ -64,7 +64,7 @@ class Colour: Returns the raw colour value. Attributes - ------------ + ---------- value: :class:`int` The raw integer colour value. """ @@ -73,7 +73,9 @@ class Colour: def __init__(self, value: int): if not isinstance(value, int): - raise TypeError(f"Expected int parameter, received {value.__class__.__name__} instead.") + raise TypeError( + f"Expected int parameter, received {value.__class__.__name__} instead." + ) self.value: int = value @@ -149,7 +151,7 @@ def random( .. versionadded:: 1.6 Parameters - ------------ + ---------- seed: Optional[Union[:class:`int`, :class:`str`, :class:`float`, :class:`bytes`, :class:`bytearray`]] The seed to initialize the RNG with. If ``None`` is passed the default RNG is used. @@ -342,7 +344,7 @@ def embed_background(cls: Type[CT], theme: str = "dark") -> CT: .. versionadded:: 2.0 Parameters - ----------- + ---------- theme: :class:`str` The theme color to apply, must be one of "dark", "light", or "amoled". """ diff --git a/discord/commands/context.py b/discord/commands/context.py index e1e2c07d5c..212f796498 100644 --- a/discord/commands/context.py +++ b/discord/commands/context.py @@ -24,10 +24,10 @@ """ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Dict, List, Optional, TypeVar, Union +from typing import TYPE_CHECKING, Any, TypeVar import discord.abc -from discord.interactions import InteractionMessage, InteractionResponse, Interaction +from discord.interactions import Interaction, InteractionMessage, InteractionResponse from discord.webhook.async_ import Webhook if TYPE_CHECKING: @@ -74,7 +74,7 @@ class ApplicationContext(discord.abc.Messageable): .. versionadded:: 2.0 Attributes - ----------- + ---------- bot: :class:`.Bot` The bot that the command belongs to. interaction: :class:`.Interaction` @@ -95,7 +95,7 @@ def __init__(self, bot: Bot, interaction: Interaction): self._state: ConnectionState = self.interaction._state - async def _get_channel(self) -> Optional[InteractionChannel]: + async def _get_channel(self) -> InteractionChannel | None: return self.interaction.channel async def invoke( @@ -136,41 +136,42 @@ async def invoke( return await command(self, *args, **kwargs) @cached_property - def channel(self) -> Optional[InteractionChannel]: + def channel(self) -> InteractionChannel | None: """Union[:class:`abc.GuildChannel`, :class:`PartialMessageable`, :class:`Thread`]: - Returns the channel associated with this context's command. Shorthand for :attr:`.Interaction.channel`.""" + Returns the channel associated with this context's command. Shorthand for :attr:`.Interaction.channel`. + """ return self.interaction.channel @cached_property - def channel_id(self) -> Optional[int]: + def channel_id(self) -> int | None: """:class:`int`: Returns the ID of the channel associated with this context's command. Shorthand for :attr:`.Interaction.channel_id`. """ return self.interaction.channel_id @cached_property - def guild(self) -> Optional[Guild]: + def guild(self) -> Guild | None: """Optional[:class:`.Guild`]: Returns the guild associated with this context's command. Shorthand for :attr:`.Interaction.guild`. """ return self.interaction.guild @cached_property - def guild_id(self) -> Optional[int]: + def guild_id(self) -> int | None: """:class:`int`: Returns the ID of the guild associated with this context's command. Shorthand for :attr:`.Interaction.guild_id`. """ return self.interaction.guild_id @cached_property - def locale(self) -> Optional[str]: + def locale(self) -> str | None: """:class:`str`: Returns the locale of the guild associated with this context's command. Shorthand for :attr:`.Interaction.locale`. """ return self.interaction.locale @cached_property - def guild_locale(self) -> Optional[str]: + def guild_locale(self) -> str | None: """:class:`str`: Returns the locale of the guild associated with this context's command. Shorthand for :attr:`.Interaction.guild_locale`. """ @@ -181,31 +182,35 @@ def app_permissions(self) -> Permissions: return self.interaction.app_permissions @cached_property - def me(self) -> Optional[Union[Member, ClientUser]]: + def me(self) -> Member | ClientUser | None: """Union[:class:`.Member`, :class:`.ClientUser`]: Similar to :attr:`.Guild.me` except it may return the :class:`.ClientUser` in private message message contexts, or when :meth:`Intents.guilds` is absent. """ - return self.interaction.guild.me if self.interaction.guild is not None else self.bot.user + return ( + self.interaction.guild.me + if self.interaction.guild is not None + else self.bot.user + ) @cached_property - def message(self) -> Optional[Message]: + def message(self) -> Message | None: """Optional[:class:`.Message`]: Returns the message sent with this context's command. Shorthand for :attr:`.Interaction.message`, if applicable. """ return self.interaction.message @cached_property - def user(self) -> Optional[Union[Member, User]]: + def user(self) -> Member | User | None: """Union[:class:`.Member`, :class:`.User`]: Returns the user that sent this context's command. Shorthand for :attr:`.Interaction.user`. """ return self.interaction.user - author: Optional[Union[Member, User]] = user + author: Member | User | None = user @property - def voice_client(self) -> Optional[VoiceProtocol]: + def voice_client(self) -> VoiceProtocol | None: """Optional[:class:`.VoiceProtocol`]: Returns the voice client associated with this context's command. Shorthand for :attr:`Interaction.guild.voice_client<~discord.Guild.voice_client>`, if applicable. """ @@ -217,11 +222,12 @@ def voice_client(self) -> Optional[VoiceProtocol]: @cached_property def response(self) -> InteractionResponse: """:class:`.InteractionResponse`: Returns the response object associated with this context's command. - Shorthand for :attr:`.Interaction.response`.""" + Shorthand for :attr:`.Interaction.response`. + """ return self.interaction.response @property - def selected_options(self) -> Optional[List[Dict[str, Any]]]: + def selected_options(self) -> list[dict[str, Any]] | None: """The options and values that were selected by the user when sending the command. Returns @@ -234,7 +240,7 @@ def selected_options(self) -> Optional[List[Dict[str, Any]]]: return self.interaction.data.get("options", None) @property - def unselected_options(self) -> Optional[List[Option]]: + def unselected_options(self) -> list[Option] | None: """The options that were not provided by the user when sending the command. Returns @@ -248,7 +254,8 @@ def unselected_options(self) -> Optional[List[Option]]: return [ option for option in self.command.options # type: ignore - if option.to_dict()["name"] not in [opt["name"] for opt in self.selected_options] + if option.to_dict()["name"] + not in [opt["name"] for opt in self.selected_options] ] else: return self.command.options # type: ignore @@ -259,7 +266,7 @@ def unselected_options(self) -> Optional[List[Option]]: def send_modal(self) -> Callable[..., Awaitable[Interaction]]: return self.interaction.response.send_modal - async def respond(self, *args, **kwargs) -> Union[Interaction, WebhookMessage]: + async def respond(self, *args, **kwargs) -> Interaction | WebhookMessage: """|coro| Sends either a response or a message using the followup webhook determined by whether the interaction @@ -272,7 +279,9 @@ async def respond(self, *args, **kwargs) -> Union[Interaction, WebhookMessage]: """ try: if not self.interaction.response.is_done(): - return await self.interaction.response.send_message(*args, **kwargs) # self.response + return await self.interaction.response.send_message( + *args, **kwargs + ) # self.response else: return await self.followup.send(*args, **kwargs) # self.send_followup except discord.errors.InteractionResponded: @@ -308,7 +317,7 @@ def followup(self) -> Webhook: """:class:`Webhook`: Returns the followup webhook for followup interactions.""" return self.interaction.followup - async def delete(self, *, delay: Optional[float] = None) -> None: + async def delete(self, *, delay: float | None = None) -> None: """|coro| Deletes the original interaction response message. @@ -316,12 +325,12 @@ async def delete(self, *, delay: Optional[float] = None) -> None: This is a higher level interface to :meth:`Interaction.delete_original_response`. Parameters - ----------- + ---------- delay: Optional[:class:`float`] If provided, the number of seconds to wait before deleting the message. Raises - ------- + ------ HTTPException Deleting the message failed. Forbidden @@ -338,7 +347,7 @@ def edit(self) -> Callable[..., Awaitable[InteractionMessage]]: return self.interaction.edit_original_response @property - def cog(self) -> Optional[Cog]: + def cog(self) -> Cog | None: """Optional[:class:`.Cog`]: Returns the cog associated with this context's command. ``None`` if it does not exist. """ @@ -356,7 +365,7 @@ class AutocompleteContext: .. versionadded:: 2.0 Attributes - ----------- + ---------- bot: :class:`.Bot` The bot that the command belongs to. interaction: :class:`.Interaction` @@ -383,7 +392,7 @@ def __init__(self, bot: Bot, interaction: Interaction): self.options: dict = None # type: ignore @property - def cog(self) -> Optional[Cog]: + def cog(self) -> Cog | None: """Optional[:class:`.Cog`]: Returns the cog associated with this context's command. ``None`` if it does not exist. """ diff --git a/discord/commands/core.py b/discord/commands/core.py index 9407eec31f..673dedf4d0 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -38,18 +38,15 @@ Any, Callable, Coroutine, - Dict, Generator, Generic, - List, - Optional, - Type, TypeVar, Union, ) from ..channel import _threaded_guild_channel_factory -from ..enums import MessageType, SlashCommandOptionType, try_enum, Enum as DiscordEnum +from ..enums import Enum as DiscordEnum +from ..enums import MessageType, SlashCommandOptionType, try_enum from ..errors import ( ApplicationCommandError, ApplicationCommandInvokeError, @@ -63,7 +60,7 @@ from ..role import Role from ..threads import Thread from ..user import User -from ..utils import async_all, find, utcnow, maybe_coroutine, MISSING +from ..utils import MISSING, async_all, find, maybe_coroutine, utcnow from .context import ApplicationContext, AutocompleteContext from .options import Option, OptionChoice @@ -134,7 +131,10 @@ async def wrapped(arg): except Exception as exc: raise ApplicationCommandInvokeError(exc) from exc finally: - if hasattr(command, "_max_concurrency") and command._max_concurrency is not None: + if ( + hasattr(command, "_max_concurrency") + and command._max_concurrency is not None + ): await command._max_concurrency.release(ctx) await command.call_after_hooks(ctx) return ret @@ -172,7 +172,7 @@ class _BaseCommand: class ApplicationCommand(_BaseCommand, Generic[CogT, P, T]): - __original_kwargs__: Dict[str, Any] + __original_kwargs__: dict[str, Any] cog = None def __init__(self, func: Callable, **kwargs) -> None: @@ -185,13 +185,17 @@ def __init__(self, func: Callable, **kwargs) -> None: elif isinstance(cooldown, CooldownMapping): buckets = cooldown else: - raise TypeError("Cooldown must be a an instance of CooldownMapping or None.") + raise TypeError( + "Cooldown must be a an instance of CooldownMapping or None." + ) self._buckets: CooldownMapping = buckets - max_concurrency = getattr(func, "__commands_max_concurrency__", kwargs.get("max_concurrency")) + max_concurrency = getattr( + func, "__commands_max_concurrency__", kwargs.get("max_concurrency") + ) - self._max_concurrency: Optional[MaxConcurrency] = max_concurrency + self._max_concurrency: MaxConcurrency | None = max_concurrency self._callback = None self.module = None @@ -205,25 +209,34 @@ def __init__(self, func: Callable, **kwargs) -> None: checks = kwargs.get("checks", []) self.checks = checks - self.id: Optional[int] = kwargs.get("id") - self.guild_ids: Optional[List[int]] = kwargs.get("guild_ids", None) + self.id: int | None = kwargs.get("id") + self.guild_ids: list[int] | None = kwargs.get("guild_ids", None) self.parent = kwargs.get("parent") # Permissions - self.default_member_permissions: Optional["Permissions"] = getattr( - func, "__default_member_permissions__", kwargs.get("default_member_permissions", None) + self.default_member_permissions: Permissions | None = getattr( + func, + "__default_member_permissions__", + kwargs.get("default_member_permissions", None), + ) + self.guild_only: bool | None = getattr( + func, "__guild_only__", kwargs.get("guild_only", None) ) - self.guild_only: Optional[bool] = getattr(func, "__guild_only__", kwargs.get("guild_only", None)) def __repr__(self) -> str: return f"" def __eq__(self, other) -> bool: - if getattr(self, "id", None) is not None and getattr(other, "id", None) is not None: + if ( + getattr(self, "id", None) is not None + and getattr(other, "id", None) is not None + ): check = self.id == other.id else: check = self.name == other.name and self.guild_ids == other.guild_ids - return isinstance(other, self.__class__) and self.parent == other.parent and check + return ( + isinstance(other, self.__class__) and self.parent == other.parent and check + ) async def __call__(self, ctx, *args, **kwargs): """|coro| @@ -240,19 +253,19 @@ async def __call__(self, ctx, *args, **kwargs): @property def callback( self, - ) -> Union[ - Callable[Concatenate[CogT, ApplicationContext, P], Coro[T]], - Callable[Concatenate[ApplicationContext, P], Coro[T]], - ]: + ) -> ( + Callable[Concatenate[CogT, ApplicationContext, P], Coro[T]] + | Callable[Concatenate[ApplicationContext, P], Coro[T]] + ): return self._callback @callback.setter def callback( self, - function: Union[ - Callable[Concatenate[CogT, ApplicationContext, P], Coro[T]], - Callable[Concatenate[ApplicationContext, P], Coro[T]], - ], + function: ( + Callable[Concatenate[CogT, ApplicationContext, P], Coro[T]] + | Callable[Concatenate[ApplicationContext, P], Coro[T]] + ), ) -> None: self._callback = function unwrap = unwrap_function(function) @@ -276,7 +289,9 @@ async def prepare(self, ctx: ApplicationContext) -> None: ctx.command = self if not await self.can_run(ctx): - raise CheckFailure(f"The check functions for the command {self.name} failed") + raise CheckFailure( + f"The check functions for the command {self.name} failed" + ) if hasattr(self, "_max_concurrency"): if self._max_concurrency is not None: @@ -299,12 +314,12 @@ def is_on_cooldown(self, ctx: ApplicationContext) -> bool: This uses the current time instead of the interaction time. Parameters - ----------- + ---------- ctx: :class:`.ApplicationContext` The invocation context to use when checking the command's cooldown status. Returns - -------- + ------- :class:`bool` A boolean indicating if the command is on cooldown. """ @@ -319,7 +334,7 @@ def reset_cooldown(self, ctx: ApplicationContext) -> None: """Resets the cooldown on this command. Parameters - ----------- + ---------- ctx: :class:`.ApplicationContext` The invocation context to reset the cooldown under. """ @@ -335,12 +350,12 @@ def get_cooldown_retry_after(self, ctx: ApplicationContext) -> float: This uses the current time instead of the interaction time. Parameters - ----------- + ---------- ctx: :class:`.ApplicationContext` The invocation context to retrieve the cooldown from. Returns - -------- + ------- :class:`float` The amount of time left on this command's cooldown in seconds. If this is ``0.0`` then the command isn't on cooldown. @@ -361,7 +376,9 @@ async def invoke(self, ctx: ApplicationContext) -> None: async def can_run(self, ctx: ApplicationContext) -> bool: if not await ctx.bot.can_run(ctx): - raise CheckFailure(f"The global check functions for command {self.name} failed.") + raise CheckFailure( + f"The global check functions for command {self.name} failed." + ) predicates = self.checks if self.parent is not None: @@ -416,12 +433,12 @@ def error(self, coro): invoked afterwards as the catch-all. Parameters - ----------- + ---------- coro: :ref:`coroutine ` The coroutine to register as the local error handler. Raises - ------- + ------ TypeError The coroutine passed is not actually a coroutine. """ @@ -446,12 +463,12 @@ def before_invoke(self, coro): See :meth:`.Bot.before_invoke` for more info. Parameters - ----------- + ---------- coro: :ref:`coroutine ` The coroutine to register as the pre-invoke hook. Raises - ------- + ------ TypeError The coroutine passed is not actually a coroutine. """ @@ -471,12 +488,12 @@ def after_invoke(self, coro): See :meth:`.Bot.after_invoke` for more info. Parameters - ----------- + ---------- coro: :ref:`coroutine ` The coroutine to register as the post-invoke hook. Raises - ------- + ------ TypeError The coroutine passed is not actually a coroutine. """ @@ -565,7 +582,7 @@ def qualified_name(self) -> str: else: return self.name - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: raise NotImplementedError def __str__(self) -> str: @@ -598,7 +615,7 @@ class SlashCommand(ApplicationCommand): parent: Optional[:class:`SlashCommandGroup`] The parent group that this command belongs to. ``None`` if there isn't one. - mention: :class:`str` + mention: :class:`str` Returns a string that allows you to mention the slash command. guild_only: :class:`bool` Whether the command should only be usable inside a guild. @@ -637,20 +654,26 @@ def __init__(self, func: Callable, *args, **kwargs) -> None: raise TypeError("Callback must be a coroutine.") self.callback = func - self.name_localizations: Optional[Dict[str, str]] = kwargs.get("name_localizations", None) + self.name_localizations: dict[str, str] | None = kwargs.get( + "name_localizations", None + ) _validate_names(self) description = kwargs.get("description") or ( - inspect.cleandoc(func.__doc__).splitlines()[0] if func.__doc__ is not None else "No description provided" + inspect.cleandoc(func.__doc__).splitlines()[0] + if func.__doc__ is not None + else "No description provided" ) self.description: str = description - self.description_localizations: Optional[Dict[str, str]] = kwargs.get("description_localizations", None) + self.description_localizations: dict[str, str] | None = kwargs.get( + "description_localizations", None + ) _validate_descriptions(self) self.attached_to_group: bool = False - self.options: List[Option] = kwargs.get("options", []) + self.options: list[Option] = kwargs.get("options", []) try: checks = func.__commands_checks__ @@ -668,27 +691,26 @@ def __init__(self, func: Callable, *args, **kwargs) -> None: def _validate_parameters(self): params = self._get_signature_parameters() if kwop := self.options: - self.options: List[Option] = self._match_option_param_names(params, kwop) + self.options: list[Option] = self._match_option_param_names(params, kwop) else: - self.options: List[Option] = self._parse_options(params) + self.options: list[Option] = self._parse_options(params) def _check_required_params(self, params): params = iter(params.items()) required_params = ( - ["self", "context"] - if self.attached_to_group - or self.cog - else ["context"] + ["self", "context"] if self.attached_to_group or self.cog else ["context"] ) for p in required_params: try: next(params) except StopIteration: - raise ClientException(f'Callback for {self.name} command is missing "{p}" parameter.') + raise ClientException( + f'Callback for {self.name} command is missing "{p}" parameter.' + ) return params - def _parse_options(self, params, *, check_params: bool = True) -> List[Option]: + def _parse_options(self, params, *, check_params: bool = True) -> list[Option]: if check_params: params = self._check_required_params(params) @@ -706,15 +728,22 @@ def _parse_options(self, params, *, check_params: bool = True) -> List[Option]: if not isinstance(option, Option): if isinstance(p_obj.default, Option): - p_obj.default.input_type = SlashCommandOptionType.from_datatype(option) + p_obj.default.input_type = SlashCommandOptionType.from_datatype( + option + ) option = p_obj.default else: option = Option(option) if option.default is None and not p_obj.default == inspect.Parameter.empty: - if isinstance(p_obj.default, type) and issubclass(p_obj.default, (DiscordEnum, Enum)): + if isinstance(p_obj.default, type) and issubclass( + p_obj.default, (DiscordEnum, Enum) + ): option = Option(p_obj.default) - elif isinstance(p_obj.default, Option) and not (default := p_obj.default.default) is None: + elif ( + isinstance(p_obj.default, Option) + and not (default := p_obj.default.default) is None + ): option.default = default else: option.default = p_obj.default @@ -734,13 +763,18 @@ def _parse_options(self, params, *, check_params: bool = True) -> List[Option]: def _match_option_param_names(self, params, options): params = self._check_required_params(params) - check_annotations: List[Callable[[Option, Type], bool]] = [ + check_annotations: list[Callable[[Option, type], bool]] = [ lambda o, a: o.input_type == SlashCommandOptionType.string and o.converter is not None, # pass on converters - lambda o, a: isinstance(o.input_type, SlashCommandOptionType), # pass on slash cmd option type enums + lambda o, a: isinstance( + o.input_type, SlashCommandOptionType + ), # pass on slash cmd option type enums lambda o, a: isinstance(o._raw_type, tuple) and a == Union[o._raw_type], # type: ignore # union types - lambda o, a: self._is_typing_optional(a) and not o.required and o._raw_type in a.__args__, # optional - lambda o, a: isinstance(a, type) and issubclass(a, o._raw_type), # 'normal' types + lambda o, a: self._is_typing_optional(a) + and not o.required + and o._raw_type in a.__args__, # optional + lambda o, a: isinstance(a, type) + and issubclass(a, o._raw_type), # 'normal' types ] for o in options: _validate_names(o) @@ -752,7 +786,9 @@ def _match_option_param_names(self, params, options): p_obj = p_obj.annotation if not any(check(o, p_obj) for check in check_annotations): - raise TypeError(f"Parameter {p_name} does not match input type of {o.name}.") + raise TypeError( + f"Parameter {p_name} does not match input type of {o.name}." + ) o._parameter_name = p_name left_out_params = OrderedDict() @@ -763,7 +799,9 @@ def _match_option_param_names(self, params, options): return options def _is_typing_union(self, annotation): - return getattr(annotation, "__origin__", None) is Union or type(annotation) is getattr( + return getattr(annotation, "__origin__", None) is Union or type( + annotation + ) is getattr( types, "UnionType", Union ) # type: ignore @@ -782,12 +820,12 @@ def cog(self, val): @property def is_subcommand(self) -> bool: return self.parent is not None - + @property def mention(self) -> str: return f"" - - def to_dict(self) -> Dict: + + def to_dict(self) -> dict: as_dict = { "name": self.name, "description": self.description, @@ -804,7 +842,9 @@ def to_dict(self) -> Dict: as_dict["dm_permission"] = not self.guild_only if self.default_member_permissions is not None: - as_dict["default_member_permissions"] = self.default_member_permissions.value + as_dict[ + "default_member_permissions" + ] = self.default_member_permissions.value return as_dict @@ -827,7 +867,8 @@ async def _invoke(self, ctx: ApplicationContext) -> None: ): resolved = ctx.interaction.data.get("resolved", {}) if ( - op.input_type in (SlashCommandOptionType.user, SlashCommandOptionType.mentionable) + op.input_type + in (SlashCommandOptionType.user, SlashCommandOptionType.mentionable) and (_data := resolved.get("members", {}).get(arg)) is not None ): # The option type is a user, we resolved a member from the snowflake and assigned it to _data @@ -840,16 +881,23 @@ async def _invoke(self, ctx: ApplicationContext) -> None: if (_data := resolved.get("users", {}).get(arg)) is not None: arg = User(state=ctx.interaction._state, data=_data) elif (_data := resolved.get("roles", {}).get(arg)) is not None: - arg = Role(state=ctx.interaction._state, data=_data, guild=ctx.guild) + arg = Role( + state=ctx.interaction._state, data=_data, guild=ctx.guild + ) else: arg = Object(id=int(arg)) - elif (_data := resolved.get(f"{op.input_type.name}s", {}).get(arg)) is not None: + elif ( + _data := resolved.get(f"{op.input_type.name}s", {}).get(arg) + ) is not None: if op.input_type is SlashCommandOptionType.channel and ( - int(arg) in ctx.guild._channels or int(arg) in ctx.guild._threads + int(arg) in ctx.guild._channels + or int(arg) in ctx.guild._threads ): arg = ctx.guild.get_channel_or_thread(int(arg)) _data["_invoke_flag"] = True - arg._update(_data) if isinstance(arg, Thread) else arg._update(ctx.guild, _data) + arg._update(_data) if isinstance(arg, Thread) else arg._update( + ctx.guild, _data + ) else: obj_type = None kw = {} @@ -874,18 +922,24 @@ async def _invoke(self, ctx: ApplicationContext) -> None: # We couldn't resolve the object, so we just return an empty object arg = Object(id=int(arg)) - elif op.input_type == SlashCommandOptionType.string and (converter := op.converter) is not None: + elif ( + op.input_type == SlashCommandOptionType.string + and (converter := op.converter) is not None + ): from discord.ext.commands import Converter + if isinstance(converter, Converter): if isinstance(converter, type): arg = await converter().convert(ctx, arg) else: arg = await converter.convert(ctx, arg) - elif op._raw_type in (SlashCommandOptionType.integer, - SlashCommandOptionType.number, - SlashCommandOptionType.string, - SlashCommandOptionType.boolean): + elif op._raw_type in ( + SlashCommandOptionType.integer, + SlashCommandOptionType.number, + SlashCommandOptionType.string, + SlashCommandOptionType.boolean, + ): pass elif issubclass(op._raw_type, Enum): @@ -916,7 +970,9 @@ async def invoke_autocomplete_callback(self, ctx: AutocompleteContext): for op in ctx.interaction.data.get("options", []): if op.get("focused", False): option = find(lambda o: o.name == op["name"], self.options) - values.update({i["name"]: i["value"] for i in ctx.interaction.data["options"]}) + values.update( + {i["name"]: i["value"] for i in ctx.interaction.data["options"]} + ) ctx.command = self ctx.focused = option ctx.value = op.get("value") @@ -931,14 +987,19 @@ async def invoke_autocomplete_callback(self, ctx: AutocompleteContext): if asyncio.iscoroutinefunction(option.autocomplete): result = await result - choices = [o if isinstance(o, OptionChoice) else OptionChoice(o) for o in result][:25] - return await ctx.interaction.response.send_autocomplete_result(choices=choices) + choices = [ + o if isinstance(o, OptionChoice) else OptionChoice(o) + for o in result + ][:25] + return await ctx.interaction.response.send_autocomplete_result( + choices=choices + ) def copy(self): """Creates a copy of this command. Returns - -------- + ------- :class:`SlashCommand` A new instance of this command. """ @@ -962,7 +1023,7 @@ def _ensure_assignment_on_copy(self, other): pass return other - def _update_copy(self, kwargs: Dict[str, Any]): + def _update_copy(self, kwargs: dict[str, Any]): if kwargs: kw = kwargs.copy() kw.update(self.__original_kwargs__) @@ -1007,7 +1068,7 @@ class SlashCommandGroup(ApplicationCommand): The description localizations for this command. The values of this should be ``"locale": "description"``. See `here `_ for a list of valid locales. """ - __initial_commands__: List[Union[SlashCommand, SlashCommandGroup]] + __initial_commands__: list[SlashCommand | SlashCommandGroup] type = 1 def __new__(cls, *args, **kwargs) -> SlashCommandGroup: @@ -1035,9 +1096,9 @@ def __new__(cls, *args, **kwargs) -> SlashCommandGroup: def __init__( self, name: str, - description: Optional[str] = None, - guild_ids: Optional[List[int]] = None, - parent: Optional[SlashCommandGroup] = None, + description: str | None = None, + guild_ids: list[int] | None = None, + parent: SlashCommandGroup | None = None, **kwargs, ) -> None: self.name = str(name) @@ -1045,7 +1106,9 @@ def __init__( validate_chat_input_name(self.name) validate_chat_input_description(self.description) self.input_type = SlashCommandOptionType.sub_command_group - self.subcommands: List[Union[SlashCommand, SlashCommandGroup]] = self.__initial_commands__ + self.subcommands: list[ + SlashCommand | SlashCommandGroup + ] = self.__initial_commands__ self.guild_ids = guild_ids self.parent = parent self.attached_to_group: bool = False @@ -1057,17 +1120,23 @@ def __init__( self.id = None # Permissions - self.default_member_permissions: Optional["Permissions"] = kwargs.get("default_member_permissions", None) - self.guild_only: Optional[bool] = kwargs.get("guild_only", None) + self.default_member_permissions: Permissions | None = kwargs.get( + "default_member_permissions", None + ) + self.guild_only: bool | None = kwargs.get("guild_only", None) - self.name_localizations: Optional[Dict[str, str]] = kwargs.get("name_localizations", None) - self.description_localizations: Optional[Dict[str, str]] = kwargs.get("description_localizations", None) + self.name_localizations: dict[str, str] | None = kwargs.get( + "name_localizations", None + ) + self.description_localizations: dict[str, str] | None = kwargs.get( + "description_localizations", None + ) @property - def module(self) -> Optional[str]: + def module(self) -> str | None: return self.__module__ - def to_dict(self) -> Dict: + def to_dict(self) -> dict: as_dict = { "name": self.name, "description": self.description, @@ -1085,11 +1154,15 @@ def to_dict(self) -> Dict: as_dict["dm_permission"] = not self.guild_only if self.default_member_permissions is not None: - as_dict["default_member_permissions"] = self.default_member_permissions.value + as_dict[ + "default_member_permissions" + ] = self.default_member_permissions.value return as_dict - def command(self, cls: Type[T] = SlashCommand, **kwargs) -> Callable[[Callable], SlashCommand]: + def command( + self, cls: type[T] = SlashCommand, **kwargs + ) -> Callable[[Callable], SlashCommand]: def wrap(func) -> T: command = cls(func, parent=self, **kwargs) self.subcommands.append(command) @@ -1100,8 +1173,8 @@ def wrap(func) -> T: def create_subgroup( self, name: str, - description: Optional[str] = None, - guild_ids: Optional[List[int]] = None, + description: str | None = None, + guild_ids: list[int] | None = None, **kwargs, ) -> SlashCommandGroup: """ @@ -1135,7 +1208,7 @@ def create_subgroup( See `here `_ for a list of valid locales. Returns - -------- + ------- SlashCommandGroup The slash command group that was created. """ @@ -1144,16 +1217,18 @@ def create_subgroup( # TODO: Improve this error message raise Exception("a subgroup cannot have a subgroup") - sub_command_group = SlashCommandGroup(name, description, guild_ids, parent=self, **kwargs) + sub_command_group = SlashCommandGroup( + name, description, guild_ids, parent=self, **kwargs + ) self.subcommands.append(sub_command_group) return sub_command_group def subgroup( self, - name: Optional[str] = None, - description: Optional[str] = None, - guild_ids: Optional[List[int]] = None, - ) -> Callable[[Type[SlashCommandGroup]], SlashCommandGroup]: + name: str | None = None, + description: str | None = None, + guild_ids: list[int] | None = None, + ) -> Callable[[type[SlashCommandGroup]], SlashCommandGroup]: """A shortcut decorator that initializes the provided subclass of :class:`.SlashCommandGroup` as a subgroup. @@ -1170,12 +1245,12 @@ def subgroup( This will be a global command if ``None`` is passed. Returns - -------- + ------- Callable[[Type[SlashCommandGroup]], SlashCommandGroup] The slash command group that was created. """ - def inner(cls: Type[SlashCommandGroup]) -> SlashCommandGroup: + def inner(cls: type[SlashCommandGroup]) -> SlashCommandGroup: group = cls( name or cls.__name__, description @@ -1223,7 +1298,7 @@ def copy(self): """Creates a copy of this command group. Returns - -------- + ------- :class:`SlashCommandGroup` A new instance of this command group. """ @@ -1252,7 +1327,7 @@ def _ensure_assignment_on_copy(self, other): return other - def _update_copy(self, kwargs: Dict[str, Any]): + def _update_copy(self, kwargs: dict[str, Any]): if kwargs: kw = kwargs.copy() kw.update(self.__original_kwargs__) @@ -1316,7 +1391,9 @@ def __init__(self, func: Callable, *args, **kwargs) -> None: raise TypeError("Callback must be a coroutine.") self.callback = func - self.name_localizations: Optional[Dict[str, str]] = kwargs.get("name_localizations", None) + self.name_localizations: dict[str, str] | None = kwargs.get( + "name_localizations", None + ) # Discord API doesn't support setting descriptions for context menu commands, so it must be empty self.description = "" @@ -1346,19 +1423,25 @@ def validate_parameters(self): try: next(params) except StopIteration: - raise ClientException(f'Callback for {self.name} command is missing "ctx" parameter.') + raise ClientException( + f'Callback for {self.name} command is missing "ctx" parameter.' + ) # next we have the 'user/message' as the next parameter try: next(params) except StopIteration: cmd = "user" if type(self) == UserCommand else "message" - raise ClientException(f'Callback for {self.name} command is missing "{cmd}" parameter.') + raise ClientException( + f'Callback for {self.name} command is missing "{cmd}" parameter.' + ) # next there should be no more parameters try: next(params) - raise ClientException(f"Callback for {self.name} command has too many parameters.") + raise ClientException( + f"Callback for {self.name} command has too many parameters." + ) except StopIteration: pass @@ -1366,7 +1449,7 @@ def validate_parameters(self): def qualified_name(self): return self.name - def to_dict(self) -> Dict[str, Union[str, int]]: + def to_dict(self) -> dict[str, str | int]: as_dict = { "name": self.name, "description": self.description, @@ -1377,7 +1460,9 @@ def to_dict(self) -> Dict[str, Union[str, int]]: as_dict["dm_permission"] = not self.guild_only if self.default_member_permissions is not None: - as_dict["default_member_permissions"] = self.default_member_permissions.value + as_dict[ + "default_member_permissions" + ] = self.default_member_permissions.value if self.name_localizations is not None: as_dict["name_localizations"] = self.name_localizations @@ -1449,7 +1534,7 @@ def copy(self): """Creates a copy of this command. Returns - -------- + ------- :class:`UserCommand` A new instance of this command. """ @@ -1473,7 +1558,7 @@ def _ensure_assignment_on_copy(self, other): pass return other - def _update_copy(self, kwargs: Dict[str, Any]): + def _update_copy(self, kwargs: dict[str, Any]): if kwargs: kw = kwargs.copy() kw.update(self.__original_kwargs__) @@ -1546,7 +1631,7 @@ def copy(self): """Creates a copy of this command. Returns - -------- + ------- :class:`MessageCommand` A new instance of this command. """ @@ -1570,7 +1655,7 @@ def _ensure_assignment_on_copy(self, other): pass return other - def _update_copy(self, kwargs: Dict[str, Any]): + def _update_copy(self, kwargs: dict[str, Any]): if kwargs: kw = kwargs.copy() kw.update(self.__original_kwargs__) @@ -1586,7 +1671,7 @@ def slash_command(**kwargs): .. versionadded:: 2.0 Returns - -------- + ------- Callable[..., :class:`.SlashCommand`] A decorator that converts the provided method into a :class:`.SlashCommand`. """ @@ -1599,7 +1684,7 @@ def user_command(**kwargs): .. versionadded:: 2.0 Returns - -------- + ------- Callable[..., :class:`.UserCommand`] A decorator that converts the provided method into a :class:`.UserCommand`. """ @@ -1612,7 +1697,7 @@ def message_command(**kwargs): .. versionadded:: 2.0 Returns - -------- + ------- Callable[..., :class:`.MessageCommand`] A decorator that converts the provided method into a :class:`.MessageCommand`. """ @@ -1632,7 +1717,7 @@ def application_command(cls=SlashCommand, **attrs): .. versionadded:: 2.0 Parameters - ----------- + ---------- cls: :class:`.ApplicationCommand` The class to construct with. By default, this is :class:`.SlashCommand`. You usually do not change this. @@ -1640,22 +1725,24 @@ def application_command(cls=SlashCommand, **attrs): Keyword arguments to pass into the construction of the class denoted by ``cls``. - Raises - ------- - TypeError - If the function is not a coroutine or is already a command. - Returns - -------- + ------- Callable[..., :class:`.ApplicationCommand`] A decorator that converts the provided method into an :class:`.ApplicationCommand`, or subclass of it. + + Raises + ------ + TypeError + If the function is not a coroutine or is already a command. """ def decorator(func: Callable) -> cls: if isinstance(func, ApplicationCommand): func = func.callback elif not callable(func): - raise TypeError("func needs to be a callable or a subclass of ApplicationCommand.") + raise TypeError( + "func needs to be a callable or a subclass of ApplicationCommand." + ) return cls(func, **attrs) return decorator @@ -1670,7 +1757,7 @@ def command(**kwargs): .. versionadded:: 2.0 Returns - -------- + ------- Callable[..., :class:`.ApplicationCommand`] A decorator that converts the provided method into an :class:`.ApplicationCommand`. """ @@ -1713,7 +1800,7 @@ def command(**kwargs): # Validation -def validate_chat_input_name(name: Any, locale: Optional[str] = None): +def validate_chat_input_name(name: Any, locale: str | None = None): # Must meet the regex ^[-_\w\d\u0901-\u097D\u0E00-\u0E7F]{1,32}$ if locale is not None and locale not in valid_locales: raise ValidationError( @@ -1721,15 +1808,21 @@ def validate_chat_input_name(name: Any, locale: Optional[str] = None): ) error = None if not isinstance(name, str): - error = TypeError(f'Command names and options must be of type str. Received "{name}"') + error = TypeError( + f'Command names and options must be of type str. Received "{name}"' + ) elif not re.match(r"^[-_\w\d\u0901-\u097D\u0E00-\u0E7F]{1,32}$", name): error = ValidationError( r"Command names and options must follow the regex \"^[-_\w\d\u0901-\u097D\u0E00-\u0E7F]{1,32}$\". " f"For more information, see {docs}/interactions/application-commands#application-command-object-" f'application-command-naming. Received "{name}"' ) - elif name.lower() != name: # Can't use islower() as it fails if none of the chars can be lowered. See #512. - error = ValidationError(f'Command names and options must be lowercase. Received "{name}"') + elif ( + name.lower() != name + ): # Can't use islower() as it fails if none of the chars can be lowered. See #512. + error = ValidationError( + f'Command names and options must be lowercase. Received "{name}"' + ) if error: if locale: @@ -1737,14 +1830,16 @@ def validate_chat_input_name(name: Any, locale: Optional[str] = None): raise error -def validate_chat_input_description(description: Any, locale: Optional[str] = None): +def validate_chat_input_description(description: Any, locale: str | None = None): if locale is not None and locale not in valid_locales: raise ValidationError( f"Locale '{locale}' is not a valid locale, see {docs}/reference#locales for list of supported locales." ) error = None if not isinstance(description, str): - error = TypeError(f'Command and option description must be of type str. Received "{description}"') + error = TypeError( + f'Command and option description must be of type str. Received "{description}"' + ) elif not 1 <= len(description) <= 100: error = ValidationError( f'Command and option description must be 1-100 characters long. Received "{description}"' diff --git a/discord/commands/options.py b/discord/commands/options.py index 59a6fafa2d..c5c013a314 100644 --- a/discord/commands/options.py +++ b/discord/commands/options.py @@ -25,19 +25,21 @@ from __future__ import annotations import inspect -from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Type, Union from enum import Enum +from typing import TYPE_CHECKING, Literal, Optional, Type, Union from ..abc import GuildChannel, Mentionable -from ..channel import TextChannel, VoiceChannel, StageChannel, CategoryChannel, Thread -from ..enums import ChannelType, SlashCommandOptionType, Enum as DiscordEnum +from ..channel import CategoryChannel, StageChannel, TextChannel, Thread, VoiceChannel +from ..enums import ChannelType +from ..enums import Enum as DiscordEnum +from ..enums import SlashCommandOptionType if TYPE_CHECKING: from ..ext.commands import Converter - from ..user import User from ..member import Member from ..message import Attachment from ..role import Role + from ..user import User InputType = Union[ Type[str], @@ -80,7 +82,7 @@ class ThreadOption: .. versionadded:: 2.0 Parameters - ----------- + ---------- thread_type: Literal["public", "private", "news"] The thread type to expect for this options input. """ @@ -97,23 +99,6 @@ def __init__(self, thread_type: Literal["public", "private", "news"]): class Option: """Represents a selectable option for a slash command. - Examples - -------- - Basic usage: :: - - @bot.slash_command(guild_ids=[...]) - async def hello( - ctx: discord.ApplicationContext, - name: Option(str, "Enter your name"), - age: Option(int, "Enter your age", min_value=1, max_value=99, default=18) - # passing the default value makes an argument optional - # you also can create optional argument using: - # age: Option(int, "Enter your age") = 18 - ): - await ctx.respond(f"Hello! Your name is {name} and you are {age} years old.") - - .. versionadded:: 2.0 - Attributes ---------- input_type: Union[Type[:class:`str`], Type[:class:`bool`], Type[:class:`int`], Type[:class:`float`], Type[:class:`.abc.GuildChannel`], Type[:class:`Thread`], Type[:class:`Member`], Type[:class:`User`], Type[:class:`Attachment`], Type[:class:`Role`], Type[:class:`.abc.Mentionable`], :class:`SlashCommandOptionType`, Type[:class:`.ext.commands.Converter`], Type[:class:`enums.Enum`], Type[:class:`Enum`]] @@ -159,17 +144,36 @@ async def hello( description_localizations: Optional[Dict[:class:`str`, :class:`str`]] The description localizations for this option. The values of this should be ``"locale": "description"``. See `here `_ for a list of valid locales. + + Examples + -------- + Basic usage: :: + + @bot.slash_command(guild_ids=[...]) + async def hello( + ctx: discord.ApplicationContext, + name: Option(str, "Enter your name"), + age: Option(int, "Enter your age", min_value=1, max_value=99, default=18) + # passing the default value makes an argument optional + # you also can create optional argument using: + # age: Option(int, "Enter your age") = 18 + ): + await ctx.respond(f"Hello! Your name is {name} and you are {age} years old.") + + .. versionadded:: 2.0 """ input_type: SlashCommandOptionType - converter: Optional[Union[Converter, Type[Converter]]] = None + converter: Converter | type[Converter] | None = None - def __init__(self, input_type: InputType = str, /, description: Optional[str] = None, **kwargs) -> None: - self.name: Optional[str] = kwargs.pop("name", None) + def __init__( + self, input_type: InputType = str, /, description: str | None = None, **kwargs + ) -> None: + self.name: str | None = kwargs.pop("name", None) if self.name is not None: self.name = str(self.name) self._parameter_name = self.name # default - self._raw_type: Union[InputType, tuple] = input_type + self._raw_type: InputType | tuple = input_type enum_choices = [] input_type_is_class = isinstance(input_type, type) @@ -178,19 +182,26 @@ def __init__(self, input_type: InputType = str, /, description: Optional[str] = enum_choices = [OptionChoice(e.name, e.value) for e in input_type] value_class = enum_choices[0].value.__class__ if all(isinstance(elem.value, value_class) for elem in enum_choices): - input_type = SlashCommandOptionType.from_datatype(enum_choices[0].value.__class__) + input_type = SlashCommandOptionType.from_datatype( + enum_choices[0].value.__class__ + ) else: enum_choices = [OptionChoice(e.name, str(e.value)) for e in input_type] input_type = SlashCommandOptionType.string self.description = description or "No description provided" - self.channel_types: List[ChannelType] = kwargs.pop("channel_types", []) + self.channel_types: list[ChannelType] = kwargs.pop("channel_types", []) if isinstance(input_type, SlashCommandOptionType): self.input_type = input_type else: from ..ext.commands import Converter - if isinstance(input_type, Converter) or input_type_is_class and issubclass(input_type, Converter): + + if ( + isinstance(input_type, Converter) + or input_type_is_class + and issubclass(input_type, Converter) + ): self.converter = input_type self._raw_type = str self.input_type = SlashCommandOptionType.string @@ -212,11 +223,18 @@ def __init__(self, input_type: InputType = str, /, description: Optional[str] = self._raw_type = input_type.__args__ # type: ignore # Union.__args__ else: self._raw_type = (input_type,) - self.channel_types = [CHANNEL_TYPE_MAP[t] for t in self._raw_type if t is not GuildChannel] - self.required: bool = kwargs.pop("required", True) if "default" not in kwargs else False + self.channel_types = [ + CHANNEL_TYPE_MAP[t] + for t in self._raw_type + if t is not GuildChannel + ] + self.required: bool = ( + kwargs.pop("required", True) if "default" not in kwargs else False + ) self.default = kwargs.pop("default", None) - self.choices: List[OptionChoice] = enum_choices or [ - o if isinstance(o, OptionChoice) else OptionChoice(o) for o in kwargs.pop("choices", list()) + self.choices: list[OptionChoice] = enum_choices or [ + o if isinstance(o, OptionChoice) else OptionChoice(o) + for o in kwargs.pop("choices", list()) ] if self.input_type == SlashCommandOptionType.integer: @@ -236,33 +254,52 @@ def __init__(self, input_type: InputType = str, /, description: Optional[str] = minmax_length_types = (type(None),) minmax_length_typehint = type(None) - self.min_value: Optional[Union[int, float]] = kwargs.pop("min_value", None) - self.max_value: Optional[Union[int, float]] = kwargs.pop("max_value", None) - self.min_length: Optional[int] = kwargs.pop("min_length", None) - self.max_length: Optional[int] = kwargs.pop("max_length", None) + self.min_value: int | float | None = kwargs.pop("min_value", None) + self.max_value: int | float | None = kwargs.pop("max_value", None) + self.min_length: int | None = kwargs.pop("min_length", None) + self.max_length: int | None = kwargs.pop("max_length", None) - if (self.input_type != SlashCommandOptionType.integer and self.input_type != SlashCommandOptionType.number - and (self.min_value or self.max_value)): - raise AttributeError("Option does not take min_value or max_value if not of type " - "SlashCommandOptionType.integer or SlashCommandOptionType.number") - if self.input_type != SlashCommandOptionType.string and (self.min_length or self.max_length): - raise AttributeError('Option does not take min_length or max_length if not of type str') + if ( + self.input_type != SlashCommandOptionType.integer + and self.input_type != SlashCommandOptionType.number + and (self.min_value or self.max_value) + ): + raise AttributeError( + "Option does not take min_value or max_value if not of type " + "SlashCommandOptionType.integer or SlashCommandOptionType.number" + ) + if self.input_type != SlashCommandOptionType.string and ( + self.min_length or self.max_length + ): + raise AttributeError( + "Option does not take min_length or max_length if not of type str" + ) if self.min_value is not None and not isinstance(self.min_value, minmax_types): - raise TypeError(f'Expected {minmax_typehint} for min_value, got "{type(self.min_value).__name__}"') + raise TypeError( + f'Expected {minmax_typehint} for min_value, got "{type(self.min_value).__name__}"' + ) if self.max_value is not None and not isinstance(self.max_value, minmax_types): - raise TypeError(f'Expected {minmax_typehint} for max_value, got "{type(self.max_value).__name__}"') + raise TypeError( + f'Expected {minmax_typehint} for max_value, got "{type(self.max_value).__name__}"' + ) if self.min_length is not None: if not isinstance(self.min_length, minmax_length_types): - raise TypeError(f'Expected {minmax_length_typehint} for min_length,' - f' got "{type(self.min_length).__name__}"') + raise TypeError( + f"Expected {minmax_length_typehint} for min_length," + f' got "{type(self.min_length).__name__}"' + ) if self.min_length < 0 or self.min_length > 6000: - raise AttributeError("min_length must be between 0 and 6000 (inclusive)") + raise AttributeError( + "min_length must be between 0 and 6000 (inclusive)" + ) if self.max_length is not None: if not isinstance(self.max_length, minmax_length_types): - raise TypeError(f'Expected {minmax_length_typehint} for max_length,' - f' got "{type(self.max_length).__name__}"') + raise TypeError( + f"Expected {minmax_length_typehint} for max_length," + f' got "{type(self.max_length).__name__}"' + ) if self.max_length < 1 or self.max_length > 6000: raise AttributeError("max_length must between 1 and 6000 (inclusive)") @@ -271,7 +308,7 @@ def __init__(self, input_type: InputType = str, /, description: Optional[str] = self.name_localizations = kwargs.pop("name_localizations", None) self.description_localizations = kwargs.pop("description_localizations", None) - def to_dict(self) -> Dict: + def to_dict(self) -> dict: as_dict = { "name": self.name, "description": self.description, @@ -321,14 +358,14 @@ class OptionChoice: def __init__( self, name: str, - value: Optional[Union[str, int, float]] = None, - name_localizations: Optional[Dict[str, str]] = None, + value: str | int | float | None = None, + name_localizations: dict[str, str] | None = None, ): self.name = str(name) self.value = value if value is not None else name self.name_localizations = name_localizations - def to_dict(self) -> Dict[str, Union[str, int, float]]: + def to_dict(self) -> dict[str, str | int | float]: as_dict = {"name": self.name, "value": self.value} if self.name_localizations is not None: as_dict["name_localizations"] = self.name_localizations @@ -346,7 +383,7 @@ def decorator(func): nonlocal type type = type or func.__annotations__.get(name, str) if parameter := kwargs.get("parameter_name"): - func.__annotations__[parameter] = Option(type, name=name ,**kwargs) + func.__annotations__[parameter] = Option(type, name=name, **kwargs) else: func.__annotations__[name] = Option(type, **kwargs) return func diff --git a/discord/commands/permissions.py b/discord/commands/permissions.py index b83d9a99f2..eb3f9b85f1 100644 --- a/discord/commands/permissions.py +++ b/discord/commands/permissions.py @@ -47,12 +47,12 @@ def default_permissions(**perms: bool) -> Callable: should use an internal check such as :func:`~.ext.commands.has_permissions`. Parameters - ------------ + ---------- **perms: Dict[:class:`str`, :class:`bool`] An argument list of permissions to check for. Example - --------- + ------- .. code-block:: python3 @@ -62,7 +62,6 @@ def default_permissions(**perms: bool) -> Callable: @default_permissions(manage_messages=True) async def test(ctx): await ctx.respond('You can manage messages.') - """ invalid = set(perms) - set(Permissions.VALID_FLAGS) @@ -72,7 +71,9 @@ async def test(ctx): def inner(command: Callable): if isinstance(command, ApplicationCommand): if command.parent is not None: - raise RuntimeError("Permission restrictions can only be set on top-level commands") + raise RuntimeError( + "Permission restrictions can only be set on top-level commands" + ) command.default_member_permissions = Permissions(**perms) else: command.__default_member_permissions__ = Permissions(**perms) @@ -86,7 +87,7 @@ def guild_only() -> Callable: The command won't be able to be used in private message channels. Example - --------- + ------- .. code-block:: python3 @@ -96,7 +97,6 @@ def guild_only() -> Callable: @guild_only() async def test(ctx): await ctx.respond("You're in a guild.") - """ def inner(command: Callable): diff --git a/discord/components.py b/discord/components.py index 52183292e4..5934b5167e 100644 --- a/discord/components.py +++ b/discord/components.py @@ -25,18 +25,7 @@ from __future__ import annotations -from typing import ( - TYPE_CHECKING, - Any, - ClassVar, - Dict, - List, - Optional, - Tuple, - Type, - TypeVar, - Union, -) +from typing import TYPE_CHECKING, Any, ClassVar, TypeVar from .enums import ButtonStyle, ComponentType, InputTextStyle, try_enum from .partial_emoji import PartialEmoji, _EmojiTag @@ -78,14 +67,14 @@ class Component: .. versionadded:: 2.0 Attributes - ------------ + ---------- type: :class:`ComponentType` The type of component. """ - __slots__: Tuple[str, ...] = ("type",) + __slots__: tuple[str, ...] = ("type",) - __repr_info__: ClassVar[Tuple[str, ...]] + __repr_info__: ClassVar[tuple[str, ...]] type: ComponentType def __repr__(self) -> str: @@ -93,7 +82,7 @@ def __repr__(self) -> str: return f"<{self.__class__.__name__} {attrs}>" @classmethod - def _raw_construct(cls: Type[C], **kwargs) -> C: + def _raw_construct(cls: type[C], **kwargs) -> C: self: C = cls.__new__(cls) for slot in get_slots(cls): try: @@ -104,7 +93,7 @@ def _raw_construct(cls: Type[C], **kwargs) -> C: setattr(self, slot, value) return self - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: raise NotImplementedError @@ -118,20 +107,22 @@ class ActionRow(Component): .. versionadded:: 2.0 Attributes - ------------ + ---------- type: :class:`ComponentType` The type of component. children: List[:class:`Component`] The children components that this holds, if any. """ - __slots__: Tuple[str, ...] = ("children",) + __slots__: tuple[str, ...] = ("children",) - __repr_info__: ClassVar[Tuple[str, ...]] = __slots__ + __repr_info__: ClassVar[tuple[str, ...]] = __slots__ def __init__(self, data: ComponentPayload): self.type: ComponentType = try_enum(ComponentType, data["type"]) - self.children: List[Component] = [_component_factory(d) for d in data.get("components", [])] + self.children: list[Component] = [ + _component_factory(d) for d in data.get("components", []) + ] def to_dict(self) -> ActionRowPayload: return { @@ -143,6 +134,7 @@ def to_dict(self) -> ActionRowPayload: class InputText(Component): """Represents an Input Text field from the Discord Bot UI Kit. This inherits from :class:`Component`. + Attributes ---------- style: :class:`.InputTextStyle` @@ -164,7 +156,7 @@ class InputText(Component): The value that has been entered in the input text field. """ - __slots__: Tuple[str, ...] = ( + __slots__: tuple[str, ...] = ( "type", "style", "custom_id", @@ -176,18 +168,18 @@ class InputText(Component): "value", ) - __repr_info__: ClassVar[Tuple[str, ...]] = __slots__ + __repr_info__: ClassVar[tuple[str, ...]] = __slots__ def __init__(self, data: InputTextComponentPayload): self.type = ComponentType.input_text self.style: InputTextStyle = try_enum(InputTextStyle, data["style"]) self.custom_id = data["custom_id"] self.label: str = data.get("label", None) - self.placeholder: Optional[str] = data.get("placeholder", None) - self.min_length: Optional[int] = data.get("min_length", None) - self.max_length: Optional[int] = data.get("max_length", None) + self.placeholder: str | None = data.get("placeholder", None) + self.min_length: int | None = data.get("min_length", None) + self.max_length: int | None = data.get("max_length", None) self.required: bool = data.get("required", True) - self.value: Optional[str] = data.get("value", None) + self.value: str | None = data.get("value", None) def to_dict(self) -> InputTextComponentPayload: payload = { @@ -229,7 +221,7 @@ class Button(Component): .. versionadded:: 2.0 Attributes - ----------- + ---------- style: :class:`.ButtonStyle` The style of the button. custom_id: Optional[:class:`str`] @@ -245,7 +237,7 @@ class Button(Component): The emoji of the button, if available. """ - __slots__: Tuple[str, ...] = ( + __slots__: tuple[str, ...] = ( "style", "custom_id", "url", @@ -254,16 +246,16 @@ class Button(Component): "emoji", ) - __repr_info__: ClassVar[Tuple[str, ...]] = __slots__ + __repr_info__: ClassVar[tuple[str, ...]] = __slots__ def __init__(self, data: ButtonComponentPayload): self.type: ComponentType = try_enum(ComponentType, data["type"]) self.style: ButtonStyle = try_enum(ButtonStyle, data["style"]) - self.custom_id: Optional[str] = data.get("custom_id") - self.url: Optional[str] = data.get("url") + self.custom_id: str | None = data.get("custom_id") + self.url: str | None = data.get("url") self.disabled: bool = data.get("disabled", False) - self.label: Optional[str] = data.get("label") - self.emoji: Optional[PartialEmoji] + self.label: str | None = data.get("label") + self.emoji: PartialEmoji | None try: self.emoji = PartialEmoji.from_dict(data["emoji"]) except KeyError: @@ -302,7 +294,7 @@ class SelectMenu(Component): .. versionadded:: 2.0 Attributes - ------------ + ---------- custom_id: Optional[:class:`str`] The ID of the select menu that gets received during an interaction. placeholder: Optional[:class:`str`] @@ -319,7 +311,7 @@ class SelectMenu(Component): Whether the select is disabled or not. """ - __slots__: Tuple[str, ...] = ( + __slots__: tuple[str, ...] = ( "custom_id", "placeholder", "min_values", @@ -328,15 +320,17 @@ class SelectMenu(Component): "disabled", ) - __repr_info__: ClassVar[Tuple[str, ...]] = __slots__ + __repr_info__: ClassVar[tuple[str, ...]] = __slots__ def __init__(self, data: SelectMenuPayload): self.type = ComponentType.select self.custom_id: str = data["custom_id"] - self.placeholder: Optional[str] = data.get("placeholder") + self.placeholder: str | None = data.get("placeholder") self.min_values: int = data.get("min_values", 1) self.max_values: int = data.get("max_values", 1) - self.options: List[SelectOption] = [SelectOption.from_dict(option) for option in data.get("options", [])] + self.options: list[SelectOption] = [ + SelectOption.from_dict(option) for option in data.get("options", []) + ] self.disabled: bool = data.get("disabled", False) def to_dict(self) -> SelectMenuPayload: @@ -363,7 +357,7 @@ class SelectOption: .. versionadded:: 2.0 Attributes - ----------- + ---------- label: :class:`str` The label of the option. This is displayed to users. Can only be up to 100 characters. @@ -378,7 +372,7 @@ class SelectOption: Whether this option is selected by default. """ - __slots__: Tuple[str, ...] = ( + __slots__: tuple[str, ...] = ( "label", "value", "description", @@ -391,8 +385,8 @@ def __init__( *, label: str, value: str = MISSING, - description: Optional[str] = None, - emoji: Optional[Union[str, Emoji, PartialEmoji]] = None, + description: str | None = None, + emoji: str | Emoji | PartialEmoji | None = None, default: bool = False, ) -> None: if len(label) > 100: @@ -423,7 +417,7 @@ def __str__(self) -> str: return base @property - def emoji(self) -> Optional[Union[str, Emoji, PartialEmoji]]: + def emoji(self) -> str | Emoji | PartialEmoji | None: """Optional[Union[:class:`str`, :class:`Emoji`, :class:`PartialEmoji`]]: The emoji of the option, if available.""" return self._emoji @@ -435,7 +429,9 @@ def emoji(self, value) -> None: elif isinstance(value, _EmojiTag): value = value._to_partial() else: - raise TypeError(f"expected emoji to be str, Emoji, or PartialEmoji not {value.__class__}") + raise TypeError( + f"expected emoji to be str, Emoji, or PartialEmoji not {value.__class__}" + ) self._emoji = value diff --git a/discord/context_managers.py b/discord/context_managers.py index 5ffe7e9d47..c9d930b5e4 100644 --- a/discord/context_managers.py +++ b/discord/context_managers.py @@ -26,7 +26,7 @@ from __future__ import annotations import asyncio -from typing import TYPE_CHECKING, Optional, Type, TypeVar +from typing import TYPE_CHECKING, TypeVar if TYPE_CHECKING: from types import TracebackType @@ -70,9 +70,9 @@ def __enter__(self: TypingT) -> TypingT: def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_value: Optional[BaseException], - traceback: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, ) -> None: self.task.cancel() @@ -83,8 +83,8 @@ async def __aenter__(self: TypingT) -> TypingT: async def __aexit__( self, - exc_type: Optional[Type[BaseException]], - exc_value: Optional[BaseException], - traceback: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, ) -> None: self.task.cancel() diff --git a/discord/embeds.py b/discord/embeds.py index d3b8f976d2..8b7c9f3273 100644 --- a/discord/embeds.py +++ b/discord/embeds.py @@ -26,19 +26,7 @@ from __future__ import annotations import datetime -from typing import ( - TYPE_CHECKING, - Any, - Dict, - Final, - List, - Mapping, - Optional, - Protocol, - Type, - TypeVar, - Union, -) +from typing import TYPE_CHECKING, Any, Final, Mapping, Protocol, TypeVar, Union from . import utils from .colour import Colour @@ -64,14 +52,16 @@ def __len__(self) -> int: class EmbedProxy: - def __init__(self, layer: Dict[str, Any]): + def __init__(self, layer: dict[str, Any]): self.__dict__.update(layer) def __len__(self) -> int: return len(self.__dict__) def __repr__(self) -> str: - inner = ", ".join((f"{k}={v!r}" for k, v in self.__dict__.items() if not k.startswith("_"))) + inner = ", ".join( + (f"{k}={v!r}" for k, v in self.__dict__.items() if not k.startswith("_")) + ) return f"EmbedProxy({inner})" def __getattr__(self, attr: str) -> _EmptyEmbed: @@ -128,13 +118,13 @@ class EmbedField: Whether the field should be displayed inline. """ - def __init__(self, name: str, value: str, inline: Optional[bool] = False): + def __init__(self, name: str, value: str, inline: bool | None = False): self.name = name self.value = value self.inline = inline @classmethod - def from_dict(cls: Type[E], data: Mapping[str, Any]) -> E: + def from_dict(cls: type[E], data: Mapping[str, Any]) -> E: """Converts a :class:`dict` to a :class:`EmbedField` provided it is in the format that Discord expects it to be in. @@ -145,7 +135,7 @@ def from_dict(cls: Type[E], data: Mapping[str, Any]) -> E: __ DiscordDocsEF_ Parameters - ----------- + ---------- data: :class:`dict` The dictionary to convert into an EmbedField object. """ @@ -157,11 +147,11 @@ def from_dict(cls: Type[E], data: Mapping[str, Any]) -> E: return self - def to_dict(self) -> Dict[str, Union[str, bool]]: + def to_dict(self) -> dict[str, str | bool]: """Converts this EmbedField object into a dict. Returns - -------- + ------- Dict[:class:`str`, Union[:class:`str`, :class:`bool`]] A dictionary of :class:`str` embed field keys bound to the respective value. """ @@ -198,7 +188,7 @@ class Embed: cast to :class:`str` for you. Attributes - ----------- + ---------- title: :class:`str` The title of the embed. This can be set during initialisation. @@ -248,14 +238,14 @@ class Embed: def __init__( self, *, - colour: Union[int, Colour, _EmptyEmbed] = EmptyEmbed, - color: Union[int, Colour, _EmptyEmbed] = EmptyEmbed, + colour: int | Colour | _EmptyEmbed = EmptyEmbed, + color: int | Colour | _EmptyEmbed = EmptyEmbed, title: MaybeEmpty[Any] = EmptyEmbed, type: EmbedType = "rich", url: MaybeEmpty[Any] = EmptyEmbed, description: MaybeEmpty[Any] = EmptyEmbed, timestamp: datetime.datetime = None, - fields: Optional[List[EmbedField]] = None, + fields: list[EmbedField] | None = None, ): self.colour = colour if colour is not EmptyEmbed else color @@ -275,10 +265,10 @@ def __init__( if timestamp: self.timestamp = timestamp - self._fields: List[EmbedField] = fields or [] + self._fields: list[EmbedField] = fields or [] @classmethod - def from_dict(cls: Type[E], data: Mapping[str, Any]) -> E: + def from_dict(cls: type[E], data: Mapping[str, Any]) -> E: """Converts a :class:`dict` to a :class:`Embed` provided it is in the format that Discord expects it to be in. @@ -289,12 +279,12 @@ def from_dict(cls: Type[E], data: Mapping[str, Any]) -> E: __ DiscordDocs_ Parameters - ----------- + ---------- data: :class:`dict` The dictionary to convert into an embed. Returns - -------- + ------- :class:`Embed` The converted embed object. """ @@ -355,7 +345,7 @@ def copy(self: E) -> E: """Creates a shallow copy of the :class:`Embed` object. Returns - -------- + ------- :class:`Embed` The copied embed object. """ @@ -405,7 +395,7 @@ def colour(self) -> MaybeEmpty[Colour]: return getattr(self, "_colour", EmptyEmbed) @colour.setter - def colour(self, value: Union[int, Colour, _EmptyEmbed]): # type: ignore + def colour(self, value: int | Colour | _EmptyEmbed): # type: ignore if isinstance(value, (Colour, _EmptyEmbed)): self._colour = value elif isinstance(value, int): @@ -430,7 +420,9 @@ def timestamp(self, value: MaybeEmpty[datetime.datetime]): elif isinstance(value, _EmptyEmbed): self._timestamp = value else: - raise TypeError(f"Expected datetime.datetime or Embed.Empty received {value.__class__.__name__} instead") + raise TypeError( + f"Expected datetime.datetime or Embed.Empty received {value.__class__.__name__} instead" + ) @property def footer(self) -> _EmbedFooterProxy: @@ -454,7 +446,7 @@ def set_footer( chaining. Parameters - ----------- + ---------- text: :class:`str` The footer text. Must be 2048 characters or fewer. @@ -511,7 +503,7 @@ def set_image(self: E, *, url: MaybeEmpty[Any]) -> E: Passing :attr:`Empty` removes the image. Parameters - ----------- + ---------- url: :class:`str` The source URL for the image. Only HTTP(S) is supported. """ @@ -568,7 +560,7 @@ def set_thumbnail(self: E, *, url: MaybeEmpty[Any]) -> E: Passing :attr:`Empty` removes the thumbnail. Parameters - ----------- + ---------- url: :class:`str` The source URL for the thumbnail. Only HTTP(S) is supported. """ @@ -647,7 +639,7 @@ def set_author( chaining. Parameters - ----------- + ---------- name: :class:`str` The name of the author. Must be 256 characters or fewer. @@ -685,7 +677,7 @@ def remove_author(self: E) -> E: return self @property - def fields(self) -> List[EmbedField]: + def fields(self) -> list[EmbedField]: """Returns a :class:`list` of :class:`EmbedField` objects denoting the field contents. See :meth:`add_field` for possible values you can access. @@ -695,7 +687,7 @@ def fields(self) -> List[EmbedField]: return self._fields @fields.setter - def fields(self, value: List[EmbedField]) -> None: + def fields(self, value: list[EmbedField]) -> None: """Sets the fields for the embed. This overwrites any existing fields. Parameters @@ -730,7 +722,7 @@ def add_field(self: E, *, name: str, value: str, inline: bool = True) -> E: chaining. There must be 25 fields or fewer. Parameters - ----------- + ---------- name: :class:`str` The name of the field. Must be 256 characters or fewer. @@ -744,7 +736,9 @@ def add_field(self: E, *, name: str, value: str, inline: bool = True) -> E: return self - def insert_field_at(self: E, index: int, *, name: Any, value: Any, inline: bool = True) -> E: + def insert_field_at( + self: E, index: int, *, name: Any, value: Any, inline: bool = True + ) -> E: """Inserts a field before a specified index to the embed. This function returns the class instance to allow for fluent-style @@ -753,7 +747,7 @@ def insert_field_at(self: E, index: int, *, name: Any, value: Any, inline: bool .. versionadded:: 1.2 Parameters - ----------- + ---------- index: :class:`int` The index of where to insert the field. name: :class:`str` @@ -788,7 +782,7 @@ def remove_field(self, index: int) -> None: shift to fill the gap just like a regular list. Parameters - ----------- + ---------- index: :class:`int` The index of the field to remove. """ @@ -797,7 +791,9 @@ def remove_field(self, index: int) -> None: except IndexError: pass - def set_field_at(self: E, index: int, *, name: Any, value: Any, inline: bool = True) -> E: + def set_field_at( + self: E, index: int, *, name: Any, value: Any, inline: bool = True + ) -> E: """Modifies a field to the embed object. The index must point to a valid pre-existing field. There must be 25 fields or fewer. @@ -806,7 +802,7 @@ def set_field_at(self: E, index: int, *, name: Any, value: Any, inline: bool = T chaining. Parameters - ----------- + ---------- index: :class:`int` The index of the field to modify. name: :class:`str` @@ -819,7 +815,7 @@ def set_field_at(self: E, index: int, *, name: Any, value: Any, inline: bool = T Whether the field should be displayed inline. Raises - ------- + ------ IndexError An invalid index was provided. """ @@ -838,7 +834,7 @@ def to_dict(self) -> EmbedData: """Converts this embed object into a dict. Returns - -------- + ------- Dict[:class:`str`, Union[:class:`str`, :class:`int`, :class:`bool`]] A dictionary of :class:`str` embed keys bound to the respective value. """ @@ -870,9 +866,13 @@ def to_dict(self) -> EmbedData: else: if timestamp: if timestamp.tzinfo: - result["timestamp"] = timestamp.astimezone(tz=datetime.timezone.utc).isoformat() + result["timestamp"] = timestamp.astimezone( + tz=datetime.timezone.utc + ).isoformat() else: - result["timestamp"] = timestamp.replace(tzinfo=datetime.timezone.utc).isoformat() + result["timestamp"] = timestamp.replace( + tzinfo=datetime.timezone.utc + ).isoformat() # add in the non-raw attribute ones if self.type: diff --git a/discord/emoji.py b/discord/emoji.py index a73427af8f..08dad90e85 100644 --- a/discord/emoji.py +++ b/discord/emoji.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Iterator, List, Optional, Tuple +from typing import TYPE_CHECKING, Any, Iterator from .asset import Asset, AssetMixin from .partial_emoji import PartialEmoji, _EmojiTag @@ -74,7 +74,7 @@ class Emoji(_EmojiTag, AssetMixin): Returns the emoji rendered for discord. Attributes - ----------- + ---------- name: :class:`str` The name of the emoji. id: :class:`int` @@ -94,7 +94,7 @@ class Emoji(_EmojiTag, AssetMixin): having the :attr:`~Permissions.manage_emojis` permission. """ - __slots__: Tuple[str, ...] = ( + __slots__: tuple[str, ...] = ( "require_colons", "animated", "managed", @@ -121,12 +121,12 @@ def _from_data(self, emoji: EmojiPayload): self.available: bool = emoji.get("available", True) self._roles: SnowflakeList = SnowflakeList(map(int, emoji.get("roles", []))) user = emoji.get("user") - self.user: Optional[User] = User(state=self._state, data=user) if user else None + self.user: User | None = User(state=self._state, data=user) if user else None def _to_partial(self) -> PartialEmoji: return PartialEmoji(name=self.name, animated=self.animated, id=self.id) - def __iter__(self) -> Iterator[Tuple[str, Any]]: + def __iter__(self) -> Iterator[tuple[str, Any]]: for attr in self.__slots__: if attr[0] != "_": value = getattr(self, attr, None) @@ -162,7 +162,7 @@ def url(self) -> str: return f"{Asset.BASE}/emojis/{self.id}.{fmt}" @property - def roles(self) -> List[Role]: + def roles(self) -> list[Role]: """List[:class:`Role`]: A :class:`list` of roles that is allowed to use this emoji. If roles is empty, the emoji is unrestricted. @@ -190,7 +190,7 @@ def is_usable(self) -> bool: emoji_roles, my_roles = self._roles, self.guild.me._roles return any(my_roles.has(role_id) for role_id in emoji_roles) - async def delete(self, *, reason: Optional[str] = None) -> None: + async def delete(self, *, reason: str | None = None) -> None: """|coro| Deletes the custom emoji. @@ -199,26 +199,28 @@ async def delete(self, *, reason: Optional[str] = None) -> None: do this. Parameters - ----------- + ---------- reason: Optional[:class:`str`] The reason for deleting this emoji. Shows up on the audit log. Raises - ------- + ------ Forbidden You are not allowed to delete emojis. HTTPException An error occurred deleting the emoji. """ - await self._state.http.delete_custom_emoji(self.guild.id, self.id, reason=reason) + await self._state.http.delete_custom_emoji( + self.guild.id, self.id, reason=reason + ) async def edit( self, *, name: str = MISSING, - roles: List[Snowflake] = MISSING, - reason: Optional[str] = None, + roles: list[Snowflake] = MISSING, + reason: str | None = None, ) -> Emoji: r"""|coro| @@ -258,5 +260,7 @@ async def edit( if roles is not MISSING: payload["roles"] = [role.id for role in roles] - data = await self._state.http.edit_custom_emoji(self.guild.id, self.id, payload=payload, reason=reason) + data = await self._state.http.edit_custom_emoji( + self.guild.id, self.id, payload=payload, reason=reason + ) return Emoji(guild=self.guild, data=data, state=self._state) diff --git a/discord/enums.py b/discord/enums.py index eb0ac79cc5..dc952d0de4 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -84,15 +84,29 @@ def _create_value_cls(name, comparable): cls.__repr__ = lambda self: f"<{name}.{self.name}: {self.value!r}>" cls.__str__ = lambda self: f"{name}.{self.name}" if comparable: - cls.__le__ = lambda self, other: isinstance(other, self.__class__) and self.value <= other.value - cls.__ge__ = lambda self, other: isinstance(other, self.__class__) and self.value >= other.value - cls.__lt__ = lambda self, other: isinstance(other, self.__class__) and self.value < other.value - cls.__gt__ = lambda self, other: isinstance(other, self.__class__) and self.value > other.value + cls.__le__ = ( + lambda self, other: isinstance(other, self.__class__) + and self.value <= other.value + ) + cls.__ge__ = ( + lambda self, other: isinstance(other, self.__class__) + and self.value >= other.value + ) + cls.__lt__ = ( + lambda self, other: isinstance(other, self.__class__) + and self.value < other.value + ) + cls.__gt__ = ( + lambda self, other: isinstance(other, self.__class__) + and self.value > other.value + ) return cls def _is_descriptor(obj): - return hasattr(obj, "__get__") or hasattr(obj, "__set__") or hasattr(obj, "__delete__") + return ( + hasattr(obj, "__get__") or hasattr(obj, "__set__") or hasattr(obj, "__delete__") + ) class EnumMeta(type): @@ -144,7 +158,9 @@ def __iter__(cls): return (cls._enum_member_map_[name] for name in cls._enum_member_names_) def __reversed__(cls): - return (cls._enum_member_map_[name] for name in reversed(cls._enum_member_names_)) + return ( + cls._enum_member_map_[name] for name in reversed(cls._enum_member_names_) + ) def __len__(cls): return len(cls._enum_member_names_) @@ -388,7 +404,7 @@ class AuditLogAction(Enum): application_command_permission_update = 121 auto_moderation_rule_create = 140 auto_moderation_rule_update = 141 - auto_moderation_rule_delete = 142 + auto_moderation_rule_delete = 142 auto_moderation_block_message = 143 @property @@ -679,7 +695,9 @@ def from_datatype(cls, datatype): else: raise TypeError("Invalid usage of typing.Union") - py_3_10_union_type = hasattr(types, "UnionType") and isinstance(datatype, types.UnionType) + py_3_10_union_type = hasattr(types, "UnionType") and isinstance( + datatype, types.UnionType + ) if py_3_10_union_type or getattr(datatype, "__origin__", None) is Union: # Python 3.10+ "|" operator or typing.Union has been used. The __args__ attribute is a tuple of the types. @@ -716,7 +734,9 @@ def from_datatype(cls, datatype): from .commands.context import ApplicationContext - if not issubclass(datatype, ApplicationContext): # TODO: prevent ctx being passed here in cog commands + if not issubclass( + datatype, ApplicationContext + ): # TODO: prevent ctx being passed here in cog commands raise TypeError( f"Invalid class {datatype} used as an input type for an Option" ) # TODO: Improve the error message @@ -779,29 +799,29 @@ class ScheduledEventLocationType(Enum): voice = 2 external = 3 - + class AutoModTriggerType(Enum): keyword = 1 harmful_link = 2 spam = 3 keyword_preset = 4 - + class AutoModEventType(Enum): message_send = 1 - - + + class AutoModActionType(Enum): block_message = 1 send_alert_message = 2 timeout = 3 - - + + class AutoModKeywordPresetType(Enum): profanity = 1 sexual_content = 2 slurs = 3 - + T = TypeVar("T") diff --git a/discord/errors.py b/discord/errors.py index c3263b5567..0d13944d30 100644 --- a/discord/errors.py +++ b/discord/errors.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any, Union if TYPE_CHECKING: from aiohttp import ClientResponse, ClientWebSocketResponse @@ -73,8 +73,6 @@ class DiscordException(Exception): Ideally speaking, this could be caught to handle any exceptions raised from this library. """ - pass - class ClientException(DiscordException): """Exception that's raised when an operation in the :class:`Client` fails. @@ -82,14 +80,10 @@ class ClientException(DiscordException): These are usually for exceptions that happened due to user input. """ - pass - class NoMoreItems(DiscordException): """Exception that is raised when an async iteration operation has no more items.""" - pass - class GatewayNotFound(DiscordException): """An exception that is raised when the gateway for Discord could not be found""" @@ -102,17 +96,15 @@ def __init__(self): class ValidationError(DiscordException): """An Exception that is raised when there is a Validation Error.""" - pass - -def _flatten_error_dict(d: Dict[str, Any], key: str = "") -> Dict[str, str]: - items: List[Tuple[str, str]] = [] +def _flatten_error_dict(d: dict[str, Any], key: str = "") -> dict[str, str]: + items: list[tuple[str, str]] = [] for k, v in d.items(): new_key = f"{key}.{k}" if key else k if isinstance(v, dict): try: - _errors: List[Dict[str, Any]] = v["_errors"] + _errors: list[dict[str, Any]] = v["_errors"] except KeyError: items.extend(_flatten_error_dict(v, new_key).items()) else: @@ -127,7 +119,7 @@ class HTTPException(DiscordException): """Exception that's raised when an HTTP request operation fails. Attributes - ------------ + ---------- response: :class:`aiohttp.ClientResponse` The response of the failed HTTP request. This is an instance of :class:`aiohttp.ClientResponse`. In some cases @@ -141,7 +133,7 @@ class HTTPException(DiscordException): The Discord specific error code for the failure. """ - def __init__(self, response: _ResponseType, message: Optional[Union[str, Dict[str, Any]]]): + def __init__(self, response: _ResponseType, message: str | dict[str, Any] | None): self.response: _ResponseType = response self.status: int = response.status # type: ignore self.code: int @@ -173,8 +165,6 @@ class Forbidden(HTTPException): Subclass of :exc:`HTTPException` """ - pass - class NotFound(HTTPException): """Exception that's raised for when status code 404 occurs. @@ -182,8 +172,6 @@ class NotFound(HTTPException): Subclass of :exc:`HTTPException` """ - pass - class DiscordServerError(HTTPException): """Exception that's raised for when a 500 range status code occurs. @@ -193,16 +181,12 @@ class DiscordServerError(HTTPException): .. versionadded:: 1.5 """ - pass - class InvalidData(ClientException): """Exception that's raised when the library encounters unknown or invalid data from Discord. """ - pass - class InvalidArgument(ClientException): """Exception that's raised when an argument to a function @@ -213,8 +197,6 @@ class InvalidArgument(ClientException): :exc:`DiscordException`. """ - pass - class LoginFailure(ClientException): """Exception that's raised when the :meth:`Client.login` function @@ -222,15 +204,13 @@ class LoginFailure(ClientException): failure. """ - pass - class ConnectionClosed(ClientException): """Exception that's raised when the gateway connection is closed for reasons that could not be handled internally. Attributes - ----------- + ---------- code: :class:`int` The close code of the websocket. reason: :class:`str` @@ -243,15 +223,15 @@ def __init__( self, socket: ClientWebSocketResponse, *, - shard_id: Optional[int], - code: Optional[int] = None, + shard_id: int | None, + code: int | None = None, ): # This exception is just the same exception except # reconfigured to subclass ClientException for users self.code: int = code or socket.close_code or -1 # aiohttp doesn't seem to consistently provide close reason self.reason: str = "" - self.shard_id: Optional[int] = shard_id + self.shard_id: int | None = shard_id super().__init__(f"Shard ID {self.shard_id} WebSocket closed with {self.code}") @@ -267,13 +247,13 @@ class PrivilegedIntentsRequired(ClientException): - :attr:`Intents.message_content` Attributes - ----------- + ---------- shard_id: Optional[:class:`int`] The shard ID that got closed if applicable. """ - def __init__(self, shard_id: Optional[int]): - self.shard_id: Optional[int] = shard_id + def __init__(self, shard_id: int | None): + self.shard_id: int | None = shard_id msg = ( "Shard ID %s is requesting privileged intents that have not been explicitly enabled in the " "developer portal. It is recommended to go to https://discord.com/developers/applications/ " @@ -292,7 +272,7 @@ class InteractionResponded(ClientException): .. versionadded:: 2.0 Attributes - ----------- + ---------- interaction: :class:`Interaction` The interaction that's already been responded to. """ @@ -308,16 +288,18 @@ class ExtensionError(DiscordException): This inherits from :exc:`~discord.DiscordException`. Attributes - ------------ + ---------- name: :class:`str` The extension that had an error. """ - def __init__(self, message: Optional[str] = None, *args: Any, name: str) -> None: + def __init__(self, message: str | None = None, *args: Any, name: str) -> None: self.name: str = name message = message or f"Extension {name!r} had an error." # clean-up @everyone and @here mentions - m = message.replace("@everyone", "@\u200beveryone").replace("@here", "@\u200bhere") + m = message.replace("@everyone", "@\u200beveryone").replace( + "@here", "@\u200bhere" + ) super().__init__(m, *args) @@ -357,7 +339,7 @@ class ExtensionFailed(ExtensionError): This inherits from :exc:`ExtensionError` Attributes - ----------- + ---------- name: :class:`str` The extension that had the error. original: :exc:`Exception` @@ -380,7 +362,7 @@ class ExtensionNotFound(ExtensionError): Made the ``original`` attribute always None. Attributes - ----------- + ---------- name: :class:`str` The extension that had the error. """ @@ -399,7 +381,6 @@ class ApplicationCommandError(DiscordException): in a special way as they are caught and passed into a special event from :class:`.Bot`\, :func:`.on_command_error`. """ - pass class CheckFailure(ApplicationCommandError): @@ -408,8 +389,6 @@ class CheckFailure(ApplicationCommandError): This inherits from :exc:`ApplicationCommandError` """ - pass - class ApplicationCommandInvokeError(ApplicationCommandError): """Exception raised when the command being invoked raised an exception. @@ -417,7 +396,7 @@ class ApplicationCommandInvokeError(ApplicationCommandError): This inherits from :exc:`ApplicationCommandError` Attributes - ----------- + ---------- original: :exc:`Exception` The original exception that was raised. You can also get this via the ``__cause__`` attribute. @@ -425,4 +404,6 @@ class ApplicationCommandInvokeError(ApplicationCommandError): def __init__(self, e: Exception) -> None: self.original: Exception = e - super().__init__(f"Application Command raised an exception: {e.__class__.__name__}: {e}") + super().__init__( + f"Application Command raised an exception: {e.__class__.__name__}: {e}" + ) diff --git a/discord/ext/bridge/bot.py b/discord/ext/bridge/bot.py index e8bc595fb0..3db78d5f42 100644 --- a/discord/ext/bridge/bot.py +++ b/discord/ext/bridge/bot.py @@ -62,7 +62,7 @@ def bridge_command(self, **kwargs): the internal command list via :meth:`~.Bot.add_bridge_command`. Returns - -------- + ------- Callable[..., :class:`BridgeCommand`] A decorator that converts the provided method into an :class:`.BridgeCommand`, adds both a slash and traditional (prefix-based) version of the command to the bot, and returns the :class:`.BridgeCommand`. @@ -102,8 +102,6 @@ class Bot(BotBase, ExtBot): .. versionadded:: 2.0 """ - pass - class AutoShardedBot(BotBase, ExtAutoShardedBot): """This is similar to :class:`.Bot` except that it is inherited from @@ -111,5 +109,3 @@ class AutoShardedBot(BotBase, ExtAutoShardedBot): .. versionadded:: 2.0 """ - - pass diff --git a/discord/ext/bridge/context.py b/discord/ext/bridge/context.py index 13afe7b161..80355720a1 100644 --- a/discord/ext/bridge/context.py +++ b/discord/ext/bridge/context.py @@ -25,7 +25,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, overload, Optional, Union +from typing import TYPE_CHECKING, Any, overload from discord.commands import ApplicationContext from discord.interactions import Interaction, InteractionMessage @@ -35,7 +35,7 @@ from ..commands import Context if TYPE_CHECKING: - from .core import BridgeSlashCommand, BridgeExtCommand + from .core import BridgeExtCommand, BridgeSlashCommand __all__ = ("BridgeContext", "BridgeExtContext", "BridgeApplicationContext") @@ -66,9 +66,7 @@ async def example(ctx: BridgeContext): """ @abstractmethod - async def _respond( - self, *args, **kwargs - ) -> Union[Union[Interaction, WebhookMessage], Message]: + async def _respond(self, *args, **kwargs) -> Interaction | WebhookMessage | Message: ... @abstractmethod @@ -76,16 +74,16 @@ async def _defer(self, *args, **kwargs) -> None: ... @abstractmethod - async def _edit(self, *args, **kwargs) -> Union[InteractionMessage, Message]: + async def _edit(self, *args, **kwargs) -> InteractionMessage | Message: ... @overload - async def invoke(self, command: Union[BridgeSlashCommand, BridgeExtCommand], *args, **kwargs) -> None: + async def invoke( + self, command: BridgeSlashCommand | BridgeExtCommand, *args, **kwargs + ) -> None: ... - async def respond( - self, *args, **kwargs - ) -> Union[Union[Interaction, WebhookMessage], Message]: + async def respond(self, *args, **kwargs) -> Interaction | WebhookMessage | Message: """|coro| Responds to the command with the respective response type to the current context. In :class:`BridgeExtContext`, @@ -94,9 +92,7 @@ async def respond( """ return await self._respond(*args, **kwargs) - async def reply( - self, *args, **kwargs - ) -> Union[Union[Interaction, WebhookMessage], Message]: + async def reply(self, *args, **kwargs) -> Interaction | WebhookMessage | Message: """|coro| Alias for :meth:`~.BridgeContext.respond`. @@ -116,7 +112,7 @@ async def defer(self, *args, **kwargs) -> None: """ return await self._defer(*args, **kwargs) - async def edit(self, *args, **kwargs) -> Union[InteractionMessage, Message]: + async def edit(self, *args, **kwargs) -> InteractionMessage | Message: """|coro| Edits the original response message with the respective approach to the current context. In @@ -146,7 +142,7 @@ def __init__(self, *args, **kwargs): # This is needed in order to represent the correct class init signature on the docs super().__init__(*args, **kwargs) - async def _respond(self, *args, **kwargs) -> Union[Interaction, WebhookMessage]: + async def _respond(self, *args, **kwargs) -> Interaction | WebhookMessage: return await self._get_super("respond")(*args, **kwargs) async def _defer(self, *args, **kwargs) -> None: @@ -166,7 +162,7 @@ class BridgeExtContext(BridgeContext, Context): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self._original_response_message: Optional[Message] = None + self._original_response_message: Message | None = None async def _respond(self, *args, **kwargs) -> Message: kwargs.pop("ephemeral", None) @@ -179,19 +175,19 @@ async def _defer(self, *args, **kwargs) -> None: kwargs.pop("ephemeral", None) return await self._get_super("trigger_typing")(*args, **kwargs) - async def _edit(self, *args, **kwargs) -> Optional[Message]: + async def _edit(self, *args, **kwargs) -> Message | None: if self._original_response_message: return await self._original_response_message.edit(*args, **kwargs) async def delete( - self, *, delay: Optional[float] = None, reason: Optional[str] = None + self, *, delay: float | None = None, reason: str | None = None ) -> None: """|coro| Deletes the original response message, if it exists. Parameters - ----------- + ---------- delay: Optional[:class:`float`] If provided, the number of seconds to wait before deleting the message. reason: Optional[:class:`str`] diff --git a/discord/ext/bridge/core.py b/discord/ext/bridge/core.py index 053418ea3d..f305615dcd 100644 --- a/discord/ext/bridge/core.py +++ b/discord/ext/bridge/core.py @@ -25,31 +25,32 @@ from __future__ import annotations import inspect -from typing import TYPE_CHECKING, Any, List, Union, Optional, Callable, Dict +from typing import TYPE_CHECKING, Any, Callable import discord.commands.options from discord import ( ApplicationCommand, + Attachment, + Option, + Permissions, SlashCommand, SlashCommandGroup, - Permissions, SlashCommandOptionType, - Attachment, - Option, ) -from ..commands.converter import _convert_to_bool, run_converters + +from ...utils import filter_params, find, get +from ..commands import BadArgument +from ..commands import Bot as ExtBot from ..commands import ( Command, - Group, + Context, Converter, + Group, GuildChannelConverter, RoleConverter, UserConverter, - BadArgument, - Context, - Bot as ExtBot, ) -from ...utils import get, filter_params, find +from ..commands.converter import _convert_to_bool, run_converters if TYPE_CHECKING: from .context import BridgeApplicationContext, BridgeExtContext @@ -95,6 +96,7 @@ async def transform(self, ctx: Context, param: inspect.Parameter) -> Any: class BridgeSlashGroup(SlashCommandGroup): """A subclass of :class:`.SlashCommandGroup` that is used for bridge commands.""" + __slots__ = ("module",) def __init__(self, callback, *args, **kwargs): @@ -119,19 +121,11 @@ async def _invoke(self, ctx: BridgeApplicationContext) -> None: class BridgeExtGroup(BridgeExtCommand, Group): """A subclass of :class:`.ext.commands.Group` that is used for bridge commands.""" - pass class BridgeCommand: """Compatibility class between prefixed-based commands and slash commands. - Attributes - ---------- - slash_variant: :class:`.BridgeSlashCommand` - The slash command version of this bridge command. - ext_variant: :class:`.BridgeExtCommand` - The prefix-based version of this bridge command. - Parameters ---------- callback: Callable[[:class:`.BridgeContext`, ...], Awaitable[Any]] @@ -141,11 +135,23 @@ class BridgeCommand: Parent of the BridgeCommand. kwargs: Optional[Dict[:class:`str`, Any]] Keyword arguments that are directly passed to the respective command constructors. (:class:`.SlashCommand` and :class:`.ext.commands.Command`) + + Attributes + ---------- + slash_variant: :class:`.BridgeSlashCommand` + The slash command version of this bridge command. + ext_variant: :class:`.BridgeExtCommand` + The prefix-based version of this bridge command. """ + def __init__(self, callback, **kwargs): self.parent = kwargs.pop("parent", None) - self.slash_variant: BridgeSlashCommand = kwargs.pop("slash_variant", None) or BridgeSlashCommand(callback, **kwargs) - self.ext_variant: BridgeExtCommand = kwargs.pop("ext_variant", None) or BridgeExtCommand(callback, **kwargs) + self.slash_variant: BridgeSlashCommand = kwargs.pop( + "slash_variant", None + ) or BridgeSlashCommand(callback, **kwargs) + self.ext_variant: BridgeExtCommand = kwargs.pop( + "ext_variant", None + ) or BridgeExtCommand(callback, **kwargs) @property def name_localizations(self): @@ -158,7 +164,6 @@ def name_localizations(self): bridge_command.name_localizations["en-UK"] = ... # or any other locale # or bridge_command.name_localizations = {"en-UK": ..., "fr-FR": ...} - """ return self.slash_variant.name_localizations @@ -177,7 +182,6 @@ def description_localizations(self): bridge_command.description_localizations["en-UK"] = ... # or any other locale # or bridge_command.description_localizations = {"en-UK": ..., "fr-FR": ...} - """ return self.slash_variant.description_localizations @@ -196,7 +200,9 @@ def add_to(self, bot: ExtBot) -> None: bot.add_application_command(self.slash_variant) bot.add_command(self.ext_variant) - async def invoke(self, ctx: Union[BridgeExtContext, BridgeApplicationContext], /, *args, **kwargs): + async def invoke( + self, ctx: BridgeExtContext | BridgeApplicationContext, /, *args, **kwargs + ): if ctx.is_app: return await self.slash_variant.invoke(ctx) return await self.ext_variant.invoke(ctx) @@ -213,12 +219,12 @@ def error(self, coro): a :class:`~discord.DiscordException`. Parameters - ----------- + ---------- coro: :ref:`coroutine ` The coroutine to register as the local error handler. Raises - ------- + ------ TypeError The coroutine passed is not actually a coroutine. """ @@ -237,12 +243,12 @@ def before_invoke(self, coro): This pre-invoke hook takes a sole parameter, a :class:`.BridgeContext`. Parameters - ----------- + ---------- coro: :ref:`coroutine ` The coroutine to register as the pre-invoke hook. Raises - ------- + ------ TypeError The coroutine passed is not actually a coroutine. """ @@ -261,12 +267,12 @@ def after_invoke(self, coro): This post-invoke hook takes a sole parameter, a :class:`.BridgeContext`. Parameters - ----------- + ---------- coro: :ref:`coroutine ` The coroutine to register as the post-invoke hook. Raises - ------- + ------ TypeError The coroutine passed is not actually a coroutine. """ @@ -298,13 +304,16 @@ class BridgeCommandGroup(BridgeCommand): mapped: Optional[:class:`.SlashCommand`] If :func:`map_to` is used, the mapped slash command. """ + def __init__(self, callback, *args, **kwargs): self.ext_variant: BridgeExtGroup = BridgeExtGroup(callback, *args, **kwargs) name = kwargs.pop("name", self.ext_variant.name) - self.slash_variant: BridgeSlashGroup = BridgeSlashGroup(callback, name, *args, **kwargs) - self.subcommands: List[BridgeCommand] = [] + self.slash_variant: BridgeSlashGroup = BridgeSlashGroup( + callback, name, *args, **kwargs + ) + self.subcommands: list[BridgeCommand] = [] - self.mapped: Optional[SlashCommand] = None + self.mapped: SlashCommand | None = None if map_to := getattr(callback, "__custom_map_to__", None): kwargs.update(map_to) self.mapped = self.slash_variant.command(**kwargs)(callback) @@ -317,10 +326,21 @@ def command(self, *args, **kwargs): kwargs: Optional[Dict[:class:`str`, Any]] Keyword arguments that are directly passed to the respective command constructors. (:class:`.SlashCommand` and :class:`.ext.commands.Command`) """ + def wrap(callback): - slash = self.slash_variant.command(*args, **filter_params(kwargs, brief="description"), cls=BridgeSlashCommand)(callback) - ext = self.ext_variant.command(*args, **filter_params(kwargs, description="brief"), cls=BridgeExtCommand)(callback) - command = BridgeCommand(callback, parent=self, slash_variant=slash, ext_variant=ext) + slash = self.slash_variant.command( + *args, + **filter_params(kwargs, brief="description"), + cls=BridgeSlashCommand, + )(callback) + ext = self.ext_variant.command( + *args, + **filter_params(kwargs, description="brief"), + cls=BridgeExtCommand, + )(callback) + command = BridgeCommand( + callback, parent=self, slash_variant=slash, ext_variant=ext + ) self.subcommands.append(command) return command @@ -335,6 +355,7 @@ def bridge_command(**kwargs): kwargs: Optional[Dict[:class:`str`, Any]] Keyword arguments that are directly passed to the respective command constructors. (:class:`.SlashCommand` and :class:`.ext.commands.Command`) """ + def decorator(callback): return BridgeCommand(callback, **kwargs) @@ -349,15 +370,23 @@ def bridge_group(**kwargs): kwargs: Optional[Dict[:class:`str`, Any]] Keyword arguments that are directly passed to the respective command constructors (:class:`.SlashCommandGroup` and :class:`.ext.commands.Group`). """ + def decorator(callback): return BridgeCommandGroup(callback, **kwargs) return decorator -def map_to(name, description = None): +def map_to(name, description=None): """To be used with bridge command groups, map the main command to a slash subcommand. + Parameters + ---------- + name: :class:`str` + The new name of the mapped command. + description: Optional[:class:`str`] + The new description of the mapped command. + Example ------- @@ -378,13 +407,6 @@ async def toggle(ctx: BridgeContext): /config show /config toggle - - Parameters - ---------- - name: :class:`str` - The new name of the mapped command. - description: Optional[:class:`str`] - The new description of the mapped command. """ def decorator(callback): @@ -400,7 +422,8 @@ def guild_only(): Basically a utility function that wraps both :func:`discord.ext.commands.guild_only` and :func:`discord.commands.guild_only`. """ - def predicate(func: Union[Callable, ApplicationCommand]): + + def predicate(func: Callable | ApplicationCommand): if isinstance(func, ApplicationCommand): func.guild_only = True else: @@ -413,8 +436,8 @@ def predicate(func: Union[Callable, ApplicationCommand]): return predicate -def has_permissions(**perms: Dict[str, bool]): - """Intended to work with :class:`.SlashCommand` and :class:`BridgeCommand`, adds a +def has_permissions(**perms: dict[str, bool]): + r"""Intended to work with :class:`.SlashCommand` and :class:`BridgeCommand`, adds a :func:`~ext.commands.check` that locks the command to be run by people with certain permissions inside guilds, and also registers the command as locked behind said permissions. @@ -427,11 +450,11 @@ def has_permissions(**perms: Dict[str, bool]): An argument list of permissions to check for. """ - def predicate(func: Union[Callable, ApplicationCommand]): + def predicate(func: Callable | ApplicationCommand): from ..commands import has_permissions func = has_permissions(**perms)(func) - _perms = Permissions(**perms) + Permissions(**perms) if isinstance(func, ApplicationCommand): func.default_member_permissions = perms else: @@ -488,7 +511,7 @@ async def convert(self, ctx, argument: str) -> Any: converted = converter(argument) if self.choices: - choices_names: List[Union[str, int, float]] = [ + choices_names: list[str | int | float] = [ choice.name for choice in self.choices ] if converted in choices_names and ( diff --git a/discord/ext/commands/_types.py b/discord/ext/commands/_types.py index 232bd32a13..7f86ac6d47 100644 --- a/discord/ext/commands/_types.py +++ b/discord/ext/commands/_types.py @@ -41,7 +41,9 @@ Callable[["Cog", "Context[Any]"], MaybeCoro[bool]], Callable[["Context[Any]"], MaybeCoro[bool]], ] -Hook = Union[Callable[["Cog", "Context[Any]"], Coro[Any]], Callable[["Context[Any]"], Coro[Any]]] +Hook = Union[ + Callable[["Cog", "Context[Any]"], Coro[Any]], Callable[["Context[Any]"], Coro[Any]] +] Error = Union[ Callable[["Cog", "Context[Any]", "CommandError"], Coro[Any]], Callable[["Context[Any]", "CommandError"], Coro[Any]], diff --git a/discord/ext/commands/bot.py b/discord/ext/commands/bot.py index 75b82f32b6..5c3cb24999 100644 --- a/discord/ext/commands/bot.py +++ b/discord/ext/commands/bot.py @@ -29,7 +29,7 @@ import collections.abc import sys import traceback -from typing import TYPE_CHECKING, Any, Callable, List, Optional, Type, TypeVar, Union +from typing import TYPE_CHECKING, Any, Callable, TypeVar import discord @@ -58,7 +58,7 @@ CXT = TypeVar("CXT", bound="Context") -def when_mentioned(bot: Union[Bot, AutoShardedBot], msg: Message) -> List[str]: +def when_mentioned(bot: Bot | AutoShardedBot, msg: Message) -> list[str]: """A callable that implements a command prefix equivalent to being mentioned. These are meant to be passed into the :attr:`.Bot.command_prefix` attribute. @@ -69,19 +69,22 @@ def when_mentioned(bot: Union[Bot, AutoShardedBot], msg: Message) -> List[str]: def when_mentioned_or( *prefixes: str, -) -> Callable[[Union[Bot, AutoShardedBot], Message], List[str]]: +) -> Callable[[Bot | AutoShardedBot, Message], list[str]]: """A callable that implements when mentioned or other prefixes provided. These are meant to be passed into the :attr:`.Bot.command_prefix` attribute. - Example + See Also -------- + :func:`.when_mentioned` + + Example + ------- .. code-block:: python3 bot = commands.Bot(command_prefix=commands.when_mentioned_or('!')) - .. note:: This callable returns another callable, so if this is done inside a custom @@ -92,11 +95,6 @@ def when_mentioned_or( async def get_prefix(bot, message): extras = await prefixes_for(message.guild) # returns a list return commands.when_mentioned_or(*extras)(bot, message) - - - See Also - ---------- - :func:`.when_mentioned` """ def inner(bot, msg): @@ -149,7 +147,9 @@ async def close(self) -> None: await super().close() # type: ignore - async def on_command_error(self, context: Context, exception: errors.CommandError) -> None: + async def on_command_error( + self, context: Context, exception: errors.CommandError + ) -> None: """|coro| The default command error handler provided by the bot. @@ -171,7 +171,9 @@ async def on_command_error(self, context: Context, exception: errors.CommandErro return print(f"Ignoring exception in command {context.command}:", file=sys.stderr) - traceback.print_exception(type(exception), exception, exception.__traceback__, file=sys.stderr) + traceback.print_exception( + type(exception), exception, exception.__traceback__, file=sys.stderr + ) async def can_run(self, ctx: Context, *, call_once: bool = False) -> bool: data = self._check_once if call_once else self._checks @@ -185,11 +187,11 @@ async def can_run(self, ctx: Context, *, call_once: bool = False) -> bool: # help command stuff @property - def help_command(self) -> Optional[HelpCommand]: + def help_command(self) -> HelpCommand | None: return self._help_command @help_command.setter - def help_command(self, value: Optional[HelpCommand]) -> None: + def help_command(self, value: HelpCommand | None) -> None: if value is not None: if not isinstance(value, HelpCommand): raise TypeError("help_command must be a subclass of HelpCommand") @@ -205,19 +207,19 @@ def help_command(self, value: Optional[HelpCommand]) -> None: # command processing - async def get_prefix(self, message: Message) -> Union[List[str], str]: + async def get_prefix(self, message: Message) -> list[str] | str: """|coro| Retrieves the prefix the bot is listening to with the message as a context. Parameters - ----------- + ---------- message: :class:`discord.Message` The message context to get the prefix of. Returns - -------- + ------- Union[List[:class:`str`], :class:`str`] A list of prefixes or a single prefix that the bot is listening for. @@ -241,11 +243,13 @@ async def get_prefix(self, message: Message) -> Union[List[str], str]: ) if not ret: - raise ValueError("Iterable command_prefix must contain at least one prefix") + raise ValueError( + "Iterable command_prefix must contain at least one prefix" + ) return ret - async def get_context(self, message: Message, *, cls: Type[CXT] = Context) -> CXT: + async def get_context(self, message: Message, *, cls: type[CXT] = Context) -> CXT: r"""|coro| Returns the invocation context from the message. @@ -331,7 +335,7 @@ async def invoke(self, ctx: Context) -> None: handles all the internal event dispatch mechanisms. Parameters - ----------- + ---------- ctx: :class:`.Context` The invocation context to invoke. """ @@ -368,7 +372,7 @@ async def process_commands(self, message: Message) -> None: call :meth:`~.Bot.get_context` or :meth:`~.Bot.invoke` if so. Parameters - ----------- + ---------- message: :class:`discord.Message` The message to process commands for. """ @@ -397,7 +401,7 @@ class Bot(BotBase, discord.Bot): Using prefixed commands requires :attr:`discord.Intents.message_content` to be enabled. Attributes - ----------- + ---------- command_prefix The command prefix is what the message content must contain initially to have a command invoked. This prefix could either be a string to @@ -443,12 +447,8 @@ class Bot(BotBase, discord.Bot): .. versionadded:: 1.7 """ - pass - class AutoShardedBot(BotBase, discord.AutoShardedBot): """This is similar to :class:`.Bot` except that it is inherited from :class:`discord.AutoShardedBot` instead. """ - - pass diff --git a/discord/ext/commands/cog.py b/discord/ext/commands/cog.py index 974e77bd4c..aa61fb24d0 100644 --- a/discord/ext/commands/cog.py +++ b/discord/ext/commands/cog.py @@ -24,7 +24,7 @@ """ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, Generator, List, Type, TypeVar, Union +from typing import TYPE_CHECKING, Any, Callable, Generator, TypeVar import discord @@ -43,7 +43,7 @@ class Cog(Cog): - def __new__(cls: Type[CogT], *args: Any, **kwargs: Any) -> CogT: + def __new__(cls: type[CogT], *args: Any, **kwargs: Any) -> CogT: # For issue 426, we need to store a copy of the command objects # since we modify them to inject `self` to them. # To do this, we need to interfere with the Cog creation process. @@ -70,7 +70,7 @@ def walk_commands(self) -> Generator[Command, None, None]: else: yield command - def get_commands(self) -> List[Union[ApplicationCommand, Command]]: + def get_commands(self) -> list[ApplicationCommand | Command]: r""" Returns -------- diff --git a/discord/ext/commands/context.py b/discord/ext/commands/context.py index 781c94cb72..f6a9669c1d 100644 --- a/discord/ext/commands/context.py +++ b/discord/ext/commands/context.py @@ -26,7 +26,7 @@ import inspect import re -from typing import TYPE_CHECKING, Any, Dict, Generic, List, Optional, TypeVar, Union +from typing import TYPE_CHECKING, Any, Generic, TypeVar, Union import discord.abc import discord.utils @@ -124,33 +124,35 @@ def __init__( message: Message, bot: BotT, view: StringView, - args: List[Any] = MISSING, - kwargs: Dict[str, Any] = MISSING, - prefix: Optional[str] = None, - command: Optional[Command] = None, - invoked_with: Optional[str] = None, - invoked_parents: List[str] = MISSING, - invoked_subcommand: Optional[Command] = None, - subcommand_passed: Optional[str] = None, + args: list[Any] = MISSING, + kwargs: dict[str, Any] = MISSING, + prefix: str | None = None, + command: Command | None = None, + invoked_with: str | None = None, + invoked_parents: list[str] = MISSING, + invoked_subcommand: Command | None = None, + subcommand_passed: str | None = None, command_failed: bool = False, - current_parameter: Optional[inspect.Parameter] = None, + current_parameter: inspect.Parameter | None = None, ): self.message: Message = message self.bot: BotT = bot - self.args: List[Any] = args or [] - self.kwargs: Dict[str, Any] = kwargs or {} - self.prefix: Optional[str] = prefix - self.command: Optional[Command] = command + self.args: list[Any] = args or [] + self.kwargs: dict[str, Any] = kwargs or {} + self.prefix: str | None = prefix + self.command: Command | None = command self.view: StringView = view - self.invoked_with: Optional[str] = invoked_with - self.invoked_parents: List[str] = invoked_parents or [] - self.invoked_subcommand: Optional[Command] = invoked_subcommand - self.subcommand_passed: Optional[str] = subcommand_passed + self.invoked_with: str | None = invoked_with + self.invoked_parents: list[str] = invoked_parents or [] + self.invoked_subcommand: Command | None = invoked_subcommand + self.subcommand_passed: str | None = subcommand_passed self.command_failed: bool = command_failed - self.current_parameter: Optional[inspect.Parameter] = current_parameter + self.current_parameter: inspect.Parameter | None = current_parameter self._state: ConnectionState = self.message._state - async def invoke(self, command: Command[CogT, P, T], /, *args: P.args, **kwargs: P.kwargs) -> T: + async def invoke( + self, command: Command[CogT, P, T], /, *args: P.args, **kwargs: P.kwargs + ) -> T: r"""|coro| Calls a command with the arguments given. @@ -200,7 +202,7 @@ async def reinvoke(self, *, call_hooks: bool = False, restart: bool = True) -> N fail again. Parameters - ------------ + ---------- call_hooks: :class:`bool` Whether to call the before and after invoke hooks. restart: :class:`bool` @@ -209,7 +211,7 @@ async def reinvoke(self, *, call_hooks: bool = False, restart: bool = True) -> N The default is to start where we left off. Raises - ------- + ------ ValueError The context to reinvoke is not valid. """ @@ -271,18 +273,20 @@ def clean_prefix(self) -> str: return pattern.sub("@%s" % user.display_name.replace("\\", r"\\"), self.prefix) @property - def cog(self) -> Optional[Cog]: + def cog(self) -> Cog | None: """Optional[:class:`.Cog`]: Returns the cog associated with this context's command. - None if it does not exist.""" + None if it does not exist. + """ if self.command is None: return None return self.command.cog @discord.utils.cached_property - def guild(self) -> Optional[Guild]: + def guild(self) -> Guild | None: """Optional[:class:`.Guild`]: Returns the guild associated with this context's command. - None if not available.""" + None if not available. + """ return self.message.guild @discord.utils.cached_property @@ -293,14 +297,14 @@ def channel(self) -> MessageableChannel: return self.message.channel @discord.utils.cached_property - def author(self) -> Union[User, Member]: + def author(self) -> User | Member: """Union[:class:`~discord.User`, :class:`.Member`]: Returns the author associated with this context's command. Shorthand for :attr:`.Message.author` """ return self.message.author @discord.utils.cached_property - def me(self) -> Union[Member, ClientUser]: + def me(self) -> Member | ClientUser: """Union[:class:`.Member`, :class:`.ClientUser`]: Similar to :attr:`.Guild.me` except it may return the :class:`.ClientUser` in private message message contexts, or when :meth:`Intents.guilds` is absent. @@ -309,7 +313,7 @@ def me(self) -> Union[Member, ClientUser]: return self.guild.me if self.guild is not None and self.guild.me is not None else self.bot.user # type: ignore @property - def voice_client(self) -> Optional[VoiceProtocol]: + def voice_client(self) -> VoiceProtocol | None: r"""Optional[:class:`.VoiceProtocol`]: A shortcut to :attr:`.Guild.voice_client`\, if applicable.""" g = self.guild return g.voice_client if g else None @@ -335,12 +339,12 @@ async def send_help(self, *args: Any) -> Any: this returns :class:`None` on bad input or no help command. Parameters - ------------ + ---------- entity: Optional[Union[:class:`Command`, :class:`Cog`, :class:`str`]] The entity to show help for. Returns - -------- + ------- Any The result of the help command, if any. """ @@ -396,5 +400,5 @@ async def send_help(self, *args: Any) -> Any: await cmd.on_help_command_error(self, e) @discord.utils.copy_doc(Message.reply) - async def reply(self, content: Optional[str] = None, **kwargs: Any) -> Message: + async def reply(self, content: str | None = None, **kwargs: Any) -> Message: return await self.message.reply(content, **kwargs) diff --git a/discord/ext/commands/converter.py b/discord/ext/commands/converter.py index fc5e795942..ee131f0424 100644 --- a/discord/ext/commands/converter.py +++ b/discord/ext/commands/converter.py @@ -30,15 +30,11 @@ from typing import ( TYPE_CHECKING, Any, - Dict, Generic, Iterable, List, Literal, - Optional, Protocol, - Tuple, - Type, TypeVar, Union, runtime_checkable, @@ -122,14 +118,14 @@ async def convert(self, ctx: Context, argument: str) -> T_co: properly propagate to the error handlers. Parameters - ----------- + ---------- ctx: :class:`.Context` The invocation context that the argument is being used in. argument: :class:`str` The argument that is being converted. Raises - ------- + ------ :exc:`.CommandError` A generic exception occurred when converting the argument. :exc:`.BadArgument` @@ -161,7 +157,9 @@ class ObjectConverter(IDConverter[discord.Object]): """ async def convert(self, ctx: Context, argument: str) -> discord.Object: - match = self._get_id_match(argument) or re.match(r"<(?:@[!&]?|#)([0-9]{15,20})>$", argument) + match = self._get_id_match(argument) or re.match( + r"<(?:@[!&]?|#)([0-9]{15,20})>$", argument + ) if match is None: raise ObjectNotFound(argument) @@ -198,10 +196,14 @@ async def query_member_named(self, guild, argument): if len(argument) > 5 and argument[-5] == "#": username, _, discriminator = argument.rpartition("#") members = await guild.query_members(username, limit=100, cache=cache) - return discord.utils.get(members, name=username, discriminator=discriminator) + return discord.utils.get( + members, name=username, discriminator=discriminator + ) else: members = await guild.query_members(argument, limit=100, cache=cache) - return discord.utils.find(lambda m: m.name == argument or m.nick == argument, members) + return discord.utils.find( + lambda m: m.name == argument or m.nick == argument, members + ) async def query_member_by_id(self, bot, guild, user_id): ws = bot._get_websocket(shard_id=guild.shard_id) @@ -226,7 +228,9 @@ async def query_member_by_id(self, bot, guild, user_id): async def convert(self, ctx: Context, argument: str) -> discord.Member: bot = ctx.bot - match = self._get_id_match(argument) or re.match(r"<@!?([0-9]{15,20})>$", argument) + match = self._get_id_match(argument) or re.match( + r"<@!?([0-9]{15,20})>$", argument + ) guild = ctx.guild result = None user_id = None @@ -281,7 +285,9 @@ class UserConverter(IDConverter[discord.User]): """ async def convert(self, ctx: Context, argument: str) -> discord.User: - match = self._get_id_match(argument) or re.match(r"<@!?([0-9]{15,20})>$", argument) + match = self._get_id_match(argument) or re.match( + r"<@!?([0-9]{15,20})>$", argument + ) result = None state = ctx._state @@ -337,7 +343,9 @@ class PartialMessageConverter(Converter[discord.PartialMessage]): @staticmethod def _get_id_matches(ctx, argument): - id_regex = re.compile(r"(?:(?P[0-9]{15,20})-)?(?P[0-9]{15,20})$") + id_regex = re.compile( + r"(?:(?P[0-9]{15,20})-)?(?P[0-9]{15,20})$" + ) link_regex = re.compile( r"https?://(?:(ptb|canary|www)\.)?discord(?:app)?\.com/channels/" r"(?P[0-9]{15,20}|@me)" @@ -363,7 +371,7 @@ def _get_id_matches(ctx, argument): return guild_id, message_id, channel_id @staticmethod - def _resolve_channel(ctx, guild_id, channel_id) -> Optional[PartialMessageableChannel]: + def _resolve_channel(ctx, guild_id, channel_id) -> PartialMessageableChannel | None: if guild_id is not None: guild = ctx.bot.get_guild(guild_id) if guild is not None and channel_id is not None: @@ -398,7 +406,9 @@ class MessageConverter(IDConverter[discord.Message]): """ async def convert(self, ctx: Context, argument: str) -> discord.Message: - guild_id, message_id, channel_id = PartialMessageConverter._get_id_matches(ctx, argument) + guild_id, message_id, channel_id = PartialMessageConverter._get_id_matches( + ctx, argument + ) message = ctx.bot._connection._get_message(message_id) if message: return message @@ -429,13 +439,19 @@ class GuildChannelConverter(IDConverter[discord.abc.GuildChannel]): """ async def convert(self, ctx: Context, argument: str) -> discord.abc.GuildChannel: - return self._resolve_channel(ctx, argument, "channels", discord.abc.GuildChannel) + return self._resolve_channel( + ctx, argument, "channels", discord.abc.GuildChannel + ) @staticmethod - def _resolve_channel(ctx: Context, argument: str, attribute: str, type: Type[CT]) -> CT: + def _resolve_channel( + ctx: Context, argument: str, attribute: str, type: type[CT] + ) -> CT: bot = ctx.bot - match = IDConverter._get_id_match(argument) or re.match(r"<#([0-9]{15,20})>$", argument) + match = IDConverter._get_id_match(argument) or re.match( + r"<#([0-9]{15,20})>$", argument + ) result = None guild = ctx.guild @@ -443,7 +459,7 @@ def _resolve_channel(ctx: Context, argument: str, attribute: str, type: Type[CT] # not a mention if guild: iterable: Iterable[CT] = getattr(guild, attribute) - result: Optional[CT] = discord.utils.get(iterable, name=argument) + result: CT | None = discord.utils.get(iterable, name=argument) else: def check(c): @@ -463,8 +479,12 @@ def check(c): return result @staticmethod - def _resolve_thread(ctx: Context, argument: str, attribute: str, type: Type[TT]) -> TT: - match = IDConverter._get_id_match(argument) or re.match(r"<#([0-9]{15,20})>$", argument) + def _resolve_thread( + ctx: Context, argument: str, attribute: str, type: type[TT] + ) -> TT: + match = IDConverter._get_id_match(argument) or re.match( + r"<#([0-9]{15,20})>$", argument + ) result = None guild = ctx.guild @@ -472,7 +492,7 @@ def _resolve_thread(ctx: Context, argument: str, attribute: str, type: Type[TT]) # not a mention if guild: iterable: Iterable[TT] = getattr(guild, attribute) - result: Optional[TT] = discord.utils.get(iterable, name=argument) + result: TT | None = discord.utils.get(iterable, name=argument) else: thread_id = int(match.group(1)) if guild: @@ -501,7 +521,9 @@ class TextChannelConverter(IDConverter[discord.TextChannel]): """ async def convert(self, ctx: Context, argument: str) -> discord.TextChannel: - return GuildChannelConverter._resolve_channel(ctx, argument, "text_channels", discord.TextChannel) + return GuildChannelConverter._resolve_channel( + ctx, argument, "text_channels", discord.TextChannel + ) class VoiceChannelConverter(IDConverter[discord.VoiceChannel]): @@ -521,7 +543,9 @@ class VoiceChannelConverter(IDConverter[discord.VoiceChannel]): """ async def convert(self, ctx: Context, argument: str) -> discord.VoiceChannel: - return GuildChannelConverter._resolve_channel(ctx, argument, "voice_channels", discord.VoiceChannel) + return GuildChannelConverter._resolve_channel( + ctx, argument, "voice_channels", discord.VoiceChannel + ) class StageChannelConverter(IDConverter[discord.StageChannel]): @@ -540,7 +564,9 @@ class StageChannelConverter(IDConverter[discord.StageChannel]): """ async def convert(self, ctx: Context, argument: str) -> discord.StageChannel: - return GuildChannelConverter._resolve_channel(ctx, argument, "stage_channels", discord.StageChannel) + return GuildChannelConverter._resolve_channel( + ctx, argument, "stage_channels", discord.StageChannel + ) class CategoryChannelConverter(IDConverter[discord.CategoryChannel]): @@ -560,7 +586,9 @@ class CategoryChannelConverter(IDConverter[discord.CategoryChannel]): """ async def convert(self, ctx: Context, argument: str) -> discord.CategoryChannel: - return GuildChannelConverter._resolve_channel(ctx, argument, "categories", discord.CategoryChannel) + return GuildChannelConverter._resolve_channel( + ctx, argument, "categories", discord.CategoryChannel + ) class ForumChannelConverter(IDConverter[discord.ForumChannel]): @@ -579,7 +607,9 @@ class ForumChannelConverter(IDConverter[discord.ForumChannel]): """ async def convert(self, ctx: Context, argument: str) -> discord.ForumChannel: - return GuildChannelConverter._resolve_channel(ctx, argument, "forum_channels", discord.ForumChannel) + return GuildChannelConverter._resolve_channel( + ctx, argument, "forum_channels", discord.ForumChannel + ) class ThreadConverter(IDConverter[discord.Thread]): @@ -597,7 +627,9 @@ class ThreadConverter(IDConverter[discord.Thread]): """ async def convert(self, ctx: Context, argument: str) -> discord.Thread: - return GuildChannelConverter._resolve_thread(ctx, argument, "threads", discord.Thread) + return GuildChannelConverter._resolve_thread( + ctx, argument, "threads", discord.Thread + ) class ColourConverter(Converter[discord.Colour]): @@ -626,7 +658,9 @@ class ColourConverter(Converter[discord.Colour]): Added support for ``rgb`` function and 3-digit hex shortcuts """ - RGB_REGEX = re.compile(r"rgb\s*\((?P[0-9]{1,3}%?)\s*,\s*(?P[0-9]{1,3}%?)\s*,\s*(?P[0-9]{1,3}%?)\s*\)") + RGB_REGEX = re.compile( + r"rgb\s*\((?P[0-9]{1,3}%?)\s*,\s*(?P[0-9]{1,3}%?)\s*,\s*(?P[0-9]{1,3}%?)\s*\)" + ) def parse_hex_number(self, argument): arg = "".join(i * 2 for i in argument) if len(argument) == 3 else argument @@ -707,7 +741,9 @@ async def convert(self, ctx: Context, argument: str) -> discord.Role: if not guild: raise NoPrivateMessage() - match = self._get_id_match(argument) or re.match(r"<@&([0-9]{15,20})>$", argument) + match = self._get_id_match(argument) or re.match( + r"<@&([0-9]{15,20})>$", argument + ) if match: result = guild.get_role(int(match.group(1))) else: @@ -786,7 +822,9 @@ class EmojiConverter(IDConverter[discord.Emoji]): """ async def convert(self, ctx: Context, argument: str) -> discord.Emoji: - match = self._get_id_match(argument) or re.match(r"$", argument) + match = self._get_id_match(argument) or re.match( + r"$", argument + ) result = None bot = ctx.bot guild = ctx.guild @@ -883,7 +921,7 @@ class clean_content(Converter[str]): This behaves similarly to :attr:`~discord.Message.clean_content`. Attributes - ------------ + ---------- fix_channel_mentions: :class:`bool` Whether to clean channel mentions. use_nicknames: :class:`bool` @@ -915,17 +953,27 @@ async def convert(self, ctx: Context, argument: str) -> str: if ctx.guild: def resolve_member(id: int) -> str: - m = (None if msg is None else _utils_get(msg.mentions, id=id)) or ctx.guild.get_member(id) - return f"@{m.display_name if self.use_nicknames else m.name}" if m else "@deleted-user" + m = ( + None if msg is None else _utils_get(msg.mentions, id=id) + ) or ctx.guild.get_member(id) + return ( + f"@{m.display_name if self.use_nicknames else m.name}" + if m + else "@deleted-user" + ) def resolve_role(id: int) -> str: - r = (None if msg is None else _utils_get(msg.mentions, id=id)) or ctx.guild.get_role(id) + r = ( + None if msg is None else _utils_get(msg.mentions, id=id) + ) or ctx.guild.get_role(id) return f"@{r.name}" if r else "@deleted-role" else: def resolve_member(id: int) -> str: - m = (None if msg is None else _utils_get(msg.mentions, id=id)) or ctx.bot.get_user(id) + m = ( + None if msg is None else _utils_get(msg.mentions, id=id) + ) or ctx.bot.get_user(id) return f"@{m.name}" if m else "@deleted-user" def resolve_role(id: int) -> str: @@ -996,7 +1044,7 @@ def __repr__(self): converter = getattr(self.converter, "__name__", repr(self.converter)) return f"Greedy[{converter}]" - def __class_getitem__(cls, params: Union[Tuple[T], T]) -> Greedy[T]: + def __class_getitem__(cls, params: tuple[T] | T) -> Greedy[T]: if not isinstance(params, tuple): params = (params,) if len(params) != 1: @@ -1006,7 +1054,11 @@ def __class_getitem__(cls, params: Union[Tuple[T], T]) -> Greedy[T]: origin = getattr(converter, "__origin__", None) args = getattr(converter, "__args__", ()) - if not (callable(converter) or isinstance(converter, Converter) or origin is not None): + if not ( + callable(converter) + or isinstance(converter, Converter) + or origin is not None + ): raise TypeError("Greedy[...] expects a type or a Converter instance.") if converter in (str, type(None)) or origin is Greedy: @@ -1041,11 +1093,11 @@ def get_converter(param: inspect.Parameter) -> Any: _GenericAlias = type(List[T]) -def is_generic_type(tp: Any, *, _GenericAlias: Type = _GenericAlias) -> bool: +def is_generic_type(tp: Any, *, _GenericAlias: type = _GenericAlias) -> bool: return isinstance(tp, type) and issubclass(tp, Generic) or isinstance(tp, _GenericAlias) # type: ignore -CONVERTER_MAPPING: Dict[Type[Any], Any] = { +CONVERTER_MAPPING: dict[type[Any], Any] = { discord.Object: ObjectConverter, discord.Member: MemberConverter, discord.User: UserConverter, @@ -1069,7 +1121,9 @@ def is_generic_type(tp: Any, *, _GenericAlias: Type = _GenericAlias) -> bool: } -async def _actual_conversion(ctx: Context, converter, argument: str, param: inspect.Parameter): +async def _actual_conversion( + ctx: Context, converter, argument: str, param: inspect.Parameter +): if converter is bool: return _convert_to_bool(argument) @@ -1078,7 +1132,9 @@ async def _actual_conversion(ctx: Context, converter, argument: str, param: insp except AttributeError: pass else: - if module is not None and (module.startswith("discord.") and not module.endswith("converter")): + if module is not None and ( + module.startswith("discord.") and not module.endswith("converter") + ): converter = CONVERTER_MAPPING.get(converter, converter) try: @@ -1104,10 +1160,14 @@ async def _actual_conversion(ctx: Context, converter, argument: str, param: insp except AttributeError: name = converter.__class__.__name__ - raise BadArgument(f'Converting to "{name}" failed for parameter "{param.name}".') from exc + raise BadArgument( + f'Converting to "{name}" failed for parameter "{param.name}".' + ) from exc -async def run_converters(ctx: Context, converter, argument: str, param: inspect.Parameter): +async def run_converters( + ctx: Context, converter, argument: str, param: inspect.Parameter +): """|coro| Runs converters for a given converter, argument, and parameter. @@ -1117,7 +1177,7 @@ async def run_converters(ctx: Context, converter, argument: str, param: inspect. .. versionadded:: 2.0 Parameters - ------------ + ---------- ctx: :class:`Context` The invocation context to run the converters under. converter: Any @@ -1127,15 +1187,15 @@ async def run_converters(ctx: Context, converter, argument: str, param: inspect. param: :class:`inspect.Parameter` The parameter being converted. This is mainly for error reporting. - Raises - ------- - CommandError - The converter failed to convert. - Returns - -------- + ------- Any The resulting conversion. + + Raises + ------ + CommandError + The converter failed to convert. """ origin = getattr(converter, "__origin__", None) diff --git a/discord/ext/commands/cooldowns.py b/discord/ext/commands/cooldowns.py index c22121c63e..90f387e994 100644 --- a/discord/ext/commands/cooldowns.py +++ b/discord/ext/commands/cooldowns.py @@ -28,7 +28,7 @@ import asyncio import time from collections import deque -from typing import TYPE_CHECKING, Any, Callable, Deque, Dict, Optional, Type, TypeVar +from typing import TYPE_CHECKING, Any, Callable, Deque, TypeVar from discord.enums import Enum @@ -85,7 +85,7 @@ class Cooldown: """Represents a cooldown for a command. Attributes - ----------- + ---------- rate: :class:`int` The total number of tokens available per :attr:`per` seconds. per: :class:`float` @@ -101,17 +101,17 @@ def __init__(self, rate: float, per: float) -> None: self._tokens: int = self.rate self._last: float = 0.0 - def get_tokens(self, current: Optional[float] = None) -> int: + def get_tokens(self, current: float | None = None) -> int: """Returns the number of available tokens before rate limiting is applied. Parameters - ------------ + ---------- current: Optional[:class:`float`] The time in seconds since Unix epoch to calculate tokens at. If not supplied then :func:`time.time()` is used. Returns - -------- + ------- :class:`int` The number of tokens available before the cooldown is to be applied. """ @@ -124,11 +124,11 @@ def get_tokens(self, current: Optional[float] = None) -> int: tokens = self.rate return tokens - def get_retry_after(self, current: Optional[float] = None) -> float: + def get_retry_after(self, current: float | None = None) -> float: """Returns the time in seconds until the cooldown will be reset. Parameters - ------------- + ---------- current: Optional[:class:`float`] The current time in seconds since Unix epoch. If not supplied, then :func:`time.time()` is used. @@ -146,11 +146,11 @@ def get_retry_after(self, current: Optional[float] = None) -> float: return 0.0 - def update_rate_limit(self, current: Optional[float] = None) -> Optional[float]: + def update_rate_limit(self, current: float | None = None) -> float | None: """Updates the cooldown rate limit. Parameters - ------------- + ---------- current: Optional[:class:`float`] The time in seconds since Unix epoch to update the rate limit at. If not supplied, then :func:`time.time()` is used. @@ -185,7 +185,7 @@ def copy(self) -> Cooldown: """Creates a copy of this cooldown. Returns - -------- + ------- :class:`Cooldown` A new instance of this cooldown. """ @@ -198,14 +198,14 @@ def __repr__(self) -> str: class CooldownMapping: def __init__( self, - original: Optional[Cooldown], + original: Cooldown | None, type: Callable[[Message], Any], ) -> None: if not callable(type): raise TypeError("Cooldown type must be a BucketType or callable") - self._cache: Dict[Any, Cooldown] = {} - self._cooldown: Optional[Cooldown] = original + self._cache: dict[Any, Cooldown] = {} + self._cooldown: Cooldown | None = original self._type: Callable[[Message], Any] = type def copy(self) -> CooldownMapping: @@ -222,13 +222,13 @@ def type(self) -> Callable[[Message], Any]: return self._type @classmethod - def from_cooldown(cls: Type[C], rate, per, type) -> C: + def from_cooldown(cls: type[C], rate, per, type) -> C: return cls(Cooldown(rate, per), type) def _bucket_key(self, msg: Message) -> Any: return self._type(msg) - def _verify_cache_integrity(self, current: Optional[float] = None) -> None: + def _verify_cache_integrity(self, current: float | None = None) -> None: # we want to delete all cache objects that haven't been used # in a cooldown window. e.g. if we have a command that has a # cooldown of 60s, and it has not been used in 60s then that key should be deleted @@ -240,7 +240,7 @@ def _verify_cache_integrity(self, current: Optional[float] = None) -> None: def create_bucket(self, message: Message) -> Cooldown: return self._cooldown.copy() # type: ignore - def get_bucket(self, message: Message, current: Optional[float] = None) -> Cooldown: + def get_bucket(self, message: Message, current: float | None = None) -> Cooldown: if self._type is BucketType.default: return self._cooldown # type: ignore @@ -255,13 +255,17 @@ def get_bucket(self, message: Message, current: Optional[float] = None) -> Coold return bucket - def update_rate_limit(self, message: Message, current: Optional[float] = None) -> Optional[float]: + def update_rate_limit( + self, message: Message, current: float | None = None + ) -> float | None: bucket = self.get_bucket(message, current) return bucket.update_rate_limit(current) class DynamicCooldownMapping(CooldownMapping): - def __init__(self, factory: Callable[[Message], Cooldown], type: Callable[[Message], Any]) -> None: + def __init__( + self, factory: Callable[[Message], Cooldown], type: Callable[[Message], Any] + ) -> None: super().__init__(None, type) self._factory: Callable[[Message], Cooldown] = factory @@ -342,7 +346,7 @@ class MaxConcurrency: __slots__ = ("number", "per", "wait", "_mapping") def __init__(self, number: int, *, per: BucketType, wait: bool) -> None: - self._mapping: Dict[Any, _Semaphore] = {} + self._mapping: dict[Any, _Semaphore] = {} self.per: BucketType = per self.number: int = number self.wait: bool = wait @@ -351,13 +355,17 @@ def __init__(self, number: int, *, per: BucketType, wait: bool) -> None: raise ValueError("max_concurrency 'number' cannot be less than 1") if not isinstance(per, BucketType): - raise TypeError(f"max_concurrency 'per' must be of type BucketType not {type(per)!r}") + raise TypeError( + f"max_concurrency 'per' must be of type BucketType not {type(per)!r}" + ) def copy(self: MC) -> MC: return self.__class__(self.number, per=self.per, wait=self.wait) def __repr__(self) -> str: - return f"" + return ( + f"" + ) def get_key(self, message: Message) -> Any: return self.per.get_key(message) diff --git a/discord/ext/commands/core.py b/discord/ext/commands/core.py index 665a631cac..eee8d8e917 100644 --- a/discord/ext/commands/core.py +++ b/discord/ext/commands/core.py @@ -29,20 +29,13 @@ import functools import inspect import types - from typing import ( TYPE_CHECKING, Any, Callable, - Dict, Generator, Generic, - List, Literal, - Optional, - Set, - Tuple, - Type, TypeVar, Union, overload, @@ -57,6 +50,7 @@ slash_command, user_command, ) +from ...enums import ChannelType from ...errors import * from .cog import Cog from .context import Context @@ -68,8 +62,6 @@ DynamicCooldownMapping, MaxConcurrency, ) -from ...enums import ChannelType - from .errors import * if TYPE_CHECKING: @@ -138,10 +130,12 @@ def unwrap_function(function: Callable[..., Any]) -> Callable[..., Any]: return function -def get_signature_parameters(function: Callable[..., Any], globalns: Dict[str, Any]) -> Dict[str, inspect.Parameter]: +def get_signature_parameters( + function: Callable[..., Any], globalns: dict[str, Any] +) -> dict[str, inspect.Parameter]: signature = inspect.signature(function) params = {} - cache: Dict[str, Any] = {} + cache: dict[str, Any] = {} eval_annotation = discord.utils.evaluate_annotation for name, parameter in signature.parameters.items(): annotation = parameter.annotation @@ -302,9 +296,9 @@ class Command(_BaseCommand, Generic[CogT, P, T]): .. versionadded:: 2.0 """ - __original_kwargs__: Dict[str, Any] + __original_kwargs__: dict[str, Any] - def __new__(cls: Type[CommandT], *args: Any, **kwargs: Any) -> CommandT: + def __new__(cls: type[CommandT], *args: Any, **kwargs: Any) -> CommandT: # if you're wondering why this is done, it's because we need to ensure # we have a complete original copy of **kwargs even for classes that # mess with it by popping before delegating to the subclass __init__. @@ -322,16 +316,10 @@ def __new__(cls: Type[CommandT], *args: Any, **kwargs: Any) -> CommandT: def __init__( self, - func: Union[ - Callable[ - Concatenate[CogT, ContextT, P], - Coro[T] - ], - Callable[ - Concatenate[ContextT, P], - Coro[T] - ], - ], + func: ( + Callable[Concatenate[CogT, ContextT, P], Coro[T]] + | Callable[Concatenate[ContextT, P], Coro[T]] + ), **kwargs: Any, ): if not asyncio.iscoroutinefunction(func): @@ -353,16 +341,18 @@ def __init__( if isinstance(help_doc, bytes): help_doc = help_doc.decode("utf-8") - self.help: Optional[str] = help_doc + self.help: str | None = help_doc - self.brief: Optional[str] = kwargs.get("brief") - self.usage: Optional[str] = kwargs.get("usage") + self.brief: str | None = kwargs.get("brief") + self.usage: str | None = kwargs.get("usage") self.rest_is_raw: bool = kwargs.get("rest_is_raw", False) - self.aliases: Union[List[str], Tuple[str]] = kwargs.get("aliases", []) - self.extras: Dict[str, Any] = kwargs.get("extras", {}) + self.aliases: list[str] | tuple[str] = kwargs.get("aliases", []) + self.extras: dict[str, Any] = kwargs.get("extras", {}) if not isinstance(self.aliases, (list, tuple)): - raise TypeError("Aliases of a command must be a list or a tuple of strings.") + raise TypeError( + "Aliases of a command must be a list or a tuple of strings." + ) self.description: str = inspect.cleandoc(kwargs.get("description", "")) self.hidden: bool = kwargs.get("hidden", False) @@ -373,7 +363,7 @@ def __init__( except AttributeError: checks = kwargs.get("checks", []) - self.checks: List[Check] = checks + self.checks: list[Check] = checks try: cooldown = func.__commands_cooldown__ @@ -385,7 +375,9 @@ def __init__( elif isinstance(cooldown, CooldownMapping): buckets = cooldown else: - raise TypeError("Cooldown must be a an instance of CooldownMapping or None.") + raise TypeError( + "Cooldown must be a an instance of CooldownMapping or None." + ) self._buckets: CooldownMapping = buckets try: @@ -393,18 +385,18 @@ def __init__( except AttributeError: max_concurrency = kwargs.get("max_concurrency") - self._max_concurrency: Optional[MaxConcurrency] = max_concurrency + self._max_concurrency: MaxConcurrency | None = max_concurrency self.require_var_positional: bool = kwargs.get("require_var_positional", False) self.ignore_extra: bool = kwargs.get("ignore_extra", True) self.cooldown_after_parsing: bool = kwargs.get("cooldown_after_parsing", False) - self.cog: Optional[CogT] = None + self.cog: CogT | None = None # bandaid for the fact that sometimes parent can be the bot instance parent = kwargs.get("parent") - self.parent: Optional[GroupMixin] = parent if isinstance(parent, _BaseCommand) else None # type: ignore + self.parent: GroupMixin | None = parent if isinstance(parent, _BaseCommand) else None # type: ignore - self._before_invoke: Optional[Hook] = None + self._before_invoke: Hook | None = None try: before_invoke = func.__before_invoke__ except AttributeError: @@ -412,7 +404,7 @@ def __init__( else: self.before_invoke(before_invoke) - self._after_invoke: Optional[Hook] = None + self._after_invoke: Hook | None = None try: after_invoke = func.__after_invoke__ except AttributeError: @@ -423,31 +415,19 @@ def __init__( @property def callback( self, - ) -> Union[ - Callable[ - Concatenate[CogT, Context, P], - Coro[T] - ], - Callable[ - Concatenate[Context, P], - Coro[T] - ], - ]: + ) -> ( + Callable[Concatenate[CogT, Context, P], Coro[T]] + | Callable[Concatenate[Context, P], Coro[T]] + ): return self._callback @callback.setter def callback( self, - function: Union[ - Callable[ - Concatenate[CogT, Context, P], - Coro[T] - ], - Callable[ - Concatenate[Context, P], - Coro[T] - ], - ], + function: ( + Callable[Concatenate[CogT, Context, P], Coro[T]] + | Callable[Concatenate[Context, P], Coro[T]] + ), ) -> None: self._callback = function unwrap = unwrap_function(function) @@ -468,7 +448,7 @@ def add_check(self, func: Check) -> None: .. versionadded:: 1.3 Parameters - ----------- + ---------- func The function that will be used as a check. """ @@ -484,7 +464,7 @@ def remove_check(self, func: Check) -> None: .. versionadded:: 1.3 Parameters - ----------- + ---------- func The function to remove from the checks. """ @@ -542,14 +522,14 @@ def copy(self: CommandT) -> CommandT: """Creates a copy of this command. Returns - -------- + ------- :class:`Command` A new instance of this command. """ ret = self.__class__(self.callback, **self.__original_kwargs__) return self._ensure_assignment_on_copy(ret) - def _update_copy(self: CommandT, kwargs: Dict[str, Any]) -> CommandT: + def _update_copy(self: CommandT, kwargs: dict[str, Any]) -> CommandT: if kwargs: kw = kwargs.copy() kw.update(self.__original_kwargs__) @@ -584,7 +564,9 @@ async def dispatch_error(self, ctx: Context, error: Exception) -> None: async def transform(self, ctx: Context, param: inspect.Parameter) -> Any: required = param.default is param.empty converter = get_converter(param) - consume_rest_is_special = param.kind == param.KEYWORD_ONLY and not self.rest_is_raw + consume_rest_is_special = ( + param.kind == param.KEYWORD_ONLY and not self.rest_is_raw + ) view = ctx.view view.skip_ws() @@ -592,9 +574,13 @@ async def transform(self, ctx: Context, param: inspect.Parameter) -> Any: # it undoes the view ready for the next parameter to use instead if isinstance(converter, Greedy): if param.kind in (param.POSITIONAL_OR_KEYWORD, param.POSITIONAL_ONLY): - return await self._transform_greedy_pos(ctx, param, required, converter.converter) + return await self._transform_greedy_pos( + ctx, param, required, converter.converter + ) elif param.kind == param.VAR_POSITIONAL: - return await self._transform_greedy_var_pos(ctx, param, converter.converter) + return await self._transform_greedy_var_pos( + ctx, param, converter.converter + ) else: # if we're here, then it's a KEYWORD_ONLY param type # since this is mostly useless, we'll helpfully transform Greedy[X] @@ -607,7 +593,10 @@ async def transform(self, ctx: Context, param: inspect.Parameter) -> Any: if required: if self._is_typing_optional(param.annotation): return None - if hasattr(converter, "__commands_is_flag__") and converter._can_be_constructible(): + if ( + hasattr(converter, "__commands_is_flag__") + and converter._can_be_constructible() + ): return await converter._construct_default(ctx) raise MissingRequiredArgument(param) return param.default @@ -651,7 +640,9 @@ async def _transform_greedy_pos( return param.default return result - async def _transform_greedy_var_pos(self, ctx: Context, param: inspect.Parameter, converter: Any) -> Any: + async def _transform_greedy_var_pos( + self, ctx: Context, param: inspect.Parameter, converter: Any + ) -> Any: view = ctx.view previous = view.index try: @@ -664,7 +655,7 @@ async def _transform_greedy_var_pos(self, ctx: Context, param: inspect.Parameter return value @property - def clean_params(self) -> Dict[str, inspect.Parameter]: + def clean_params(self) -> dict[str, inspect.Parameter]: """Dict[:class:`str`, :class:`inspect.Parameter`]: Retrieves the parameter dictionary without the context or self parameters. @@ -703,7 +694,7 @@ def full_parent_name(self) -> str: return " ".join(reversed(entries)) @property - def parents(self) -> List[Group]: + def parents(self) -> list[Group]: """List[:class:`Group`]: Retrieves the parents of this command. If the command has no parents then it returns an empty :class:`list`. @@ -721,7 +712,7 @@ def parents(self) -> List[Group]: return entries @property - def root_parent(self) -> Optional[Group]: + def root_parent(self) -> Group | None: """Optional[:class:`Group`]: Retrieves the root parent of this command. If the command has no parents then it returns ``None``. @@ -765,13 +756,17 @@ async def _parse_arguments(self, ctx: Context) -> None: try: next(iterator) except StopIteration: - raise discord.ClientException(f'Callback for {self.name} command is missing "self" parameter.') + raise discord.ClientException( + f'Callback for {self.name} command is missing "self" parameter.' + ) # next we have the 'ctx' as the next parameter try: next(iterator) except StopIteration: - raise discord.ClientException(f'Callback for {self.name} command is missing "ctx" parameter.') + raise discord.ClientException( + f'Callback for {self.name} command is missing "ctx" parameter.' + ) for name, param in iterator: ctx.current_parameter = param @@ -798,7 +793,9 @@ async def _parse_arguments(self, ctx: Context) -> None: break if not self.ignore_extra and not view.eof: - raise TooManyArguments(f"Too many arguments passed to {self.qualified_name}") + raise TooManyArguments( + f"Too many arguments passed to {self.qualified_name}" + ) async def call_before_hooks(self, ctx: Context) -> None: # now that we're done preparing we can call the pre-command hooks @@ -858,7 +855,9 @@ async def prepare(self, ctx: Context) -> None: ctx.command = self if not await self.can_run(ctx): - raise CheckFailure(f"The check functions for command {self.qualified_name} failed.") + raise CheckFailure( + f"The check functions for command {self.qualified_name} failed." + ) if self._max_concurrency is not None: # For this application, context can be duck-typed as a Message @@ -879,19 +878,19 @@ async def prepare(self, ctx: Context) -> None: raise @property - def cooldown(self) -> Optional[Cooldown]: + def cooldown(self) -> Cooldown | None: return self._buckets._cooldown def is_on_cooldown(self, ctx: Context) -> bool: """Checks whether the command is currently on cooldown. Parameters - ----------- + ---------- ctx: :class:`.Context` The invocation context to use when checking the command's cooldown status. Returns - -------- + ------- :class:`bool` A boolean indicating if the command is on cooldown. """ @@ -907,7 +906,7 @@ def reset_cooldown(self, ctx: Context) -> None: """Resets the cooldown on this command. Parameters - ----------- + ---------- ctx: :class:`.Context` The invocation context to reset the cooldown under. """ @@ -921,12 +920,12 @@ def get_cooldown_retry_after(self, ctx: Context) -> float: .. versionadded:: 1.4 Parameters - ----------- + ---------- ctx: :class:`.Context` The invocation context to retrieve the cooldown from. Returns - -------- + ------- :class:`float` The amount of time left on this command's cooldown in seconds. If this is ``0.0`` then the command isn't on cooldown. @@ -975,12 +974,12 @@ def error(self, coro: ErrorT) -> ErrorT: invoked afterwards as the catch-all. Parameters - ----------- + ---------- coro: :ref:`coroutine ` The coroutine to register as the local error handler. Raises - ------- + ------ TypeError The coroutine passed is not actually a coroutine. """ @@ -1010,12 +1009,12 @@ def before_invoke(self, coro: HookT) -> HookT: See :meth:`.Bot.before_invoke` for more info. Parameters - ----------- + ---------- coro: :ref:`coroutine ` The coroutine to register as the pre-invoke hook. Raises - ------- + ------ TypeError The coroutine passed is not actually a coroutine. """ @@ -1037,12 +1036,12 @@ def after_invoke(self, coro: HookT) -> HookT: See :meth:`.Bot.after_invoke` for more info. Parameters - ----------- + ---------- coro: :ref:`coroutine ` The coroutine to register as the post-invoke hook. Raises - ------- + ------ TypeError The coroutine passed is not actually a coroutine. """ @@ -1053,7 +1052,7 @@ def after_invoke(self, coro: HookT) -> HookT: return coro @property - def cog_name(self) -> Optional[str]: + def cog_name(self) -> str | None: """Optional[:class:`str`]: The name of the cog this command belongs to, if any.""" return type(self.cog).__cog_name__ if self.cog is not None else None @@ -1071,9 +1070,10 @@ def short_doc(self) -> str: return self.help.split("\n", 1)[0] return "" - def _is_typing_optional(self, annotation: Union[T, Optional[T]]) -> TypeGuard[Optional[T]]: + def _is_typing_optional(self, annotation: T | T | None) -> TypeGuard[T | None]: return ( - getattr(annotation, "__origin__", None) is Union or type(annotation) is getattr(types, "UnionType", Union) + getattr(annotation, "__origin__", None) is Union + or type(annotation) is getattr(types, "UnionType", Union) ) and type( None ) in annotation.__args__ # type: ignore @@ -1106,13 +1106,24 @@ def signature(self) -> str: origin = getattr(annotation, "__origin__", None) if origin is Literal: - name = "|".join(f'"{v}"' if isinstance(v, str) else str(v) for v in annotation.__args__) + name = "|".join( + f'"{v}"' if isinstance(v, str) else str(v) + for v in annotation.__args__ + ) if param.default is not param.empty: # We don't want None or '' to trigger the [name=value] case, and instead it should # do [name] since [name=None] or [name=] are not exactly useful for the user. - should_print = param.default if isinstance(param.default, str) else param.default is not None + should_print = ( + param.default + if isinstance(param.default, str) + else param.default is not None + ) if should_print: - result.append(f"[{name}={param.default}]" if not greedy else f"[{name}={param.default}]...") + result.append( + f"[{name}={param.default}]" + if not greedy + else f"[{name}={param.default}]..." + ) continue else: result.append(f"[{name}]") @@ -1142,20 +1153,20 @@ async def can_run(self, ctx: Context) -> bool: Checks whether the command is disabled or not Parameters - ----------- + ---------- ctx: :class:`.Context` The ctx of the command currently being invoked. - Raises + Returns ------- + :class:`bool` + A boolean indicating if the command can be invoked. + + Raises + ------ :class:`CommandError` Any command error that was raised during a check call will be propagated by this function. - - Returns - -------- - :class:`bool` - A boolean indicating if the command can be invoked. """ if not self.enabled: @@ -1166,7 +1177,9 @@ async def can_run(self, ctx: Context) -> bool: try: if not await ctx.bot.can_run(ctx): - raise CheckFailure(f"The global check functions for command {self.qualified_name} failed.") + raise CheckFailure( + f"The global check functions for command {self.qualified_name} failed." + ) cog = self.cog if cog is not None: @@ -1194,7 +1207,7 @@ class GroupMixin(Generic[CogT]): similar to :class:`.Group` and are allowed to register commands. Attributes - ----------- + ---------- all_commands: :class:`dict` A mapping of command name to :class:`.Command` objects. @@ -1204,7 +1217,9 @@ class GroupMixin(Generic[CogT]): def __init__(self, *args: Any, **kwargs: Any) -> None: case_insensitive = kwargs.get("case_insensitive", False) - self.prefixed_commands: Dict[str, Command[CogT, Any, Any]] = _CaseInsensitiveDict() if case_insensitive else {} + self.prefixed_commands: dict[str, Command[CogT, Any, Any]] = ( + _CaseInsensitiveDict() if case_insensitive else {} + ) self.case_insensitive: bool = case_insensitive super().__init__(*args, **kwargs) @@ -1216,7 +1231,7 @@ def all_commands(self): return self.prefixed_commands @property - def commands(self) -> Set[Command[CogT, Any, Any]]: + def commands(self) -> set[Command[CogT, Any, Any]]: """Set[:class:`.Command`]: A unique set of commands without aliases that are registered.""" return set(self.prefixed_commands.values()) @@ -1236,12 +1251,12 @@ def add_command(self, command: Command[CogT, Any, Any]) -> None: Raise :exc:`.CommandRegistrationError` instead of generic :exc:`.ClientException` Parameters - ----------- + ---------- command: :class:`Command` The command to add. Raises - ------- + ------ :exc:`.CommandRegistrationError` If the command or its alias is already registered by different command. TypeError @@ -1264,19 +1279,19 @@ def add_command(self, command: Command[CogT, Any, Any]) -> None: raise CommandRegistrationError(alias, alias_conflict=True) self.prefixed_commands[alias] = command - def remove_command(self, name: str) -> Optional[Command[CogT, Any, Any]]: + def remove_command(self, name: str) -> Command[CogT, Any, Any] | None: """Remove a :class:`.Command` from the internal list of commands. This could also be used as a way to remove aliases. Parameters - ----------- + ---------- name: :class:`str` The name of the command to remove. Returns - -------- + ------- Optional[:class:`.Command`] The command that was removed. If the name is not valid then ``None`` is returned instead. @@ -1317,7 +1332,7 @@ def walk_commands(self) -> Generator[Command[CogT, Any, Any], None, None]: if isinstance(command, GroupMixin): yield from command.walk_commands() - def get_command(self, name: str) -> Optional[Command[CogT, Any, Any]]: + def get_command(self, name: str) -> Command[CogT, Any, Any] | None: """Get a :class:`.Command` from the internal list of commands. @@ -1328,12 +1343,12 @@ def get_command(self, name: str) -> Optional[Command[CogT, Any, Any]]: subcommand is not found then ``None`` is returned just as usual. Parameters - ----------- + ---------- name: :class:`str` The name of the command to get. Returns - -------- + ------- Optional[:class:`Command`] The command that was requested. If not found, returns ``None``. """ @@ -1361,21 +1376,15 @@ def get_command(self, name: str) -> Optional[Command[CogT, Any, Any]]: def command( self, name: str = ..., - cls: Type[Command[CogT, P, T]] = ..., + cls: type[Command[CogT, P, T]] = ..., *args: Any, **kwargs: Any, ) -> Callable[ [ - Union[ - Callable[ - Concatenate[CogT, ContextT, P], - Coro[T] - ], - Callable[ - Concatenate[ContextT, P], - Coro[T] - ], - ] + ( + Callable[Concatenate[CogT, ContextT, P], Coro[T]] + | Callable[Concatenate[ContextT, P], Coro[T]] + ) ], Command[CogT, P, T], ]: @@ -1385,40 +1394,24 @@ def command( def command( self, name: str = ..., - cls: Type[CommandT] = ..., + cls: type[CommandT] = ..., *args: Any, **kwargs: Any, - ) -> Callable[ - [ - Callable[ - Concatenate[ContextT, P], - Coro[Any] - ] - ], - CommandT - ]: + ) -> Callable[[Callable[Concatenate[ContextT, P], Coro[Any]]], CommandT]: ... def command( self, name: str = MISSING, - cls: Type[CommandT] = MISSING, + cls: type[CommandT] = MISSING, *args: Any, **kwargs: Any, - ) -> Callable[ - [ - Callable[ - Concatenate[ContextT, P], - Coro[Any] - ] - ], - CommandT - ]: + ) -> Callable[[Callable[Concatenate[ContextT, P], Coro[Any]]], CommandT]: """A shortcut decorator that invokes :func:`.command` and adds it to the internal command list via :meth:`~.GroupMixin.add_command`. Returns - -------- + ------- Callable[..., :class:`Command`] A decorator that converts the provided method into a Command, adds it to the bot, then returns it. """ @@ -1435,15 +1428,15 @@ def decorator(func: Callable[Concatenate[ContextT, P], Coro[Any]]) -> CommandT: def group( self, name: str = ..., - cls: Type[Group[CogT, P, T]] = ..., + cls: type[Group[CogT, P, T]] = ..., *args: Any, **kwargs: Any, ) -> Callable[ [ - Union[ - Callable[Concatenate[CogT, ContextT, P], Coro[T]], - Callable[Concatenate[ContextT, P], Coro[T]], - ] + ( + Callable[Concatenate[CogT, ContextT, P], Coro[T]] + | Callable[Concatenate[ContextT, P], Coro[T]] + ) ], Group[CogT, P, T], ]: @@ -1453,7 +1446,7 @@ def group( def group( self, name: str = ..., - cls: Type[GroupT] = ..., + cls: type[GroupT] = ..., *args: Any, **kwargs: Any, ) -> Callable[[Callable[Concatenate[ContextT, P], Coro[Any]]], GroupT]: @@ -1462,7 +1455,7 @@ def group( def group( self, name: str = MISSING, - cls: Type[GroupT] = MISSING, + cls: type[GroupT] = MISSING, *args: Any, **kwargs: Any, ) -> Callable[[Callable[Concatenate[ContextT, P], Coro[Any]]], GroupT]: @@ -1470,7 +1463,7 @@ def group( the internal command list via :meth:`~.GroupMixin.add_command`. Returns - -------- + ------- Callable[..., :class:`Group`] A decorator that converts the provided method into a Group, adds it to the bot, then returns it. """ @@ -1492,7 +1485,7 @@ class Group(GroupMixin[CogT], Command[CogT, P, T]): valid in :class:`.Command` are valid in here as well. Attributes - ----------- + ---------- invoke_without_command: :class:`bool` Indicates if the group callback should begin parsing and invocation only if no subcommand was found. Useful for @@ -1515,7 +1508,7 @@ def copy(self: GroupT) -> GroupT: """Creates a copy of this :class:`Group`. Returns - -------- + ------- :class:`Group` A new instance of this group. """ @@ -1602,14 +1595,16 @@ async def reinvoke(self, ctx: Context, *, call_hooks: bool = False) -> None: @overload # for py 3.10 def command( name: str = ..., - cls: Type[Command[CogT, P, T]] = ..., + cls: type[Command[CogT, P, T]] = ..., **attrs: Any, ) -> Callable[ [ - Union[ - Callable[Concatenate[CogT, ContextT, P]], Coro[T], - Callable[Concatenate[ContextT, P]], Coro[T], - ] + ( + Callable[Concatenate[CogT, ContextT, P]] + | Coro[T] + | Callable[Concatenate[ContextT, P]] + | Coro[T] + ) ], Command[CogT, P, T], ]: @@ -1619,14 +1614,14 @@ def command( @overload def command( name: str = ..., - cls: Type[Command[CogT, P, T]] = ..., + cls: type[Command[CogT, P, T]] = ..., **attrs: Any, ) -> Callable[ [ - Union[ - Callable[Concatenate[CogT, ContextT, P], Coro[T]], - Callable[Concatenate[ContextT, P], Coro[T]], - ] + ( + Callable[Concatenate[CogT, ContextT, P], Coro[T]] + | Callable[Concatenate[ContextT, P], Coro[T]] + ) ], Command[CogT, P, T], ]: @@ -1636,14 +1631,14 @@ def command( @overload def command( name: str = ..., - cls: Type[CommandT] = ..., + cls: type[CommandT] = ..., **attrs: Any, ) -> Callable[ [ - Union[ - Callable[Concatenate[CogT, ContextT, P], Coro[Any]], - Callable[Concatenate[ContextT, P], Coro[Any]], - ] + ( + Callable[Concatenate[CogT, ContextT, P], Coro[Any]] + | Callable[Concatenate[ContextT, P], Coro[Any]] + ) ], CommandT, ]: @@ -1651,15 +1646,15 @@ def command( def command( - name: str = MISSING, cls: Type[CommandT] = MISSING, **attrs: Any + name: str = MISSING, cls: type[CommandT] = MISSING, **attrs: Any ) -> Callable[ [ - Union[ - Callable[Concatenate[ContextT, P], Coro[Any]], - Callable[Concatenate[CogT, ContextT, P], Coro[T]], - ] + ( + Callable[Concatenate[ContextT, P], Coro[Any]] + | Callable[Concatenate[CogT, ContextT, P], Coro[T]] + ) ], - Union[Command[CogT, P, T], CommandT], + Command[CogT, P, T] | CommandT, ]: """A decorator that transforms a function into a :class:`.Command` or if called with :func:`.group`, :class:`.Group`. @@ -1674,7 +1669,7 @@ def command( decorator. Parameters - ----------- + ---------- name: :class:`str` The name to create the command with. By default, this uses the function name unchanged. @@ -1686,7 +1681,7 @@ def command( by ``cls``. Raises - ------- + ------ TypeError If the function is not a coroutine or is already a command. """ @@ -1694,10 +1689,10 @@ def command( cls = Command # type: ignore def decorator( - func: Union[ - Callable[Concatenate[ContextT, P], Coro[Any]], - Callable[Concatenate[CogT, ContextT, P], Coro[Any]], - ] + func: ( + Callable[Concatenate[ContextT, P], Coro[Any]] + | Callable[Concatenate[CogT, ContextT, P], Coro[Any]] + ) ) -> CommandT: if isinstance(func, Command): raise TypeError("Callback is already a command.") @@ -1709,14 +1704,14 @@ def decorator( @overload def group( name: str = ..., - cls: Type[Group[CogT, P, T]] = ..., + cls: type[Group[CogT, P, T]] = ..., **attrs: Any, ) -> Callable[ [ - Union[ - Callable[Concatenate[CogT, ContextT, P], Coro[T]], - Callable[Concatenate[ContextT, P], Coro[T]], - ] + ( + Callable[Concatenate[CogT, ContextT, P], Coro[T]] + | Callable[Concatenate[ContextT, P], Coro[T]] + ) ], Group[CogT, P, T], ]: @@ -1726,14 +1721,14 @@ def group( @overload def group( name: str = ..., - cls: Type[GroupT] = ..., + cls: type[GroupT] = ..., **attrs: Any, ) -> Callable[ [ - Union[ - Callable[Concatenate[CogT, ContextT, P], Coro[Any]], - Callable[Concatenate[ContextT, P], Coro[Any]], - ] + ( + Callable[Concatenate[CogT, ContextT, P], Coro[Any]] + | Callable[Concatenate[ContextT, P], Coro[Any]] + ) ], GroupT, ]: @@ -1742,16 +1737,16 @@ def group( def group( name: str = MISSING, - cls: Type[GroupT] = MISSING, + cls: type[GroupT] = MISSING, **attrs: Any, ) -> Callable[ [ - Union[ - Callable[Concatenate[ContextT, P], Coro[Any]], - Callable[Concatenate[CogT, ContextT, P], Coro[T]], - ] + ( + Callable[Concatenate[ContextT, P], Coro[Any]] + | Callable[Concatenate[CogT, ContextT, P], Coro[T]] + ) ], - Union[Group[CogT, P, T], GroupT], + Group[CogT, P, T] | GroupT, ]: """A decorator that transforms a function into a :class:`.Group`. @@ -1837,7 +1832,7 @@ async def only_me(ctx): The predicate to check if the command should be invoked. """ - def decorator(func: Union[Command, CoroFunc]) -> Union[Command, CoroFunc]: + def decorator(func: Command | CoroFunc) -> Command | CoroFunc: if isinstance(func, _BaseCommand): func.checks.append(predicate) else: @@ -1910,7 +1905,9 @@ async def only_for_owners(ctx): try: pred = wrapped.predicate except AttributeError: - raise TypeError(f"{wrapped!r} must be wrapped by commands.check decorator") from None + raise TypeError( + f"{wrapped!r} must be wrapped by commands.check decorator" + ) from None else: unwrapped.append(pred) @@ -1930,7 +1927,7 @@ async def predicate(ctx: Context) -> bool: return check(predicate) -def has_role(item: Union[int, str]) -> Callable[[T], T]: +def has_role(item: int | str) -> Callable[[T], T]: """A :func:`.check` that is added that checks if the member invoking the command has the role specified via the name or ID specified. @@ -1952,7 +1949,7 @@ def has_role(item: Union[int, str]) -> Callable[[T], T]: instead of generic :exc:`.CheckFailure` Parameters - ----------- + ---------- item: Union[:class:`int`, :class:`str`] The name or ID of the role to check. """ @@ -1973,7 +1970,7 @@ def predicate(ctx: Context) -> bool: return check(predicate) -def has_any_role(*items: Union[int, str]) -> Callable[[T], T]: +def has_any_role(*items: int | str) -> Callable[[T], T]: r"""A :func:`.check` that is added that checks if the member invoking the command has **any** of the roles specified. This means that if they have one out of the three roles specified, then this check will return `True`. @@ -2012,7 +2009,10 @@ def predicate(ctx): # ctx.guild is None doesn't narrow ctx.author to Member getter = functools.partial(discord.utils.get, ctx.author.roles) # type: ignore if any( - getter(id=item) is not None if isinstance(item, int) else getter(name=item) is not None for item in items + getter(id=item) is not None + if isinstance(item, int) + else getter(name=item) is not None + for item in items ): return True raise MissingAnyRole(list(items)) @@ -2071,7 +2071,10 @@ def predicate(ctx): me = ctx.me getter = functools.partial(discord.utils.get, me.roles) if any( - getter(id=item) is not None if isinstance(item, int) else getter(name=item) is not None for item in items + getter(id=item) is not None + if isinstance(item, int) + else getter(name=item) is not None + for item in items ): return True raise BotMissingAnyRole(list(items)) @@ -2080,7 +2083,7 @@ def predicate(ctx): def has_permissions(**perms: bool) -> Callable[[T], T]: - """A :func:`.check` that is added that checks if the member has all of + r"""A :func:`.check` that is added that checks if the member has all of the permissions necessary. Note that this check operates on the current channel permissions, not the @@ -2120,7 +2123,9 @@ def predicate(ctx: Context) -> bool: return True permissions = ctx.channel.permissions_for(ctx.author) # type: ignore - missing = [perm for perm, value in perms.items() if getattr(permissions, perm) != value] + missing = [ + perm for perm, value in perms.items() if getattr(permissions, perm) != value + ] if not missing: return True @@ -2148,12 +2153,14 @@ def predicate(ctx: Context) -> bool: if ctx.channel.type == ChannelType.private: return True - if hasattr(ctx, 'app_permissions'): + if hasattr(ctx, "app_permissions"): permissions = ctx.app_permissions else: permissions = ctx.channel.permissions_for(me) # type: ignore - missing = [perm for perm, value in perms.items() if getattr(permissions, perm) != value] + missing = [ + perm for perm, value in perms.items() if getattr(permissions, perm) != value + ] if not missing: return True @@ -2182,7 +2189,9 @@ def predicate(ctx: Context) -> bool: raise NoPrivateMessage permissions = ctx.author.guild_permissions # type: ignore - missing = [perm for perm, value in perms.items() if getattr(permissions, perm) != value] + missing = [ + perm for perm, value in perms.items() if getattr(permissions, perm) != value + ] if not missing: return True @@ -2208,7 +2217,9 @@ def predicate(ctx: Context) -> bool: raise NoPrivateMessage permissions = ctx.me.guild_permissions # type: ignore - missing = [perm for perm, value in perms.items() if getattr(permissions, perm) != value] + missing = [ + perm for perm, value in perms.items() if getattr(permissions, perm) != value + ] if not missing: return True @@ -2286,7 +2297,9 @@ def is_nsfw() -> Callable[[T], T]: def pred(ctx: Context) -> bool: ch = ctx.channel - if ctx.guild is None or (isinstance(ch, (discord.TextChannel, discord.Thread)) and ch.is_nsfw()): + if ctx.guild is None or ( + isinstance(ch, (discord.TextChannel, discord.Thread)) and ch.is_nsfw() + ): return True raise NSFWChannelRequired(ch) # type: ignore @@ -2296,7 +2309,7 @@ def pred(ctx: Context) -> bool: def cooldown( rate: int, per: float, - type: Union[BucketType, Callable[[Message], Any]] = BucketType.default, + type: BucketType | Callable[[Message], Any] = BucketType.default, ) -> Callable[[T], T]: """A decorator that adds a cooldown to a command @@ -2312,7 +2325,7 @@ def cooldown( A command can only have a single cooldown. Parameters - ------------ + ---------- rate: :class:`int` The number of times a command can be used before triggering a cooldown. per: :class:`float` @@ -2324,7 +2337,7 @@ def cooldown( Callables are now supported for custom bucket types. """ - def decorator(func: Union[Command, CoroFunc]) -> Union[Command, CoroFunc]: + def decorator(func: Command | CoroFunc) -> Command | CoroFunc: if isinstance(func, (Command, ApplicationCommand)): func._buckets = CooldownMapping(Cooldown(rate, per), type) else: @@ -2335,7 +2348,7 @@ def decorator(func: Union[Command, CoroFunc]) -> Union[Command, CoroFunc]: def dynamic_cooldown( - cooldown: Union[BucketType, Callable[[Message], Any]], + cooldown: BucketType | Callable[[Message], Any], type: BucketType = BucketType.default, ) -> Callable[[T], T]: """A decorator that adds a dynamic cooldown to a command @@ -2359,7 +2372,7 @@ def dynamic_cooldown( .. versionadded:: 2.0 Parameters - ------------ + ---------- cooldown: Callable[[:class:`.discord.Message`], Optional[:class:`.Cooldown`]] A function that takes a message and returns a cooldown that will apply to this invocation or ``None`` if the cooldown should be bypassed. @@ -2369,7 +2382,7 @@ def dynamic_cooldown( if not callable(cooldown): raise TypeError("A callable must be provided") - def decorator(func: Union[Command, CoroFunc]) -> Union[Command, CoroFunc]: + def decorator(func: Command | CoroFunc) -> Command | CoroFunc: if isinstance(func, Command): func._buckets = DynamicCooldownMapping(cooldown, type) else: @@ -2379,7 +2392,9 @@ def decorator(func: Union[Command, CoroFunc]) -> Union[Command, CoroFunc]: return decorator # type: ignore -def max_concurrency(number: int, per: BucketType = BucketType.default, *, wait: bool = False) -> Callable[[T], T]: +def max_concurrency( + number: int, per: BucketType = BucketType.default, *, wait: bool = False +) -> Callable[[T], T]: """A decorator that adds a maximum concurrency to a command This enables you to only allow a certain number of command invocations at the same time, @@ -2390,7 +2405,7 @@ def max_concurrency(number: int, per: BucketType = BucketType.default, *, wait: .. versionadded:: 1.3 Parameters - ------------- + ---------- number: :class:`int` The maximum number of invocations of this command that can be running at the same time. per: :class:`.BucketType` @@ -2403,7 +2418,7 @@ def max_concurrency(number: int, per: BucketType = BucketType.default, *, wait: then the command waits until it can be executed. """ - def decorator(func: Union[Command, CoroFunc]) -> Union[Command, CoroFunc]: + def decorator(func: Command | CoroFunc) -> Command | CoroFunc: value = MaxConcurrency(number, per=per, wait=wait) if isinstance(func, (Command, ApplicationCommand)): func._max_concurrency = value @@ -2423,7 +2438,7 @@ def before_invoke(coro) -> Callable[[T], T]: .. versionadded:: 1.4 Example - --------- + ------- .. code-block:: python3 @@ -2453,7 +2468,7 @@ async def why(self, ctx): # Output: bot.add_cog(What()) """ - def decorator(func: Union[Command, CoroFunc]) -> Union[Command, CoroFunc]: + def decorator(func: Command | CoroFunc) -> Command | CoroFunc: if isinstance(func, Command): func.before_invoke(coro) else: @@ -2472,7 +2487,7 @@ def after_invoke(coro) -> Callable[[T], T]: .. versionadded:: 1.4 """ - def decorator(func: Union[Command, CoroFunc]) -> Union[Command, CoroFunc]: + def decorator(func: Command | CoroFunc) -> Command | CoroFunc: if isinstance(func, Command): func.after_invoke(coro) else: diff --git a/discord/ext/commands/errors.py b/discord/ext/commands/errors.py index 1b463a460f..955f7fab2a 100644 --- a/discord/ext/commands/errors.py +++ b/discord/ext/commands/errors.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple, Type, Union +from typing import TYPE_CHECKING, Any, Callable from discord.errors import ClientException, DiscordException @@ -107,10 +107,12 @@ class CommandError(DiscordException): from :class:`.Bot`\, :func:`.on_command_error`. """ - def __init__(self, message: Optional[str] = None, *args: Any) -> None: + def __init__(self, message: str | None = None, *args: Any) -> None: if message is not None: # clean-up @everyone and @here mentions - m = message.replace("@everyone", "@\u200beveryone").replace("@here", "@\u200bhere") + m = message.replace("@everyone", "@\u200beveryone").replace( + "@here", "@\u200bhere" + ) super().__init__(m, *args) else: super().__init__(*args) @@ -142,8 +144,6 @@ class UserInputError(CommandError): This inherits from :exc:`CommandError`. """ - pass - class CommandNotFound(CommandError): """Exception raised when a command is attempted to be invoked @@ -155,8 +155,6 @@ class CommandNotFound(CommandError): This inherits from :exc:`CommandError`. """ - pass - class MissingRequiredArgument(UserInputError): """Exception raised when parsing a command and a parameter @@ -165,7 +163,7 @@ class MissingRequiredArgument(UserInputError): This inherits from :exc:`UserInputError` Attributes - ----------- + ---------- param: :class:`inspect.Parameter` The argument that is missing. """ @@ -182,8 +180,6 @@ class TooManyArguments(UserInputError): This inherits from :exc:`UserInputError` """ - pass - class BadArgument(UserInputError): """Exception raised when a parsing or conversion failure is encountered @@ -192,8 +188,6 @@ class BadArgument(UserInputError): This inherits from :exc:`UserInputError` """ - pass - class CheckFailure(CommandError): """Exception raised when the predicates in :attr:`.Command.checks` have failed. @@ -201,8 +195,6 @@ class CheckFailure(CommandError): This inherits from :exc:`CommandError` """ - pass - class CheckAnyFailure(CheckFailure): """Exception raised when all predicates in :func:`check_any` fail. @@ -212,16 +204,18 @@ class CheckAnyFailure(CheckFailure): .. versionadded:: 1.3 Attributes - ------------ + ---------- errors: List[:class:`CheckFailure`] A list of errors that were caught during execution. checks: List[Callable[[:class:`Context`], :class:`bool`]] A list of check predicates that failed. """ - def __init__(self, checks: List[CheckFailure], errors: List[Callable[[Context], bool]]) -> None: - self.checks: List[CheckFailure] = checks - self.errors: List[Callable[[Context], bool]] = errors + def __init__( + self, checks: list[CheckFailure], errors: list[Callable[[Context], bool]] + ) -> None: + self.checks: list[CheckFailure] = checks + self.errors: list[Callable[[Context], bool]] = errors super().__init__("You do not have permission to run this command.") @@ -232,8 +226,10 @@ class PrivateMessageOnly(CheckFailure): This inherits from :exc:`CheckFailure` """ - def __init__(self, message: Optional[str] = None) -> None: - super().__init__(message or "This command can only be used in private messages.") + def __init__(self, message: str | None = None) -> None: + super().__init__( + message or "This command can only be used in private messages." + ) class NoPrivateMessage(CheckFailure): @@ -243,7 +239,7 @@ class NoPrivateMessage(CheckFailure): This inherits from :exc:`CheckFailure` """ - def __init__(self, message: Optional[str] = None) -> None: + def __init__(self, message: str | None = None) -> None: super().__init__(message or "This command cannot be used in private messages.") @@ -253,8 +249,6 @@ class NotOwner(CheckFailure): This inherits from :exc:`CheckFailure` """ - pass - class ObjectNotFound(BadArgument): """Exception raised when the argument provided did not match the format @@ -265,7 +259,7 @@ class ObjectNotFound(BadArgument): .. versionadded:: 2.0 Attributes - ----------- + ---------- argument: :class:`str` The argument supplied by the caller that was not matched """ @@ -284,7 +278,7 @@ class MemberNotFound(BadArgument): .. versionadded:: 1.5 Attributes - ----------- + ---------- argument: :class:`str` The member supplied by the caller that was not found """ @@ -302,7 +296,7 @@ class GuildNotFound(BadArgument): .. versionadded:: 1.7 Attributes - ----------- + ---------- argument: :class:`str` The guild supplied by the called that was not found """ @@ -321,7 +315,7 @@ class UserNotFound(BadArgument): .. versionadded:: 1.5 Attributes - ----------- + ---------- argument: :class:`str` The user supplied by the caller that was not found """ @@ -339,7 +333,7 @@ class MessageNotFound(BadArgument): .. versionadded:: 1.5 Attributes - ----------- + ---------- argument: :class:`str` The message supplied by the caller that was not found """ @@ -358,13 +352,13 @@ class ChannelNotReadable(BadArgument): .. versionadded:: 1.5 Attributes - ----------- + ---------- argument: Union[:class:`.abc.GuildChannel`, :class:`.Thread`] The channel supplied by the caller that was not readable """ - def __init__(self, argument: Union[GuildChannel, Thread]) -> None: - self.argument: Union[GuildChannel, Thread] = argument + def __init__(self, argument: GuildChannel | Thread) -> None: + self.argument: GuildChannel | Thread = argument super().__init__(f"Can't read messages in {argument.mention}.") @@ -376,7 +370,7 @@ class ChannelNotFound(BadArgument): .. versionadded:: 1.5 Attributes - ----------- + ---------- argument: :class:`str` The channel supplied by the caller that was not found """ @@ -394,7 +388,7 @@ class ThreadNotFound(BadArgument): .. versionadded:: 2.0 Attributes - ----------- + ---------- argument: :class:`str` The thread supplied by the caller that was not found """ @@ -412,7 +406,7 @@ class BadColourArgument(BadArgument): .. versionadded:: 1.5 Attributes - ----------- + ---------- argument: :class:`str` The colour supplied by the caller that was not valid """ @@ -433,7 +427,7 @@ class RoleNotFound(BadArgument): .. versionadded:: 1.5 Attributes - ----------- + ---------- argument: :class:`str` The role supplied by the caller that was not found """ @@ -464,7 +458,7 @@ class EmojiNotFound(BadArgument): .. versionadded:: 1.5 Attributes - ----------- + ---------- argument: :class:`str` The emoji supplied by the caller that was not found """ @@ -483,7 +477,7 @@ class PartialEmojiConversionFailure(BadArgument): .. versionadded:: 1.5 Attributes - ----------- + ---------- argument: :class:`str` The emoji supplied by the caller that did not match the regex """ @@ -501,7 +495,7 @@ class GuildStickerNotFound(BadArgument): .. versionadded:: 2.0 Attributes - ----------- + ---------- argument: :class:`str` The sticker supplied by the caller that was not found """ @@ -519,7 +513,7 @@ class BadBoolArgument(BadArgument): .. versionadded:: 1.5 Attributes - ----------- + ---------- argument: :class:`str` The boolean argument supplied by the caller that is not in the predefined list """ @@ -535,8 +529,6 @@ class DisabledCommand(CommandError): This inherits from :exc:`CommandError` """ - pass - class CommandInvokeError(CommandError): """Exception raised when the command being invoked raised an exception. @@ -544,7 +536,7 @@ class CommandInvokeError(CommandError): This inherits from :exc:`CommandError` Attributes - ----------- + ---------- original: :exc:`Exception` The original exception that was raised. You can also get this via the ``__cause__`` attribute. @@ -561,7 +553,7 @@ class CommandOnCooldown(CommandError): This inherits from :exc:`CommandError` Attributes - ----------- + ---------- cooldown: :class:`.Cooldown` A class with attributes ``rate`` and ``per`` similar to the :func:`.cooldown` decorator. @@ -571,7 +563,9 @@ class CommandOnCooldown(CommandError): The amount of seconds to wait before you can retry again. """ - def __init__(self, cooldown: Cooldown, retry_after: float, type: BucketType) -> None: + def __init__( + self, cooldown: Cooldown, retry_after: float, type: BucketType + ) -> None: self.cooldown: Cooldown = cooldown self.retry_after: float = retry_after self.type: BucketType = type @@ -584,7 +578,7 @@ class MaxConcurrencyReached(CommandError): This inherits from :exc:`CommandError`. Attributes - ------------ + ---------- number: :class:`int` The maximum number of concurrent invokers allowed. per: :class:`.BucketType` @@ -598,7 +592,9 @@ def __init__(self, number: int, per: BucketType) -> None: suffix = f"per {name}" if per.name != "default" else "globally" plural = "%s times %s" if number > 1 else "%s time %s" fmt = plural % (number, suffix) - super().__init__(f"Too many people are using this command. It can only be used {fmt} concurrently.") + super().__init__( + f"Too many people are using this command. It can only be used {fmt} concurrently." + ) class MissingRole(CheckFailure): @@ -609,7 +605,7 @@ class MissingRole(CheckFailure): .. versionadded:: 1.1 Attributes - ----------- + ---------- missing_role: Union[:class:`str`, :class:`int`] The required role that is missing. This is the parameter passed to :func:`~.commands.has_role`. @@ -629,7 +625,7 @@ class BotMissingRole(CheckFailure): .. versionadded:: 1.1 Attributes - ----------- + ---------- missing_role: Union[:class:`str`, :class:`int`] The required role that is missing. This is the parameter passed to :func:`~.commands.has_role`. @@ -650,7 +646,7 @@ class MissingAnyRole(CheckFailure): .. versionadded:: 1.1 Attributes - ----------- + ---------- missing_roles: List[Union[:class:`str`, :class:`int`]] The roles that the invoker is missing. These are the parameters passed to :func:`~.commands.has_any_role`. @@ -679,11 +675,10 @@ class BotMissingAnyRole(CheckFailure): .. versionadded:: 1.1 Attributes - ----------- + ---------- missing_roles: List[Union[:class:`str`, :class:`int`]] The roles that the bot's member is missing. These are the parameters passed to :func:`~.commands.has_any_role`. - """ def __init__(self, missing_roles: SnowflakeList) -> None: @@ -708,14 +703,16 @@ class NSFWChannelRequired(CheckFailure): .. versionadded:: 1.1 Parameters - ----------- + ---------- channel: Union[:class:`.abc.GuildChannel`, :class:`.Thread`] The channel that does not have NSFW enabled. """ - def __init__(self, channel: Union[GuildChannel, Thread]) -> None: - self.channel: Union[GuildChannel, Thread] = channel - super().__init__(f"Channel '{channel}' needs to be NSFW for this command to work.") + def __init__(self, channel: GuildChannel | Thread) -> None: + self.channel: GuildChannel | Thread = channel + super().__init__( + f"Channel '{channel}' needs to be NSFW for this command to work." + ) class MissingPermissions(CheckFailure): @@ -725,15 +722,18 @@ class MissingPermissions(CheckFailure): This inherits from :exc:`CheckFailure` Attributes - ----------- + ---------- missing_permissions: List[:class:`str`] The required permissions that are missing. """ - def __init__(self, missing_permissions: List[str], *args: Any) -> None: - self.missing_permissions: List[str] = missing_permissions + def __init__(self, missing_permissions: list[str], *args: Any) -> None: + self.missing_permissions: list[str] = missing_permissions - missing = [perm.replace("_", " ").replace("guild", "server").title() for perm in missing_permissions] + missing = [ + perm.replace("_", " ").replace("guild", "server").title() + for perm in missing_permissions + ] if len(missing) > 2: fmt = f"{', '.join(missing[:-1])}, and {missing[-1]}" @@ -750,15 +750,18 @@ class BotMissingPermissions(CheckFailure): This inherits from :exc:`CheckFailure` Attributes - ----------- + ---------- missing_permissions: List[:class:`str`] The required permissions that are missing. """ - def __init__(self, missing_permissions: List[str], *args: Any) -> None: - self.missing_permissions: List[str] = missing_permissions + def __init__(self, missing_permissions: list[str], *args: Any) -> None: + self.missing_permissions: list[str] = missing_permissions - missing = [perm.replace("_", " ").replace("guild", "server").title() for perm in missing_permissions] + missing = [ + perm.replace("_", " ").replace("guild", "server").title() + for perm in missing_permissions + ] if len(missing) > 2: fmt = f"{', '.join(missing[:-1])}, and {missing[-1]}" @@ -775,7 +778,7 @@ class BadUnionArgument(UserInputError): This inherits from :exc:`UserInputError` Attributes - ----------- + ---------- param: :class:`inspect.Parameter` The parameter that failed being converted. converters: Tuple[Type, ``...``] @@ -784,10 +787,12 @@ class BadUnionArgument(UserInputError): A list of errors that were caught from failing the conversion. """ - def __init__(self, param: Parameter, converters: Tuple[Type, ...], errors: List[CommandError]) -> None: + def __init__( + self, param: Parameter, converters: tuple[type, ...], errors: list[CommandError] + ) -> None: self.param: Parameter = param - self.converters: Tuple[Type, ...] = converters - self.errors: List[CommandError] = errors + self.converters: tuple[type, ...] = converters + self.errors: list[CommandError] = errors def _get_name(x): try: @@ -815,7 +820,7 @@ class BadLiteralArgument(UserInputError): .. versionadded:: 2.0 Attributes - ----------- + ---------- param: :class:`inspect.Parameter` The parameter that failed being converted. literals: Tuple[Any, ``...``] @@ -824,10 +829,12 @@ class BadLiteralArgument(UserInputError): A list of errors that were caught from failing the conversion. """ - def __init__(self, param: Parameter, literals: Tuple[Any, ...], errors: List[CommandError]) -> None: + def __init__( + self, param: Parameter, literals: tuple[Any, ...], errors: list[CommandError] + ) -> None: self.param: Parameter = param - self.literals: Tuple[Any, ...] = literals - self.errors: List[CommandError] = errors + self.literals: tuple[Any, ...] = literals + self.errors: list[CommandError] = errors to_string = [repr(l) for l in literals] if len(to_string) > 2: @@ -847,8 +854,6 @@ class ArgumentParsingError(UserInputError): i18n purposes. """ - pass - class UnexpectedQuoteError(ArgumentParsingError): """An exception raised when the parser encounters a quote mark inside a non-quoted string. @@ -856,7 +861,7 @@ class UnexpectedQuoteError(ArgumentParsingError): This inherits from :exc:`ArgumentParsingError`. Attributes - ------------ + ---------- quote: :class:`str` The quote mark that was found inside the non-quoted string. """ @@ -873,14 +878,16 @@ class InvalidEndOfQuotedStringError(ArgumentParsingError): This inherits from :exc:`ArgumentParsingError`. Attributes - ----------- + ---------- char: :class:`str` The character found instead of the expected string. """ def __init__(self, char: str) -> None: self.char: str = char - super().__init__(f"Expected space after closing quotation but received {char!r}") + super().__init__( + f"Expected space after closing quotation but received {char!r}" + ) class ExpectedClosingQuoteError(ArgumentParsingError): @@ -889,7 +896,7 @@ class ExpectedClosingQuoteError(ArgumentParsingError): This inherits from :exc:`ArgumentParsingError`. Attributes - ----------- + ---------- close_quote: :class:`str` The quote character expected. """ @@ -930,8 +937,6 @@ class FlagError(BadArgument): .. versionadded:: 2.0 """ - pass - class TooManyFlags(FlagError): """An exception raised when a flag has received too many values. @@ -941,17 +946,19 @@ class TooManyFlags(FlagError): .. versionadded:: 2.0 Attributes - ------------ + ---------- flag: :class:`~discord.ext.commands.Flag` The flag that received too many values. values: List[:class:`str`] The values that were passed. """ - def __init__(self, flag: Flag, values: List[str]) -> None: + def __init__(self, flag: Flag, values: list[str]) -> None: self.flag: Flag = flag - self.values: List[str] = values - super().__init__(f"Too many flag values, expected {flag.max_args} but received {len(values)}.") + self.values: list[str] = values + super().__init__( + f"Too many flag values, expected {flag.max_args} but received {len(values)}." + ) class BadFlagArgument(FlagError): @@ -962,7 +969,7 @@ class BadFlagArgument(FlagError): .. versionadded:: 2.0 Attributes - ----------- + ---------- flag: :class:`~discord.ext.commands.Flag` The flag that failed to convert. """ @@ -985,7 +992,7 @@ class MissingRequiredFlag(FlagError): .. versionadded:: 2.0 Attributes - ----------- + ---------- flag: :class:`~discord.ext.commands.Flag` The required flag that was not found. """ @@ -1003,7 +1010,7 @@ class MissingFlagArgument(FlagError): .. versionadded:: 2.0 Attributes - ----------- + ---------- flag: :class:`~discord.ext.commands.Flag` The flag that did not get a value. """ diff --git a/discord/ext/commands/flags.py b/discord/ext/commands/flags.py index 1bae2304ac..82183fb368 100644 --- a/discord/ext/commands/flags.py +++ b/discord/ext/commands/flags.py @@ -29,21 +29,7 @@ import re import sys from dataclasses import dataclass, field -from typing import ( - TYPE_CHECKING, - Any, - Dict, - Iterator, - List, - Literal, - Optional, - Pattern, - Set, - Tuple, - Type, - TypeVar, - Union, -) +from typing import TYPE_CHECKING, Any, Iterator, Literal, Pattern, TypeVar, Union from discord.utils import MISSING, maybe_coroutine, resolve_annotation @@ -77,7 +63,7 @@ class Flag: do so. These cannot be constructed manually. Attributes - ------------ + ---------- name: :class:`str` The name of the flag. aliases: List[:class:`str`] @@ -96,7 +82,7 @@ class Flag: """ name: str = MISSING - aliases: List[str] = field(default_factory=list) + aliases: list[str] = field(default_factory=list) attribute: str = MISSING annotation: Any = MISSING default: Any = MISSING @@ -116,7 +102,7 @@ def required(self) -> bool: def flag( *, name: str = MISSING, - aliases: List[str] = MISSING, + aliases: list[str] = MISSING, default: Any = MISSING, max_args: int = MISSING, override: bool = MISSING, @@ -125,7 +111,7 @@ def flag( class attributes. Parameters - ------------ + ---------- name: :class:`str` The flag name. If not given, defaults to the attribute name. aliases: List[:class:`str`] @@ -151,7 +137,7 @@ class attributes. ) -def validate_flag_name(name: str, forbidden: Set[str]): +def validate_flag_name(name: str, forbidden: set[str]): if not name: raise ValueError("flag names should not be empty") @@ -161,15 +147,19 @@ def validate_flag_name(name: str, forbidden: Set[str]): if ch == "\\": raise ValueError(f"flag name {name!r} cannot have backslashes") if ch in forbidden: - raise ValueError(f"flag name {name!r} cannot have any of {forbidden!r} within them") + raise ValueError( + f"flag name {name!r} cannot have any of {forbidden!r} within them" + ) -def get_flags(namespace: Dict[str, Any], globals: Dict[str, Any], locals: Dict[str, Any]) -> Dict[str, Flag]: +def get_flags( + namespace: dict[str, Any], globals: dict[str, Any], locals: dict[str, Any] +) -> dict[str, Flag]: annotations = namespace.get("__annotations__", {}) case_insensitive = namespace["__commands_flag_case_insensitive__"] - flags: Dict[str, Flag] = {} - cache: Dict[str, Any] = {} - names: Set[str] = set() + flags: dict[str, Flag] = {} + cache: dict[str, Any] = {} + names: set[str] = set() for name, annotation in annotations.items(): flag = namespace.pop(name, MISSING) if isinstance(flag, Flag): @@ -181,7 +171,9 @@ def get_flags(namespace: Dict[str, Any], globals: Dict[str, Any], locals: Dict[s if flag.name is MISSING: flag.name = name - annotation = flag.annotation = resolve_annotation(flag.annotation, globals, locals, cache) + annotation = flag.annotation = resolve_annotation( + flag.annotation, globals, locals, cache + ) if ( flag.default is MISSING @@ -238,7 +230,9 @@ def get_flags(namespace: Dict[str, Any], globals: Dict[str, Any], locals: Dict[s if flag.max_args is MISSING: flag.max_args = 1 else: - raise TypeError(f"Unsupported typing annotation {annotation!r} for {flag.name!r} flag") + raise TypeError( + f"Unsupported typing annotation {annotation!r} for {flag.name!r} flag" + ) if flag.override is MISSING: flag.override = False @@ -246,7 +240,9 @@ def get_flags(namespace: Dict[str, Any], globals: Dict[str, Any], locals: Dict[s # Validate flag names are unique name = flag.name.casefold() if case_insensitive else flag.name if name in names: - raise TypeError(f"{flag.name!r} flag conflicts with previous flag or alias.") + raise TypeError( + f"{flag.name!r} flag conflicts with previous flag or alias." + ) else: names.add(name) @@ -254,7 +250,9 @@ def get_flags(namespace: Dict[str, Any], globals: Dict[str, Any], locals: Dict[s # Validate alias is unique alias = alias.casefold() if case_insensitive else alias if alias in names: - raise TypeError(f"{flag.name!r} flag alias {alias!r} conflicts with previous flag or alias.") + raise TypeError( + f"{flag.name!r} flag alias {alias!r} conflicts with previous flag or alias." + ) else: names.add(alias) @@ -266,18 +264,18 @@ def get_flags(namespace: Dict[str, Any], globals: Dict[str, Any], locals: Dict[s class FlagsMeta(type): if TYPE_CHECKING: __commands_is_flag__: bool - __commands_flags__: Dict[str, Flag] - __commands_flag_aliases__: Dict[str, str] + __commands_flags__: dict[str, Flag] + __commands_flag_aliases__: dict[str, str] __commands_flag_regex__: Pattern[str] __commands_flag_case_insensitive__: bool __commands_flag_delimiter__: str __commands_flag_prefix__: str def __new__( - cls: Type[type], + cls: type[type], name: str, - bases: Tuple[type, ...], - attrs: Dict[str, Any], + bases: tuple[type, ...], + attrs: dict[str, Any], *, case_insensitive: bool = MISSING, delimiter: str = MISSING, @@ -302,18 +300,24 @@ def __new__( finally: del frame - flags: Dict[str, Flag] = {} - aliases: Dict[str, str] = {} + flags: dict[str, Flag] = {} + aliases: dict[str, str] = {} for base in reversed(bases): if base.__dict__.get("__commands_is_flag__", False): flags.update(base.__dict__["__commands_flags__"]) aliases.update(base.__dict__["__commands_flag_aliases__"]) if case_insensitive is MISSING: - attrs["__commands_flag_case_insensitive__"] = base.__dict__["__commands_flag_case_insensitive__"] + attrs["__commands_flag_case_insensitive__"] = base.__dict__[ + "__commands_flag_case_insensitive__" + ] if delimiter is MISSING: - attrs["__commands_flag_delimiter__"] = base.__dict__["__commands_flag_delimiter__"] + attrs["__commands_flag_delimiter__"] = base.__dict__[ + "__commands_flag_delimiter__" + ] if prefix is MISSING: - attrs["__commands_flag_prefix__"] = base.__dict__["__commands_flag_prefix__"] + attrs["__commands_flag_prefix__"] = base.__dict__[ + "__commands_flag_prefix__" + ] if case_insensitive is not MISSING: attrs["__commands_flag_case_insensitive__"] = case_insensitive @@ -339,7 +343,9 @@ def __new__( regex_flags = 0 if case_insensitive: flags = {key.casefold(): value for key, value in flags.items()} - aliases = {key.casefold(): value.casefold() for key, value in aliases.items()} + aliases = { + key.casefold(): value.casefold() for key, value in aliases.items() + } regex_flags = re.IGNORECASE keys = [re.escape(k) for k in flags] @@ -358,7 +364,9 @@ def __new__( return type.__new__(cls, name, bases, attrs) -async def tuple_convert_all(ctx: Context, argument: str, flag: Flag, converter: Any) -> Tuple[Any, ...]: +async def tuple_convert_all( + ctx: Context, argument: str, flag: Flag, converter: Any +) -> tuple[Any, ...]: view = StringView(argument) results = [] param: inspect.Parameter = ctx.current_parameter # type: ignore @@ -383,7 +391,9 @@ async def tuple_convert_all(ctx: Context, argument: str, flag: Flag, converter: return tuple(results) -async def tuple_convert_flag(ctx: Context, argument: str, flag: Flag, converters: Any) -> Tuple[Any, ...]: +async def tuple_convert_flag( + ctx: Context, argument: str, flag: Flag, converters: Any +) -> tuple[Any, ...]: view = StringView(argument) results = [] param: inspect.Parameter = ctx.current_parameter # type: ignore @@ -421,9 +431,13 @@ async def convert_flag(ctx, argument: str, flag: Flag, annotation: Any = None) - else: if origin is tuple: if annotation.__args__[-1] is Ellipsis: - return await tuple_convert_all(ctx, argument, flag, annotation.__args__[0]) + return await tuple_convert_all( + ctx, argument, flag, annotation.__args__[0] + ) else: - return await tuple_convert_flag(ctx, argument, flag, annotation.__args__) + return await tuple_convert_flag( + ctx, argument, flag, annotation.__args__ + ) elif origin is list: # typing.List[x] annotation = annotation.__args__[0] @@ -466,7 +480,7 @@ class FlagConverter(metaclass=FlagsMeta): .. versionadded:: 2.0 Parameters - ----------- + ---------- case_insensitive: :class:`bool` A class parameter to toggle case insensitivity of the flag parsing. If ``True`` then flags are parsed in a case-insensitive manner. @@ -480,7 +494,7 @@ class FlagConverter(metaclass=FlagsMeta): """ @classmethod - def get_flags(cls) -> Dict[str, Flag]: + def get_flags(cls) -> dict[str, Flag]: """Dict[:class:`str`, :class:`Flag`]: A mapping of flag name to flag object this converter has.""" return cls.__commands_flags__.copy() @@ -488,12 +502,12 @@ def get_flags(cls) -> Dict[str, Flag]: def _can_be_constructible(cls) -> bool: return all(not flag.required for flag in cls.__commands_flags__.values()) - def __iter__(self) -> Iterator[Tuple[str, Any]]: + def __iter__(self) -> Iterator[tuple[str, Any]]: for flag in self.__class__.__commands_flags__.values(): yield flag.name, getattr(self, flag.attribute) @classmethod - async def _construct_default(cls: Type[F], ctx: Context) -> F: + async def _construct_default(cls: type[F], ctx: Context) -> F: self: F = cls.__new__(cls) flags = cls.__commands_flags__ for flag in flags.values(): @@ -505,16 +519,21 @@ async def _construct_default(cls: Type[F], ctx: Context) -> F: return self def __repr__(self) -> str: - pairs = " ".join([f"{flag.attribute}={getattr(self, flag.attribute)!r}" for flag in self.get_flags().values()]) + pairs = " ".join( + [ + f"{flag.attribute}={getattr(self, flag.attribute)!r}" + for flag in self.get_flags().values() + ] + ) return f"<{self.__class__.__name__} {pairs}>" @classmethod - def parse_flags(cls, argument: str) -> Dict[str, List[str]]: - result: Dict[str, List[str]] = {} + def parse_flags(cls, argument: str) -> dict[str, list[str]]: + result: dict[str, list[str]] = {} flags = cls.__commands_flags__ aliases = cls.__commands_flag_aliases__ last_position = 0 - last_flag: Optional[Flag] = None + last_flag: Flag | None = None case_insensitive = cls.__commands_flag_case_insensitive__ for match in cls.__commands_flag_regex__.finditer(argument): @@ -559,7 +578,7 @@ def parse_flags(cls, argument: str) -> Dict[str, List[str]]: return result @classmethod - async def convert(cls: Type[F], ctx: Context, argument: str) -> F: + async def convert(cls: type[F], ctx: Context, argument: str) -> F: """|coro| The method that actually converters an argument to the flag mapping. @@ -573,17 +592,17 @@ async def convert(cls: Type[F], ctx: Context, argument: str) -> F: argument: :class:`str` The argument to convert from. + Returns + ------- + :class:`FlagConverter` + The flag converter instance with all flags parsed. + Raises - -------- + ------ FlagError A flag related parsing error. CommandError A command related error. - - Returns - -------- - :class:`FlagConverter` - The flag converter instance with all flags parsed. """ arguments = cls.parse_flags(argument) flags = cls.__commands_flags__ @@ -605,7 +624,7 @@ async def convert(cls: Type[F], ctx: Context, argument: str) -> F: if 0 < flag.max_args < len(values): if flag.override: - values = values[-flag.max_args:] + values = values[-flag.max_args :] else: raise TooManyFlags(flag, values) diff --git a/discord/ext/commands/help.py b/discord/ext/commands/help.py index 6ff39e6ac9..15228d9f73 100644 --- a/discord/ext/commands/help.py +++ b/discord/ext/commands/help.py @@ -76,7 +76,7 @@ class Paginator: Returns the total number of characters in the paginator. Attributes - ----------- + ---------- prefix: :class:`str` The prefix inserted to every page. e.g. three backticks. suffix: :class:`str` @@ -124,7 +124,7 @@ def add_line(self, line="", *, empty=False): is raised. Parameters - ----------- + ---------- line: :class:`str` The line to add. empty: :class:`bool` @@ -135,11 +135,16 @@ def add_line(self, line="", *, empty=False): RuntimeError The line was too big for the current :attr:`max_size`. """ - max_page_size = self.max_size - self._prefix_len - self._suffix_len - 2 * self._linesep_len + max_page_size = ( + self.max_size - self._prefix_len - self._suffix_len - 2 * self._linesep_len + ) if len(line) > max_page_size: raise RuntimeError(f"Line exceeds maximum page size {max_page_size}") - if self._count + len(line) + self._linesep_len > self.max_size - self._suffix_len: + if ( + self._count + len(line) + self._linesep_len + > self.max_size - self._suffix_len + ): self.close_page() self._count += len(line) + self._linesep_len @@ -175,8 +180,10 @@ def pages(self): return self._pages def __repr__(self): - return (f"") + return ( + f"" + ) def _not_overridden(f): @@ -391,13 +398,17 @@ def invoked_with(self): then it returns the internal command name of the help command. Returns - --------- + ------- :class:`str` The command name that triggered this invocation. """ command_name = self._command_impl.name ctx = self.context - if ctx is None or ctx.command is None or ctx.command.qualified_name != command_name: + if ( + ctx is None + or ctx.command is None + or ctx.command.qualified_name != command_name + ): return command_name return ctx.invoked_with @@ -405,12 +416,12 @@ def get_command_signature(self, command): """Retrieves the signature portion of the help page. Parameters - ------------ + ---------- command: :class:`Command` The command to get the signature of. Returns - -------- + ------- :class:`str` The signature for the command. """ @@ -463,7 +474,7 @@ def cog(self): To unbind the cog from the help command, you can set it to ``None``. Returns - -------- + ------- Optional[:class:`Cog`] The cog that is currently set for the help command. """ @@ -487,13 +498,13 @@ def command_not_found(self, string): Defaults to ``No command called {0} found.`` Parameters - ------------ + ---------- string: :class:`str` The string that contains the invalid command. Note that this has had mentions removed to prevent abuse. Returns - --------- + ------- :class:`str` The string to use when a command has not been found. """ @@ -513,7 +524,7 @@ def subcommand_not_found(self, command, string): - If the ``command`` parameter has subcommands but not one named ``string``. Parameters - ------------ + ---------- command: :class:`Command` The command that did not have the subcommand requested. string: :class:`str` @@ -521,12 +532,14 @@ def subcommand_not_found(self, command, string): had mentions removed to prevent abuse. Returns - --------- + ------- :class:`str` The string to use when the command did not have the subcommand requested. """ if isinstance(command, Group) and len(command.all_commands) > 0: - return f'Command "{command.qualified_name}" has no subcommand named {string}' + return ( + f'Command "{command.qualified_name}" has no subcommand named {string}' + ) return f'Command "{command.qualified_name}" has no subcommands.' async def filter_commands(self, commands, *, sort=False, key=None): @@ -538,7 +551,7 @@ async def filter_commands(self, commands, *, sort=False, key=None): attributes. Parameters - ------------ + ---------- commands: Iterable[:class:`Command`] An iterable of commands that are getting filtered. sort: :class:`bool` @@ -549,7 +562,7 @@ async def filter_commands(self, commands, *, sort=False, key=None): passed as ``True`` then this will default as the command name. Returns - --------- + ------- List[:class:`Command`] A list of commands that passed the filter. """ @@ -559,9 +572,15 @@ async def filter_commands(self, commands, *, sort=False, key=None): # Ignore Application Commands because they don't have hidden/docs prefix_commands = [ - command for command in commands if not isinstance(command, discord.commands.ApplicationCommand) + command + for command in commands + if not isinstance(command, discord.commands.ApplicationCommand) ] - iterator = prefix_commands if self.show_hidden else filter(lambda c: not c.hidden, prefix_commands) + iterator = ( + prefix_commands + if self.show_hidden + else filter(lambda c: not c.hidden, prefix_commands) + ) if self.verify_checks is False: # if we do not need to verify the checks then we can just @@ -593,12 +612,12 @@ def get_max_size(self, commands): """Returns the largest name length of the specified command list. Parameters - ------------ + ---------- commands: Sequence[:class:`Command`] A sequence of commands to check for the largest size. Returns - -------- + ------- :class:`int` The maximum width of the commands. """ @@ -636,7 +655,7 @@ async def send_error_message(self, error): You can access the invocation context with :attr:`HelpCommand.context`. Parameters - ------------ + ---------- error: :class:`str` The error message to display to the user. Note that this has had mentions removed to prevent abuse. @@ -657,13 +676,12 @@ async def on_help_command_error(self, ctx, error): error handlers. Parameters - ------------ + ---------- ctx: :class:`Context` The invocation context. error: :class:`CommandError` The error that was raised. """ - pass async def send_bot_help(self, mapping): """|coro| @@ -686,7 +704,7 @@ async def send_bot_help(self, mapping): you will have to call :meth:`filter_commands` yourself. Parameters - ------------ + ---------- mapping: Mapping[Optional[:class:`Cog`], List[:class:`Command`]] A mapping of cogs to commands that have been requested by the user for help. The key of the mapping is the :class:`~.commands.Cog` that the command belongs to, or @@ -716,7 +734,7 @@ async def send_cog_help(self, cog): :meth:`filter_commands` yourself. Parameters - ----------- + ---------- cog: :class:`Cog` The cog that was requested for help. """ @@ -744,7 +762,7 @@ async def send_group_help(self, group): filtering you will have to call :meth:`filter_commands` yourself. Parameters - ----------- + ---------- group: :class:`Group` The group that was requested for help. """ @@ -782,7 +800,7 @@ async def send_command_help(self, command): these to help you get started to get the output that you want. Parameters - ----------- + ---------- command: :class:`Command` The command that was requested for help. """ @@ -804,13 +822,12 @@ async def prepare_help_command(self, ctx, command=None): the usual rules that happen inside apply here as well. Parameters - ----------- + ---------- ctx: :class:`Context` The invocation context. command: Optional[:class:`str`] The argument passed to the help command. """ - pass async def command_callback(self, ctx, *, command=None): """|coro| @@ -852,18 +869,24 @@ async def command_callback(self, ctx, *, command=None): keys = command.split(" ") cmd = bot.all_commands.get(keys[0]) if cmd is None: - string = await maybe_coro(self.command_not_found, self.remove_mentions(keys[0])) + string = await maybe_coro( + self.command_not_found, self.remove_mentions(keys[0]) + ) return await self.send_error_message(string) for key in keys[1:]: try: found = cmd.all_commands.get(key) except AttributeError: - string = await maybe_coro(self.subcommand_not_found, cmd, self.remove_mentions(key)) + string = await maybe_coro( + self.subcommand_not_found, cmd, self.remove_mentions(key) + ) return await self.send_error_message(string) else: if found is None: - string = await maybe_coro(self.subcommand_not_found, cmd, self.remove_mentions(key)) + string = await maybe_coro( + self.subcommand_not_found, cmd, self.remove_mentions(key) + ) return await self.send_error_message(string) cmd = found @@ -881,7 +904,7 @@ class DefaultHelpCommand(HelpCommand): It extends it with the following attributes. Attributes - ------------ + ---------- width: :class:`int` The maximum number of characters that fit in a line. Defaults to 80. @@ -949,7 +972,7 @@ def add_indented_commands(self, commands, *, heading, max_size=None): to fit into the :attr:`width`. Parameters - ----------- + ---------- commands: Sequence[:class:`Command`] A list of commands to indent for output. heading: :class:`str` @@ -984,7 +1007,7 @@ def add_command_formatting(self, command): """A utility function to format the non-indented block of commands and groups. Parameters - ------------ + ---------- command: :class:`Command` The command to format. """ @@ -1036,7 +1059,11 @@ def get_category(command, *, no_category=no_category): # Now we can add the commands to the page. for category, commands in to_iterate: - commands = sorted(commands, key=lambda c: c.name) if self.sort_commands else list(commands) + commands = ( + sorted(commands, key=lambda c: c.name) + if self.sort_commands + else list(commands) + ) self.add_indented_commands(commands, heading=category, max_size=max_size) note = self.get_ending_note() @@ -1069,7 +1096,9 @@ async def send_cog_help(self, cog): if cog.description: self.paginator.add_line(cog.description, empty=True) - filtered = await self.filter_commands(cog.get_commands(), sort=self.sort_commands) + filtered = await self.filter_commands( + cog.get_commands(), sort=self.sort_commands + ) self.add_indented_commands(filtered, heading=self.commands_heading) note = self.get_ending_note() @@ -1086,7 +1115,7 @@ class MinimalHelpCommand(HelpCommand): This inherits from :class:`HelpCommand`. Attributes - ------------ + ---------- sort_commands: :class:`bool` Whether to sort the commands in the output alphabetically. Defaults to ``True``. commands_heading: :class:`str` @@ -1152,7 +1181,9 @@ def get_opening_note(self): ) def get_command_signature(self, command): - return f"{self.context.clean_prefix}{command.qualified_name} {command.signature}" + return ( + f"{self.context.clean_prefix}{command.qualified_name} {command.signature}" + ) def get_ending_note(self): """Return the help command's ending note. This is mainly useful to override for i18n purposes. @@ -1175,7 +1206,7 @@ def add_bot_commands_formatting(self, commands, heading): by commands separated by an EN SPACE (U+2002) in the next line. Parameters - ----------- + ---------- commands: Sequence[:class:`Command`] A list of commands that belong to the heading. heading: :class:`str` @@ -1196,12 +1227,16 @@ def add_subcommand_formatting(self, command): optionally followed by an En dash and the command's :attr:`Command.short_doc`. Parameters - ----------- + ---------- command: :class:`Command` The command to show information of. """ fmt = "{0}{1} \N{EN DASH} {2}" if command.short_doc else "{0}{1}" - self.paginator.add_line(fmt.format(self.context.clean_prefix, command.qualified_name, command.short_doc)) + self.paginator.add_line( + fmt.format( + self.context.clean_prefix, command.qualified_name, command.short_doc + ) + ) def add_aliases_formatting(self, aliases): """Adds the formatting information on a command's aliases. @@ -1214,17 +1249,19 @@ def add_aliases_formatting(self, aliases): This is not called if there are no aliases to format. Parameters - ----------- + ---------- aliases: Sequence[:class:`str`] A list of aliases to format. """ - self.paginator.add_line(f'**{self.aliases_heading}** {", ".join(aliases)}', empty=True) + self.paginator.add_line( + f'**{self.aliases_heading}** {", ".join(aliases)}', empty=True + ) def add_command_formatting(self, command): """A utility function to format commands and groups. Parameters - ------------ + ---------- command: :class:`Command` The command to format. """ @@ -1281,7 +1318,11 @@ def get_category(command, *, no_category=no_category): to_iterate = itertools.groupby(filtered, key=get_category) for category, commands in to_iterate: - commands = sorted(commands, key=lambda c: c.name) if self.sort_commands else list(commands) + commands = ( + sorted(commands, key=lambda c: c.name) + if self.sort_commands + else list(commands) + ) self.add_bot_commands_formatting(commands, category) note = self.get_ending_note() @@ -1303,7 +1344,9 @@ async def send_cog_help(self, cog): if cog.description: self.paginator.add_line(cog.description, empty=True) - filtered = await self.filter_commands(cog.get_commands(), sort=self.sort_commands) + filtered = await self.filter_commands( + cog.get_commands(), sort=self.sort_commands + ) if filtered: self.paginator.add_line(f"**{cog.qualified_name} {self.commands_heading}**") for command in filtered: diff --git a/discord/ext/commands/view.py b/discord/ext/commands/view.py index 20408a365b..2b4ff7ab11 100644 --- a/discord/ext/commands/view.py +++ b/discord/ext/commands/view.py @@ -94,7 +94,7 @@ def skip_string(self, string): return False def read_rest(self): - result = self.buffer[self.index:] + result = self.buffer[self.index :] self.previous = self.index self.index = self.end return result diff --git a/discord/ext/pages/pagination.py b/discord/ext/pages/pagination.py index e2ecbb31e3..07e2b7afb8 100644 --- a/discord/ext/pages/pagination.py +++ b/discord/ext/pages/pagination.py @@ -94,7 +94,7 @@ async def callback(self, interaction: discord.Interaction): The coroutine that is called when the navigation button is clicked. Parameters - ----------- + ---------- interaction: :class:`discord.Interaction` The interaction created by clicking the navigation button. """ @@ -106,13 +106,18 @@ async def callback(self, interaction: discord.Interaction): else: self.paginator.current_page -= 1 elif self.button_type == "next": - if self.paginator.loop_pages and self.paginator.current_page == self.paginator.page_count: + if ( + self.paginator.loop_pages + and self.paginator.current_page == self.paginator.page_count + ): self.paginator.current_page = 0 else: self.paginator.current_page += 1 elif self.button_type == "last": self.paginator.current_page = self.paginator.page_count - await self.paginator.goto_page(page_number=self.paginator.current_page, interaction=interaction) + await self.paginator.goto_page( + page_number=self.paginator.current_page, interaction=interaction + ) class Page: @@ -141,7 +146,9 @@ def __init__( **kwargs, ): if content is None and embeds is None: - raise discord.InvalidArgument("A page cannot have both content and embeds equal to None.") + raise discord.InvalidArgument( + "A page cannot have both content and embeds equal to None." + ) self._content = content self._embeds = embeds or [] self._custom_view = custom_view @@ -157,11 +164,11 @@ async def callback(self, interaction: Optional[discord.Interaction] = None): interaction: Optional[:class:`discord.Interaction`] The interaction associated with the callback, if any. """ - pass def update_files(self) -> Optional[List[discord.File]]: """Updates the files associated with the page by re-uploading them. - Typically used when the page is changed.""" + Typically used when the page is changed. + """ for file in self._files: with open(file.fp.name, "rb") as fp: # type: ignore self._files[self._files.index(file)] = discord.File( @@ -223,7 +230,6 @@ class PageGroup: If multiple :class:`PageGroup` objects have different options, they should all be set explicitly when creating each instance. - Parameters ---------- pages: Union[List[:class:`str`], List[:class:`Page`], List[Union[List[:class:`discord.Embed`], :class:`discord.Embed`]]] @@ -266,7 +272,9 @@ class PageGroup: def __init__( self, - pages: Union[List[str], List[Page], List[Union[List[discord.Embed], discord.Embed]]], + pages: Union[ + List[str], List[Page], List[Union[List[discord.Embed], discord.Embed]] + ], label: str, description: Optional[str] = None, emoji: Union[str, discord.Emoji, discord.PartialEmoji] = None, @@ -286,7 +294,9 @@ def __init__( self.label = label self.description: Optional[str] = description self.emoji: Union[str, discord.Emoji, discord.PartialEmoji] = emoji - self.pages: Union[List[str], List[Union[List[discord.Embed], discord.Embed]]] = pages + self.pages: Union[ + List[str], List[Union[List[discord.Embed], discord.Embed]] + ] = pages self.default: Optional[bool] = default self.show_disabled = show_disabled self.show_indicator = show_indicator @@ -365,7 +375,12 @@ class Paginator(discord.ui.View): def __init__( self, - pages: Union[List[PageGroup], List[Page], List[str], List[Union[List[discord.Embed], discord.Embed]]], + pages: Union[ + List[PageGroup], + List[Page], + List[str], + List[Union[List[discord.Embed], discord.Embed]], + ], show_disabled: bool = True, show_indicator=True, show_menu=False, @@ -383,7 +398,10 @@ def __init__( super().__init__(timeout=timeout) self.timeout: float = timeout self.pages: Union[ - List[PageGroup], List[str], List[Page], List[Union[List[discord.Embed], discord.Embed]] + List[PageGroup], + List[str], + List[Page], + List[Union[List[discord.Embed], discord.Embed]], ] = pages self.current_page = 0 self.menu: Optional[PaginatorMenu] = None @@ -400,7 +418,9 @@ def __init__( if pg.default: self.default_page_group = self.page_groups.index(pg) break - self.pages: List[Page] = self.get_page_group_content(self.page_groups[self.default_page_group]) + self.pages: List[Page] = self.get_page_group_content( + self.page_groups[self.default_page_group] + ) self.page_count = max(len(self.pages) - 1, 0) self.buttons = {} @@ -430,7 +450,12 @@ def __init__( async def update( self, pages: Optional[ - Union[List[PageGroup], List[Page], List[str], List[Union[List[discord.Embed], discord.Embed]]] + Union[ + List[PageGroup], + List[Page], + List[str], + List[Union[List[discord.Embed], discord.Embed]], + ] ] = None, show_disabled: Optional[bool] = None, show_indicator: Optional[bool] = None, @@ -489,7 +514,12 @@ async def update( """ # Update pages and reset current_page to 0 (default) - self.pages: Union[List[PageGroup], List[str], List[Page], List[Union[List[discord.Embed], discord.Embed]]] = ( + self.pages: Union[ + List[PageGroup], + List[str], + List[Page], + List[Union[List[discord.Embed], discord.Embed]], + ] = ( pages if pages is not None else self.pages ) self.show_menu = show_menu if show_menu is not None else self.show_menu @@ -501,21 +531,45 @@ async def update( if pg.default: self.default_page_group = self.page_groups.index(pg) break - self.pages: List[Page] = self.get_page_group_content(self.page_groups[self.default_page_group]) + self.pages: List[Page] = self.get_page_group_content( + self.page_groups[self.default_page_group] + ) self.page_count = max(len(self.pages) - 1, 0) self.current_page = 0 # Apply config changes, if specified - self.show_disabled = show_disabled if show_disabled is not None else self.show_disabled - self.show_indicator = show_indicator if show_indicator is not None else self.show_indicator + self.show_disabled = ( + show_disabled if show_disabled is not None else self.show_disabled + ) + self.show_indicator = ( + show_indicator if show_indicator is not None else self.show_indicator + ) self.usercheck = author_check if author_check is not None else self.usercheck - self.menu_placeholder = menu_placeholder if menu_placeholder is not None else self.menu_placeholder - self.disable_on_timeout = disable_on_timeout if disable_on_timeout is not None else self.disable_on_timeout - self.use_default_buttons = use_default_buttons if use_default_buttons is not None else self.use_default_buttons - self.default_button_row = default_button_row if default_button_row is not None else self.default_button_row + self.menu_placeholder = ( + menu_placeholder if menu_placeholder is not None else self.menu_placeholder + ) + self.disable_on_timeout = ( + disable_on_timeout + if disable_on_timeout is not None + else self.disable_on_timeout + ) + self.use_default_buttons = ( + use_default_buttons + if use_default_buttons is not None + else self.use_default_buttons + ) + self.default_button_row = ( + default_button_row + if default_button_row is not None + else self.default_button_row + ) self.loop_pages = loop_pages if loop_pages is not None else self.loop_pages self.custom_view: discord.ui.View = None if custom_view is None else custom_view self.timeout: float = timeout if timeout is not None else self.timeout - self.trigger_on_display = trigger_on_display if trigger_on_display is not None else self.trigger_on_display + self.trigger_on_display = ( + trigger_on_display + if trigger_on_display is not None + else self.trigger_on_display + ) if custom_buttons and not self.use_default_buttons: self.buttons = {} for button in custom_buttons: @@ -543,7 +597,9 @@ async def on_timeout(self) -> None: async def disable( self, include_custom: bool = False, - page: Optional[Union[str, Page, Union[List[discord.Embed], discord.Embed]]] = None, + page: Optional[ + Union[str, Page, Union[List[discord.Embed], discord.Embed]] + ] = None, ) -> None: """Stops the paginator, disabling all of its components. @@ -556,7 +612,11 @@ async def disable( """ page = self.get_page_content(page) for item in self.children: - if include_custom or not self.custom_view or item not in self.custom_view.children: + if ( + include_custom + or not self.custom_view + or item not in self.custom_view.children + ): item.disabled = True if page: await self.message.edit( @@ -570,7 +630,9 @@ async def disable( async def cancel( self, include_custom: bool = False, - page: Optional[Union[str, Page, Union[List[discord.Embed], discord.Embed]]] = None, + page: Optional[ + Union[str, Page, Union[List[discord.Embed], discord.Embed]] + ] = None, ) -> None: """Cancels the paginator, removing all of its components from the message. @@ -584,7 +646,11 @@ async def cancel( items = self.children.copy() page = self.get_page_content(page) for item in items: - if include_custom or not self.custom_view or item not in self.custom_view.children: + if ( + include_custom + or not self.custom_view + or item not in self.custom_view.children + ): self.remove_item(item) if page: await self.message.edit( @@ -595,7 +661,9 @@ async def cancel( else: await self.message.edit(view=self) - async def goto_page(self, page_number: int = 0, *, interaction: Optional[discord.Interaction] = None) -> None: + async def goto_page( + self, page_number: int = 0, *, interaction: Optional[discord.Interaction] = None + ) -> None: """Updates the paginator message to show the specified page number. Parameters @@ -620,7 +688,9 @@ async def goto_page(self, page_number: int = 0, *, interaction: Optional[discord self.update_buttons() self.current_page = page_number if self.show_indicator: - self.buttons["page_indicator"]["object"].label = f"{self.current_page + 1}/{self.page_count + 1}" + self.buttons["page_indicator"][ + "object" + ].label = f"{self.current_page + 1}/{self.page_count + 1}" page = self.pages[page_number] page = self.get_page_content(page) @@ -664,7 +734,8 @@ def add_menu(self): def add_default_buttons(self): """Adds the full list of default buttons that can be used with the paginator. - Includes ``first``, ``prev``, ``page_indicator``, ``next``, and ``last``.""" + Includes ``first``, ``prev``, ``page_indicator``, ``next``, and ``last``. + """ default_buttons = [ PaginatorButton( "first", @@ -719,7 +790,9 @@ def add_button(self, button: PaginatorButton): ), "label": button.label, "loop_label": button.loop_label, - "hidden": button.disabled if button.button_type != "page_indicator" else not self.show_indicator, + "hidden": button.disabled + if button.button_type != "page_indicator" + else not self.show_indicator, } self.buttons[button.button_type]["object"].callback = button.callback button.paginator = self @@ -727,7 +800,9 @@ def add_button(self, button: PaginatorButton): def remove_button(self, button_type: str): """Removes a :class:`PaginatorButton` from the paginator.""" if button_type not in self.buttons.keys(): - raise ValueError(f"no button_type {button_type} was found in this paginator.") + raise ValueError( + f"no button_type {button_type} was found in this paginator." + ) self.buttons.pop(button_type) def update_buttons(self) -> Dict: @@ -771,7 +846,9 @@ def update_buttons(self) -> Dict: button["object"].label = button["label"] self.clear_items() if self.show_indicator: - self.buttons["page_indicator"]["object"].label = f"{self.current_page + 1}/{self.page_count + 1}" + self.buttons["page_indicator"][ + "object" + ].label = f"{self.current_page + 1}/{self.page_count + 1}" for key, button in self.buttons.items(): if key != "page_indicator": if button["hidden"]: @@ -807,7 +884,9 @@ def get_page_group_content(self, page_group: PageGroup) -> List[Page]: return [self.get_page_content(page) for page in page_group.pages] @staticmethod - def get_page_content(page: Union[Page, str, discord.Embed, List[discord.Embed]]) -> Page: + def get_page_content( + page: Union[Page, str, discord.Embed, List[discord.Embed]] + ) -> Page: """Converts a page into a :class:`Page` object based on its content.""" if isinstance(page, Page): return page @@ -829,7 +908,9 @@ def get_page_content(page: Union[Page, str, discord.Embed, List[discord.Embed]]) "Page content must be a Page object, string, an embed, a list of embeds, a file, or a list of files." ) - async def page_action(self, interaction: Optional[discord.Interaction] = None) -> None: + async def page_action( + self, interaction: Optional[discord.Interaction] = None + ) -> None: """Triggers the callback associated with the current page, if any. Parameters @@ -838,14 +919,18 @@ async def page_action(self, interaction: Optional[discord.Interaction] = None) - The interaction that was used to trigger the page action. """ if self.get_page_content(self.pages[self.current_page]).callback: - await self.get_page_content(self.pages[self.current_page]).callback(interaction=interaction) + await self.get_page_content(self.pages[self.current_page]).callback( + interaction=interaction + ) async def send( self, ctx: Context, target: Optional[discord.abc.Messageable] = None, target_message: Optional[str] = None, - reference: Optional[Union[discord.Message, discord.MessageReference, discord.PartialMessage]] = None, + reference: Optional[ + Union[discord.Message, discord.MessageReference, discord.PartialMessage] + ] = None, allowed_mentions: Optional[discord.AllowedMentions] = None, mention_author: Optional[bool] = None, delete_after: Optional[float] = None, @@ -853,7 +938,7 @@ async def send( """Sends a message with the paginated items. Parameters - ------------ + ---------- ctx: Union[:class:`~discord.ext.commands.Context`] A command's invocation context. target: Optional[:class:`~discord.abc.Messageable`] @@ -879,7 +964,7 @@ async def send( If set, deletes the paginator after the specified time. Returns - -------- + ------- :class:`~discord.Message` The message that was sent with the paginator. """ @@ -890,12 +975,19 @@ async def send( raise TypeError(f"expected abc.Messageable not {target.__class__!r}") if reference is not None and not isinstance( - reference, (discord.Message, discord.MessageReference, discord.PartialMessage) + reference, + (discord.Message, discord.MessageReference, discord.PartialMessage), ): - raise TypeError(f"expected Message, MessageReference, or PartialMessage not {reference.__class__!r}") + raise TypeError( + f"expected Message, MessageReference, or PartialMessage not {reference.__class__!r}" + ) - if allowed_mentions is not None and not isinstance(allowed_mentions, discord.AllowedMentions): - raise TypeError(f"expected AllowedMentions not {allowed_mentions.__class__!r}") + if allowed_mentions is not None and not isinstance( + allowed_mentions, discord.AllowedMentions + ): + raise TypeError( + f"expected AllowedMentions not {allowed_mentions.__class__!r}" + ) if mention_author is not None and not isinstance(mention_author, bool): raise TypeError(f"expected bool not {mention_author.__class__!r}") @@ -945,9 +1037,8 @@ async def edit( If invoked from an interaction, you will still need to respond to the interaction. - Parameters - ----------- + ---------- message: :class:`discord.Message` The message to edit with the paginator. suppress: :class:`bool` @@ -966,7 +1057,7 @@ async def edit( If set, deletes the paginator after the specified time. Returns - -------- + ------- Optional[:class:`discord.Message`] The message that was edited. Returns ``None`` if the operation failed. """ @@ -975,7 +1066,9 @@ async def edit( self.update_buttons() - page: Union[Page, str, discord.Embed, List[discord.Embed]] = self.pages[self.current_page] + page: Union[Page, str, discord.Embed, List[discord.Embed]] = self.pages[ + self.current_page + ] page_content: Page = self.get_page_content(page) if page_content.custom_view: @@ -1009,7 +1102,7 @@ async def respond( """Sends an interaction response or followup with the paginated items. Parameters - ------------ + ---------- interaction: Union[:class:`discord.Interaction`, :class:`BridgeContext`] The interaction or BridgeContext which invoked the paginator. If passing a BridgeContext object, you cannot make this an ephemeral paginator. @@ -1029,13 +1122,15 @@ async def respond( The content of the interaction response shown when the paginator message is sent elsewhere. Returns - -------- + ------- Union[:class:`~discord.Message`, :class:`~discord.WebhookMessage`] The :class:`~discord.Message` or :class:`~discord.WebhookMessage` that was sent with the paginator. """ if not isinstance(interaction, (discord.Interaction, BridgeContext)): - raise TypeError(f"expected Interaction or BridgeContext, not {interaction.__class__!r}") + raise TypeError( + f"expected Interaction or BridgeContext, not {interaction.__class__!r}" + ) if target is not None and not isinstance(target, discord.abc.Messageable): raise TypeError(f"expected abc.Messageable not {target.__class__!r}") @@ -1047,7 +1142,9 @@ async def respond( self.update_buttons() - page: Union[Page, str, discord.Embed, List[discord.Embed]] = self.pages[self.current_page] + page: Union[Page, str, discord.Embed, List[discord.Embed]] = self.pages[ + self.current_page + ] page_content: Page = self.get_page_content(page) if page_content.custom_view: @@ -1057,7 +1154,9 @@ async def respond( self.user = interaction.user if target: - await interaction.response.send_message(target_message, ephemeral=ephemeral) + await interaction.response.send_message( + target_message, ephemeral=ephemeral + ) msg = await target.send( content=page_content.content, embeds=page_content.embeds, @@ -1142,7 +1241,13 @@ def __init__( ) for page_group in self.page_groups ] - super().__init__(placeholder=placeholder, max_values=1, min_values=1, options=opts, custom_id=custom_id) + super().__init__( + placeholder=placeholder, + max_values=1, + min_values=1, + options=opts, + custom_id=custom_id, + ) async def callback(self, interaction: discord.Interaction): """|coro| @@ -1150,7 +1255,7 @@ async def callback(self, interaction: discord.Interaction): The coroutine that is called when a menu option is selected. Parameters - ----------- + ---------- interaction: :class:`discord.Interaction` The interaction created by selecting the menu option. """ diff --git a/discord/ext/tasks/__init__.py b/discord/ext/tasks/__init__.py index 63068c73d3..df5318b99f 100644 --- a/discord/ext/tasks/__init__.py +++ b/discord/ext/tasks/__init__.py @@ -31,18 +31,7 @@ import sys import traceback from collections.abc import Sequence -from typing import ( - Any, - Awaitable, - Callable, - Generic, - List, - Optional, - Type, - TypeVar, - Union, - cast, -) +from typing import Any, Awaitable, Callable, Generic, TypeVar, cast import aiohttp @@ -62,7 +51,9 @@ class SleepHandle: __slots__ = ("future", "loop", "handle") - def __init__(self, dt: datetime.datetime, *, loop: asyncio.AbstractEventLoop) -> None: + def __init__( + self, dt: datetime.datetime, *, loop: asyncio.AbstractEventLoop + ) -> None: self.loop = loop self.future = future = loop.create_future() relative_delta = discord.utils.compute_timedelta(dt) @@ -96,15 +87,15 @@ def __init__( seconds: float, hours: float, minutes: float, - time: Union[datetime.time, Sequence[datetime.time]], - count: Optional[int], + time: datetime.time | Sequence[datetime.time], + count: int | None, reconnect: bool, loop: asyncio.AbstractEventLoop, ) -> None: self.coro: LF = coro self.reconnect: bool = reconnect self.loop: asyncio.AbstractEventLoop = loop - self.count: Optional[int] = count + self.count: int | None = count self._current_loop = 0 self._handle: SleepHandle = MISSING self._task: asyncio.Task[None] = MISSING @@ -134,7 +125,9 @@ def __init__( self._next_iteration = None if not inspect.iscoroutinefunction(self.coro): - raise TypeError(f"Expected coroutine function, not {type(self.coro).__name__!r}.") + raise TypeError( + f"Expected coroutine function, not {type(self.coro).__name__!r}." + ) async def _call_loop_function(self, name: str, *args: Any, **kwargs: Any) -> None: coro = getattr(self, f"_{name}") @@ -211,7 +204,7 @@ async def _loop(self, *args: Any, **kwargs: Any) -> None: self._stop_next_iteration = False self._has_failed = False - def __get__(self, obj: T, objtype: Type[T]) -> Loop[LF]: + def __get__(self, obj: T, objtype: type[T]) -> Loop[LF]: if obj is None: return self @@ -233,7 +226,7 @@ def __get__(self, obj: T, objtype: Type[T]) -> Loop[LF]: return copy @property - def seconds(self) -> Optional[float]: + def seconds(self) -> float | None: """Optional[:class:`float`]: Read-only value for the number of seconds between each iteration. ``None`` if an explicit ``time`` value was passed instead. @@ -243,7 +236,7 @@ def seconds(self) -> Optional[float]: return self._seconds @property - def minutes(self) -> Optional[float]: + def minutes(self) -> float | None: """Optional[:class:`float`]: Read-only value for the number of minutes between each iteration. ``None`` if an explicit ``time`` value was passed instead. @@ -253,7 +246,7 @@ def minutes(self) -> Optional[float]: return self._minutes @property - def hours(self) -> Optional[float]: + def hours(self) -> float | None: """Optional[:class:`float`]: Read-only value for the number of hours between each iteration. ``None`` if an explicit ``time`` value was passed instead. @@ -263,7 +256,7 @@ def hours(self) -> Optional[float]: return self._hours @property - def time(self) -> Optional[List[datetime.time]]: + def time(self) -> list[datetime.time] | None: """Optional[List[:class:`datetime.time`]]: Read-only list for the exact times this loop runs at. ``None`` if relative times were passed instead. @@ -278,7 +271,7 @@ def current_loop(self) -> int: return self._current_loop @property - def next_iteration(self) -> Optional[datetime.datetime]: + def next_iteration(self) -> datetime.datetime | None: """Optional[:class:`datetime.datetime`]: When the next iteration of the loop will occur. .. versionadded:: 1.3 @@ -364,7 +357,9 @@ def stop(self) -> None: self._stop_next_iteration = True def _can_be_cancelled(self) -> bool: - return bool(not self._is_being_cancelled and self._task and not self._task.done()) + return bool( + not self._is_being_cancelled and self._task and not self._task.done() + ) def cancel(self) -> None: """Cancels the internal task, if it is running.""" @@ -387,7 +382,9 @@ def restart(self, *args: Any, **kwargs: Any) -> None: The keyword arguments to use. """ - def restart_when_over(fut: Any, *, args: Any = args, kwargs: Any = kwargs) -> None: + def restart_when_over( + fut: Any, *, args: Any = args, kwargs: Any = kwargs + ) -> None: self._task.remove_done_callback(restart_when_over) self.start(*args, **kwargs) @@ -395,7 +392,7 @@ def restart_when_over(fut: Any, *, args: Any = args, kwargs: Any = kwargs) -> No self._task.add_done_callback(restart_when_over) self._task.cancel() - def add_exception_type(self, *exceptions: Type[BaseException]) -> None: + def add_exception_type(self, *exceptions: type[BaseException]) -> None: r"""Adds exception types to be handled during the reconnect logic. By default, the exception types handled are those handled by @@ -433,7 +430,7 @@ def clear_exception_types(self) -> None: """ self._valid_exception = tuple() - def remove_exception_type(self, *exceptions: Type[BaseException]) -> bool: + def remove_exception_type(self, *exceptions: type[BaseException]) -> bool: r"""Removes exception types from being handled during the reconnect logic. Parameters @@ -447,10 +444,12 @@ def remove_exception_type(self, *exceptions: Type[BaseException]) -> bool: Whether all exceptions were successfully removed. """ old_length = len(self._valid_exception) - self._valid_exception = tuple(x for x in self._valid_exception if x not in exceptions) + self._valid_exception = tuple( + x for x in self._valid_exception if x not in exceptions + ) return len(self._valid_exception) == old_length - len(exceptions) - def get_task(self) -> Optional[asyncio.Task[None]]: + def get_task(self) -> asyncio.Task[None] | None: """Optional[:class:`asyncio.Task`]: Fetches the internal task or ``None`` if there isn't one running.""" return self._task if self._task is not MISSING else None @@ -478,7 +477,9 @@ async def _error(self, *args: Any) -> None: f"Unhandled exception in internal background task {self.coro.__name__!r}.", file=sys.stderr, ) - traceback.print_exception(type(exception), exception, exception.__traceback__, file=sys.stderr) + traceback.print_exception( + type(exception), exception, exception.__traceback__, file=sys.stderr + ) def before_loop(self, coro: FT) -> FT: """A decorator that registers a coroutine to be called before the loop starts running. @@ -489,18 +490,20 @@ def before_loop(self, coro: FT) -> FT: The coroutine must take no arguments (except ``self`` in a class context). Parameters - ------------ + ---------- coro: :ref:`coroutine ` The coroutine to register before the loop runs. Raises - ------- + ------ TypeError The function was not a coroutine. """ if not inspect.iscoroutinefunction(coro): - raise TypeError(f"Expected coroutine function, received {coro.__class__.__name__!r}.") + raise TypeError( + f"Expected coroutine function, received {coro.__class__.__name__!r}." + ) self._before_loop = coro return coro @@ -517,18 +520,20 @@ def after_loop(self, coro: FT) -> FT: whether :meth:`is_being_cancelled` is ``True`` or not. Parameters - ------------ + ---------- coro: :ref:`coroutine ` The coroutine to register after the loop finishes. Raises - ------- + ------ TypeError The function was not a coroutine. """ if not inspect.iscoroutinefunction(coro): - raise TypeError(f"Expected coroutine function, received {coro.__class__.__name__!r}.") + raise TypeError( + f"Expected coroutine function, received {coro.__class__.__name__!r}." + ) self._after_loop = coro return coro @@ -544,17 +549,19 @@ def error(self, coro: ET) -> ET: .. versionadded:: 1.4 Parameters - ------------ + ---------- coro: :ref:`coroutine ` The coroutine to register in the event of an unhandled exception. Raises - ------- + ------ TypeError The function was not a coroutine. """ if not inspect.iscoroutinefunction(coro): - raise TypeError(f"Expected coroutine function, received {coro.__class__.__name__!r}.") + raise TypeError( + f"Expected coroutine function, received {coro.__class__.__name__!r}." + ) self._error = coro # type: ignore return coro @@ -568,7 +575,8 @@ def _get_next_sleep_time(self) -> datetime.datetime: if self._current_loop == 0: # if we're at the last index on the first iteration, we need to sleep until tomorrow return datetime.datetime.combine( - datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1), + datetime.datetime.now(datetime.timezone.utc) + + datetime.timedelta(days=1), self._time[0], ) @@ -577,10 +585,13 @@ def _get_next_sleep_time(self) -> datetime.datetime: if self._current_loop == 0: self._time_index += 1 if next_time > datetime.datetime.now(datetime.timezone.utc).timetz(): - return datetime.datetime.combine(datetime.datetime.now(datetime.timezone.utc), next_time) + return datetime.datetime.combine( + datetime.datetime.now(datetime.timezone.utc), next_time + ) else: return datetime.datetime.combine( - datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1), + datetime.datetime.now(datetime.timezone.utc) + + datetime.timedelta(days=1), next_time, ) @@ -597,7 +608,9 @@ def _prepare_time_index(self, now: datetime.datetime = MISSING) -> None: # pre-condition: self._time is set time_now = ( - now if now is not MISSING else datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0) + now + if now is not MISSING + else datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0) ).timetz() for idx, time in enumerate(self._time): if time >= time_now: @@ -608,11 +621,11 @@ def _prepare_time_index(self, now: datetime.datetime = MISSING) -> None: def _get_time_parameter( self, - time: Union[datetime.time, Sequence[datetime.time]], + time: datetime.time | Sequence[datetime.time], *, - dt: Type[datetime.time] = datetime.time, + dt: type[datetime.time] = datetime.time, utc: datetime.timezone = datetime.timezone.utc, - ) -> List[datetime.time]: + ) -> list[datetime.time]: if isinstance(time, dt): inner = time if time.tzinfo is not None else time.replace(tzinfo=utc) return [inner] @@ -623,7 +636,7 @@ def _get_time_parameter( if not time: raise ValueError("time parameter must not be an empty sequence.") - ret: List[datetime.time] = [] + ret: list[datetime.time] = [] for index, t in enumerate(time): if not isinstance(t, dt): raise TypeError( @@ -640,14 +653,14 @@ def change_interval( seconds: float = 0, minutes: float = 0, hours: float = 0, - time: Union[datetime.time, Sequence[datetime.time]] = MISSING, + time: datetime.time | Sequence[datetime.time] = MISSING, ) -> None: """Changes the interval for the sleep time. .. versionadded:: 1.2 Parameters - ------------ + ---------- seconds: :class:`float` The number of seconds between every iteration. minutes: :class:`float` @@ -666,7 +679,7 @@ def change_interval( Duplicate times will be ignored, and only run once. Raises - ------- + ------ ValueError An invalid value was given. TypeError @@ -686,14 +699,16 @@ def change_interval( self._seconds = float(seconds) self._hours = float(hours) self._minutes = float(minutes) - self._time: List[datetime.time] = MISSING + self._time: list[datetime.time] = MISSING else: if any((seconds, minutes, hours)): raise TypeError("Cannot mix explicit time with relative time") self._time = self._get_time_parameter(time) self._sleep = self._seconds = self._minutes = self._hours = MISSING - if self.is_running() and not (self._before_loop_running or self._after_loop_running): + if self.is_running() and not ( + self._before_loop_running or self._after_loop_running + ): if self._time is not MISSING: # prepare the next time index starting from after the last iteration self._prepare_time_index(now=self._last_iteration) @@ -709,8 +724,8 @@ def loop( seconds: float = MISSING, minutes: float = MISSING, hours: float = MISSING, - time: Union[datetime.time, Sequence[datetime.time]] = MISSING, - count: Optional[int] = None, + time: datetime.time | Sequence[datetime.time] = MISSING, + count: int | None = None, reconnect: bool = True, loop: asyncio.AbstractEventLoop = MISSING, ) -> Callable[[LF], Loop[LF]]: @@ -718,7 +733,7 @@ def loop( optional reconnect logic. The decorator returns a :class:`Loop`. Parameters - ------------ + ---------- seconds: :class:`float` The number of seconds between every iteration. minutes: :class:`float` @@ -750,7 +765,7 @@ def loop( defaults to :func:`asyncio.get_event_loop`. Raises - -------- + ------ ValueError An invalid value was given. TypeError diff --git a/discord/file.py b/discord/file.py index e258c10eaa..cb1a766bc9 100644 --- a/discord/file.py +++ b/discord/file.py @@ -27,7 +27,7 @@ import io import os -from typing import TYPE_CHECKING, Optional, Union +from typing import TYPE_CHECKING __all__ = ("File",) @@ -77,16 +77,16 @@ class File: if TYPE_CHECKING: fp: io.BufferedIOBase - filename: Optional[str] - description: Optional[str] + filename: str | None + description: str | None spoiler: bool def __init__( self, - fp: Union[str, bytes, os.PathLike, io.BufferedIOBase], - filename: Optional[str] = None, + fp: str | bytes | os.PathLike | io.BufferedIOBase, + filename: str | None = None, *, - description: Optional[str] = None, + description: str | None = None, spoiler: bool = False, ): if isinstance(fp, io.IOBase): @@ -115,13 +115,19 @@ def __init__( else: self.filename = filename - if spoiler and self.filename is not None and not self.filename.startswith("SPOILER_"): + if ( + spoiler + and self.filename is not None + and not self.filename.startswith("SPOILER_") + ): self.filename = f"SPOILER_{self.filename}" - self.spoiler = spoiler or (self.filename is not None and self.filename.startswith("SPOILER_")) + self.spoiler = spoiler or ( + self.filename is not None and self.filename.startswith("SPOILER_") + ) self.description = description - def reset(self, *, seek: Union[int, bool] = True) -> None: + def reset(self, *, seek: int | bool = True) -> None: # The `seek` parameter is needed because # the retry-loop is iterated over multiple times # starting from 0, as an implementation quirk diff --git a/discord/flags.py b/discord/flags.py index 967339c007..2bf94b18d8 100644 --- a/discord/flags.py +++ b/discord/flags.py @@ -25,20 +25,7 @@ from __future__ import annotations -from typing import ( - Any, - Callable, - ClassVar, - Dict, - Iterator, - List, - Optional, - Tuple, - Type, - TypeVar, - Union, - overload, -) +from typing import Any, Callable, ClassVar, Iterator, TypeVar, overload from .enums import UserFlags @@ -62,14 +49,14 @@ def __init__(self, func: Callable[[Any], int]): self.__doc__ = func.__doc__ @overload - def __get__(self: FV, instance: None, owner: Type[BF]) -> FV: + def __get__(self: FV, instance: None, owner: type[BF]) -> FV: ... @overload - def __get__(self, instance: BF, owner: Type[BF]) -> bool: + def __get__(self, instance: BF, owner: type[BF]) -> bool: ... - def __get__(self, instance: Optional[BF], owner: Type[BF]) -> Any: + def __get__(self, instance: BF | None, owner: type[BF]) -> Any: if instance is None: return self return instance._has_flag(self.flag) @@ -86,8 +73,12 @@ class alias_flag_value(flag_value): def fill_with_flags(*, inverted: bool = False): - def decorator(cls: Type[BF]): - cls.VALID_FLAGS = {name: value.flag for name, value in cls.__dict__.items() if isinstance(value, flag_value)} + def decorator(cls: type[BF]): + cls.VALID_FLAGS = { + name: value.flag + for name, value in cls.__dict__.items() + if isinstance(value, flag_value) + } if inverted: max_bits = max(cls.VALID_FLAGS.values()).bit_length() @@ -102,7 +93,7 @@ def decorator(cls: Type[BF]): # n.b. flags must inherit from this and use the decorator above class BaseFlags: - VALID_FLAGS: ClassVar[Dict[str, int]] + VALID_FLAGS: ClassVar[dict[str, int]] DEFAULT_VALUE: ClassVar[int] value: int @@ -134,7 +125,7 @@ def __hash__(self) -> int: def __repr__(self) -> str: return f"<{self.__class__.__name__} value={self.value}>" - def __iter__(self) -> Iterator[Tuple[str, bool]]: + def __iter__(self) -> Iterator[tuple[str, bool]]: for name, value in self.__class__.__dict__.items(): if isinstance(value, alias_flag_value): continue @@ -148,7 +139,9 @@ def __and__(self, other): elif isinstance(other, flag_value): return self.__class__._from_value(self.value & other.flag) else: - raise TypeError(f"'&' not supported between instances of {type(self)} and {type(other)}") + raise TypeError( + f"'&' not supported between instances of {type(self)} and {type(other)}" + ) def __or__(self, other): if isinstance(other, self.__class__): @@ -156,13 +149,17 @@ def __or__(self, other): elif isinstance(other, flag_value): return self.__class__._from_value(self.value | other.flag) else: - raise TypeError(f"'|' not supported between instances of {type(self)} and {type(other)}") + raise TypeError( + f"'|' not supported between instances of {type(self)} and {type(other)}" + ) def __add__(self, other): try: return self | other except TypeError: - raise TypeError(f"'+' not supported between instances of {type(self)} and {type(other)}") + raise TypeError( + f"'+' not supported between instances of {type(self)} and {type(other)}" + ) def __sub__(self, other): if isinstance(other, self.__class__): @@ -170,15 +167,17 @@ def __sub__(self, other): elif isinstance(other, flag_value): return self.__class__._from_value(self.value & ~other.flag) else: - raise TypeError(f"'-' not supported between instances of {type(self)} and {type(other)}") + raise TypeError( + f"'-' not supported between instances of {type(self)} and {type(other)}" + ) def __invert__(self): return self.__class__._from_value(~self.value) - __rand__: Callable[[Union[BaseFlags, flag_value]], bool] = __and__ - __ror__: Callable[[Union[BaseFlags, flag_value]], bool] = __or__ - __radd__: Callable[[Union[BaseFlags, flag_value]], bool] = __add__ - __rsub__: Callable[[Union[BaseFlags, flag_value]], bool] = __sub__ + __rand__: Callable[[BaseFlags | flag_value], bool] = __and__ + __ror__: Callable[[BaseFlags | flag_value], bool] = __or__ + __radd__: Callable[[BaseFlags | flag_value], bool] = __add__ + __rsub__: Callable[[BaseFlags | flag_value], bool] = __sub__ def _has_flag(self, o: int) -> bool: return (self.value & o) == o @@ -537,9 +536,13 @@ def bot_http_interactions(self): """ return UserFlags.bot_http_interactions.value - def all(self) -> List[UserFlags]: + def all(self) -> list[UserFlags]: """List[:class:`UserFlags`]: Returns all public flags the user has.""" - return [public_flag for public_flag in UserFlags if self._has_flag(public_flag.value)] + return [ + public_flag + for public_flag in UserFlags + if self._has_flag(public_flag.value) + ] @fill_with_flags() @@ -607,7 +610,7 @@ def __init__(self, **kwargs: bool): setattr(self, key, value) @classmethod - def all(cls: Type[Intents]) -> Intents: + def all(cls: type[Intents]) -> Intents: """A factory method that creates a :class:`Intents` with everything enabled.""" bits = max(cls.VALID_FLAGS.values()).bit_length() value = (1 << bits) - 1 @@ -616,14 +619,14 @@ def all(cls: Type[Intents]) -> Intents: return self @classmethod - def none(cls: Type[Intents]) -> Intents: + def none(cls: type[Intents]) -> Intents: """A factory method that creates a :class:`Intents` with everything disabled.""" self = cls.__new__(cls) self.value = self.DEFAULT_VALUE return self @classmethod - def default(cls: Type[Intents]) -> Intents: + def default(cls: type[Intents]) -> Intents: """A factory method that creates a :class:`Intents` with everything enabled except :attr:`presences`, :attr:`members`, and :attr:`message_content`. """ @@ -1048,7 +1051,6 @@ def message_content(self): of the guild messages. This intent is privileged, meaning that bots in over 100 guilds that require this intent would need to request this intent on the Developer Portal. See https://support-dev.discord.com/hc/en-us/articles/4404772028055 for more information. - """ return 1 << 15 @@ -1148,7 +1150,7 @@ class MemberCacheFlags(BaseFlags): to be, for example, constructed as a dict or a list of pairs. Attributes - ----------- + ---------- value: :class:`int` The raw value. You should query flags via the properties rather than using this raw value. @@ -1165,7 +1167,7 @@ def __init__(self, **kwargs: bool): setattr(self, key, value) @classmethod - def all(cls: Type[MemberCacheFlags]) -> MemberCacheFlags: + def all(cls: type[MemberCacheFlags]) -> MemberCacheFlags: """A factory method that creates a :class:`MemberCacheFlags` with everything enabled.""" bits = max(cls.VALID_FLAGS.values()).bit_length() value = (1 << bits) - 1 @@ -1174,7 +1176,7 @@ def all(cls: Type[MemberCacheFlags]) -> MemberCacheFlags: return self @classmethod - def none(cls: Type[MemberCacheFlags]) -> MemberCacheFlags: + def none(cls: type[MemberCacheFlags]) -> MemberCacheFlags: """A factory method that creates a :class:`MemberCacheFlags` with everything disabled.""" self = cls.__new__(cls) self.value = self.DEFAULT_VALUE @@ -1215,17 +1217,17 @@ def interaction(self): return 4 @classmethod - def from_intents(cls: Type[MemberCacheFlags], intents: Intents) -> MemberCacheFlags: + def from_intents(cls: type[MemberCacheFlags], intents: Intents) -> MemberCacheFlags: """A factory method that creates a :class:`MemberCacheFlags` based on the currently selected :class:`Intents`. Parameters - ------------ + ---------- intents: :class:`Intents` The intents to select from. Returns - --------- + ------- :class:`MemberCacheFlags` The resulting member cache flags. """ diff --git a/discord/gateway.py b/discord/gateway.py index 0d44f06bff..15aa2040a5 100644 --- a/discord/gateway.py +++ b/discord/gateway.py @@ -64,8 +64,6 @@ def __init__(self, shard_id, *, resume=True): class WebSocketClosure(Exception): """An exception to make up for the fact that aiohttp doesn't signal closure.""" - pass - EventListener = namedtuple("EventListener", "predicate event result future") @@ -150,7 +148,9 @@ def run(self): try: f.result() except Exception: - _log.exception("An error occurred while stopping the gateway. Ignoring.") + _log.exception( + "An error occurred while stopping the gateway. Ignoring." + ) finally: self.stop() return @@ -227,7 +227,7 @@ class DiscordWebSocket: """Implements a WebSocket for Discord's gateway v6. Attributes - ----------- + ---------- DISPATCH Receive only. Denotes an event to be sent to Discord, such as READY. HEARTBEAT @@ -368,7 +368,7 @@ def wait_for(self, event, predicate, result=None): """Waits for a DISPATCH'd event that meets the predicate. Parameters - ----------- + ---------- event: :class:`str` The event name in all upper case to wait for. predicate @@ -379,13 +379,15 @@ def wait_for(self, event, predicate, result=None): the result to the future. If ``None``, returns the data. Returns - -------- + ------- asyncio.Future A future to wait for. """ future = self.loop.create_future() - entry = EventListener(event=event, predicate=predicate, result=result, future=future) + entry = EventListener( + event=event, predicate=predicate, result=result, future=future + ) self._dispatch_listeners.append(entry) return future @@ -421,7 +423,9 @@ async def identify(self): if state._intents is not None: payload["d"]["intents"] = state._intents.value - await self.call_hooks("before_identify", self.shard_id, initial=self._initial_identify) + await self.call_hooks( + "before_identify", self.shard_id, initial=self._initial_identify + ) await self.send_as_json(payload) _log.info("Shard ID %s has sent the IDENTIFY payload.", self.shard_id) @@ -488,7 +492,9 @@ async def received_message(self, msg, /): if op == self.HELLO: interval = data["heartbeat_interval"] / 1000.0 - self._keep_alive = KeepAliveHandler(ws=self, interval=interval, shard_id=self.shard_id) + self._keep_alive = KeepAliveHandler( + ws=self, interval=interval, shard_id=self.shard_id + ) # send a heartbeat immediately await self.send_as_json(self._keep_alive.get_payload()) self._keep_alive.start() @@ -615,7 +621,9 @@ async def poll_event(self): raise ReconnectWebSocket(self.shard_id) from None else: _log.info("Websocket closed with %s, cannot reconnect.", code) - raise ConnectionClosed(self.socket, shard_id=self.shard_id, code=code) from None + raise ConnectionClosed( + self.socket, shard_id=self.shard_id, code=code + ) from None async def debug_send(self, data, /): await self._rate_limiter.block() @@ -666,7 +674,9 @@ async def change_presence(self, *, activity=None, status=None, since=0.0): _log.debug('Sending "%s" to change status', sent) await self.send(sent) - async def request_chunks(self, guild_id, query=None, *, limit, user_ids=None, presences=False, nonce=None): + async def request_chunks( + self, guild_id, query=None, *, limit, user_ids=None, presences=False, nonce=None + ): payload = { "op": self.REQUEST_MEMBERS, "d": {"guild_id": guild_id, "presences": presences, "limit": limit}, @@ -710,7 +720,7 @@ class DiscordVoiceWebSocket: """Implements the WebSocket protocol for handling voice connections. Attributes - ----------- + ---------- IDENTIFY Send only. Starts a new voice session. SELECT_PROTOCOL @@ -853,7 +863,9 @@ async def received_message(self, msg): await self.load_secret_key(data) elif op == self.HELLO: interval = data["heartbeat_interval"] / 1000.0 - self._keep_alive = VoiceKeepAliveHandler(ws=self, interval=min(interval, 5.0)) + self._keep_alive = VoiceKeepAliveHandler( + ws=self, interval=min(interval, 5.0) + ) self._keep_alive.start() elif op == self.SPEAKING: @@ -890,7 +902,9 @@ async def initial_connection(self, data): _log.debug("detected ip: %s port: %s", state.ip, state.port) # there *should* always be at least one supported mode (xsalsa20_poly1305) - modes = [mode for mode in data["modes"] if mode in self._connection.supported_modes] + modes = [ + mode for mode in data["modes"] if mode in self._connection.supported_modes + ] _log.debug("received supported encryption modes: %s", ", ".join(modes)) mode = modes[0] diff --git a/discord/guild.py b/discord/guild.py index b7bed84dc6..2faea68034 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -31,21 +31,19 @@ TYPE_CHECKING, Any, ClassVar, - Dict, List, Literal, NamedTuple, Optional, Sequence, - Set, Tuple, Union, overload, ) from . import abc, utils -from .automod import AutoModAction, AutoModRule, AutoModTriggerMetadata from .asset import Asset +from .automod import AutoModAction, AutoModRule, AutoModTriggerMetadata from .channel import * from .channel import _guild_channel_factory, _threaded_guild_channel_factory from .colour import Colour @@ -111,12 +109,14 @@ from .webhook import Webhook VocalGuildChannel = Union[VoiceChannel, StageChannel] - GuildChannel = Union[VoiceChannel, StageChannel, TextChannel, ForumChannel, CategoryChannel] + GuildChannel = Union[ + VoiceChannel, StageChannel, TextChannel, ForumChannel, CategoryChannel + ] ByCategoryItem = Tuple[Optional[CategoryChannel], List[GuildChannel]] class BanEntry(NamedTuple): - reason: Optional[str] + reason: str | None user: User @@ -319,7 +319,7 @@ class Guild(Hashable): "approximate_presence_count", ) - _PREMIUM_GUILD_LIMITS: ClassVar[Dict[Optional[int], _GuildLimit]] = { + _PREMIUM_GUILD_LIMITS: ClassVar[dict[int | None, _GuildLimit]] = { None: _GuildLimit(emoji=50, stickers=5, bitrate=96e3, filesize=8388608), 0: _GuildLimit(emoji=50, stickers=5, bitrate=96e3, filesize=8388608), 1: _GuildLimit(emoji=100, stickers=15, bitrate=128e3, filesize=8388608), @@ -333,11 +333,11 @@ def __init__(self, *, data: GuildPayload, state: ConnectionState): # the attr doesn't exist? it has something to do with the order # of the attr in __slots__ - self._channels: Dict[int, GuildChannel] = {} - self._members: Dict[int, Member] = {} - self._scheduled_events: Dict[int, ScheduledEvent] = {} - self._voice_states: Dict[int, VoiceState] = {} - self._threads: Dict[int, Thread] = {} + self._channels: dict[int, GuildChannel] = {} + self._members: dict[int, Member] = {} + self._scheduled_events: dict[int, ScheduledEvent] = {} + self._voice_states: dict[int, VoiceState] = {} + self._threads: dict[int, Thread] = {} self._state: ConnectionState = state self._from_data(data) @@ -347,13 +347,15 @@ def _add_channel(self, channel: GuildChannel, /) -> None: def _remove_channel(self, channel: Snowflake, /) -> None: self._channels.pop(channel.id, None) - def _voice_state_for(self, user_id: int, /) -> Optional[VoiceState]: + def _voice_state_for(self, user_id: int, /) -> VoiceState | None: return self._voice_states.get(user_id) def _add_member(self, member: Member, /) -> None: self._members[member.id] = member - def _get_and_update_member(self, payload: MemberPayload, user_id: int, cache_flag: bool, /) -> Member: + def _get_and_update_member( + self, payload: MemberPayload, user_id: int, cache_flag: bool, / + ) -> Member: # we always get the member, and we only update if the cache_flag (this cache # flag should always be MemberCacheFlag.interaction) is set to True if user_id in self._members: @@ -383,7 +385,7 @@ def _add_scheduled_event(self, event: ScheduledEvent, /) -> None: def _remove_scheduled_event(self, event: Snowflake, /) -> None: self._scheduled_events.pop(event.id, None) - def _scheduled_events_from_list(self, events: List[ScheduledEvent], /) -> None: + def _scheduled_events_from_list(self, events: list[ScheduledEvent], /) -> None: self._scheduled_events.clear() for event in events: self._scheduled_events[event.id] = event @@ -402,8 +404,10 @@ def _remove_threads_by_channel(self, channel_id: int) -> None: for k in to_remove: del self._threads[k] - def _filter_threads(self, channel_ids: Set[int]) -> Dict[int, Thread]: - to_remove: Dict[int, Thread] = {k: t for k, t in self._threads.items() if t.parent_id in channel_ids} + def _filter_threads(self, channel_ids: set[int]) -> dict[int, Thread]: + to_remove: dict[int, Thread] = { + k: t for k, t in self._threads.items() if t.parent_id in channel_ids + } for k in to_remove: del self._threads[k] return to_remove @@ -424,7 +428,7 @@ def __repr__(self) -> str: def _update_voice_state( self, data: GuildVoiceState, channel_id: int - ) -> Tuple[Optional[Member], VoiceState, VoiceState]: + ) -> tuple[Member | None, VoiceState, VoiceState]: user_id = int(data["user_id"]) channel = self.get_channel(channel_id) try: @@ -482,47 +486,63 @@ def _from_data(self, guild: GuildPayload) -> None: self._member_count: int = member_count self.name: str = guild.get("name") - self.verification_level: VerificationLevel = try_enum(VerificationLevel, guild.get("verification_level")) + self.verification_level: VerificationLevel = try_enum( + VerificationLevel, guild.get("verification_level") + ) self.default_notifications: NotificationLevel = try_enum( NotificationLevel, guild.get("default_message_notifications") ) - self.explicit_content_filter: ContentFilter = try_enum(ContentFilter, guild.get("explicit_content_filter", 0)) + self.explicit_content_filter: ContentFilter = try_enum( + ContentFilter, guild.get("explicit_content_filter", 0) + ) self.afk_timeout: int = guild.get("afk_timeout") - self._icon: Optional[str] = guild.get("icon") - self._banner: Optional[str] = guild.get("banner") + self._icon: str | None = guild.get("icon") + self._banner: str | None = guild.get("banner") self.unavailable: bool = guild.get("unavailable", False) self.id: int = int(guild["id"]) - self._roles: Dict[int, Role] = {} + self._roles: dict[int, Role] = {} state = self._state # speed up attribute access for r in guild.get("roles", []): role = Role(guild=self, data=r, state=state) self._roles[role.id] = role self.mfa_level: MFALevel = guild.get("mfa_level") - self.emojis: Tuple[Emoji, ...] = tuple(map(lambda d: state.store_emoji(self, d), guild.get("emojis", []))) - self.stickers: Tuple[GuildSticker, ...] = tuple( + self.emojis: tuple[Emoji, ...] = tuple( + map(lambda d: state.store_emoji(self, d), guild.get("emojis", [])) + ) + self.stickers: tuple[GuildSticker, ...] = tuple( map(lambda d: state.store_sticker(self, d), guild.get("stickers", [])) ) - self.features: List[GuildFeature] = guild.get("features", []) - self._splash: Optional[str] = guild.get("splash") - self._system_channel_id: Optional[int] = utils._get_as_snowflake(guild, "system_channel_id") - self.description: Optional[str] = guild.get("description") - self.max_presences: Optional[int] = guild.get("max_presences") - self.max_members: Optional[int] = guild.get("max_members") - self.max_video_channel_users: Optional[int] = guild.get("max_video_channel_users") + self.features: list[GuildFeature] = guild.get("features", []) + self._splash: str | None = guild.get("splash") + self._system_channel_id: int | None = utils._get_as_snowflake( + guild, "system_channel_id" + ) + self.description: str | None = guild.get("description") + self.max_presences: int | None = guild.get("max_presences") + self.max_members: int | None = guild.get("max_members") + self.max_video_channel_users: int | None = guild.get("max_video_channel_users") self.premium_tier: int = guild.get("premium_tier", 0) - self.premium_subscription_count: int = guild.get("premium_subscription_count") or 0 - self.premium_progress_bar_enabled: bool = guild.get("premium_progress_bar_enabled") or False + self.premium_subscription_count: int = ( + guild.get("premium_subscription_count") or 0 + ) + self.premium_progress_bar_enabled: bool = ( + guild.get("premium_progress_bar_enabled") or False + ) self._system_channel_flags: int = guild.get("system_channel_flags", 0) - self.preferred_locale: Optional[str] = guild.get("preferred_locale") - self._discovery_splash: Optional[str] = guild.get("discovery_splash") - self._rules_channel_id: Optional[int] = utils._get_as_snowflake(guild, "rules_channel_id") - self._public_updates_channel_id: Optional[int] = utils._get_as_snowflake(guild, "public_updates_channel_id") + self.preferred_locale: str | None = guild.get("preferred_locale") + self._discovery_splash: str | None = guild.get("discovery_splash") + self._rules_channel_id: int | None = utils._get_as_snowflake( + guild, "rules_channel_id" + ) + self._public_updates_channel_id: int | None = utils._get_as_snowflake( + guild, "public_updates_channel_id" + ) self.nsfw_level: NSFWLevel = try_enum(NSFWLevel, guild.get("nsfw_level", 0)) self.approximate_presence_count = guild.get("approximate_presence_count") self.approximate_member_count = guild.get("approximate_member_count") - self._stage_instances: Dict[int, StageInstance] = {} + self._stage_instances: dict[int, StageInstance] = {} for s in guild.get("stage_instances", []): stage_instance = StageInstance(guild=self, data=s, state=state) self._stage_instances[stage_instance.id] = stage_instance @@ -536,15 +556,25 @@ def _from_data(self, guild: GuildPayload) -> None: events = [] for event in guild.get("guild_scheduled_events", []): - creator = None if not event.get("creator", None) else self.get_member(event.get("creator_id")) - events.append(ScheduledEvent(state=self._state, guild=self, creator=creator, data=event)) + creator = ( + None + if not event.get("creator", None) + else self.get_member(event.get("creator_id")) + ) + events.append( + ScheduledEvent( + state=self._state, guild=self, creator=creator, data=event + ) + ) self._scheduled_events_from_list(events) self._sync(guild) - self._large: Optional[bool] = None if member_count is None else self._member_count >= 250 + self._large: bool | None = ( + None if member_count is None else self._member_count >= 250 + ) - self.owner_id: Optional[int] = utils._get_as_snowflake(guild, "owner_id") - self.afk_channel: Optional[VocalGuildChannel] = self.get_channel( + self.owner_id: int | None = utils._get_as_snowflake(guild, "owner_id") + self.afk_channel: VocalGuildChannel | None = self.get_channel( utils._get_as_snowflake(guild, "afk_channel_id") ) # type: ignore @@ -578,12 +608,12 @@ def _sync(self, data: GuildPayload) -> None: self._add_thread(Thread(guild=self, state=self._state, data=thread)) @property - def channels(self) -> List[GuildChannel]: + def channels(self) -> list[GuildChannel]: """List[:class:`abc.GuildChannel`]: A list of channels that belong to this guild.""" return list(self._channels.values()) @property - def threads(self) -> List[Thread]: + def threads(self) -> list[Thread]: """List[:class:`Thread`]: A list of threads that you have permission to view. .. versionadded:: 2.0 @@ -613,7 +643,7 @@ def large(self) -> bool: return self._large @property - def voice_channels(self) -> List[VoiceChannel]: + def voice_channels(self) -> list[VoiceChannel]: """List[:class:`VoiceChannel`]: A list of voice channels that belong to this guild. This is sorted by the position and are in UI order from top to bottom. @@ -623,7 +653,7 @@ def voice_channels(self) -> List[VoiceChannel]: return r @property - def stage_channels(self) -> List[StageChannel]: + def stage_channels(self) -> list[StageChannel]: """List[:class:`StageChannel`]: A list of stage channels that belong to this guild. .. versionadded:: 1.7 @@ -635,7 +665,7 @@ def stage_channels(self) -> List[StageChannel]: return r @property - def forum_channels(self) -> List[ForumChannel]: + def forum_channels(self) -> list[ForumChannel]: """List[:class:`ForumChannel`]: A list of forum channels that belong to this guild. .. versionadded:: 2.0 @@ -656,12 +686,12 @@ def me(self) -> Member: return self.get_member(self_id) # type: ignore @property - def voice_client(self) -> Optional[VoiceProtocol]: + def voice_client(self) -> VoiceProtocol | None: """Optional[:class:`VoiceProtocol`]: Returns the :class:`VoiceProtocol` associated with this guild, if any.""" return self._state._get_voice_client(self.id) @property - def text_channels(self) -> List[TextChannel]: + def text_channels(self) -> list[TextChannel]: """List[:class:`TextChannel`]: A list of text channels that belong to this guild. This is sorted by the position and are in UI order from top to bottom. @@ -671,7 +701,7 @@ def text_channels(self) -> List[TextChannel]: return r @property - def categories(self) -> List[CategoryChannel]: + def categories(self) -> list[CategoryChannel]: """List[:class:`CategoryChannel`]: A list of categories that belong to this guild. This is sorted by the position and are in UI order from top to bottom. @@ -680,7 +710,7 @@ def categories(self) -> List[CategoryChannel]: r.sort(key=lambda c: (c.position or -1, c.id)) return r - def by_category(self) -> List[ByCategoryItem]: + def by_category(self) -> list[ByCategoryItem]: """Returns every :class:`CategoryChannel` and their associated channels. These channels and categories are sorted in the official Discord UI order. @@ -689,11 +719,11 @@ def by_category(self) -> List[ByCategoryItem]: ``None``. Returns - -------- + ------- List[Tuple[Optional[:class:`CategoryChannel`], List[:class:`abc.GuildChannel`]]]: The categories and their associated channels. """ - grouped: Dict[Optional[int], List[GuildChannel]] = {} + grouped: dict[int | None, list[GuildChannel]] = {} for channel in self._channels.values(): if isinstance(channel, CategoryChannel): grouped.setdefault(channel.id, []) @@ -704,41 +734,41 @@ def by_category(self) -> List[ByCategoryItem]: except KeyError: grouped[channel.category_id] = [channel] - def key(t: ByCategoryItem) -> Tuple[Tuple[int, int], List[GuildChannel]]: + def key(t: ByCategoryItem) -> tuple[tuple[int, int], list[GuildChannel]]: k, v = t return (k.position or -1, k.id) if k else (-1, -1), v _get = self._channels.get - as_list: List[ByCategoryItem] = [(_get(k), v) for k, v in grouped.items()] # type: ignore + as_list: list[ByCategoryItem] = [(_get(k), v) for k, v in grouped.items()] # type: ignore as_list.sort(key=key) for _, channels in as_list: channels.sort(key=lambda c: (c._sorting_bucket, c.position or -1, c.id)) return as_list - def _resolve_channel(self, id: Optional[int], /) -> Optional[Union[GuildChannel, Thread]]: + def _resolve_channel(self, id: int | None, /) -> GuildChannel | Thread | None: if id is None: return return self._channels.get(id) or self._threads.get(id) - def get_channel_or_thread(self, channel_id: int, /) -> Optional[Union[Thread, GuildChannel]]: + def get_channel_or_thread(self, channel_id: int, /) -> Thread | GuildChannel | None: """Returns a channel or thread with the given ID. .. versionadded:: 2.0 Parameters - ----------- + ---------- channel_id: :class:`int` The ID to search for. Returns - -------- + ------- Optional[Union[:class:`Thread`, :class:`.abc.GuildChannel`]] The returned channel or thread or ``None`` if not found. """ return self._channels.get(channel_id) or self._threads.get(channel_id) - def get_channel(self, channel_id: int, /) -> Optional[GuildChannel]: + def get_channel(self, channel_id: int, /) -> GuildChannel | None: """Returns a channel with the given ID. .. note:: @@ -746,36 +776,36 @@ def get_channel(self, channel_id: int, /) -> Optional[GuildChannel]: This does *not* search for threads. Parameters - ----------- + ---------- channel_id: :class:`int` The ID to search for. Returns - -------- + ------- Optional[:class:`.abc.GuildChannel`] The returned channel or ``None`` if not found. """ return self._channels.get(channel_id) - def get_thread(self, thread_id: int, /) -> Optional[Thread]: + def get_thread(self, thread_id: int, /) -> Thread | None: """Returns a thread with the given ID. .. versionadded:: 2.0 Parameters - ----------- + ---------- thread_id: :class:`int` The ID to search for. Returns - -------- + ------- Optional[:class:`Thread`] The returned thread or ``None`` if not found. """ return self._threads.get(thread_id) @property - def system_channel(self) -> Optional[TextChannel]: + def system_channel(self) -> TextChannel | None: """Optional[:class:`TextChannel`]: Returns the guild's channel used for system messages. If no channel is set, then this returns ``None``. @@ -789,7 +819,7 @@ def system_channel_flags(self) -> SystemChannelFlags: return SystemChannelFlags._from_value(self._system_channel_flags) @property - def rules_channel(self) -> Optional[TextChannel]: + def rules_channel(self) -> TextChannel | None: """Optional[:class:`TextChannel`]: Return's the guild's channel used for the rules. The guild must be a Community guild. @@ -801,7 +831,7 @@ def rules_channel(self) -> Optional[TextChannel]: return channel_id and self._channels.get(channel_id) # type: ignore @property - def public_updates_channel(self) -> Optional[TextChannel]: + def public_updates_channel(self) -> TextChannel | None: """Optional[:class:`TextChannel`]: Return's the guild's channel where admins and moderators of the guilds receive notices from Discord. The guild must be a Community guild. @@ -826,12 +856,18 @@ def sticker_limit(self) -> int: .. versionadded:: 2.0 """ more_stickers = 60 if "MORE_STICKERS" in self.features else 0 - return max(more_stickers, self._PREMIUM_GUILD_LIMITS[self.premium_tier].stickers) + return max( + more_stickers, self._PREMIUM_GUILD_LIMITS[self.premium_tier].stickers + ) @property def bitrate_limit(self) -> float: """:class:`float`: The maximum bitrate for voice channels this guild can have.""" - vip_guild = self._PREMIUM_GUILD_LIMITS[1].bitrate if "VIP_REGIONS" in self.features else 96e3 + vip_guild = ( + self._PREMIUM_GUILD_LIMITS[1].bitrate + if "VIP_REGIONS" in self.features + else 96e3 + ) return max(vip_guild, self._PREMIUM_GUILD_LIMITS[self.premium_tier].bitrate) @property @@ -840,32 +876,32 @@ def filesize_limit(self) -> int: return self._PREMIUM_GUILD_LIMITS[self.premium_tier].filesize @property - def members(self) -> List[Member]: + def members(self) -> list[Member]: """List[:class:`Member`]: A list of members that belong to this guild.""" return list(self._members.values()) - def get_member(self, user_id: int, /) -> Optional[Member]: + def get_member(self, user_id: int, /) -> Member | None: """Returns a member with the given ID. Parameters - ----------- + ---------- user_id: :class:`int` The ID to search for. Returns - -------- + ------- Optional[:class:`Member`] The member or ``None`` if not found. """ return self._members.get(user_id) @property - def premium_subscribers(self) -> List[Member]: + def premium_subscribers(self) -> list[Member]: """List[:class:`Member`]: A list of members who have "boosted" this guild.""" return [member for member in self.members if member.premium_since is not None] @property - def roles(self) -> List[Role]: + def roles(self) -> list[Role]: """List[:class:`Role`]: Returns a :class:`list` of the guild's roles in hierarchy order. The first element of this list will be the lowest role in the @@ -873,16 +909,16 @@ def roles(self) -> List[Role]: """ return sorted(self._roles.values()) - def get_role(self, role_id: int, /) -> Optional[Role]: + def get_role(self, role_id: int, /) -> Role | None: """Returns a role with the given ID. Parameters - ----------- + ---------- role_id: :class:`int` The ID to search for. Returns - -------- + ------- Optional[:class:`Role`] The role or ``None`` if not found. """ @@ -895,7 +931,7 @@ def default_role(self) -> Role: return self.get_role(self.id) # type: ignore @property - def premium_subscriber_role(self) -> Optional[Role]: + def premium_subscriber_role(self) -> Role | None: """Optional[:class:`Role`]: Gets the premium subscriber role, AKA "boost" role, in this guild. .. versionadded:: 1.6 @@ -906,7 +942,7 @@ def premium_subscriber_role(self) -> Optional[Role]: return None @property - def self_role(self) -> Optional[Role]: + def self_role(self) -> Role | None: """Optional[:class:`Role`]: Gets the role associated with this client's user, if any. .. versionadded:: 1.6 @@ -919,7 +955,7 @@ def self_role(self) -> Optional[Role]: return None @property - def stage_instances(self) -> List[StageInstance]: + def stage_instances(self) -> list[StageInstance]: """List[:class:`StageInstance`]: Returns a :class:`list` of the guild's stage instances that are currently running. @@ -927,55 +963,61 @@ def stage_instances(self) -> List[StageInstance]: """ return list(self._stage_instances.values()) - def get_stage_instance(self, stage_instance_id: int, /) -> Optional[StageInstance]: + def get_stage_instance(self, stage_instance_id: int, /) -> StageInstance | None: """Returns a stage instance with the given ID. .. versionadded:: 2.0 Parameters - ----------- + ---------- stage_instance_id: :class:`int` The ID to search for. Returns - -------- + ------- Optional[:class:`StageInstance`] The stage instance or ``None`` if not found. """ return self._stage_instances.get(stage_instance_id) @property - def owner(self) -> Optional[Member]: + def owner(self) -> Member | None: """Optional[:class:`Member`]: The member that owns the guild.""" return self.get_member(self.owner_id) # type: ignore @property - def icon(self) -> Optional[Asset]: + def icon(self) -> Asset | None: """Optional[:class:`Asset`]: Returns the guild's icon asset, if available.""" if self._icon is None: return None return Asset._from_guild_icon(self._state, self.id, self._icon) @property - def banner(self) -> Optional[Asset]: + def banner(self) -> Asset | None: """Optional[:class:`Asset`]: Returns the guild's banner asset, if available.""" if self._banner is None: return None - return Asset._from_guild_image(self._state, self.id, self._banner, path="banners") + return Asset._from_guild_image( + self._state, self.id, self._banner, path="banners" + ) @property - def splash(self) -> Optional[Asset]: + def splash(self) -> Asset | None: """Optional[:class:`Asset`]: Returns the guild's invite splash asset, if available.""" if self._splash is None: return None - return Asset._from_guild_image(self._state, self.id, self._splash, path="splashes") + return Asset._from_guild_image( + self._state, self.id, self._splash, path="splashes" + ) @property - def discovery_splash(self) -> Optional[Asset]: + def discovery_splash(self) -> Asset | None: """Optional[:class:`Asset`]: Returns the guild's discovery splash asset, if available.""" if self._discovery_splash is None: return None - return Asset._from_guild_image(self._state, self.id, self._discovery_splash, path="discovery-splashes") + return Asset._from_guild_image( + self._state, self.id, self._discovery_splash, path="discovery-splashes" + ) @property def member_count(self) -> int: @@ -985,7 +1027,6 @@ def member_count(self) -> int: Due to a Discord limitation, in order for this attribute to remain up-to-date and accurate, it requires :attr:`Intents.members` to be specified. - """ return self._member_count @@ -1022,7 +1063,7 @@ def invites_disabled(self) -> bool: """:class:`bool`: Returns a boolean indicating if the guild invites are disabled.""" return "INVITES_DISABLED" in self.features - def get_member_named(self, name: str, /) -> Optional[Member]: + def get_member_named(self, name: str, /) -> Member | None: """Returns the first member found that matches the name provided. The name can have an optional discriminator argument, e.g. "Jake#0001" @@ -1038,12 +1079,12 @@ def get_member_named(self, name: str, /) -> Optional[Member]: If no member is found, ``None`` is returned. Parameters - ----------- + ---------- name: :class:`str` The name of the member to lookup with an optional discriminator. Returns - -------- + ------- Optional[:class:`Member`] The member in this guild with the associated name. If not found then ``None`` is returned. @@ -1059,7 +1100,9 @@ def get_member_named(self, name: str, /) -> Optional[Member]: # do the actual lookup and return if found # if it isn't found then we'll do a full name lookup below. - result = utils.get(members, name=name[:-5], discriminator=potential_discriminator) + result = utils.get( + members, name=name[:-5], discriminator=potential_discriminator + ) if result is not None: return result @@ -1072,8 +1115,8 @@ def _create_channel( self, name: str, channel_type: ChannelType, - overwrites: Dict[Union[Role, Member], PermissionOverwrite] = MISSING, - category: Optional[Snowflake] = None, + overwrites: dict[Role | Member, PermissionOverwrite] = MISSING, + category: Snowflake | None = None, **options: Any, ): if overwrites is MISSING: @@ -1084,14 +1127,18 @@ def _create_channel( perms = [] for target, perm in overwrites.items(): if not isinstance(perm, PermissionOverwrite): - raise InvalidArgument(f"Expected PermissionOverwrite received {perm.__class__.__name__}") + raise InvalidArgument( + f"Expected PermissionOverwrite received {perm.__class__.__name__}" + ) allow, deny = perm.pair() payload = { "allow": allow.value, "deny": deny.value, "id": target.id, - "type": abc._Overwrites.ROLE if isinstance(target, Role) else abc._Overwrites.MEMBER, + "type": abc._Overwrites.ROLE + if isinstance(target, Role) + else abc._Overwrites.MEMBER, } perms.append(payload) @@ -1110,13 +1157,13 @@ async def create_text_channel( self, name: str, *, - reason: Optional[str] = None, - category: Optional[CategoryChannel] = None, + reason: str | None = None, + category: CategoryChannel | None = None, position: int = MISSING, topic: str = MISSING, slowmode_delay: int = MISSING, nsfw: bool = MISSING, - overwrites: Dict[Union[Role, Member], PermissionOverwrite] = MISSING, + overwrites: dict[Role | Member, PermissionOverwrite] = MISSING, ) -> TextChannel: """|coro| @@ -1136,28 +1183,8 @@ async def create_text_channel( other channels to follow suit. A follow-up call to :meth:`~TextChannel.edit` will be required to update the position of the channel in the channel list. - Examples - ---------- - - Creating a basic channel: - - .. code-block:: python3 - - channel = await guild.create_text_channel('cool-channel') - - Creating a "secret" channel: - - .. code-block:: python3 - - overwrites = { - guild.default_role: discord.PermissionOverwrite(read_messages=False), - guild.me: discord.PermissionOverwrite(read_messages=True) - } - - channel = await guild.create_text_channel('secret', overwrites=overwrites) - Parameters - ----------- + ---------- name: :class:`str` The channel's name. overwrites: Dict[Union[:class:`Role`, :class:`Member`, :class:`~discord.abc.Snowflake`], :class:`PermissionOverwrite`] @@ -1179,8 +1206,13 @@ async def create_text_channel( reason: Optional[:class:`str`] The reason for creating this channel. Shows up on the audit log. - Raises + Returns ------- + :class:`TextChannel` + The channel that was just created. + + Raises + ------ Forbidden You do not have the proper permissions to create this channel. HTTPException @@ -1188,10 +1220,25 @@ async def create_text_channel( InvalidArgument The permission overwrite information is not in proper form. - Returns - ------- - :class:`TextChannel` - The channel that was just created. + Examples + -------- + + Creating a basic channel: + + .. code-block:: python3 + + channel = await guild.create_text_channel('cool-channel') + + Creating a "secret" channel: + + .. code-block:: python3 + + overwrites = { + guild.default_role: discord.PermissionOverwrite(read_messages=False), + guild.me: discord.PermissionOverwrite(read_messages=True) + } + + channel = await guild.create_text_channel('secret', overwrites=overwrites) """ options = {} @@ -1225,21 +1272,21 @@ async def create_voice_channel( self, name: str, *, - reason: Optional[str] = None, - category: Optional[CategoryChannel] = None, + reason: str | None = None, + category: CategoryChannel | None = None, position: int = MISSING, bitrate: int = MISSING, user_limit: int = MISSING, - rtc_region: Optional[VoiceRegion] = MISSING, + rtc_region: VoiceRegion | None = MISSING, video_quality_mode: VideoQualityMode = MISSING, - overwrites: Dict[Union[Role, Member], PermissionOverwrite] = MISSING, + overwrites: dict[Role | Member, PermissionOverwrite] = MISSING, ) -> VoiceChannel: """|coro| This is similar to :meth:`create_text_channel` except makes a :class:`VoiceChannel` instead. Parameters - ----------- + ---------- name: :class:`str` The channel's name. overwrites: Dict[Union[:class:`Role`, :class:`Member`, :class:`~discord.abc.Snowflake`], :class:`PermissionOverwrite`] @@ -1267,6 +1314,11 @@ async def create_voice_channel( reason: Optional[:class:`str`] The reason for creating this channel. Shows up on the audit log. + Returns + ------- + :class:`VoiceChannel` + The channel that was just created. + Raises ------ Forbidden @@ -1275,11 +1327,6 @@ async def create_voice_channel( Creating the channel failed. InvalidArgument The permission overwrite information is not in proper form. - - Returns - ------- - :class:`VoiceChannel` - The channel that was just created. """ options = {} if position is not MISSING: @@ -1317,9 +1364,9 @@ async def create_stage_channel( *, topic: str, position: int = MISSING, - overwrites: Dict[Union[Role, Member], PermissionOverwrite] = MISSING, - category: Optional[CategoryChannel] = None, - reason: Optional[str] = None, + overwrites: dict[Role | Member, PermissionOverwrite] = MISSING, + category: CategoryChannel | None = None, + reason: str | None = None, ) -> StageChannel: """|coro| @@ -1328,7 +1375,7 @@ async def create_stage_channel( .. versionadded:: 1.7 Parameters - ----------- + ---------- name: :class:`str` The channel's name. topic: :class:`str` @@ -1345,6 +1392,11 @@ async def create_stage_channel( reason: Optional[:class:`str`] The reason for creating this channel. Shows up on the audit log. + Returns + ------- + :class:`StageChannel` + The channel that was just created. + Raises ------ Forbidden @@ -1353,14 +1405,9 @@ async def create_stage_channel( Creating the channel failed. InvalidArgument The permission overwrite information is not in proper form. - - Returns - ------- - :class:`StageChannel` - The channel that was just created. """ - options: Dict[str, Any] = { + options: dict[str, Any] = { "topic": topic, } if position is not MISSING: @@ -1384,13 +1431,13 @@ async def create_forum_channel( self, name: str, *, - reason: Optional[str] = None, - category: Optional[CategoryChannel] = None, + reason: str | None = None, + category: CategoryChannel | None = None, position: int = MISSING, topic: str = MISSING, slowmode_delay: int = MISSING, nsfw: bool = MISSING, - overwrites: Dict[Union[Role, Member], PermissionOverwrite] = MISSING, + overwrites: dict[Role | Member, PermissionOverwrite] = MISSING, ) -> ForumChannel: """|coro| @@ -1410,28 +1457,8 @@ async def create_forum_channel( other channels to follow suit. A follow-up call to :meth:`~ForumChannel.edit` will be required to update the position of the channel in the channel list. - Examples - ---------- - - Creating a basic channel: - - .. code-block:: python3 - - channel = await guild.create_forum_channel('cool-channel') - - Creating a "secret" channel: - - .. code-block:: python3 - - overwrites = { - guild.default_role: discord.PermissionOverwrite(read_messages=False), - guild.me: discord.PermissionOverwrite(read_messages=True) - } - - channel = await guild.create_forum_channel('secret', overwrites=overwrites) - Parameters - ----------- + ---------- name: :class:`str` The channel's name. overwrites: Dict[Union[:class:`Role`, :class:`Member`, :class:`~discord.abc.Snowflake`], :class:`PermissionOverwrite`] @@ -1453,8 +1480,13 @@ async def create_forum_channel( reason: Optional[:class:`str`] The reason for creating this channel. Shows up on the audit log. - Raises + Returns ------- + :class:`ForumChannel` + The channel that was just created. + + Raises + ------ Forbidden You do not have the proper permissions to create this channel. HTTPException @@ -1462,10 +1494,25 @@ async def create_forum_channel( InvalidArgument The permission overwrite information is not in proper form. - Returns - ------- - :class:`ForumChannel` - The channel that was just created. + Examples + -------- + + Creating a basic channel: + + .. code-block:: python3 + + channel = await guild.create_forum_channel('cool-channel') + + Creating a "secret" channel: + + .. code-block:: python3 + + overwrites = { + guild.default_role: discord.PermissionOverwrite(read_messages=False), + guild.me: discord.PermissionOverwrite(read_messages=True) + } + + channel = await guild.create_forum_channel('secret', overwrites=overwrites) """ options = {} @@ -1499,8 +1546,8 @@ async def create_category( self, name: str, *, - overwrites: Dict[Union[Role, Member], PermissionOverwrite] = MISSING, - reason: Optional[str] = None, + overwrites: dict[Role | Member, PermissionOverwrite] = MISSING, + reason: str | None = None, position: int = MISSING, ) -> CategoryChannel: """|coro| @@ -1512,6 +1559,11 @@ async def create_category( The ``category`` parameter is not supported in this function since categories cannot have categories. + Returns + ------- + :class:`CategoryChannel` + The channel that was just created. + Raises ------ Forbidden @@ -1520,13 +1572,8 @@ async def create_category( Creating the channel failed. InvalidArgument The permission overwrite information is not in proper form. - - Returns - ------- - :class:`CategoryChannel` - The channel that was just created. """ - options: Dict[str, Any] = {} + options: dict[str, Any] = {} if position is not MISSING: options["position"] = position @@ -1556,7 +1603,7 @@ async def leave(self) -> None: via :meth:`delete`. Raises - ------- + ------ HTTPException Leaving the guild failed. """ @@ -1569,7 +1616,7 @@ async def delete(self) -> None: guild. Raises - ------- + ------ HTTPException Deleting the guild failed. Forbidden @@ -1579,19 +1626,19 @@ async def delete(self) -> None: async def set_mfa_required(self, required: bool, *, reason: str = None) -> None: """|coro| - + Set whether it is required to have MFA enabled on your account to perform moderation actions. You must be the guild owner to do this. Parameters - ----------- + ---------- required: :class:`bool` Whether MFA should be required to perform moderation actions. reason: :class:`str` The reason to show up in the audit log. Raises - ------- + ------ HTTPException The operation failed. Forbidden @@ -1602,28 +1649,28 @@ async def set_mfa_required(self, required: bool, *, reason: str = None) -> None: async def edit( self, *, - reason: Optional[str] = MISSING, + reason: str | None = MISSING, name: str = MISSING, - description: Optional[str] = MISSING, - icon: Optional[bytes] = MISSING, - banner: Optional[bytes] = MISSING, - splash: Optional[bytes] = MISSING, - discovery_splash: Optional[bytes] = MISSING, + description: str | None = MISSING, + icon: bytes | None = MISSING, + banner: bytes | None = MISSING, + splash: bytes | None = MISSING, + discovery_splash: bytes | None = MISSING, community: bool = MISSING, - afk_channel: Optional[VoiceChannel] = MISSING, + afk_channel: VoiceChannel | None = MISSING, owner: Snowflake = MISSING, afk_timeout: int = MISSING, default_notifications: NotificationLevel = MISSING, verification_level: VerificationLevel = MISSING, explicit_content_filter: ContentFilter = MISSING, vanity_code: str = MISSING, - system_channel: Optional[TextChannel] = MISSING, + system_channel: TextChannel | None = MISSING, system_channel_flags: SystemChannelFlags = MISSING, preferred_locale: str = MISSING, - rules_channel: Optional[TextChannel] = MISSING, - public_updates_channel: Optional[TextChannel] = MISSING, + rules_channel: TextChannel | None = MISSING, + public_updates_channel: TextChannel | None = MISSING, premium_progress_bar_enabled: bool = MISSING, - disable_invites: bool = MISSING + disable_invites: bool = MISSING, ) -> Guild: r"""|coro| @@ -1729,7 +1776,7 @@ async def edit( if vanity_code is not MISSING: await http.change_vanity_code(self.id, vanity_code, reason=reason) - fields: Dict[str, Any] = {} + fields: dict[str, Any] = {} if name is not MISSING: fields["name"] = name @@ -1760,11 +1807,15 @@ async def edit( if discovery_splash is None: fields["discovery_splash"] = discovery_splash else: - fields["discovery_splash"] = utils._bytes_to_base64_data(discovery_splash) + fields["discovery_splash"] = utils._bytes_to_base64_data( + discovery_splash + ) if default_notifications is not MISSING: if not isinstance(default_notifications, NotificationLevel): - raise InvalidArgument("default_notifications field must be of type NotificationLevel") + raise InvalidArgument( + "default_notifications field must be of type NotificationLevel" + ) fields["default_message_notifications"] = default_notifications.value if afk_channel is not MISSING: @@ -1793,32 +1844,43 @@ async def edit( if owner is not MISSING: if self.owner_id != self._state.self_id: - raise InvalidArgument("To transfer ownership you must be the owner of the guild.") + raise InvalidArgument( + "To transfer ownership you must be the owner of the guild." + ) fields["owner_id"] = owner.id if verification_level is not MISSING: if not isinstance(verification_level, VerificationLevel): - raise InvalidArgument("verification_level field must be of type VerificationLevel") + raise InvalidArgument( + "verification_level field must be of type VerificationLevel" + ) fields["verification_level"] = verification_level.value if explicit_content_filter is not MISSING: if not isinstance(explicit_content_filter, ContentFilter): - raise InvalidArgument("explicit_content_filter field must be of type ContentFilter") + raise InvalidArgument( + "explicit_content_filter field must be of type ContentFilter" + ) fields["explicit_content_filter"] = explicit_content_filter.value if system_channel_flags is not MISSING: if not isinstance(system_channel_flags, SystemChannelFlags): - raise InvalidArgument("system_channel_flags field must be of type SystemChannelFlags") + raise InvalidArgument( + "system_channel_flags field must be of type SystemChannelFlags" + ) fields["system_channel_flags"] = system_channel_flags.value if community is not MISSING: features = self.features.copy() if community: - if "rules_channel_id" in fields and "public_updates_channel_id" in fields: + if ( + "rules_channel_id" in fields + and "public_updates_channel_id" in fields + ): if "COMMUNITY" not in features: features.append("COMMUNITY") else: @@ -1863,31 +1925,33 @@ async def fetch_channels(self) -> Sequence[GuildChannel]: .. versionadded:: 1.2 - Raises + Returns ------- + Sequence[:class:`abc.GuildChannel`] + All channels in the guild. + + Raises + ------ InvalidData An unknown channel type was received from Discord. HTTPException Retrieving the channels failed. - - Returns - ------- - Sequence[:class:`abc.GuildChannel`] - All channels in the guild. """ data = await self._state.http.get_all_guild_channels(self.id) def convert(d): factory, ch_type = _guild_channel_factory(d["type"]) if factory is None: - raise InvalidData("Unknown channel type {type} for channel ID {id}.".format_map(d)) + raise InvalidData( + "Unknown channel type {type} for channel ID {id}.".format_map(d) + ) channel = factory(guild=self, state=self._state, data=d) return channel return [convert(d) for d in data] - async def active_threads(self) -> List[Thread]: + async def active_threads(self) -> list[Thread]: """|coro| Returns a list of active :class:`Thread` that the client can access. @@ -1896,19 +1960,22 @@ async def active_threads(self) -> List[Thread]: .. versionadded:: 2.0 + Returns + ------- + List[:class:`Thread`] + The active threads + Raises ------ HTTPException The request to get the active threads failed. - - Returns - -------- - List[:class:`Thread`] - The active threads """ data = await self._state.http.get_active_threads(self.id) - threads = [Thread(guild=self, state=self._state, data=d) for d in data.get("threads", [])] - thread_lookup: Dict[int, Thread] = {thread.id: thread for thread in threads} + threads = [ + Thread(guild=self, state=self._state, data=d) + for d in data.get("threads", []) + ] + thread_lookup: dict[int, Thread] = {thread.id: thread for thread in threads} for member in data.get("members", []): thread = thread_lookup.get(int(member["id"])) if thread is not None: @@ -1917,7 +1984,9 @@ async def active_threads(self) -> List[Thread]: return threads # TODO: Remove Optional typing here when async iterators are refactored - def fetch_members(self, *, limit: Optional[int] = 1000, after: Optional[SnowflakeTime] = None) -> MemberIterator: + def fetch_members( + self, *, limit: int | None = 1000, after: SnowflakeTime | None = None + ) -> MemberIterator: """Retrieves an :class:`.AsyncIterator` that enables receiving the guild's members. In order to use this, :meth:`Intents.members` must be enabled. @@ -1939,6 +2008,11 @@ def fetch_members(self, *, limit: Optional[int] = 1000, after: Optional[Snowflak If a datetime is provided, it is recommended to use a UTC aware datetime. If the datetime is naive, it is assumed to be local time. + Yields + ------ + :class:`.Member` + The member with the member data parsed. + Raises ------ ClientException @@ -1946,11 +2020,6 @@ def fetch_members(self, *, limit: Optional[int] = 1000, after: Optional[Snowflak HTTPException Getting the members failed. - Yields - ------ - :class:`.Member` - The member with the member data parsed. - Examples -------- @@ -1981,21 +2050,21 @@ async def fetch_member(self, member_id: int, /) -> Member: member cache enabled, consider :meth:`get_member` instead. Parameters - ----------- + ---------- member_id: :class:`int` The member's ID to fetch from. - Raises + Returns ------- + :class:`Member` + The member from the member ID. + + Raises + ------ Forbidden You do not have access to the guild. HTTPException Fetching the member failed. - - Returns - -------- - :class:`Member` - The member from the member ID. """ data = await self._state.http.get_member(self.id, member_id) return Member(data=data, state=self._state, guild=self) @@ -2009,10 +2078,15 @@ async def fetch_ban(self, user: Snowflake) -> BanEntry: to get this information. Parameters - ----------- + ---------- user: :class:`abc.Snowflake` The user to get ban information from. + Returns + ------- + :class:`BanEntry` + The :class:`BanEntry` object for the specified user. + Raises ------ Forbidden @@ -2021,16 +2095,13 @@ async def fetch_ban(self, user: Snowflake) -> BanEntry: This user is not banned. HTTPException An error occurred while fetching the information. - - Returns - ------- - :class:`BanEntry` - The :class:`BanEntry` object for the specified user. """ data: BanPayload = await self._state.http.get_ban(user.id, self.id) - return BanEntry(user=User(state=self._state, data=data["user"]), reason=data["reason"]) + return BanEntry( + user=User(state=self._state, data=data["user"]), reason=data["reason"] + ) - async def fetch_channel(self, channel_id: int, /) -> Union[GuildChannel, Thread]: + async def fetch_channel(self, channel_id: int, /) -> GuildChannel | Thread: """|coro| Retrieves a :class:`.abc.GuildChannel` or :class:`.Thread` with the specified ID. @@ -2041,8 +2112,13 @@ async def fetch_channel(self, channel_id: int, /) -> Union[GuildChannel, Thread] .. versionadded:: 2.0 - Raises + Returns ------- + Union[:class:`.abc.GuildChannel`, :class:`.Thread`] + The channel from the ID. + + Raises + ------ InvalidData An unknown channel type was received from Discord or the guild the channel belongs to is not the same @@ -2053,17 +2129,14 @@ async def fetch_channel(self, channel_id: int, /) -> Union[GuildChannel, Thread] Invalid Channel ID. Forbidden You do not have permission to fetch this channel. - - Returns - -------- - Union[:class:`.abc.GuildChannel`, :class:`.Thread`] - The channel from the ID. """ data = await self._state.http.get_channel(channel_id) factory, ch_type = _threaded_guild_channel_factory(data["type"]) if factory is None: - raise InvalidData("Unknown channel type {type} for channel ID {id}.".format_map(data)) + raise InvalidData( + "Unknown channel type {type} for channel ID {id}.".format_map(data) + ) if ch_type in (ChannelType.group, ChannelType.private): raise InvalidData("Channel ID resolved to a private channel") @@ -2076,7 +2149,10 @@ async def fetch_channel(self, channel_id: int, /) -> Union[GuildChannel, Thread] return channel def bans( - self, limit: Optional[int] = None, before: Optional[SnowflakeTime] = None, after: Optional[SnowflakeTime] = None + self, + limit: int | None = None, + before: SnowflakeTime | None = None, + after: SnowflakeTime | None = None, ) -> BanIterator: """|coro| @@ -2102,6 +2178,11 @@ def bans( If a datetime is provided, it is recommended to use a UTC aware datetime. If the datetime is naive, it is assumed to be local time. + Yields + ------ + :class:`.BanEntry` + The ban entry for the ban. + Raises ------ Forbidden @@ -2109,11 +2190,6 @@ def bans( HTTPException An error occurred while fetching the information. - Yields - ------ - :class:`.BanEntry` - The ban entry for the ban. - Examples -------- @@ -2135,9 +2211,9 @@ async def prune_members( *, days: int, compute_prune_count: bool = True, - roles: List[Snowflake] = MISSING, - reason: Optional[str] = None, - ) -> Optional[int]: + roles: list[Snowflake] = MISSING, + reason: str | None = None, + ) -> int | None: r"""|coro| Prunes the guild from its inactive members. @@ -2188,7 +2264,9 @@ async def prune_members( """ if not isinstance(days, int): - raise InvalidArgument(f"Expected int for ``days``, received {days.__class__.__name__} instead.") + raise InvalidArgument( + f"Expected int for ``days``, received {days.__class__.__name__} instead." + ) role_ids = [str(role.id) for role in roles] if roles else [] data = await self._state.http.prune_members( @@ -2200,7 +2278,7 @@ async def prune_members( ) return data["pruned"] - async def templates(self) -> List[Template]: + async def templates(self) -> list[Template]: """|coro| Gets the list of templates from this guild. @@ -2209,37 +2287,37 @@ async def templates(self) -> List[Template]: .. versionadded:: 1.7 - Raises - ------- - Forbidden - You don't have permissions to get the templates. - Returns - -------- + ------- List[:class:`Template`] The templates for this guild. + + Raises + ------ + Forbidden + You don't have permissions to get the templates. """ from .template import Template data = await self._state.http.guild_templates(self.id) return [Template(data=d, state=self._state) for d in data] - async def webhooks(self) -> List[Webhook]: + async def webhooks(self) -> list[Webhook]: """|coro| Gets the list of webhooks from this guild. Requires :attr:`~.Permissions.manage_webhooks` permissions. - Raises - ------- - Forbidden - You don't have permissions to get the webhooks. - Returns - -------- + ------- List[:class:`Webhook`] The webhooks for this guild. + + Raises + ------ + Forbidden + You don't have permissions to get the webhooks. """ from .webhook import Webhook @@ -2247,7 +2325,9 @@ async def webhooks(self) -> List[Webhook]: data = await self._state.http.guild_webhooks(self.id) return [Webhook.from_state(d, state=self._state) for d in data] - async def estimate_pruned_members(self, *, days: int, roles: List[Snowflake] = MISSING) -> int: + async def estimate_pruned_members( + self, *, days: int, roles: list[Snowflake] = MISSING + ) -> int: """|coro| Similar to :meth:`prune_members` except instead of actually @@ -2255,7 +2335,7 @@ async def estimate_pruned_members(self, *, days: int, roles: List[Snowflake] = M from the guild had it been called. Parameters - ----------- + ---------- days: :class:`int` The number of days before counting as inactive. roles: List[:class:`abc.Snowflake`] @@ -2264,29 +2344,31 @@ async def estimate_pruned_members(self, *, days: int, roles: List[Snowflake] = M .. versionadded:: 1.7 - Raises + Returns ------- + :class:`int` + The number of members estimated to be pruned. + + Raises + ------ Forbidden You do not have permissions to prune members. HTTPException An error occurred while fetching the prune members estimate. InvalidArgument An integer was not passed for ``days``. - - Returns - --------- - :class:`int` - The number of members estimated to be pruned. """ if not isinstance(days, int): - raise InvalidArgument(f"Expected int for ``days``, received {days.__class__.__name__} instead.") + raise InvalidArgument( + f"Expected int for ``days``, received {days.__class__.__name__} instead." + ) role_ids = [str(role.id) for role in roles] if roles else [] data = await self._state.http.estimate_pruned_members(self.id, days, role_ids) return data["pruned"] - async def invites(self) -> List[Invite]: + async def invites(self) -> list[Invite]: """|coro| Returns a list of all active instant invites from the guild. @@ -2294,28 +2376,32 @@ async def invites(self) -> List[Invite]: You must have the :attr:`~Permissions.manage_guild` permission to get this information. - Raises + Returns ------- + List[:class:`Invite`] + The list of invites that are currently active. + + Raises + ------ Forbidden You do not have proper permissions to get the information. HTTPException An error occurred while fetching the information. - - Returns - ------- - List[:class:`Invite`] - The list of invites that are currently active. """ data = await self._state.http.invites_from(self.id) result = [] for invite in data: channel = self.get_channel(int(invite["channel"]["id"])) - result.append(Invite(state=self._state, data=invite, guild=self, channel=channel)) + result.append( + Invite(state=self._state, data=invite, guild=self, channel=channel) + ) return result - async def create_template(self, *, name: str, description: str = MISSING) -> Template: + async def create_template( + self, *, name: str, description: str = MISSING + ) -> Template: """|coro| Creates a template for the guild. @@ -2326,7 +2412,7 @@ async def create_template(self, *, name: str, description: str = MISSING) -> Tem .. versionadded:: 1.7 Parameters - ----------- + ---------- name: :class:`str` The name of the template. description: :class:`str` @@ -2354,14 +2440,14 @@ async def create_integration(self, *, type: str, id: int) -> None: .. versionadded:: 1.4 Parameters - ----------- + ---------- type: :class:`str` The integration type (e.g. Twitch). id: :class:`int` The integration ID. Raises - ------- + ------ Forbidden You do not have permission to create the integration. HTTPException @@ -2369,7 +2455,7 @@ async def create_integration(self, *, type: str, id: int) -> None: """ await self._state.http.create_integration(self.id, type, id) - async def integrations(self) -> List[Integration]: + async def integrations(self) -> list[Integration]: """|coro| Returns a list of all integrations attached to the guild. @@ -2379,29 +2465,33 @@ async def integrations(self) -> List[Integration]: .. versionadded:: 1.4 - Raises + Returns ------- + List[:class:`Integration`] + The list of integrations that are attached to the guild. + + Raises + ------ Forbidden You do not have permission to create the integration. HTTPException Fetching the integrations failed. - - Returns - -------- - List[:class:`Integration`] - The list of integrations that are attached to the guild. """ data = await self._state.http.get_all_integrations(self.id) def convert(d): factory, _ = _integration_factory(d["type"]) if factory is None: - raise InvalidData("Unknown integration type {type!r} for integration ID {id}".format_map(d)) + raise InvalidData( + "Unknown integration type {type!r} for integration ID {id}".format_map( + d + ) + ) return factory(guild=self, data=d) return [convert(d) for d in data] - async def fetch_stickers(self) -> List[GuildSticker]: + async def fetch_stickers(self) -> list[GuildSticker]: r"""|coro| Retrieves a list of all :class:`Sticker`\s for the guild. @@ -2438,21 +2528,21 @@ async def fetch_sticker(self, sticker_id: int, /) -> GuildSticker: For general usage, consider iterating over :attr:`stickers` instead. Parameters - ------------- + ---------- sticker_id: :class:`int` The sticker's ID. + Returns + ------- + :class:`GuildSticker` + The retrieved sticker. + Raises - --------- + ------ NotFound The sticker requested could not be found. HTTPException An error occurred fetching the sticker. - - Returns - -------- - :class:`GuildSticker` - The retrieved sticker. """ data = await self._state.http.get_guild_sticker(self.id, sticker_id) return GuildSticker(state=self._state, data=data) @@ -2461,10 +2551,10 @@ async def create_sticker( self, *, name: str, - description: Optional[str] = None, + description: str | None = None, emoji: str, file: File, - reason: Optional[str] = None, + reason: str | None = None, ) -> GuildSticker: """|coro| @@ -2476,7 +2566,7 @@ async def create_sticker( .. versionadded:: 2.0 Parameters - ----------- + ---------- name: :class:`str` The sticker name. Must be 2 to 30 characters. description: Optional[:class:`str`] @@ -2488,30 +2578,27 @@ async def create_sticker( reason: :class:`str` The reason for creating this sticker. Shows up on the audit log. - Raises + Returns ------- + :class:`GuildSticker` + The created sticker. + + Raises + ------ Forbidden You are not allowed to create stickers. HTTPException An error occurred creating a sticker. TypeError The parameters for the sticker are not correctly formatted. - - Returns - -------- - :class:`GuildSticker` - The created sticker. """ if not (2 <= len(name) <= 30): - raise TypeError("\"name\" parameter must be 2 to 30 characters long.") + raise TypeError('"name" parameter must be 2 to 30 characters long.') if description and not (2 <= len(description) <= 100): - raise TypeError("\"description\" parameter must be 2 to 200 characters long.") + raise TypeError('"description" parameter must be 2 to 200 characters long.') - payload = { - "name": name, - "description": description or "" - } + payload = {"name": name, "description": description or ""} try: emoji = unicodedata.name(emoji) @@ -2522,10 +2609,14 @@ async def create_sticker( payload["tags"] = emoji - data = await self._state.http.create_guild_sticker(self.id, payload, file, reason) + data = await self._state.http.create_guild_sticker( + self.id, payload, file, reason + ) return self._state.store_sticker(self, data) - async def delete_sticker(self, sticker: Snowflake, *, reason: Optional[str] = None) -> None: + async def delete_sticker( + self, sticker: Snowflake, *, reason: str | None = None + ) -> None: """|coro| Deletes the custom :class:`Sticker` from the guild. @@ -2536,14 +2627,14 @@ async def delete_sticker(self, sticker: Snowflake, *, reason: Optional[str] = No .. versionadded:: 2.0 Parameters - ----------- + ---------- sticker: :class:`abc.Snowflake` The sticker you are deleting. reason: Optional[:class:`str`] The reason for deleting this sticker. Shows up on the audit log. Raises - ------- + ------ Forbidden You are not allowed to delete stickers. HTTPException @@ -2551,7 +2642,7 @@ async def delete_sticker(self, sticker: Snowflake, *, reason: Optional[str] = No """ await self._state.http.delete_guild_sticker(self.id, sticker.id, reason) - async def fetch_emojis(self) -> List[Emoji]: + async def fetch_emojis(self) -> list[Emoji]: r"""|coro| Retrieves all custom :class:`Emoji`\s from the guild. @@ -2584,21 +2675,21 @@ async def fetch_emoji(self, emoji_id: int, /) -> Emoji: For general usage, consider iterating over :attr:`emojis` instead. Parameters - ------------- + ---------- emoji_id: :class:`int` The emoji's ID. + Returns + ------- + :class:`Emoji` + The retrieved emoji. + Raises - --------- + ------ NotFound The emoji requested could not be found. HTTPException An error occurred fetching the emoji. - - Returns - -------- - :class:`Emoji` - The retrieved emoji. """ data = await self._state.http.get_custom_emoji(self.id, emoji_id) return Emoji(guild=self, state=self._state, data=data) @@ -2608,8 +2699,8 @@ async def create_custom_emoji( *, name: str, image: bytes, - roles: List[Role] = MISSING, - reason: Optional[str] = None, + roles: list[Role] = MISSING, + reason: str | None = None, ) -> Emoji: r"""|coro| @@ -2648,10 +2739,14 @@ async def create_custom_emoji( img = utils._bytes_to_base64_data(image) role_ids = [role.id for role in roles] if roles else [] - data = await self._state.http.create_custom_emoji(self.id, name, img, roles=role_ids, reason=reason) + data = await self._state.http.create_custom_emoji( + self.id, name, img, roles=role_ids, reason=reason + ) return self._state.store_emoji(self, data) - async def delete_emoji(self, emoji: Snowflake, *, reason: Optional[str] = None) -> None: + async def delete_emoji( + self, emoji: Snowflake, *, reason: str | None = None + ) -> None: """|coro| Deletes the custom :class:`Emoji` from the guild. @@ -2660,14 +2755,14 @@ async def delete_emoji(self, emoji: Snowflake, *, reason: Optional[str] = None) do this. Parameters - ----------- + ---------- emoji: :class:`abc.Snowflake` The emoji you are deleting. reason: Optional[:class:`str`] The reason for deleting this emoji. Shows up on the audit log. Raises - ------- + ------ Forbidden You are not allowed to delete emojis. HTTPException @@ -2676,7 +2771,7 @@ async def delete_emoji(self, emoji: Snowflake, *, reason: Optional[str] = None) await self._state.http.delete_custom_emoji(self.id, emoji.id, reason=reason) - async def fetch_roles(self) -> List[Role]: + async def fetch_roles(self) -> list[Role]: """|coro| Retrieves all :class:`Role` that the guild has. @@ -2687,15 +2782,15 @@ async def fetch_roles(self) -> List[Role]: .. versionadded:: 1.3 - Raises - ------- - HTTPException - Retrieving the roles failed. - Returns ------- List[:class:`Role`] All roles in the guild. + + Raises + ------ + HTTPException + Retrieving the roles failed. """ data = await self._state.http.get_roles(self.id) return [Role(guild=self, state=self._state, data=d) for d in data] @@ -2712,20 +2807,20 @@ async def _fetch_role(self, role_id: int) -> Role: .. versionadded:: 2.0 Parameters - ----------- + ---------- role_id: :class:`int` The role ID to fetch from the guild. - Raises - ------- - HTTPException - Retrieving the role failed. - Returns ------- Optional[:class:`Role`] The role in the guild with the specified ID. Returns ``None`` if not found. + + Raises + ------ + HTTPException + Retrieving the role failed. """ roles = await self.fetch_roles() for role in roles: @@ -2736,10 +2831,10 @@ async def _fetch_role(self, role_id: int) -> Role: async def create_role( self, *, - reason: Optional[str] = ..., + reason: str | None = ..., name: str = ..., permissions: Permissions = ..., - colour: Union[Colour, int] = ..., + colour: Colour | int = ..., hoist: bool = ..., mentionable: bool = ..., ) -> Role: @@ -2749,10 +2844,10 @@ async def create_role( async def create_role( self, *, - reason: Optional[str] = ..., + reason: str | None = ..., name: str = ..., permissions: Permissions = ..., - color: Union[Colour, int] = ..., + color: Colour | int = ..., hoist: bool = ..., mentionable: bool = ..., ) -> Role: @@ -2763,11 +2858,11 @@ async def create_role( *, name: str = MISSING, permissions: Permissions = MISSING, - color: Union[Colour, int] = MISSING, - colour: Union[Colour, int] = MISSING, + color: Colour | int = MISSING, + colour: Colour | int = MISSING, hoist: bool = MISSING, mentionable: bool = MISSING, - reason: Optional[str] = None, + reason: str | None = None, ) -> Role: """|coro| @@ -2782,7 +2877,7 @@ async def create_role( Can now pass ``int`` to ``colour`` keyword-only parameter. Parameters - ----------- + ---------- name: :class:`str` The role name. Defaults to 'new role'. permissions: :class:`Permissions` @@ -2799,21 +2894,21 @@ async def create_role( reason: Optional[:class:`str`] The reason for creating this role. Shows up on the audit log. - Raises + Returns ------- + :class:`Role` + The newly created role. + + Raises + ------ Forbidden You do not have permissions to create the role. HTTPException Creating the role failed. InvalidArgument An invalid keyword argument was given. - - Returns - -------- - :class:`Role` - The newly created role. """ - fields: Dict[str, Any] = {} + fields: dict[str, Any] = {} if permissions is not MISSING: fields["permissions"] = str(permissions.value) else: @@ -2840,7 +2935,9 @@ async def create_role( # TODO: add to cache return role - async def edit_role_positions(self, positions: Dict[Snowflake, int], *, reason: Optional[str] = None) -> List[Role]: + async def edit_role_positions( + self, positions: dict[Snowflake, int], *, reason: str | None = None + ) -> list[Role]: """|coro| Bulk edits a list of :class:`Role` in the guild. @@ -2863,39 +2960,41 @@ async def edit_role_positions(self, positions: Dict[Snowflake, int], *, reason: await guild.edit_role_positions(positions=positions) Parameters - ----------- + ---------- positions: Dict[:class:`Role`, :class:`int`] A :class:`dict` of :class:`Role` to :class:`int` to change the positions of each given role. reason: Optional[:class:`str`] The reason for editing the role positions. Shows up on the audit log. - Raises + Returns ------- + List[:class:`Role`] + A list of all the roles in the guild. + + Raises + ------ Forbidden You do not have permissions to move the roles. HTTPException Moving the roles failed. InvalidArgument An invalid keyword argument was given. - - Returns - -------- - List[:class:`Role`] - A list of all the roles in the guild. """ if not isinstance(positions, dict): raise InvalidArgument("positions parameter expects a dict.") - role_positions: List[Dict[str, Any]] = [] + role_positions: list[dict[str, Any]] = [] for role, position in positions.items(): payload = {"id": role.id, "position": position} role_positions.append(payload) - data = await self._state.http.move_role_position(self.id, role_positions, reason=reason) - roles: List[Role] = [] + data = await self._state.http.move_role_position( + self.id, role_positions, reason=reason + ) + roles: list[Role] = [] for d in data: role = Role(guild=self, data=d, state=self._state) roles.append(role) @@ -2903,7 +3002,7 @@ async def edit_role_positions(self, positions: Dict[Snowflake, int], *, reason: return roles - async def kick(self, user: Snowflake, *, reason: Optional[str] = None) -> None: + async def kick(self, user: Snowflake, *, reason: str | None = None) -> None: """|coro| Kicks a user from the guild. @@ -2914,14 +3013,14 @@ async def kick(self, user: Snowflake, *, reason: Optional[str] = None) -> None: do this. Parameters - ----------- + ---------- user: :class:`abc.Snowflake` The user to kick from their guild. reason: Optional[:class:`str`] The reason the user got kicked. Raises - ------- + ------ Forbidden You do not have the proper permissions to kick. HTTPException @@ -2933,9 +3032,9 @@ async def ban( self, user: Snowflake, *, - delete_message_seconds: Optional[int] = None, - delete_message_days: Optional[Literal[0, 1, 2, 3, 4, 5, 6, 7]] = None, - reason: Optional[str] = None, + delete_message_seconds: int | None = None, + delete_message_days: Literal[0, 1, 2, 3, 4, 5, 6, 7] | None = None, + reason: str | None = None, ) -> None: """|coro| @@ -2947,7 +3046,7 @@ async def ban( do this. Parameters - ----------- + ---------- user: :class:`abc.Snowflake` The user to ban from their guild. delete_message_seconds: Optional[:class:`int`] @@ -2961,21 +3060,27 @@ async def ban( The reason the user got banned. Raises - ------- + ------ Forbidden You do not have the proper permissions to ban. HTTPException Banning failed. """ if delete_message_seconds and delete_message_days: - raise TypeError("delete_message_seconds and delete_message_days are mutually exclusive.") + raise TypeError( + "delete_message_seconds and delete_message_days are mutually exclusive." + ) if not (0 <= delete_message_seconds <= 604800): - raise TypeError("delete_message_seconds must be between 0 and 604800 seconds.") + raise TypeError( + "delete_message_seconds must be between 0 and 604800 seconds." + ) - await self._state.http.ban(user.id, self.id, delete_message_seconds, delete_message_days, reason=reason) + await self._state.http.ban( + user.id, self.id, delete_message_seconds, delete_message_days, reason=reason + ) - async def unban(self, user: Snowflake, *, reason: Optional[str] = None) -> None: + async def unban(self, user: Snowflake, *, reason: str | None = None) -> None: """|coro| Unbans a user from the guild. @@ -2986,14 +3091,14 @@ async def unban(self, user: Snowflake, *, reason: Optional[str] = None) -> None: do this. Parameters - ----------- + ---------- user: :class:`abc.Snowflake` The user to unban. reason: Optional[:class:`str`] The reason for doing this action. Shows up on the audit log. Raises - ------- + ------ Forbidden You do not have the proper permissions to unban. HTTPException @@ -3001,7 +3106,7 @@ async def unban(self, user: Snowflake, *, reason: Optional[str] = None) -> None: """ await self._state.http.unban(user.id, self.id, reason=reason) - async def vanity_invite(self) -> Optional[Invite]: + async def vanity_invite(self) -> Invite | None: """|coro| Returns the guild's special vanity invite. @@ -3011,18 +3116,18 @@ async def vanity_invite(self) -> Optional[Invite]: You must have the :attr:`~Permissions.manage_guild` permission to use this as well. - Raises + Returns ------- + Optional[:class:`Invite`] + The special vanity invite. If ``None`` then the guild does not + have a vanity invite set. + + Raises + ------ Forbidden You do not have the proper permissions to get this. HTTPException Retrieving the vanity invite failed. - - Returns - -------- - Optional[:class:`Invite`] - The special vanity invite. If ``None`` then the guild does not - have a vanity invite set. """ # we start with { code: abc } @@ -3046,10 +3151,10 @@ async def vanity_invite(self) -> Optional[Invite]: def audit_logs( self, *, - limit: Optional[int] = 100, - before: Optional[SnowflakeTime] = None, - after: Optional[SnowflakeTime] = None, - oldest_first: Optional[bool] = None, + limit: int | None = 100, + before: SnowflakeTime | None = None, + after: SnowflakeTime | None = None, + oldest_first: bool | None = None, user: Snowflake = None, action: AuditLogAction = None, ) -> AuditLogIterator: @@ -3057,26 +3162,8 @@ def audit_logs( You must have the :attr:`~Permissions.view_audit_log` permission to use this. - Examples - ---------- - - Getting the first 100 entries: :: - - async for entry in guild.audit_logs(limit=100): - print(f'{entry.user} did {entry.action} to {entry.target}') - - Getting entries for a specific action: :: - - async for entry in guild.audit_logs(action=discord.AuditLogAction.ban): - print(f'{entry.user} banned {entry.target}') - - Getting entries made by a specific user: :: - - entries = await guild.audit_logs(limit=None, user=guild.me).flatten() - await channel.send(f'I made {len(entries)} moderation actions.') - Parameters - ----------- + ---------- limit: Optional[:class:`int`] The number of entries to retrieve. If ``None`` retrieve all entries. before: Union[:class:`abc.Snowflake`, :class:`datetime.datetime`] @@ -3095,17 +3182,35 @@ def audit_logs( action: :class:`AuditLogAction` The action to filter with. + Yields + ------ + :class:`AuditLogEntry` + The audit log entry. + Raises - ------- + ------ Forbidden You are not allowed to fetch audit logs HTTPException An error occurred while fetching the audit logs. - Yields + Examples -------- - :class:`AuditLogEntry` - The audit log entry. + + Getting the first 100 entries: :: + + async for entry in guild.audit_logs(limit=100): + print(f'{entry.user} did {entry.action} to {entry.target}') + + Getting entries for a specific action: :: + + async for entry in guild.audit_logs(action=discord.AuditLogAction.ban): + print(f'{entry.user} banned {entry.target}') + + Getting entries made by a specific user: :: + + entries = await guild.audit_logs(limit=None, user=guild.me).flatten() + await channel.send(f'I made {len(entries)} moderation actions.') """ user_id = user.id if user is not None else None if action: @@ -3130,23 +3235,25 @@ async def widget(self) -> Widget: The guild must have the widget enabled to get this information. - Raises + Returns ------- + :class:`Widget` + The guild's widget. + + Raises + ------ Forbidden The widget for this guild is disabled. HTTPException Retrieving the widget failed. - - Returns - -------- - :class:`Widget` - The guild's widget. """ data = await self._state.http.get_widget(self.id) return Widget(state=self._state, data=data) - async def edit_widget(self, *, enabled: bool = MISSING, channel: Optional[Snowflake] = MISSING) -> None: + async def edit_widget( + self, *, enabled: bool = MISSING, channel: Snowflake | None = MISSING + ) -> None: """|coro| Edits the widget of the guild. @@ -3157,14 +3264,14 @@ async def edit_widget(self, *, enabled: bool = MISSING, channel: Optional[Snowfl .. versionadded:: 2.0 Parameters - ----------- + ---------- enabled: :class:`bool` Whether to enable the widget for the guild. channel: Optional[:class:`~discord.abc.Snowflake`] The new widget channel. ``None`` removes the widget channel. Raises - ------- + ------ Forbidden You do not have permission to edit the widget. HTTPException @@ -3189,12 +3296,12 @@ async def chunk(self, *, cache: bool = True) -> None: .. versionadded:: 1.5 Parameters - ----------- + ---------- cache: :class:`bool` Whether to cache the members as well. Raises - ------- + ------ ClientException The members intent is not enabled. """ @@ -3207,13 +3314,13 @@ async def chunk(self, *, cache: bool = True) -> None: async def query_members( self, - query: Optional[str] = None, + query: str | None = None, *, limit: int = 5, - user_ids: Optional[List[int]] = None, + user_ids: list[int] | None = None, presences: bool = False, cache: bool = True, - ) -> List[Member]: + ) -> list[Member]: """|coro| Request members that belong to this guild whose username starts with @@ -3224,7 +3331,7 @@ async def query_members( .. versionadded:: 1.3 Parameters - ----------- + ---------- query: Optional[:class:`str`] The string that the username's start with. limit: :class:`int` @@ -3244,20 +3351,19 @@ async def query_members( .. versionadded:: 1.4 + Returns + ------- + List[:class:`Member`] + The list of members that have matched the query. Raises - ------- + ------ asyncio.TimeoutError The query timed out waiting for the members. ValueError Invalid parameters were passed to the function ClientException The presences intent is not enabled. - - Returns - -------- - List[:class:`Member`] - The list of members that have matched the query. """ if presences and not self._state._intents.presences: @@ -3289,7 +3395,7 @@ async def query_members( async def change_voice_state( self, *, - channel: Optional[VocalGuildChannel], + channel: VocalGuildChannel | None, self_mute: bool = False, self_deaf: bool = False, ): @@ -3300,7 +3406,7 @@ async def change_voice_state( .. versionadded:: 1.4 Parameters - ----------- + ---------- channel: Optional[:class:`VoiceChannel`] Channel the client wants to join. Use ``None`` to disconnect. self_mute: :class:`bool` @@ -3323,20 +3429,19 @@ async def welcome_screen(self): .. versionadded:: 2.0 - Raises + Returns ------- + :class:`WelcomeScreen` + The welcome screen of guild. + + Raises + ------ Forbidden You do not have the proper permissions to get this. HTTPException Retrieving the welcome screen failed somehow. NotFound The guild doesn't have a welcome screen or community feature is disabled. - - - Returns - -------- - :class:`WelcomeScreen` - The welcome screen of guild. """ data = await self._state.http.get_welcome_screen(self.id) return WelcomeScreen(data=data, guild=self) @@ -3345,9 +3450,9 @@ async def welcome_screen(self): async def edit_welcome_screen( self, *, - description: Optional[str] = ..., - welcome_channels: Optional[List[WelcomeScreenChannel]] = ..., - enabled: Optional[bool] = ..., + description: str | None = ..., + welcome_channels: list[WelcomeScreenChannel] | None = ..., + enabled: bool | None = ..., ) -> WelcomeScreen: ... @@ -3366,7 +3471,7 @@ async def edit_welcome_screen(self, **options): The guild must have ``COMMUNITY`` in :attr:`Guild.features` Parameters - ----------- + ---------- description: Optional[:class:`str`] The new description of welcome screen. welcome_channels: Optional[List[:class:`WelcomeScreenChannel`]] @@ -3376,19 +3481,19 @@ async def edit_welcome_screen(self, **options): reason: Optional[:class:`str`] The reason that shows up on audit log. - Raises + Returns ------- + :class:`WelcomeScreen` + The edited welcome screen. + + Raises + ------ HTTPException Editing the welcome screen failed somehow. Forbidden You don't have permissions to edit the welcome screen. NotFound This welcome screen does not exist. - - Returns - -------- - :class:`WelcomeScreen` - The edited welcome screen. """ welcome_channels = options.get("welcome_channels", []) @@ -3396,17 +3501,23 @@ async def edit_welcome_screen(self, **options): for channel in welcome_channels: if not isinstance(channel, WelcomeScreenChannel): - raise TypeError("welcome_channels parameter must be a list of WelcomeScreenChannel.") + raise TypeError( + "welcome_channels parameter must be a list of WelcomeScreenChannel." + ) welcome_channels_data.append(channel.to_dict()) options["welcome_channels"] = welcome_channels_data if options: - new = await self._state.http.edit_welcome_screen(self.id, options, reason=options.get("reason")) + new = await self._state.http.edit_welcome_screen( + self.id, options, reason=options.get("reason") + ) return WelcomeScreen(data=new, guild=self) - async def fetch_scheduled_events(self, *, with_user_count: bool = True) -> List[ScheduledEvent]: + async def fetch_scheduled_events( + self, *, with_user_count: bool = True + ) -> list[ScheduledEvent]: """|coro| Returns a list of :class:`ScheduledEvent` in the guild. @@ -3416,36 +3527,46 @@ async def fetch_scheduled_events(self, *, with_user_count: bool = True) -> List[ This method is an API call. For general usage, consider :attr:`scheduled_events` instead. Parameters - ----------- + ---------- with_user_count: Optional[:class:`bool`] If the scheduled event should be fetched with the number of users that are interested in the events. Defaults to ``True``. - Raises + Returns ------- + List[:class:`ScheduledEvent`] + The fetched scheduled events. + + Raises + ------ ClientException The scheduled events intent is not enabled. HTTPException Getting the scheduled events failed. - - Returns - -------- - List[:class:`ScheduledEvent`] - The fetched scheduled events. """ - data = await self._state.http.get_scheduled_events(self.id, with_user_count=with_user_count) + data = await self._state.http.get_scheduled_events( + self.id, with_user_count=with_user_count + ) result = [] for event in data: - creator = None if not event.get("creator", None) else self.get_member(event.get("creator_id")) - result.append(ScheduledEvent(state=self._state, guild=self, creator=creator, data=event)) + creator = ( + None + if not event.get("creator", None) + else self.get_member(event.get("creator_id")) + ) + result.append( + ScheduledEvent( + state=self._state, guild=self, creator=creator, data=event + ) + ) self._scheduled_events_from_list(result) return result async def fetch_scheduled_event( self, event_id: int, /, *, with_user_count: bool = True - ) -> Optional[ScheduledEvent]: + ) -> ScheduledEvent | None: """|coro| Retrieves a :class:`ScheduledEvent` from event ID. @@ -3456,7 +3577,7 @@ async def fetch_scheduled_event( consider :meth:`get_scheduled_event` instead. Parameters - ----------- + ---------- event_id: :class:`int` The event's ID to fetch with. with_user_count: Optional[:class:`bool`] @@ -3464,23 +3585,29 @@ async def fetch_scheduled_event( users that are interested in the event. Defaults to ``True``. - Raises + Returns ------- + Optional[:class:`ScheduledEvent`] + The scheduled event from the event ID. + + Raises + ------ HTTPException Fetching the event failed. NotFound Event not found. - - Returns - -------- - Optional[:class:`ScheduledEvent`] - The scheduled event from the event ID. """ data = await self._state.http.get_scheduled_event( guild_id=self.id, event_id=event_id, with_user_count=with_user_count ) - creator = None if not data.get("creator", None) else self.get_member(data.get("creator_id")) - event = ScheduledEvent(state=self._state, guild=self, creator=creator, data=data) + creator = ( + None + if not data.get("creator", None) + else self.get_member(data.get("creator_id")) + ) + event = ScheduledEvent( + state=self._state, guild=self, creator=creator, data=data + ) old_event = self._scheduled_events.get(event.id) if old_event: @@ -3490,16 +3617,16 @@ async def fetch_scheduled_event( return event - def get_scheduled_event(self, event_id: int, /) -> Optional[ScheduledEvent]: + def get_scheduled_event(self, event_id: int, /) -> ScheduledEvent | None: """Returns a Scheduled Event with the given ID. Parameters - ----------- + ---------- event_id: :class:`int` The ID to search for. Returns - -------- + ------- Optional[:class:`ScheduledEvent`] The scheduled event or ``None`` if not found. """ @@ -3512,15 +3639,15 @@ async def create_scheduled_event( description: str = MISSING, start_time: datetime, end_time: datetime = MISSING, - location: Union[str, int, VoiceChannel, StageChannel, ScheduledEventLocation], + location: str | int | VoiceChannel | StageChannel | ScheduledEventLocation, privacy_level: ScheduledEventPrivacyLevel = ScheduledEventPrivacyLevel.guild_only, - reason: Optional[str] = None, - ) -> Optional[ScheduledEvent]: + reason: str | None = None, + ) -> ScheduledEvent | None: """|coro| Creates a scheduled event. Parameters - ----------- + ---------- name: :class:`str` The name of the scheduled event. description: Optional[:class:`str`] @@ -3538,22 +3665,22 @@ async def create_scheduled_event( reason: Optional[:class:`str`] The reason to show in the audit log. - Raises + Returns ------- + Optional[:class:`ScheduledEvent`] + The created scheduled event. + + Raises + ------ Forbidden You do not have the Manage Events permission. HTTPException The operation failed. - - Returns - -------- - Optional[:class:`ScheduledEvent`] - The created scheduled event. """ - payload: Dict[str, Union[str, int]] = { + payload: dict[str, str | int] = { "name": name, "scheduled_start_time": start_time.isoformat(), - "privacy_level": int(privacy_level) + "privacy_level": int(privacy_level), } if not isinstance(location, ScheduledEventLocation): @@ -3574,56 +3701,60 @@ async def create_scheduled_event( if end_time is not MISSING: payload["scheduled_end_time"] = end_time.isoformat() - data = await self._state.http.create_scheduled_event(guild_id=self.id, reason=reason, **payload) - event = ScheduledEvent(state=self._state, guild=self, creator=self.me, data=data) + data = await self._state.http.create_scheduled_event( + guild_id=self.id, reason=reason, **payload + ) + event = ScheduledEvent( + state=self._state, guild=self, creator=self.me, data=data + ) self._add_scheduled_event(event) return event @property - def scheduled_events(self) -> List[ScheduledEvent]: + def scheduled_events(self) -> list[ScheduledEvent]: """List[:class:`.ScheduledEvent`]: A list of scheduled events in this guild.""" return list(self._scheduled_events.values()) - - async def fetch_auto_moderation_rules(self) -> List[AutoModRule]: + + async def fetch_auto_moderation_rules(self) -> list[AutoModRule]: """|coro| Retrieves a list of auto moderation rules for this guild. - Raises + Returns ------- + List[:class:`AutoModRule`] + The auto moderation rules for this guild. + + Raises + ------ HTTPException Getting the auto moderation rules failed. Forbidden You do not have the Manage Guild permission. - - Returns - -------- - List[:class:`AutoModRule`] - The auto moderation rules for this guild. """ data = await self._state.http.get_auto_moderation_rules(self.id) return [AutoModRule(state=self._state, data=rule) for rule in data] - + async def fetch_auto_moderation_rule(self, id: int) -> AutoModRule: """|coro| - + Retrieves a :class:`AutoModRule` from rule ID. - - Raises + + Returns ------- + :class:`AutoModRule` + The requested auto moderation rule. + + Raises + ------ HTTPException Getting the auto moderation rule failed. Forbidden You do not have the Manage Guild permission. - - Returns - -------- - :class:`AutoModRule` - The requested auto moderation rule. """ data = await self._state.http.get_auto_moderation_rule(self.id, id) return AutoModRule(state=self._state, data=data) - + async def create_auto_moderation_rule( self, *, @@ -3631,17 +3762,17 @@ async def create_auto_moderation_rule( event_type: AutoModEventType, trigger_type: AutoModTriggerType, trigger_metadata: AutoModTriggerMetadata, - actions: List[AutoModAction], + actions: list[AutoModAction], enabled: bool = False, - exempt_roles: List[Snowflake] = None, - exempt_channels: List[Snowflake] = None, - reason: Optional[str] = None, + exempt_roles: list[Snowflake] = None, + exempt_channels: list[Snowflake] = None, + reason: str | None = None, ) -> AutoModRule: """ Creates an auto moderation rule. - + Parameters - ----------- + ---------- name: :class:`str` The name of the auto moderation rule. event_type: :class:`AutoModEventType` @@ -3660,33 +3791,33 @@ async def create_auto_moderation_rule( A list of channels that are exempt from the rule. reason: Optional[:class:`str`] The reason for creating the rule. Shows up in the audit log. - - Raises + + Returns ------- + :class:`AutoModRule` + The new auto moderation rule. + + Raises + ------ HTTPException Creating the auto moderation rule failed. Forbidden You do not have the Manage Guild permission. - - Returns - -------- - :class:`AutoModRule` - The new auto moderation rule. """ payload = { "name": name, "event_type": event_type.value, "trigger_type": trigger_type.value, - "trigger_metadata": trigger_metadata.to_dict(), + "trigger_metadata": trigger_metadata.to_dict(), "actions": [a.to_dict() for a in actions], "enabled": enabled, } - + if exempt_roles: payload["exempt_roles"] = [r.id for r in exempt_roles] - + if exempt_channels: payload["exempt_channels"] = [c.id for c in exempt_channels] - + data = await self._state.http.create_auto_moderation_rule(self.id, payload) return AutoModRule(state=self._state, data=data, reason=reason) diff --git a/discord/http.py b/discord/http.py index b778dd7f6f..3e246004ed 100644 --- a/discord/http.py +++ b/discord/http.py @@ -29,20 +29,7 @@ import logging import sys import weakref -from typing import ( - TYPE_CHECKING, - Any, - Coroutine, - Dict, - Iterable, - List, - Optional, - Sequence, - Tuple, - Type, - TypeVar, - Union, -) +from typing import TYPE_CHECKING, Any, Coroutine, Iterable, Sequence, TypeVar from urllib.parse import quote as _uriquote import aiohttp @@ -101,7 +88,7 @@ API_VERSION: int = 10 -async def json_or_text(response: aiohttp.ClientResponse) -> Union[Dict[str, Any], str]: +async def json_or_text(response: aiohttp.ClientResponse) -> dict[str, Any] | str: text = await response.text(encoding="utf-8") try: if response.headers["content-type"] == "application/json": @@ -119,14 +106,19 @@ def __init__(self, method: str, path: str, **parameters: Any) -> None: self.method: str = method url = self.base + self.path if parameters: - url = url.format_map({k: _uriquote(v) if isinstance(v, str) else v for k, v in parameters.items()}) + url = url.format_map( + { + k: _uriquote(v) if isinstance(v, str) else v + for k, v in parameters.items() + } + ) self.url: str = url # major parameters: - self.channel_id: Optional[Snowflake] = parameters.get("channel_id") - self.guild_id: Optional[Snowflake] = parameters.get("guild_id") - self.webhook_id: Optional[Snowflake] = parameters.get("webhook_id") - self.webhook_token: Optional[str] = parameters.get("webhook_token") + self.channel_id: Snowflake | None = parameters.get("channel_id") + self.guild_id: Snowflake | None = parameters.get("guild_id") + self.webhook_id: Snowflake | None = parameters.get("webhook_id") + self.webhook_token: str | None = parameters.get("webhook_token") @property def base(self) -> str: @@ -151,9 +143,9 @@ def defer(self) -> None: def __exit__( self, - exc_type: Optional[Type[BE]], - exc: Optional[BE], - traceback: Optional[TracebackType], + exc_type: type[BE] | None, + exc: BE | None, + traceback: TracebackType | None, ) -> None: if self._unlock: self.lock.release() @@ -169,27 +161,31 @@ class HTTPClient: def __init__( self, - connector: Optional[aiohttp.BaseConnector] = None, + connector: aiohttp.BaseConnector | None = None, *, - proxy: Optional[str] = None, - proxy_auth: Optional[aiohttp.BasicAuth] = None, - loop: Optional[asyncio.AbstractEventLoop] = None, + proxy: str | None = None, + proxy_auth: aiohttp.BasicAuth | None = None, + loop: asyncio.AbstractEventLoop | None = None, unsync_clock: bool = True, ) -> None: - self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() if loop is None else loop + self.loop: asyncio.AbstractEventLoop = ( + asyncio.get_event_loop() if loop is None else loop + ) self.connector = connector self.__session: aiohttp.ClientSession = MISSING # filled in static_login self._locks: weakref.WeakValueDictionary = weakref.WeakValueDictionary() self._global_over: asyncio.Event = asyncio.Event() self._global_over.set() - self.token: Optional[str] = None + self.token: str | None = None self.bot_token: bool = False - self.proxy: Optional[str] = proxy - self.proxy_auth: Optional[aiohttp.BasicAuth] = proxy_auth + self.proxy: str | None = proxy + self.proxy_auth: aiohttp.BasicAuth | None = proxy_auth self.use_clock: bool = not unsync_clock user_agent = "DiscordBot (https://github.com/Pycord-Development/pycord {0}) Python/{1[0]}.{1[1]} aiohttp/{2}" - self.user_agent: str = user_agent.format(__version__, sys.version_info, aiohttp.__version__) + self.user_agent: str = user_agent.format( + __version__, sys.version_info, aiohttp.__version__ + ) def recreate(self) -> None: if self.__session.closed: @@ -217,8 +213,8 @@ async def request( self, route: Route, *, - files: Optional[Sequence[File]] = None, - form: Optional[Iterable[Dict[str, Any]]] = None, + files: Sequence[File] | None = None, + form: Iterable[dict[str, Any]] | None = None, **kwargs: Any, ) -> Any: bucket = route.bucket @@ -232,7 +228,7 @@ async def request( self._locks[bucket] = lock # header creation - headers: Dict[str, str] = { + headers: dict[str, str] = { "User-Agent": self.user_agent, } @@ -265,8 +261,8 @@ async def request( # wait until the global lock is complete await self._global_over.wait() - response: Optional[aiohttp.ClientResponse] = None - data: Optional[Union[Dict[str, Any], str]] = None + response: aiohttp.ClientResponse | None = None + data: dict[str, Any] | str | None = None await lock.acquire() with MaybeUnlock(lock) as maybe_lock: for tries in range(5): @@ -281,7 +277,9 @@ async def request( kwargs["data"] = form_data try: - async with self.__session.request(method, url, **kwargs) as response: + async with self.__session.request( + method, url, **kwargs + ) as response: _log.debug( "%s %s with %s has returned %s", method, @@ -297,7 +295,9 @@ async def request( remaining = response.headers.get("X-Ratelimit-Remaining") if remaining == "0" and response.status != 429: # we've depleted our current bucket - delta = utils._parse_ratelimit_header(response, use_clock=self.use_clock) + delta = utils._parse_ratelimit_header( + response, use_clock=self.use_clock + ) _log.debug( "A rate limit bucket has been exhausted (bucket: %s, retry: %s).", bucket, @@ -417,15 +417,21 @@ def logout(self) -> Response[None]: # Group functionality - def start_group(self, user_id: Snowflake, recipients: List[int]) -> Response[channel.GroupDMChannel]: + def start_group( + self, user_id: Snowflake, recipients: list[int] + ) -> Response[channel.GroupDMChannel]: payload = { "recipients": recipients, } - return self.request(Route("POST", "/users/{user_id}/channels", user_id=user_id), json=payload) + return self.request( + Route("POST", "/users/{user_id}/channels", user_id=user_id), json=payload + ) def leave_group(self, channel_id) -> Response[None]: - return self.request(Route("DELETE", "/channels/{channel_id}", channel_id=channel_id)) + return self.request( + Route("DELETE", "/channels/{channel_id}", channel_id=channel_id) + ) # Message management @@ -439,17 +445,17 @@ def start_private_message(self, user_id: Snowflake) -> Response[channel.DMChanne def send_message( self, channel_id: Snowflake, - content: Optional[str], + content: str | None, *, tts: bool = False, - embed: Optional[embed.Embed] = None, - embeds: Optional[List[embed.Embed]] = None, - nonce: Optional[str] = None, - allowed_mentions: Optional[message.AllowedMentions] = None, - message_reference: Optional[message.MessageReference] = None, - stickers: Optional[List[sticker.StickerItem]] = None, - components: Optional[List[components.Component]] = None, - flags: Optional[int] = None, + embed: embed.Embed | None = None, + embeds: list[embed.Embed] | None = None, + nonce: str | None = None, + allowed_mentions: message.AllowedMentions | None = None, + message_reference: message.MessageReference | None = None, + stickers: list[sticker.StickerItem] | None = None, + components: list[components.Component] | None = None, + flags: int | None = None, ) -> Response[message.Message]: r = Route("POST", "/channels/{channel_id}/messages", channel_id=channel_id) payload = {} @@ -480,34 +486,36 @@ def send_message( if stickers: payload["sticker_ids"] = stickers - + if flags: payload["flags"] = flags return self.request(r, json=payload) def send_typing(self, channel_id: Snowflake) -> Response[None]: - return self.request(Route("POST", "/channels/{channel_id}/typing", channel_id=channel_id)) + return self.request( + Route("POST", "/channels/{channel_id}/typing", channel_id=channel_id) + ) def send_multipart_helper( self, route: Route, *, files: Sequence[File], - content: Optional[str] = None, + content: str | None = None, tts: bool = False, - embed: Optional[embed.Embed] = None, - embeds: Optional[Iterable[Optional[embed.Embed]]] = None, - nonce: Optional[str] = None, - allowed_mentions: Optional[message.AllowedMentions] = None, - message_reference: Optional[message.MessageReference] = None, - stickers: Optional[List[sticker.StickerItem]] = None, - components: Optional[List[components.Component]] = None, - flags: Optional[int] = None, + embed: embed.Embed | None = None, + embeds: Iterable[embed.Embed | None] | None = None, + nonce: str | None = None, + allowed_mentions: message.AllowedMentions | None = None, + message_reference: message.MessageReference | None = None, + stickers: list[sticker.StickerItem] | None = None, + components: list[components.Component] | None = None, + flags: int | None = None, ) -> Response[message.Message]: form = [] - payload: Dict[str, Any] = {"tts": tts} + payload: dict[str, Any] = {"tts": tts} if content: payload["content"] = content if embed: @@ -554,16 +562,16 @@ def send_files( channel_id: Snowflake, *, files: Sequence[File], - content: Optional[str] = None, + content: str | None = None, tts: bool = False, - embed: Optional[embed.Embed] = None, - embeds: Optional[List[embed.Embed]] = None, - nonce: Optional[str] = None, - allowed_mentions: Optional[message.AllowedMentions] = None, - message_reference: Optional[message.MessageReference] = None, - stickers: Optional[List[sticker.StickerItem]] = None, - components: Optional[List[components.Component]] = None, - flags: Optional[int] = None, + embed: embed.Embed | None = None, + embeds: list[embed.Embed] | None = None, + nonce: str | None = None, + allowed_mentions: message.AllowedMentions | None = None, + message_reference: message.MessageReference | None = None, + stickers: list[sticker.StickerItem] | None = None, + components: list[components.Component] | None = None, + flags: int | None = None, ) -> Response[message.Message]: r = Route("POST", "/channels/{channel_id}/messages", channel_id=channel_id) return self.send_multipart_helper( @@ -628,7 +636,7 @@ def edit_files( channel_id=channel_id, message_id=message_id, ) - payload: Dict[str, Any] = {} + payload: dict[str, Any] = {} if "attachments" in fields: payload["attachments"] = fields["attachments"] if "flags" in fields: @@ -652,7 +660,7 @@ def delete_message( channel_id: Snowflake, message_id: Snowflake, *, - reason: Optional[str] = None, + reason: str | None = None, ) -> Response[None]: r = Route( "DELETE", @@ -667,16 +675,20 @@ def delete_messages( channel_id: Snowflake, message_ids: SnowflakeList, *, - reason: Optional[str] = None, + reason: str | None = None, ) -> Response[None]: - r = Route("POST", "/channels/{channel_id}/messages/bulk-delete", channel_id=channel_id) + r = Route( + "POST", "/channels/{channel_id}/messages/bulk-delete", channel_id=channel_id + ) payload = { "messages": message_ids, } return self.request(r, json=payload, reason=reason) - def edit_message(self, channel_id: Snowflake, message_id: Snowflake, **fields: Any) -> Response[message.Message]: + def edit_message( + self, channel_id: Snowflake, message_id: Snowflake, **fields: Any + ) -> Response[message.Message]: r = Route( "PATCH", "/channels/{channel_id}/messages/{message_id}", @@ -685,7 +697,9 @@ def edit_message(self, channel_id: Snowflake, message_id: Snowflake, **fields: A ) return self.request(r, json=fields) - def add_reaction(self, channel_id: Snowflake, message_id: Snowflake, emoji: str) -> Response[None]: + def add_reaction( + self, channel_id: Snowflake, message_id: Snowflake, emoji: str + ) -> Response[None]: r = Route( "PUT", "/channels/{channel_id}/messages/{message_id}/reactions/{emoji}/@me", @@ -712,7 +726,9 @@ def remove_reaction( ) return self.request(r) - def remove_own_reaction(self, channel_id: Snowflake, message_id: Snowflake, emoji: str) -> Response[None]: + def remove_own_reaction( + self, channel_id: Snowflake, message_id: Snowflake, emoji: str + ) -> Response[None]: r = Route( "DELETE", "/channels/{channel_id}/messages/{message_id}/reactions/{emoji}/@me", @@ -728,8 +744,8 @@ def get_reaction_users( message_id: Snowflake, emoji: str, limit: int, - after: Optional[Snowflake] = None, - ) -> Response[List[user.User]]: + after: Snowflake | None = None, + ) -> Response[list[user.User]]: r = Route( "GET", "/channels/{channel_id}/messages/{message_id}/reactions/{emoji}", @@ -738,14 +754,16 @@ def get_reaction_users( emoji=emoji, ) - params: Dict[str, Any] = { + params: dict[str, Any] = { "limit": limit, } if after: params["after"] = after return self.request(r, params=params) - def clear_reactions(self, channel_id: Snowflake, message_id: Snowflake) -> Response[None]: + def clear_reactions( + self, channel_id: Snowflake, message_id: Snowflake + ) -> Response[None]: r = Route( "DELETE", "/channels/{channel_id}/messages/{message_id}/reactions", @@ -755,7 +773,9 @@ def clear_reactions(self, channel_id: Snowflake, message_id: Snowflake) -> Respo return self.request(r) - def clear_single_reaction(self, channel_id: Snowflake, message_id: Snowflake, emoji: str) -> Response[None]: + def clear_single_reaction( + self, channel_id: Snowflake, message_id: Snowflake, emoji: str + ) -> Response[None]: r = Route( "DELETE", "/channels/{channel_id}/messages/{message_id}/reactions/{emoji}", @@ -765,7 +785,9 @@ def clear_single_reaction(self, channel_id: Snowflake, message_id: Snowflake, em ) return self.request(r) - def get_message(self, channel_id: Snowflake, message_id: Snowflake) -> Response[message.Message]: + def get_message( + self, channel_id: Snowflake, message_id: Snowflake + ) -> Response[message.Message]: r = Route( "GET", "/channels/{channel_id}/messages/{message_id}", @@ -782,11 +804,11 @@ def logs_from( self, channel_id: Snowflake, limit: int, - before: Optional[Snowflake] = None, - after: Optional[Snowflake] = None, - around: Optional[Snowflake] = None, - ) -> Response[List[message.Message]]: - params: Dict[str, Any] = { + before: Snowflake | None = None, + after: Snowflake | None = None, + around: Snowflake | None = None, + ) -> Response[list[message.Message]]: + params: dict[str, Any] = { "limit": limit, } @@ -802,7 +824,9 @@ def logs_from( params=params, ) - def publish_message(self, channel_id: Snowflake, message_id: Snowflake) -> Response[message.Message]: + def publish_message( + self, channel_id: Snowflake, message_id: Snowflake + ) -> Response[message.Message]: return self.request( Route( "POST", @@ -812,7 +836,9 @@ def publish_message(self, channel_id: Snowflake, message_id: Snowflake) -> Respo ) ) - def pin_message(self, channel_id: Snowflake, message_id: Snowflake, reason: Optional[str] = None) -> Response[None]: + def pin_message( + self, channel_id: Snowflake, message_id: Snowflake, reason: str | None = None + ) -> Response[None]: r = Route( "PUT", "/channels/{channel_id}/pins/{message_id}", @@ -822,7 +848,7 @@ def pin_message(self, channel_id: Snowflake, message_id: Snowflake, reason: Opti return self.request(r, reason=reason) def unpin_message( - self, channel_id: Snowflake, message_id: Snowflake, reason: Optional[str] = None + self, channel_id: Snowflake, message_id: Snowflake, reason: str | None = None ) -> Response[None]: r = Route( "DELETE", @@ -832,12 +858,16 @@ def unpin_message( ) return self.request(r, reason=reason) - def pins_from(self, channel_id: Snowflake) -> Response[List[message.Message]]: - return self.request(Route("GET", "/channels/{channel_id}/pins", channel_id=channel_id)) + def pins_from(self, channel_id: Snowflake) -> Response[list[message.Message]]: + return self.request( + Route("GET", "/channels/{channel_id}/pins", channel_id=channel_id) + ) # Member management - def kick(self, user_id: Snowflake, guild_id: Snowflake, reason: Optional[str] = None) -> Response[None]: + def kick( + self, user_id: Snowflake, guild_id: Snowflake, reason: str | None = None + ) -> Response[None]: r = Route( "DELETE", "/guilds/{guild_id}/members/{user_id}", @@ -852,7 +882,7 @@ def ban( guild_id: Snowflake, delete_message_seconds: int = None, delete_message_days: int = None, - reason: Optional[str] = None, + reason: str | None = None, ) -> Response[None]: r = Route( "PUT", @@ -866,8 +896,7 @@ def ban( params["delete_message_seconds"] = delete_message_seconds elif delete_message_days: warn_deprecated( - "delete_message_days" - "delete_message_seconds", + "delete_message_days" "delete_message_seconds", "2.2", reference="https://github.com/discord/discord-api-docs/pull/5219", ) @@ -875,7 +904,9 @@ def ban( return self.request(r, params=params, reason=reason) - def unban(self, user_id: Snowflake, guild_id: Snowflake, *, reason: Optional[str] = None) -> Response[None]: + def unban( + self, user_id: Snowflake, guild_id: Snowflake, *, reason: str | None = None + ) -> Response[None]: r = Route( "DELETE", "/guilds/{guild_id}/bans/{user_id}", @@ -889,9 +920,9 @@ def guild_voice_state( user_id: Snowflake, guild_id: Snowflake, *, - mute: Optional[bool] = None, - deafen: Optional[bool] = None, - reason: Optional[str] = None, + mute: bool | None = None, + deafen: bool | None = None, + reason: str | None = None, ) -> Response[member.Member]: r = Route( "PATCH", @@ -908,7 +939,7 @@ def guild_voice_state( return self.request(r, json=payload, reason=reason) - def edit_profile(self, payload: Dict[str, Any]) -> Response[user.User]: + def edit_profile(self, payload: dict[str, Any]) -> Response[user.User]: return self.request(Route("PATCH", "/users/@me"), json=payload) def change_my_nickname( @@ -916,7 +947,7 @@ def change_my_nickname( guild_id: Snowflake, nickname: str, *, - reason: Optional[str] = None, + reason: str | None = None, ) -> Response[member.Nickname]: r = Route("PATCH", "/guilds/{guild_id}/members/@me", guild_id=guild_id) payload = { @@ -930,7 +961,7 @@ def change_nickname( user_id: Snowflake, nickname: str, *, - reason: Optional[str] = None, + reason: str | None = None, ) -> Response[member.Member]: r = Route( "PATCH", @@ -943,11 +974,15 @@ def change_nickname( } return self.request(r, json=payload, reason=reason) - def edit_my_voice_state(self, guild_id: Snowflake, payload: Dict[str, Any]) -> Response[None]: + def edit_my_voice_state( + self, guild_id: Snowflake, payload: dict[str, Any] + ) -> Response[None]: r = Route("PATCH", "/guilds/{guild_id}/voice-states/@me", guild_id=guild_id) return self.request(r, json=payload) - def edit_voice_state(self, guild_id: Snowflake, user_id: Snowflake, payload: Dict[str, Any]) -> Response[None]: + def edit_voice_state( + self, guild_id: Snowflake, user_id: Snowflake, payload: dict[str, Any] + ) -> Response[None]: r = Route( "PATCH", "/guilds/{guild_id}/voice-states/{user_id}", @@ -961,7 +996,7 @@ def edit_member( guild_id: Snowflake, user_id: Snowflake, *, - reason: Optional[str] = None, + reason: str | None = None, **fields: Any, ) -> Response[member.MemberWithUser]: r = Route( @@ -978,7 +1013,7 @@ def edit_channel( self, channel_id: Snowflake, *, - reason: Optional[str] = None, + reason: str | None = None, **options: Any, ) -> Response[channel.Channel]: r = Route("PATCH", "/channels/{channel_id}", channel_id=channel_id) @@ -1007,9 +1042,9 @@ def edit_channel( def bulk_channel_update( self, guild_id: Snowflake, - data: List[guild.ChannelPositionUpdate], + data: list[guild.ChannelPositionUpdate], *, - reason: Optional[str] = None, + reason: str | None = None, ) -> Response[None]: r = Route("PATCH", "/guilds/{guild_id}/channels", guild_id=guild_id) return self.request(r, json=data, reason=reason) @@ -1019,7 +1054,7 @@ def create_channel( guild_id: Snowflake, channel_type: channel.ChannelType, *, - reason: Optional[str] = None, + reason: str | None = None, **options: Any, ) -> Response[channel.GuildChannel]: payload = { @@ -1040,7 +1075,9 @@ def create_channel( "video_quality_mode", "auto_archive_duration", ) - payload.update({k: v for k, v in options.items() if k in valid_keys and v is not None}) + payload.update( + {k: v for k, v in options.items() if k in valid_keys and v is not None} + ) return self.request( Route("POST", "/guilds/{guild_id}/channels", guild_id=guild_id), @@ -1052,7 +1089,7 @@ def delete_channel( self, channel_id: Snowflake, *, - reason: Optional[str] = None, + reason: str | None = None, ) -> Response[None]: return self.request( Route("DELETE", "/channels/{channel_id}", channel_id=channel_id), @@ -1068,7 +1105,7 @@ def start_thread_with_message( *, name: str, auto_archive_duration: threads.ThreadArchiveDuration, - reason: Optional[str] = None, + reason: str | None = None, ) -> Response[threads.Thread]: payload = { "name": name, @@ -1091,7 +1128,7 @@ def start_thread_without_message( auto_archive_duration: threads.ThreadArchiveDuration, type: threads.ThreadType, invitable: bool = True, - reason: Optional[str] = None, + reason: str | None = None, ) -> Response[threads.Thread]: payload = { "name": name, @@ -1106,19 +1143,19 @@ def start_thread_without_message( def start_forum_thread( self, channel_id: Snowflake, - content: Optional[str], + content: str | None, *, name: str, auto_archive_duration: threads.ThreadArchiveDuration, rate_limit_per_user: int, invitable: bool = True, - reason: Optional[str] = None, - embed: Optional[embed.Embed] = None, - embeds: Optional[List[embed.Embed]] = None, - nonce: Optional[str] = None, - allowed_mentions: Optional[message.AllowedMentions] = None, - stickers: Optional[List[sticker.StickerItem]] = None, - components: Optional[List[components.Component]] = None, + reason: str | None = None, + embed: embed.Embed | None = None, + embeds: list[embed.Embed] | None = None, + nonce: str | None = None, + allowed_mentions: message.AllowedMentions | None = None, + stickers: list[sticker.StickerItem] | None = None, + components: list[components.Component] | None = None, ) -> Response[threads.Thread]: payload = { "name": name, @@ -1149,7 +1186,11 @@ def start_forum_thread( if rate_limit_per_user: payload["rate_limit_per_user"] = rate_limit_per_user # TODO: Once supported by API, remove has_message=true query parameter - route = Route("POST", "/channels/{channel_id}/threads?has_message=true", channel_id=channel_id) + route = Route( + "POST", + "/channels/{channel_id}/threads?has_message=true", + channel_id=channel_id, + ) return self.request(route, json=payload, reason=reason) def join_thread(self, channel_id: Snowflake) -> Response[None]: @@ -1161,7 +1202,9 @@ def join_thread(self, channel_id: Snowflake) -> Response[None]: ) ) - def add_user_to_thread(self, channel_id: Snowflake, user_id: Snowflake) -> Response[None]: + def add_user_to_thread( + self, channel_id: Snowflake, user_id: Snowflake + ) -> Response[None]: return self.request( Route( "PUT", @@ -1180,7 +1223,9 @@ def leave_thread(self, channel_id: Snowflake) -> Response[None]: ) ) - def remove_user_from_thread(self, channel_id: Snowflake, user_id: Snowflake) -> Response[None]: + def remove_user_from_thread( + self, channel_id: Snowflake, user_id: Snowflake + ) -> Response[None]: route = Route( "DELETE", "/channels/{channel_id}/thread-members/{user_id}", @@ -1190,7 +1235,7 @@ def remove_user_from_thread(self, channel_id: Snowflake, user_id: Snowflake) -> return self.request(route) def get_public_archived_threads( - self, channel_id: Snowflake, before: Optional[Snowflake] = None, limit: int = 50 + self, channel_id: Snowflake, before: Snowflake | None = None, limit: int = 50 ) -> Response[threads.ThreadPaginationPayload]: route = Route( "GET", @@ -1205,7 +1250,7 @@ def get_public_archived_threads( return self.request(route, params=params) def get_private_archived_threads( - self, channel_id: Snowflake, before: Optional[Snowflake] = None, limit: int = 50 + self, channel_id: Snowflake, before: Snowflake | None = None, limit: int = 50 ) -> Response[threads.ThreadPaginationPayload]: route = Route( "GET", @@ -1220,7 +1265,7 @@ def get_private_archived_threads( return self.request(route, params=params) def get_joined_private_archived_threads( - self, channel_id: Snowflake, before: Optional[Snowflake] = None, limit: int = 50 + self, channel_id: Snowflake, before: Snowflake | None = None, limit: int = 50 ) -> Response[threads.ThreadPaginationPayload]: route = Route( "GET", @@ -1233,12 +1278,18 @@ def get_joined_private_archived_threads( params["limit"] = limit return self.request(route, params=params) - def get_active_threads(self, guild_id: Snowflake) -> Response[threads.ThreadPaginationPayload]: + def get_active_threads( + self, guild_id: Snowflake + ) -> Response[threads.ThreadPaginationPayload]: route = Route("GET", "/guilds/{guild_id}/threads/active", guild_id=guild_id) return self.request(route) - def get_thread_members(self, channel_id: Snowflake) -> Response[List[threads.ThreadMember]]: - route = Route("GET", "/channels/{channel_id}/thread-members", channel_id=channel_id) + def get_thread_members( + self, channel_id: Snowflake + ) -> Response[list[threads.ThreadMember]]: + route = Route( + "GET", "/channels/{channel_id}/thread-members", channel_id=channel_id + ) return self.request(route) # Webhook management @@ -1248,10 +1299,10 @@ def create_webhook( channel_id: Snowflake, *, name: str, - avatar: Optional[bytes] = None, - reason: Optional[str] = None, + avatar: bytes | None = None, + reason: str | None = None, ) -> Response[webhook.Webhook]: - payload: Dict[str, Any] = { + payload: dict[str, Any] = { "name": name, } if avatar is not None: @@ -1260,20 +1311,28 @@ def create_webhook( r = Route("POST", "/channels/{channel_id}/webhooks", channel_id=channel_id) return self.request(r, json=payload, reason=reason) - def channel_webhooks(self, channel_id: Snowflake) -> Response[List[webhook.Webhook]]: - return self.request(Route("GET", "/channels/{channel_id}/webhooks", channel_id=channel_id)) + def channel_webhooks( + self, channel_id: Snowflake + ) -> Response[list[webhook.Webhook]]: + return self.request( + Route("GET", "/channels/{channel_id}/webhooks", channel_id=channel_id) + ) - def guild_webhooks(self, guild_id: Snowflake) -> Response[List[webhook.Webhook]]: - return self.request(Route("GET", "/guilds/{guild_id}/webhooks", guild_id=guild_id)) + def guild_webhooks(self, guild_id: Snowflake) -> Response[list[webhook.Webhook]]: + return self.request( + Route("GET", "/guilds/{guild_id}/webhooks", guild_id=guild_id) + ) def get_webhook(self, webhook_id: Snowflake) -> Response[webhook.Webhook]: - return self.request(Route("GET", "/webhooks/{webhook_id}", webhook_id=webhook_id)) + return self.request( + Route("GET", "/webhooks/{webhook_id}", webhook_id=webhook_id) + ) def follow_webhook( self, channel_id: Snowflake, webhook_channel_id: Snowflake, - reason: Optional[str] = None, + reason: str | None = None, ) -> Response[None]: payload = { "webhook_channel_id": str(webhook_channel_id), @@ -1289,10 +1348,10 @@ def follow_webhook( def get_guilds( self, limit: int, - before: Optional[Snowflake] = None, - after: Optional[Snowflake] = None, - ) -> Response[List[guild.Guild]]: - params: Dict[str, Any] = { + before: Snowflake | None = None, + after: Snowflake | None = None, + ) -> Response[list[guild.Guild]]: + params: dict[str, Any] = { "limit": limit, } @@ -1304,16 +1363,22 @@ def get_guilds( return self.request(Route("GET", "/users/@me/guilds"), params=params) def leave_guild(self, guild_id: Snowflake) -> Response[None]: - return self.request(Route("DELETE", "/users/@me/guilds/{guild_id}", guild_id=guild_id)) + return self.request( + Route("DELETE", "/users/@me/guilds/{guild_id}", guild_id=guild_id) + ) - def get_guild(self, guild_id: Snowflake, *, with_counts=True) -> Response[guild.Guild]: + def get_guild( + self, guild_id: Snowflake, *, with_counts=True + ) -> Response[guild.Guild]: params = {"with_counts": int(with_counts)} - return self.request(Route("GET", "/guilds/{guild_id}", guild_id=guild_id), params=params) + return self.request( + Route("GET", "/guilds/{guild_id}", guild_id=guild_id), params=params + ) def delete_guild(self, guild_id: Snowflake) -> Response[None]: return self.request(Route("DELETE", "/guilds/{guild_id}", guild_id=guild_id)) - def create_guild(self, name: str, icon: Optional[str]) -> Response[guild.Guild]: + def create_guild(self, name: str, icon: str | None) -> Response[guild.Guild]: payload = { "name": name, } @@ -1322,7 +1387,9 @@ def create_guild(self, name: str, icon: Optional[str]) -> Response[guild.Guild]: return self.request(Route("POST", "/guilds"), json=payload) - def edit_guild(self, guild_id: Snowflake, *, reason: Optional[str] = None, **fields: Any) -> Response[guild.Guild]: + def edit_guild( + self, guild_id: Snowflake, *, reason: str | None = None, **fields: Any + ) -> Response[guild.Guild]: valid_keys = ( "name", "icon", @@ -1353,26 +1420,34 @@ def edit_guild(self, guild_id: Snowflake, *, reason: Optional[str] = None, **fie reason=reason, ) - def edit_guild_mfa(self, guild_id: Snowflake, required: bool, *, reason: Optional[str]) -> Response[guild.GuildMFAModify]: + def edit_guild_mfa( + self, guild_id: Snowflake, required: bool, *, reason: str | None + ) -> Response[guild.GuildMFAModify]: return self.request( Route("POST", "/guilds/{guild_id}/mfa", guild_id=guild_id), json={"level": int(required)}, - reason=reason + reason=reason, ) def get_template(self, code: str) -> Response[template.Template]: return self.request(Route("GET", "/guilds/templates/{code}", code=code)) - def guild_templates(self, guild_id: Snowflake) -> Response[List[template.Template]]: - return self.request(Route("GET", "/guilds/{guild_id}/templates", guild_id=guild_id)) + def guild_templates(self, guild_id: Snowflake) -> Response[list[template.Template]]: + return self.request( + Route("GET", "/guilds/{guild_id}/templates", guild_id=guild_id) + ) - def create_template(self, guild_id: Snowflake, payload: template.CreateTemplate) -> Response[template.Template]: + def create_template( + self, guild_id: Snowflake, payload: template.CreateTemplate + ) -> Response[template.Template]: return self.request( Route("POST", "/guilds/{guild_id}/templates", guild_id=guild_id), json=payload, ) - def sync_template(self, guild_id: Snowflake, code: str) -> Response[template.Template]: + def sync_template( + self, guild_id: Snowflake, code: str + ) -> Response[template.Template]: return self.request( Route( "PUT", @@ -1382,7 +1457,9 @@ def sync_template(self, guild_id: Snowflake, code: str) -> Response[template.Tem ) ) - def edit_template(self, guild_id: Snowflake, code: str, payload) -> Response[template.Template]: + def edit_template( + self, guild_id: Snowflake, code: str, payload + ) -> Response[template.Template]: valid_keys = ( "name", "description", @@ -1408,23 +1485,27 @@ def delete_template(self, guild_id: Snowflake, code: str) -> Response[None]: ) ) - def create_from_template(self, code: str, name: str, icon: Optional[str]) -> Response[guild.Guild]: + def create_from_template( + self, code: str, name: str, icon: str | None + ) -> Response[guild.Guild]: payload = { "name": name, } if icon: payload["icon"] = icon - return self.request(Route("POST", "/guilds/templates/{code}", code=code), json=payload) + return self.request( + Route("POST", "/guilds/templates/{code}", code=code), json=payload + ) def get_bans( self, guild_id: Snowflake, - limit: Optional[int] = None, - before: Optional[Snowflake] = None, - after: Optional[Snowflake] = None, - ) -> Response[List[guild.Ban]]: - params: Dict[str, Union[int, Snowflake]] = {} + limit: int | None = None, + before: Snowflake | None = None, + after: Snowflake | None = None, + ) -> Response[list[guild.Ban]]: + params: dict[str, int | Snowflake] = {} if limit is not None: params["limit"] = limit @@ -1433,7 +1514,9 @@ def get_bans( if after is not None: params["after"] = after - return self.request(Route("GET", "/guilds/{guild_id}/bans", guild_id=guild_id), params=params) + return self.request( + Route("GET", "/guilds/{guild_id}/bans", guild_id=guild_id), params=params + ) def get_ban(self, user_id: Snowflake, guild_id: Snowflake) -> Response[guild.Ban]: return self.request( @@ -1446,23 +1529,31 @@ def get_ban(self, user_id: Snowflake, guild_id: Snowflake) -> Response[guild.Ban ) def get_vanity_code(self, guild_id: Snowflake) -> Response[invite.VanityInvite]: - return self.request(Route("GET", "/guilds/{guild_id}/vanity-url", guild_id=guild_id)) + return self.request( + Route("GET", "/guilds/{guild_id}/vanity-url", guild_id=guild_id) + ) - def change_vanity_code(self, guild_id: Snowflake, code: str, *, reason: Optional[str] = None) -> Response[None]: - payload: Dict[str, Any] = {"code": code} + def change_vanity_code( + self, guild_id: Snowflake, code: str, *, reason: str | None = None + ) -> Response[None]: + payload: dict[str, Any] = {"code": code} return self.request( Route("PATCH", "/guilds/{guild_id}/vanity-url", guild_id=guild_id), json=payload, reason=reason, ) - def get_all_guild_channels(self, guild_id: Snowflake) -> Response[List[guild.GuildChannel]]: - return self.request(Route("GET", "/guilds/{guild_id}/channels", guild_id=guild_id)) + def get_all_guild_channels( + self, guild_id: Snowflake + ) -> Response[list[guild.GuildChannel]]: + return self.request( + Route("GET", "/guilds/{guild_id}/channels", guild_id=guild_id) + ) def get_members( - self, guild_id: Snowflake, limit: int, after: Optional[Snowflake] - ) -> Response[List[member.MemberWithUser]]: - params: Dict[str, Any] = { + self, guild_id: Snowflake, limit: int, after: Snowflake | None + ) -> Response[list[member.MemberWithUser]]: + params: dict[str, Any] = { "limit": limit, } if after: @@ -1471,7 +1562,9 @@ def get_members( r = Route("GET", "/guilds/{guild_id}/members", guild_id=guild_id) return self.request(r, params=params) - def get_member(self, guild_id: Snowflake, member_id: Snowflake) -> Response[member.MemberWithUser]: + def get_member( + self, guild_id: Snowflake, member_id: Snowflake + ) -> Response[member.MemberWithUser]: return self.request( Route( "GET", @@ -1486,11 +1579,11 @@ def prune_members( guild_id: Snowflake, days: int, compute_prune_count: bool, - roles: List[str], + roles: list[str], *, - reason: Optional[str] = None, + reason: str | None = None, ) -> Response[guild.GuildPrune]: - payload: Dict[str, Any] = { + payload: dict[str, Any] = { "days": days, "compute_prune_count": "true" if compute_prune_count else "false", } @@ -1507,26 +1600,36 @@ def estimate_pruned_members( self, guild_id: Snowflake, days: int, - roles: List[str], + roles: list[str], ) -> Response[guild.GuildPrune]: - params: Dict[str, Any] = { + params: dict[str, Any] = { "days": days, } if roles: params["include_roles"] = ", ".join(roles) - return self.request(Route("GET", "/guilds/{guild_id}/prune", guild_id=guild_id), params=params) + return self.request( + Route("GET", "/guilds/{guild_id}/prune", guild_id=guild_id), params=params + ) def get_sticker(self, sticker_id: Snowflake) -> Response[sticker.Sticker]: - return self.request(Route("GET", "/stickers/{sticker_id}", sticker_id=sticker_id)) + return self.request( + Route("GET", "/stickers/{sticker_id}", sticker_id=sticker_id) + ) def list_premium_sticker_packs(self) -> Response[sticker.ListPremiumStickerPacks]: return self.request(Route("GET", "/sticker-packs")) - def get_all_guild_stickers(self, guild_id: Snowflake) -> Response[List[sticker.GuildSticker]]: - return self.request(Route("GET", "/guilds/{guild_id}/stickers", guild_id=guild_id)) + def get_all_guild_stickers( + self, guild_id: Snowflake + ) -> Response[list[sticker.GuildSticker]]: + return self.request( + Route("GET", "/guilds/{guild_id}/stickers", guild_id=guild_id) + ) - def get_guild_sticker(self, guild_id: Snowflake, sticker_id: Snowflake) -> Response[sticker.GuildSticker]: + def get_guild_sticker( + self, guild_id: Snowflake, sticker_id: Snowflake + ) -> Response[sticker.GuildSticker]: return self.request( Route( "GET", @@ -1555,7 +1658,7 @@ def create_guild_sticker( finally: file.reset() - form: List[Dict[str, Any]] = [ + form: list[dict[str, Any]] = [ { "name": "file", "value": file.fp, @@ -1584,7 +1687,7 @@ def modify_guild_sticker( guild_id: Snowflake, sticker_id: Snowflake, payload: sticker.EditGuildSticker, - reason: Optional[str], + reason: str | None, ) -> Response[sticker.GuildSticker]: return self.request( Route( @@ -1597,7 +1700,9 @@ def modify_guild_sticker( reason=reason, ) - def delete_guild_sticker(self, guild_id: Snowflake, sticker_id: Snowflake, reason: Optional[str]) -> Response[None]: + def delete_guild_sticker( + self, guild_id: Snowflake, sticker_id: Snowflake, reason: str | None + ) -> Response[None]: return self.request( Route( "DELETE", @@ -1608,10 +1713,14 @@ def delete_guild_sticker(self, guild_id: Snowflake, sticker_id: Snowflake, reaso reason=reason, ) - def get_all_custom_emojis(self, guild_id: Snowflake) -> Response[List[emoji.Emoji]]: - return self.request(Route("GET", "/guilds/{guild_id}/emojis", guild_id=guild_id)) + def get_all_custom_emojis(self, guild_id: Snowflake) -> Response[list[emoji.Emoji]]: + return self.request( + Route("GET", "/guilds/{guild_id}/emojis", guild_id=guild_id) + ) - def get_custom_emoji(self, guild_id: Snowflake, emoji_id: Snowflake) -> Response[emoji.Emoji]: + def get_custom_emoji( + self, guild_id: Snowflake, emoji_id: Snowflake + ) -> Response[emoji.Emoji]: return self.request( Route( "GET", @@ -1627,8 +1736,8 @@ def create_custom_emoji( name: str, image: bytes, *, - roles: Optional[SnowflakeList] = None, - reason: Optional[str] = None, + roles: SnowflakeList | None = None, + reason: str | None = None, ) -> Response[emoji.Emoji]: payload = { "name": name, @@ -1644,7 +1753,7 @@ def delete_custom_emoji( guild_id: Snowflake, emoji_id: Snowflake, *, - reason: Optional[str] = None, + reason: str | None = None, ) -> Response[None]: r = Route( "DELETE", @@ -1659,8 +1768,8 @@ def edit_custom_emoji( guild_id: Snowflake, emoji_id: Snowflake, *, - payload: Dict[str, Any], - reason: Optional[str] = None, + payload: dict[str, Any], + reason: str | None = None, ) -> Response[emoji.Emoji]: r = Route( "PATCH", @@ -1670,12 +1779,16 @@ def edit_custom_emoji( ) return self.request(r, json=payload, reason=reason) - def get_all_integrations(self, guild_id: Snowflake) -> Response[List[integration.Integration]]: + def get_all_integrations( + self, guild_id: Snowflake + ) -> Response[list[integration.Integration]]: r = Route("GET", "/guilds/{guild_id}/integrations", guild_id=guild_id) return self.request(r) - def create_integration(self, guild_id: Snowflake, type: integration.IntegrationType, id: int) -> Response[None]: + def create_integration( + self, guild_id: Snowflake, type: integration.IntegrationType, id: int + ) -> Response[None]: payload = { "type": type, "id": id, @@ -1684,7 +1797,9 @@ def create_integration(self, guild_id: Snowflake, type: integration.IntegrationT r = Route("POST", "/guilds/{guild_id}/integrations", guild_id=guild_id) return self.request(r, json=payload) - def edit_integration(self, guild_id: Snowflake, integration_id: Snowflake, **payload: Any) -> Response[None]: + def edit_integration( + self, guild_id: Snowflake, integration_id: Snowflake, **payload: Any + ) -> Response[None]: r = Route( "PATCH", "/guilds/{guild_id}/integrations/{integration_id}", @@ -1694,7 +1809,9 @@ def edit_integration(self, guild_id: Snowflake, integration_id: Snowflake, **pay return self.request(r, json=payload) - def sync_integration(self, guild_id: Snowflake, integration_id: Snowflake) -> Response[None]: + def sync_integration( + self, guild_id: Snowflake, integration_id: Snowflake + ) -> Response[None]: r = Route( "POST", "/guilds/{guild_id}/integrations/{integration_id}/sync", @@ -1709,7 +1826,7 @@ def delete_integration( guild_id: Snowflake, integration_id: Snowflake, *, - reason: Optional[str] = None, + reason: str | None = None, ) -> Response[None]: r = Route( "DELETE", @@ -1724,12 +1841,12 @@ def get_audit_logs( self, guild_id: Snowflake, limit: int = 100, - before: Optional[Snowflake] = None, - after: Optional[Snowflake] = None, - user_id: Optional[Snowflake] = None, - action_type: Optional[AuditLogAction] = None, + before: Snowflake | None = None, + after: Snowflake | None = None, + user_id: Snowflake | None = None, + action_type: AuditLogAction | None = None, ) -> Response[audit_log.AuditLog]: - params: Dict[str, Any] = {"limit": limit} + params: dict[str, Any] = {"limit": limit} if before: params["before"] = before if after: @@ -1743,10 +1860,16 @@ def get_audit_logs( return self.request(r, params=params) def get_widget(self, guild_id: Snowflake) -> Response[widget.Widget]: - return self.request(Route("GET", "/guilds/{guild_id}/widget.json", guild_id=guild_id)) + return self.request( + Route("GET", "/guilds/{guild_id}/widget.json", guild_id=guild_id) + ) - def edit_widget(self, guild_id: Snowflake, payload) -> Response[widget.WidgetSettings]: - return self.request(Route("PATCH", "/guilds/{guild_id}/widget", guild_id=guild_id), json=payload) + def edit_widget( + self, guild_id: Snowflake, payload + ) -> Response[widget.WidgetSettings]: + return self.request( + Route("PATCH", "/guilds/{guild_id}/widget", guild_id=guild_id), json=payload + ) # Invite management @@ -1754,14 +1877,14 @@ def create_invite( self, channel_id: Snowflake, *, - reason: Optional[str] = None, + reason: str | None = None, max_age: int = 0, max_uses: int = 0, temporary: bool = False, unique: bool = True, - target_type: Optional[invite.InviteTargetType] = None, - target_user_id: Optional[Snowflake] = None, - target_application_id: Optional[Snowflake] = None, + target_type: invite.InviteTargetType | None = None, + target_user_id: Snowflake | None = None, + target_application_id: Snowflake | None = None, ) -> Response[invite.Invite]: r = Route("POST", "/channels/{channel_id}/invites", channel_id=channel_id) payload = { @@ -1788,7 +1911,7 @@ def get_invite( *, with_counts: bool = True, with_expiration: bool = True, - guild_scheduled_event_id: Optional[int] = None, + guild_scheduled_event_id: int | None = None, ) -> Response[invite.Invite]: params = { "with_counts": int(with_counts), @@ -1798,20 +1921,32 @@ def get_invite( if guild_scheduled_event_id is not None: params["guild_scheduled_event_id"] = int(guild_scheduled_event_id) - return self.request(Route("GET", "/invites/{invite_id}", invite_id=invite_id), params=params) + return self.request( + Route("GET", "/invites/{invite_id}", invite_id=invite_id), params=params + ) - def invites_from(self, guild_id: Snowflake) -> Response[List[invite.Invite]]: - return self.request(Route("GET", "/guilds/{guild_id}/invites", guild_id=guild_id)) + def invites_from(self, guild_id: Snowflake) -> Response[list[invite.Invite]]: + return self.request( + Route("GET", "/guilds/{guild_id}/invites", guild_id=guild_id) + ) - def invites_from_channel(self, channel_id: Snowflake) -> Response[List[invite.Invite]]: - return self.request(Route("GET", "/channels/{channel_id}/invites", channel_id=channel_id)) + def invites_from_channel( + self, channel_id: Snowflake + ) -> Response[list[invite.Invite]]: + return self.request( + Route("GET", "/channels/{channel_id}/invites", channel_id=channel_id) + ) - def delete_invite(self, invite_id: str, *, reason: Optional[str] = None) -> Response[None]: - return self.request(Route("DELETE", "/invites/{invite_id}", invite_id=invite_id), reason=reason) + def delete_invite( + self, invite_id: str, *, reason: str | None = None + ) -> Response[None]: + return self.request( + Route("DELETE", "/invites/{invite_id}", invite_id=invite_id), reason=reason + ) # Role management - def get_roles(self, guild_id: Snowflake) -> Response[List[role.Role]]: + def get_roles(self, guild_id: Snowflake) -> Response[list[role.Role]]: return self.request(Route("GET", "/guilds/{guild_id}/roles", guild_id=guild_id)) def edit_role( @@ -1819,7 +1954,7 @@ def edit_role( guild_id: Snowflake, role_id: Snowflake, *, - reason: Optional[str] = None, + reason: str | None = None, **fields: Any, ) -> Response[role.Role]: r = Route( @@ -1840,7 +1975,9 @@ def edit_role( payload = {k: v for k, v in fields.items() if k in valid_keys} return self.request(r, json=payload, reason=reason) - def delete_role(self, guild_id: Snowflake, role_id: Snowflake, *, reason: Optional[str] = None) -> Response[None]: + def delete_role( + self, guild_id: Snowflake, role_id: Snowflake, *, reason: str | None = None + ) -> Response[None]: r = Route( "DELETE", "/guilds/{guild_id}/roles/{role_id}", @@ -1853,23 +1990,27 @@ def replace_roles( self, user_id: Snowflake, guild_id: Snowflake, - role_ids: List[int], + role_ids: list[int], *, - reason: Optional[str] = None, + reason: str | None = None, ) -> Response[member.MemberWithUser]: - return self.edit_member(guild_id=guild_id, user_id=user_id, roles=role_ids, reason=reason) + return self.edit_member( + guild_id=guild_id, user_id=user_id, roles=role_ids, reason=reason + ) - def create_role(self, guild_id: Snowflake, *, reason: Optional[str] = None, **fields: Any) -> Response[role.Role]: + def create_role( + self, guild_id: Snowflake, *, reason: str | None = None, **fields: Any + ) -> Response[role.Role]: r = Route("POST", "/guilds/{guild_id}/roles", guild_id=guild_id) return self.request(r, json=fields, reason=reason) def move_role_position( self, guild_id: Snowflake, - positions: List[guild.RolePositionUpdate], + positions: list[guild.RolePositionUpdate], *, - reason: Optional[str] = None, - ) -> Response[List[role.Role]]: + reason: str | None = None, + ) -> Response[list[role.Role]]: r = Route("PATCH", "/guilds/{guild_id}/roles", guild_id=guild_id) return self.request(r, json=positions, reason=reason) @@ -1879,7 +2020,7 @@ def add_role( user_id: Snowflake, role_id: Snowflake, *, - reason: Optional[str] = None, + reason: str | None = None, ) -> Response[None]: r = Route( "PUT", @@ -1896,7 +2037,7 @@ def remove_role( user_id: Snowflake, role_id: Snowflake, *, - reason: Optional[str] = None, + reason: str | None = None, ) -> Response[None]: r = Route( "DELETE", @@ -1915,7 +2056,7 @@ def edit_channel_permissions( deny: str, type: channel.OverwriteType, *, - reason: Optional[str] = None, + reason: str | None = None, ) -> Response[None]: payload = {"id": target, "allow": allow, "deny": deny, "type": type} r = Route( @@ -1931,7 +2072,7 @@ def delete_channel_permissions( channel_id: Snowflake, target: channel.OverwriteType, *, - reason: Optional[str] = None, + reason: str | None = None, ) -> Response[None]: r = Route( "DELETE", @@ -1943,11 +2084,15 @@ def delete_channel_permissions( # Welcome Screen - def get_welcome_screen(self, guild_id: Snowflake) -> Response[welcome_screen.WelcomeScreen]: - return self.request(Route("GET", "/guilds/{guild_id}/welcome-screen", guild_id=guild_id)) + def get_welcome_screen( + self, guild_id: Snowflake + ) -> Response[welcome_screen.WelcomeScreen]: + return self.request( + Route("GET", "/guilds/{guild_id}/welcome-screen", guild_id=guild_id) + ) def edit_welcome_screen( - self, guild_id: Snowflake, payload: Any, *, reason: Optional[str] = None + self, guild_id: Snowflake, payload: Any, *, reason: str | None = None ) -> Response[welcome_screen.WelcomeScreen]: keys = ( "description", @@ -1969,16 +2114,24 @@ def move_member( guild_id: Snowflake, channel_id: Snowflake, *, - reason: Optional[str] = None, + reason: str | None = None, ) -> Response[member.MemberWithUser]: - return self.edit_member(guild_id=guild_id, user_id=user_id, channel_id=channel_id, reason=reason) + return self.edit_member( + guild_id=guild_id, user_id=user_id, channel_id=channel_id, reason=reason + ) # Stage instance management - def get_stage_instance(self, channel_id: Snowflake) -> Response[channel.StageInstance]: - return self.request(Route("GET", "/stage-instances/{channel_id}", channel_id=channel_id)) + def get_stage_instance( + self, channel_id: Snowflake + ) -> Response[channel.StageInstance]: + return self.request( + Route("GET", "/stage-instances/{channel_id}", channel_id=channel_id) + ) - def create_stage_instance(self, *, reason: Optional[str], **payload: Any) -> Response[channel.StageInstance]: + def create_stage_instance( + self, *, reason: str | None, **payload: Any + ) -> Response[channel.StageInstance]: valid_keys = ( "channel_id", "topic", @@ -1990,10 +2143,12 @@ def create_stage_instance(self, *, reason: Optional[str], **payload: Any) -> Res if payload.get("send_start_notification") is not None: payload["send_start_notification"] = int(payload["send_start_notification"]) - return self.request(Route("POST", "/stage-instances"), json=payload, reason=reason) + return self.request( + Route("POST", "/stage-instances"), json=payload, reason=reason + ) def edit_stage_instance( - self, channel_id: Snowflake, *, reason: Optional[str] = None, **payload: Any + self, channel_id: Snowflake, *, reason: str | None = None, **payload: Any ) -> Response[None]: valid_keys = ( "topic", @@ -2007,7 +2162,9 @@ def edit_stage_instance( reason=reason, ) - def delete_stage_instance(self, channel_id: Snowflake, *, reason: Optional[str] = None) -> Response[None]: + def delete_stage_instance( + self, channel_id: Snowflake, *, reason: str | None = None + ) -> Response[None]: return self.request( Route("DELETE", "/stage-instances/{channel_id}", channel_id=channel_id), reason=reason, @@ -2017,7 +2174,7 @@ def delete_stage_instance(self, channel_id: Snowflake, *, reason: Optional[str] def get_scheduled_events( self, guild_id: Snowflake, with_user_count: bool = True - ) -> Response[List[scheduled_events.ScheduledEvent]]: + ) -> Response[list[scheduled_events.ScheduledEvent]]: params = { "with_user_count": int(with_user_count), } @@ -2045,7 +2202,7 @@ def get_scheduled_event( ) def create_scheduled_event( - self, guild_id: Snowflake, reason: Optional[str] = None, **payload: Any + self, guild_id: Snowflake, reason: str | None = None, **payload: Any ) -> Response[scheduled_events.ScheduledEvent]: valid_keys = ( "channel_id", @@ -2065,7 +2222,9 @@ def create_scheduled_event( reason=reason, ) - def delete_scheduled_event(self, guild_id: Snowflake, event_id: Snowflake) -> Response[None]: + def delete_scheduled_event( + self, guild_id: Snowflake, event_id: Snowflake + ) -> Response[None]: return self.request( Route( "DELETE", @@ -2079,7 +2238,7 @@ def edit_scheduled_event( self, guild_id: Snowflake, event_id: Snowflake, - reason: Optional[str] = None, + reason: str | None = None, **payload: Any, ) -> Response[scheduled_events.ScheduledEvent]: valid_keys = ( @@ -2115,7 +2274,7 @@ def get_scheduled_event_users( with_member: bool = False, before: Snowflake = None, after: Snowflake = None, - ) -> Response[List[scheduled_events.ScheduledEventSubscriber]]: + ) -> Response[list[scheduled_events.ScheduledEventSubscriber]]: params = { "limit": int(limit), "with_member": int(with_member), @@ -2145,7 +2304,7 @@ def get_global_commands( *, with_localizations: bool = True, locale: str = None, - ) -> Response[List[interactions.ApplicationCommand]]: + ) -> Response[list[interactions.ApplicationCommand]]: params = {"with_localizations": int(with_localizations)} return self.request( @@ -2172,7 +2331,9 @@ def get_global_command( ) return self.request(r, locale=locale) - def upsert_global_command(self, application_id: Snowflake, payload) -> Response[interactions.ApplicationCommand]: + def upsert_global_command( + self, application_id: Snowflake, payload + ) -> Response[interactions.ApplicationCommand]: r = Route( "POST", "/applications/{application_id}/commands", @@ -2200,7 +2361,9 @@ def edit_global_command( ) return self.request(r, json=payload) - def delete_global_command(self, application_id: Snowflake, command_id: Snowflake) -> Response[None]: + def delete_global_command( + self, application_id: Snowflake, command_id: Snowflake + ) -> Response[None]: r = Route( "DELETE", "/applications/{application_id}/commands/{command_id}", @@ -2211,7 +2374,7 @@ def delete_global_command(self, application_id: Snowflake, command_id: Snowflake def bulk_upsert_global_commands( self, application_id: Snowflake, payload - ) -> Response[List[interactions.ApplicationCommand]]: + ) -> Response[list[interactions.ApplicationCommand]]: r = Route( "PUT", "/applications/{application_id}/commands", @@ -2223,7 +2386,7 @@ def bulk_upsert_global_commands( def get_guild_commands( self, application_id: Snowflake, guild_id: Snowflake - ) -> Response[List[interactions.ApplicationCommand]]: + ) -> Response[list[interactions.ApplicationCommand]]: r = Route( "GET", "/applications/{application_id}/guilds/{guild_id}/commands", @@ -2302,8 +2465,8 @@ def bulk_upsert_guild_commands( self, application_id: Snowflake, guild_id: Snowflake, - payload: List[interactions.EditApplicationCommand], - ) -> Response[List[interactions.ApplicationCommand]]: + payload: list[interactions.EditApplicationCommand], + ) -> Response[list[interactions.ApplicationCommand]]: r = Route( "PUT", "/applications/{application_id}/guilds/{guild_id}/commands", @@ -2332,7 +2495,7 @@ def get_guild_command_permissions( self, application_id: Snowflake, guild_id: Snowflake, - ) -> Response[List[interactions.GuildApplicationCommandPermissions]]: + ) -> Response[list[interactions.GuildApplicationCommandPermissions]]: r = Route( "GET", "/applications/{application_id}/guilds/{guild_id}/commands/permissions", @@ -2342,18 +2505,18 @@ def get_guild_command_permissions( return self.request(r) # Guild Automod Rules - + def get_auto_moderation_rules( self, guild_id: Snowflake, - ) -> Response[List[automod.AutoModRule]]: + ) -> Response[list[automod.AutoModRule]]: r = Route( "GET", "/guilds/{guild_id}/auto-moderation/rules", guild_id=guild_id, ) return self.request(r) - + def get_auto_moderation_rule( self, guild_id: Snowflake, @@ -2366,12 +2529,12 @@ def get_auto_moderation_rule( rule_id=rule_id, ) return self.request(r) - + def create_auto_moderation_rule( self, guild_id: Snowflake, payload: automod.CreateAutoModRule, - reason: Optional[str] = None, + reason: str | None = None, ) -> Response[automod.AutoModRule]: r = Route( "POST", @@ -2379,13 +2542,13 @@ def create_auto_moderation_rule( guild_id=guild_id, ) return self.request(r, json=payload, reason=reason) - + def edit_auto_moderation_rule( self, guild_id: Snowflake, rule_id: Snowflake, - payload: automod.EditAutoModRule, - reason: Optional[str] = None, + payload: automod.EditAutoModRule, + reason: str | None = None, ) -> Response[automod.AutoModRule]: r = Route( "PATCH", @@ -2394,12 +2557,12 @@ def edit_auto_moderation_rule( rule_id=rule_id, ) return self.request(r, json=payload, reason=reason) - + def delete_auto_moderation_rule( self, guild_id: Snowflake, rule_id: Snowflake, - reason: Optional[str] = None, + reason: str | None = None, ) -> Response[None]: r = Route( "DELETE", @@ -2408,19 +2571,19 @@ def delete_auto_moderation_rule( rule_id=rule_id, ) return self.request(r, reason=reason) - + # Interaction responses def _edit_webhook_helper( self, route: Route, - file: Optional[File] = None, - content: Optional[str] = None, - embeds: Optional[List[embed.Embed]] = None, - allowed_mentions: Optional[message.AllowedMentions] = None, + file: File | None = None, + content: str | None = None, + embeds: list[embed.Embed] | None = None, + allowed_mentions: message.AllowedMentions | None = None, ): - payload: Dict[str, Any] = {} + payload: dict[str, Any] = {} if content: payload["content"] = content if embeds: @@ -2428,7 +2591,7 @@ def _edit_webhook_helper( if allowed_mentions: payload["allowed_mentions"] = allowed_mentions - form: List[Dict[str, Any]] = [ + form: list[dict[str, Any]] = [ { "name": "payload_json", "value": utils._to_json(payload), @@ -2453,7 +2616,7 @@ def create_interaction_response( token: str, *, type: InteractionResponseType, - data: Optional[interactions.InteractionApplicationCommandCallbackData] = None, + data: interactions.InteractionApplicationCommandCallbackData | None = None, ) -> Response[None]: r = Route( "POST", @@ -2461,7 +2624,7 @@ def create_interaction_response( interaction_id=interaction_id, interaction_token=token, ) - payload: Dict[str, Any] = { + payload: dict[str, Any] = { "type": type, } @@ -2487,10 +2650,10 @@ def edit_original_interaction_response( self, application_id: Snowflake, token: str, - file: Optional[File] = None, - content: Optional[str] = None, - embeds: Optional[List[embed.Embed]] = None, - allowed_mentions: Optional[message.AllowedMentions] = None, + file: File | None = None, + content: str | None = None, + embeds: list[embed.Embed] | None = None, + allowed_mentions: message.AllowedMentions | None = None, ) -> Response[message.Message]: r = Route( "PATCH", @@ -2506,7 +2669,9 @@ def edit_original_interaction_response( allowed_mentions=allowed_mentions, ) - def delete_original_interaction_response(self, application_id: Snowflake, token: str) -> Response[None]: + def delete_original_interaction_response( + self, application_id: Snowflake, token: str + ) -> Response[None]: r = Route( "DELETE", "/webhooks/{application_id}/{interaction_token}/messages/@original", @@ -2519,11 +2684,11 @@ def create_followup_message( self, application_id: Snowflake, token: str, - files: Optional[List[File]] = None, - content: Optional[str] = None, + files: list[File] | None = None, + content: str | None = None, tts: bool = False, - embeds: Optional[List[embed.Embed]] = None, - allowed_mentions: Optional[message.AllowedMentions] = None, + embeds: list[embed.Embed] | None = None, + allowed_mentions: message.AllowedMentions | None = None, ) -> Response[message.Message]: r = Route( "POST", @@ -2545,10 +2710,10 @@ def edit_followup_message( application_id: Snowflake, token: str, message_id: Snowflake, - file: Optional[File] = None, - content: Optional[str] = None, - embeds: Optional[List[embed.Embed]] = None, - allowed_mentions: Optional[message.AllowedMentions] = None, + file: File | None = None, + content: str | None = None, + embeds: list[embed.Embed] | None = None, + allowed_mentions: message.AllowedMentions | None = None, ) -> Response[message.Message]: r = Route( "PATCH", @@ -2565,7 +2730,9 @@ def edit_followup_message( allowed_mentions=allowed_mentions, ) - def delete_followup_message(self, application_id: Snowflake, token: str, message_id: Snowflake) -> Response[None]: + def delete_followup_message( + self, application_id: Snowflake, token: str, message_id: Snowflake + ) -> Response[None]: r = Route( "DELETE", "/webhooks/{application_id}/{interaction_token}/messages/{message_id}", @@ -2579,7 +2746,7 @@ def get_guild_application_command_permissions( self, application_id: Snowflake, guild_id: Snowflake, - ) -> Response[List[interactions.GuildApplicationCommandPermissions]]: + ) -> Response[list[interactions.GuildApplicationCommandPermissions]]: r = Route( "GET", "/applications/{application_id}/guilds/{guild_id}/commands/permissions", @@ -2623,7 +2790,7 @@ def bulk_edit_guild_application_command_permissions( self, application_id: Snowflake, guild_id: Snowflake, - payload: List[interactions.PartialGuildApplicationCommandPermissions], + payload: list[interactions.PartialGuildApplicationCommandPermissions], ) -> Response[None]: r = Route( "PUT", @@ -2638,8 +2805,16 @@ def bulk_edit_guild_application_command_permissions( def application_info(self) -> Response[appinfo.AppInfo]: return self.request(Route("GET", "/oauth2/applications/@me")) - def get_application(self, application_id: Snowflake, /) -> Response[appinfo.PartialAppInfo]: - return self.request(Route('GET', '/applications/{application_id}/rpc', application_id=application_id)) + def get_application( + self, application_id: Snowflake, / + ) -> Response[appinfo.PartialAppInfo]: + return self.request( + Route( + "GET", + "/applications/{application_id}/rpc", + application_id=application_id, + ) + ) async def get_gateway(self, *, encoding: str = "json", zlib: bool = True) -> str: try: @@ -2652,7 +2827,9 @@ async def get_gateway(self, *, encoding: str = "json", zlib: bool = True) -> str value = "{0}?encoding={1}&v={2}" return value.format(data["url"], encoding, API_VERSION) - async def get_bot_gateway(self, *, encoding: str = "json", zlib: bool = True) -> Tuple[int, str]: + async def get_bot_gateway( + self, *, encoding: str = "json", zlib: bool = True + ) -> tuple[int, str]: try: data = await self.request(Route("GET", "/gateway/bot")) except HTTPException as exc: diff --git a/discord/integrations.py b/discord/integrations.py index 1f5a0523fd..427dd4deae 100644 --- a/discord/integrations.py +++ b/discord/integrations.py @@ -26,7 +26,7 @@ from __future__ import annotations import datetime -from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Type +from typing import TYPE_CHECKING, Any from .enums import ExpireBehaviour, try_enum from .errors import InvalidArgument @@ -60,7 +60,7 @@ class IntegrationAccount: .. versionadded:: 1.4 Attributes - ----------- + ---------- id: :class:`str` The account ID. name: :class:`str` @@ -83,7 +83,7 @@ class Integration: .. versionadded:: 1.4 Attributes - ----------- + ---------- id: :class:`int` The integration ID. name: :class:`str` @@ -129,7 +129,7 @@ def _from_data(self, data: IntegrationPayload) -> None: self.user = User(state=self._state, data=user) if user else None self.enabled: bool = data["enabled"] - async def delete(self, *, reason: Optional[str] = None) -> None: + async def delete(self, *, reason: str | None = None) -> None: """|coro| Deletes the integration. @@ -138,14 +138,14 @@ async def delete(self, *, reason: Optional[str] = None) -> None: do this. Parameters - ----------- + ---------- reason: :class:`str` The reason the integration was deleted. Shows up on the audit log. .. versionadded:: 2.0 Raises - ------- + ------ Forbidden You do not have permission to delete the integration. HTTPException @@ -201,10 +201,12 @@ class StreamIntegration(Integration): def _from_data(self, data: StreamIntegrationPayload) -> None: super()._from_data(data) self.revoked: bool = data["revoked"] - self.expire_behaviour: ExpireBehaviour = try_enum(ExpireBehaviour, data["expire_behavior"]) + self.expire_behaviour: ExpireBehaviour = try_enum( + ExpireBehaviour, data["expire_behavior"] + ) self.expire_grace_period: int = data["expire_grace_period"] self.synced_at: datetime.datetime = parse_time(data["synced_at"]) - self._role_id: Optional[int] = _get_as_snowflake(data, "role_id") + self._role_id: int | None = _get_as_snowflake(data, "role_id") self.syncing: bool = data["syncing"] self.enable_emoticons: bool = data["enable_emoticons"] self.subscriber_count: int = data["subscriber_count"] @@ -215,7 +217,7 @@ def expire_behavior(self) -> ExpireBehaviour: return self.expire_behaviour @property - def role(self) -> Optional[Role]: + def role(self) -> Role | None: """Optional[:class:`Role`]: The role which the integration uses for subscribers.""" return self.guild.get_role(self._role_id) # type: ignore @@ -234,7 +236,7 @@ async def edit( do this. Parameters - ----------- + ---------- expire_behaviour: :class:`ExpireBehaviour` The behaviour when an integration subscription lapses. Aliased to ``expire_behavior`` as well. expire_grace_period: :class:`int` @@ -243,7 +245,7 @@ async def edit( Where emoticons should be synced for this integration (currently twitch only). Raises - ------- + ------ Forbidden You do not have permission to edit the integration. HTTPException @@ -251,10 +253,12 @@ async def edit( InvalidArgument ``expire_behaviour`` did not receive a :class:`ExpireBehaviour`. """ - payload: Dict[str, Any] = {} + payload: dict[str, Any] = {} if expire_behaviour is not MISSING: if not isinstance(expire_behaviour, ExpireBehaviour): - raise InvalidArgument("expire_behaviour field must be of type ExpireBehaviour") + raise InvalidArgument( + "expire_behaviour field must be of type ExpireBehaviour" + ) payload["expire_behavior"] = expire_behaviour.value @@ -277,7 +281,7 @@ async def sync(self) -> None: do this. Raises - ------- + ------ Forbidden You do not have permission to sync the integration. HTTPException @@ -320,11 +324,11 @@ class IntegrationApplication: def __init__(self, *, data: IntegrationApplicationPayload, state): self.id: int = int(data["id"]) self.name: str = data["name"] - self.icon: Optional[str] = data["icon"] + self.icon: str | None = data["icon"] self.description: str = data["description"] self.summary: str = data["summary"] user = data.get("bot") - self.user: Optional[User] = User(state=state, data=user) if user else None + self.user: User | None = User(state=state, data=user) if user else None class BotIntegration(Integration): @@ -356,10 +360,12 @@ class BotIntegration(Integration): def _from_data(self, data: BotIntegrationPayload) -> None: super()._from_data(data) - self.application = IntegrationApplication(data=data["application"], state=self._state) + self.application = IntegrationApplication( + data=data["application"], state=self._state + ) -def _integration_factory(value: str) -> Tuple[Type[Integration], str]: +def _integration_factory(value: str) -> tuple[type[Integration], str]: if value == "discord": return BotIntegration, value elif value in {"twitch", "youtube"}: diff --git a/discord/interactions.py b/discord/interactions.py index 7d78cc683d..c772190812 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -26,7 +26,7 @@ from __future__ import annotations import asyncio -from typing import TYPE_CHECKING, Any, Coroutine, Dict, List, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any, Coroutine, Union from . import utils from .channel import ChannelType, PartialMessageable @@ -92,7 +92,7 @@ class Interaction: .. versionadded:: 2.0 Attributes - ----------- + ---------- id: :class:`int` The interaction's ID. type: :class:`InteractionType` @@ -120,7 +120,7 @@ class Interaction: The custom ID for the interaction. """ - __slots__: Tuple[str, ...] = ( + __slots__: tuple[str, ...] = ( "id", "type", "guild_id", @@ -149,36 +149,44 @@ class Interaction: def __init__(self, *, data: InteractionPayload, state: ConnectionState): self._state: ConnectionState = state self._session: ClientSession = state.http._HTTPClient__session - self._original_response: Optional[InteractionMessage] = None + self._original_response: InteractionMessage | None = None self._from_data(data) def _from_data(self, data: InteractionPayload): self.id: int = int(data["id"]) self.type: InteractionType = try_enum(InteractionType, data["type"]) - self.data: Optional[InteractionData] = data.get("data") + self.data: InteractionData | None = data.get("data") self.token: str = data["token"] self.version: int = data["version"] - self.channel_id: Optional[int] = utils._get_as_snowflake(data, "channel_id") - self.guild_id: Optional[int] = utils._get_as_snowflake(data, "guild_id") + self.channel_id: int | None = utils._get_as_snowflake(data, "channel_id") + self.guild_id: int | None = utils._get_as_snowflake(data, "guild_id") self.application_id: int = int(data["application_id"]) - self.locale: Optional[str] = data.get("locale") - self.guild_locale: Optional[str] = data.get("guild_locale") - self.custom_id: Optional[str] = self.data.get("custom_id") if self.data is not None else None + self.locale: str | None = data.get("locale") + self.guild_locale: str | None = data.get("guild_locale") + self.custom_id: str | None = ( + self.data.get("custom_id") if self.data is not None else None + ) self._app_permissions: int = int(data.get("app_permissions", 0)) - self.message: Optional[Message] = None + self.message: Message | None = None if message_data := data.get("message"): - self.message = Message(state=self._state, channel=self.channel, data=message_data) + self.message = Message( + state=self._state, channel=self.channel, data=message_data + ) self._message_data = message_data - self.user: Optional[Union[User, Member]] = None + self.user: User | Member | None = None self._permissions: int = 0 # TODO: there's a potential data loss here if self.guild_id: - guild = self.guild or self._state._get_guild(self.guild_id) or Object(id=self.guild_id) + guild = ( + self.guild + or self._state._get_guild(self.guild_id) + or Object(id=self.guild_id) + ) try: member = data["member"] # type: ignore except KeyError: @@ -187,7 +195,9 @@ def _from_data(self, data: InteractionPayload): self._permissions = int(member.get("permissions", 0)) if not isinstance(guild, Object): cache_flag = self._state.member_cache_flags.interaction - self.user = guild._get_and_update_member(member, int(member["user"]["id"]), cache_flag) + self.user = guild._get_and_update_member( + member, int(member["user"]["id"]), cache_flag + ) else: self.user = Member(state=self._state, data=member, guild=guild) else: @@ -202,7 +212,7 @@ def client(self) -> Client: return self._state._get_client() @property - def guild(self) -> Optional[Guild]: + def guild(self) -> Guild | None: """Optional[:class:`Guild`]: The guild the interaction was sent from.""" return self._state and self._state._get_guild(self.guild_id) @@ -215,7 +225,7 @@ def is_component(self) -> bool: return self.type == InteractionType.component @utils.cached_slot_property("_cs_channel") - def channel(self) -> Optional[InteractionChannel]: + def channel(self) -> InteractionChannel | None: """Optional[Union[:class:`abc.GuildChannel`, :class:`PartialMessageable`, :class:`Thread`]]: The channel the interaction was sent from. @@ -226,8 +236,14 @@ def channel(self) -> Optional[InteractionChannel]: channel = guild and guild._resolve_channel(self.channel_id) if channel is None: if self.channel_id is not None: - type = ChannelType.text if self.guild_id is not None else ChannelType.private - return PartialMessageable(state=self._state, id=self.channel_id, type=type) + type = ( + ChannelType.text + if self.guild_id is not None + else ChannelType.private + ) + return PartialMessageable( + state=self._state, id=self.channel_id, type=type + ) return None return channel @@ -274,17 +290,17 @@ async def original_response(self) -> InteractionMessage: Repeated calls to this will return a cached value. - Raises + Returns ------- + InteractionMessage + The original interaction response message. + + Raises + ------ HTTPException Fetching the original response message failed. ClientException The channel for the message could not be resolved. - - Returns - -------- - InteractionMessage - The original interaction response message. """ if self._original_response is not None: @@ -312,33 +328,33 @@ async def original_response(self) -> InteractionMessage: @utils.deprecated("Interaction.original_response", "2.1") async def original_message(self): """An alias for :meth:`original_response`. - - Raises + + Returns ------- + InteractionMessage + The original interaction response message. + + Raises + ------ HTTPException Fetching the original response message failed. ClientException The channel for the message could not be resolved. - - Returns - -------- - InteractionMessage - The original interaction response message. """ return self.original_response() async def edit_original_response( self, *, - content: Optional[str] = MISSING, - embeds: List[Embed] = MISSING, - embed: Optional[Embed] = MISSING, + content: str | None = MISSING, + embeds: list[Embed] = MISSING, + embed: Embed | None = MISSING, file: File = MISSING, - files: List[File] = MISSING, - attachments: List[Attachment] = MISSING, - view: Optional[View] = MISSING, - allowed_mentions: Optional[AllowedMentions] = None, - delete_after: Optional[float] = None, + files: list[File] = MISSING, + attachments: list[Attachment] = MISSING, + view: View | None = MISSING, + allowed_mentions: AllowedMentions | None = None, + delete_after: float | None = None, ) -> InteractionMessage: """|coro| @@ -351,7 +367,7 @@ async def edit_original_response( the message sent was ephemeral. Parameters - ------------ + ---------- content: Optional[:class:`str`] The content to edit the message with or ``None`` to clear it. embeds: List[:class:`Embed`] @@ -378,8 +394,13 @@ async def edit_original_response( before deleting the message we just edited. If the deletion fails, then it is silently ignored. - Raises + Returns ------- + :class:`InteractionMessage` + The newly edited message. + + Raises + ------ HTTPException Editing the message failed. Forbidden @@ -388,14 +409,9 @@ async def edit_original_response( You specified both ``embed`` and ``embeds`` or ``file`` and ``files`` ValueError The length of ``embeds`` was invalid. - - Returns - -------- - :class:`InteractionMessage` - The newly edited message. """ - previous_mentions: Optional[AllowedMentions] = self._state.allowed_mentions + previous_mentions: AllowedMentions | None = self._state.allowed_mentions params = handle_message_parameters( content=content, file=file, @@ -434,9 +450,14 @@ async def edit_original_response( @utils.deprecated("Interaction.edit_original_response", "2.1") async def edit_original_message(self, **kwargs): """An alias for :meth:`edit_original_response`. - - Raises + + Returns ------- + :class:`InteractionMessage` + The newly edited message. + + Raises + ------ HTTPException Editing the message failed. Forbidden @@ -445,15 +466,10 @@ async def edit_original_message(self, **kwargs): You specified both ``embed`` and ``embeds`` or ``file`` and ``files`` ValueError The length of ``embeds`` was invalid. - - Returns - -------- - :class:`InteractionMessage` - The newly edited message. """ return self.edit_original_response(**kwargs) - async def delete_original_response(self, *, delay: Optional[float] = None) -> None: + async def delete_original_response(self, *, delay: float | None = None) -> None: """|coro| Deletes the original interaction response message. @@ -462,13 +478,13 @@ async def delete_original_response(self, *, delay: Optional[float] = None) -> No you do not want to fetch the message and save an HTTP request. Parameters - ----------- + ---------- delay: Optional[:class:`float`] If provided, the number of seconds to wait before deleting the message. The waiting is done in the background and deletion failures are ignored. Raises - ------- + ------ HTTPException Deleting the message failed. Forbidden @@ -494,7 +510,7 @@ async def delete_original_message(self, **kwargs): """An alias for :meth:`delete_original_response`. Raises - ------- + ------ HTTPException Deleting the message failed. Forbidden @@ -502,12 +518,12 @@ async def delete_original_message(self, **kwargs): """ return self.delete_original_response(**kwargs) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """ Converts this interaction object into a dict. Returns - -------- + ------- Dict[:class:`str`, Any] A dictionary of :class:`str` interaction keys bound to the respective value. """ @@ -523,9 +539,13 @@ def to_dict(self) -> Dict[str, Any]: if self.data is not None: data["data"] = self.data if (resolved := self.data.get("resolved")) and self.user is not None: - if (users := resolved.get("users")) and (user := users.get(self.user.id)): + if (users := resolved.get("users")) and ( + user := users.get(self.user.id) + ): data["user"] = user - if (members := resolved.get("members")) and (member := members.get(self.user.id)): + if (members := resolved.get("members")) and ( + member := members.get(self.user.id) + ): data["member"] = member if self.guild_id is not None: @@ -554,7 +574,7 @@ class InteractionResponse: .. versionadded:: 2.0 """ - __slots__: Tuple[str, ...] = ( + __slots__: tuple[str, ...] = ( "_responded", "_parent", "_response_lock", @@ -587,7 +607,7 @@ async def defer(self, *, ephemeral: bool = False, invisible: bool = True) -> Non - :attr:`InteractionType.modal_submit` Parameters - ----------- + ---------- ephemeral: :class:`bool` Indicates whether the deferred message will eventually be ephemeral. This only applies to :attr:`InteractionType.application_command` interactions, @@ -601,7 +621,7 @@ async def defer(self, *, ephemeral: bool = False, invisible: bool = True) -> Non This parameter does not apply to interactions of type :attr:`InteractionType.application_command`. Raises - ------- + ------ HTTPException Deferring the interaction failed. InteractionResponded @@ -611,20 +631,23 @@ async def defer(self, *, ephemeral: bool = False, invisible: bool = True) -> Non raise InteractionResponded(self._parent) defer_type: int = 0 - data: Optional[Dict[str, Any]] = None + data: dict[str, Any] | None = None parent = self._parent - if parent.type is InteractionType.component or parent.type is InteractionType.modal_submit: + if ( + parent.type is InteractionType.component + or parent.type is InteractionType.modal_submit + ): defer_type = ( InteractionResponseType.deferred_message_update.value if invisible else InteractionResponseType.deferred_channel_message.value ) if not invisible and ephemeral: - data = {'flags': 64} + data = {"flags": 64} elif parent.type is InteractionType.application_command: defer_type = InteractionResponseType.deferred_channel_message.value if ephemeral: - data = {'flags': 64} + data = {"flags": 64} if defer_type: adapter = async_context.get() @@ -650,7 +673,7 @@ async def pong(self) -> None: This should rarely be used. Raises - ------- + ------ HTTPException Ponging the interaction failed. InteractionResponded @@ -677,16 +700,16 @@ async def pong(self) -> None: async def send_message( self, - content: Optional[Any] = None, + content: Any | None = None, *, embed: Embed = None, - embeds: List[Embed] = None, + embeds: list[Embed] = None, view: View = None, tts: bool = False, ephemeral: bool = False, allowed_mentions: AllowedMentions = None, file: File = None, - files: List[File] = None, + files: list[File] = None, delete_after: float = None, ) -> Interaction: """|coro| @@ -694,7 +717,7 @@ async def send_message( Responds to this interaction by sending a message. Parameters - ----------- + ---------- content: Optional[:class:`str`] The content of the message to send. embeds: List[:class:`Embed`] @@ -722,8 +745,13 @@ async def send_message( files: List[:class:`File`] A list of files to upload. Must be a maximum of 10. - Raises + Returns ------- + :class:`.Interaction` + The interaction object associated with the sent message. + + Raises + ------ HTTPException Sending the message failed. TypeError @@ -732,16 +760,11 @@ async def send_message( The length of ``embeds`` was invalid. InteractionResponded This interaction has already been responded to before. - - Returns - -------- - :class:`.Interaction` - The interaction object associated with the sent message. """ if self._responded: raise InteractionResponded(self._parent) - payload: Dict[str, Any] = { + payload: dict[str, Any] = { "tts": tts, } @@ -768,10 +791,14 @@ async def send_message( state = self._parent._state if allowed_mentions is None: - payload["allowed_mentions"] = state.allowed_mentions and state.allowed_mentions.to_dict() + payload["allowed_mentions"] = ( + state.allowed_mentions and state.allowed_mentions.to_dict() + ) elif state.allowed_mentions is not None: - payload["allowed_mentions"] = state.allowed_mentions.merge(allowed_mentions).to_dict() + payload["allowed_mentions"] = state.allowed_mentions.merge( + allowed_mentions + ).to_dict() else: payload["allowed_mentions"] = allowed_mentions.to_dict() if file is not None and files is not None: @@ -785,7 +812,9 @@ async def send_message( if files is not None: if len(files) > 10: - raise InvalidArgument("files parameter must be a list of up to 10 elements") + raise InvalidArgument( + "files parameter must be a list of up to 10 elements" + ) elif not all(isinstance(file, File) for file in files): raise InvalidArgument("files parameter must be a list of File") @@ -825,14 +854,14 @@ async def send_message( async def edit_message( self, *, - content: Optional[Any] = MISSING, - embed: Optional[Embed] = MISSING, - embeds: List[Embed] = MISSING, + content: Any | None = MISSING, + embed: Embed | None = MISSING, + embeds: list[Embed] = MISSING, file: File = MISSING, - files: List[File] = MISSING, - attachments: List[Attachment] = MISSING, - view: Optional[View] = MISSING, - delete_after: Optional[float] = None, + files: list[File] = MISSING, + attachments: list[Attachment] = MISSING, + view: View | None = MISSING, + delete_after: float | None = None, ) -> None: """|coro| @@ -840,7 +869,7 @@ async def edit_message( a component or modal interaction. Parameters - ----------- + ---------- content: Optional[:class:`str`] The new content to replace the message with. ``None`` removes the content. embeds: List[:class:`Embed`] @@ -865,7 +894,7 @@ async def edit_message( then it is silently ignored. Raises - ------- + ------ HTTPException Editing the message failed. TypeError @@ -902,7 +931,9 @@ async def edit_message( payload["components"] = [] if view is None else view.to_components() if file is not MISSING and files is not MISSING: - raise InvalidArgument("cannot pass both file and files parameter to edit_message()") + raise InvalidArgument( + "cannot pass both file and files parameter to edit_message()" + ) if file is not MISSING: if not isinstance(file, File): @@ -915,7 +946,9 @@ async def edit_message( if files is not MISSING: if len(files) > 10: - raise InvalidArgument("files parameter must be a list of up to 10 elements") + raise InvalidArgument( + "files parameter must be a list of up to 10 elements" + ) elif not all(isinstance(file, File) for file in files): raise InvalidArgument("files parameter must be a list of File") if "attachments" not in payload: @@ -952,18 +985,18 @@ async def edit_message( async def send_autocomplete_result( self, *, - choices: List[OptionChoice], + choices: list[OptionChoice], ) -> None: """|coro| Responds to this interaction by sending the autocomplete choices. Parameters - ----------- + ---------- choices: List[:class:`OptionChoice`] A list of choices. Raises - ------- + ------ HTTPException Sending the result failed. InteractionResponded @@ -1041,12 +1074,12 @@ async def _locked_response(self, coro: Coroutine[Any]): Wraps a response and makes sure that it's locked while executing. Parameters - ----------- + ---------- coro: Coroutine[Any] The coroutine to wrap. Raises - ------- + ------ InteractionResponded This interaction has already been responded to before. """ @@ -1098,22 +1131,22 @@ class InteractionMessage(Message): async def edit( self, - content: Optional[str] = MISSING, - embeds: List[Embed] = MISSING, - embed: Optional[Embed] = MISSING, + content: str | None = MISSING, + embeds: list[Embed] = MISSING, + embed: Embed | None = MISSING, file: File = MISSING, - files: List[File] = MISSING, - attachments: List[Attachment] = MISSING, - view: Optional[View] = MISSING, - allowed_mentions: Optional[AllowedMentions] = None, - delete_after: Optional[float] = None, + files: list[File] = MISSING, + attachments: list[Attachment] = MISSING, + view: View | None = MISSING, + allowed_mentions: AllowedMentions | None = None, + delete_after: float | None = None, ) -> InteractionMessage: """|coro| Edits the message. Parameters - ------------ + ---------- content: Optional[:class:`str`] The content to edit the message with or ``None`` to clear it. embeds: List[:class:`Embed`] @@ -1140,8 +1173,13 @@ async def edit( before deleting the message we just edited. If the deletion fails, then it is silently ignored. - Raises + Returns ------- + :class:`InteractionMessage` + The newly edited message. + + Raises + ------ HTTPException Editing the message failed. Forbidden @@ -1150,11 +1188,6 @@ async def edit( You specified both ``embed`` and ``embeds`` or ``file`` and ``files`` ValueError The length of ``embeds`` was invalid. - - Returns - --------- - :class:`InteractionMessage` - The newly edited message. """ if attachments is MISSING: attachments = self.attachments or MISSING @@ -1170,13 +1203,13 @@ async def edit( delete_after=delete_after, ) - async def delete(self, *, delay: Optional[float] = None) -> None: + async def delete(self, *, delay: float | None = None) -> None: """|coro| Deletes the message. Parameters - ----------- + ---------- delay: Optional[:class:`float`] If provided, the number of seconds to wait before deleting the message. The waiting is done in the background and deletion failures are ignored. @@ -1205,7 +1238,7 @@ class MessageInteraction: Responses to message components do not include this property. Attributes - ----------- + ---------- id: :class:`int` The interaction's ID. type: :class:`InteractionType` @@ -1218,7 +1251,7 @@ class MessageInteraction: The raw interaction data. """ - __slots__: Tuple[str, ...] = ("id", "type", "name", "user", "data", "_state") + __slots__: tuple[str, ...] = ("id", "type", "name", "user", "data", "_state") def __init__(self, *, data: MessageInteractionPayload, state: ConnectionState): self._state = state diff --git a/discord/invite.py b/discord/invite.py index 66eb4f8879..713a2d6317 100644 --- a/discord/invite.py +++ b/discord/invite.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List, Optional, Type, TypeVar, Union +from typing import TYPE_CHECKING, TypeVar, Union from .appinfo import PartialAppInfo from .asset import Asset @@ -83,7 +83,7 @@ class PartialInviteChannel: Returns the partial channel's name. Attributes - ----------- + ---------- name: :class:`str` The partial channel's name. id: :class:`int` @@ -103,7 +103,9 @@ def __str__(self) -> str: return self.name def __repr__(self) -> str: - return f"" + return ( + f"" + ) @property def mention(self) -> str: @@ -141,7 +143,7 @@ class PartialInviteGuild: Returns the partial guild's name. Attributes - ----------- + ---------- name: :class:`str` The partial guild's name. id: :class:`int` @@ -170,12 +172,14 @@ def __init__(self, state: ConnectionState, data: InviteGuildPayload, id: int): self._state: ConnectionState = state self.id: int = id self.name: str = data["name"] - self.features: List[str] = data.get("features", []) - self._icon: Optional[str] = data.get("icon") - self._banner: Optional[str] = data.get("banner") - self._splash: Optional[str] = data.get("splash") - self.verification_level: VerificationLevel = try_enum(VerificationLevel, data.get("verification_level")) - self.description: Optional[str] = data.get("description") + self.features: list[str] = data.get("features", []) + self._icon: str | None = data.get("icon") + self._banner: str | None = data.get("banner") + self._splash: str | None = data.get("splash") + self.verification_level: VerificationLevel = try_enum( + VerificationLevel, data.get("verification_level") + ) + self.description: str | None = data.get("description") def __str__(self) -> str: return self.name @@ -192,25 +196,29 @@ def created_at(self) -> datetime.datetime: return snowflake_time(self.id) @property - def icon(self) -> Optional[Asset]: + def icon(self) -> Asset | None: """Optional[:class:`Asset`]: Returns the guild's icon asset, if available.""" if self._icon is None: return None return Asset._from_guild_icon(self._state, self.id, self._icon) @property - def banner(self) -> Optional[Asset]: + def banner(self) -> Asset | None: """Optional[:class:`Asset`]: Returns the guild's banner asset, if available.""" if self._banner is None: return None - return Asset._from_guild_image(self._state, self.id, self._banner, path="banners") + return Asset._from_guild_image( + self._state, self.id, self._banner, path="banners" + ) @property - def splash(self) -> Optional[Asset]: + def splash(self) -> Asset | None: """Optional[:class:`Asset`]: Returns the guild's invite splash asset, if available.""" if self._splash is None: return None - return Asset._from_guild_image(self._state, self.id, self._splash, path="splashes") + return Asset._from_guild_image( + self._state, self.id, self._splash, path="splashes" + ) I = TypeVar("I", bound="Invite") @@ -346,51 +354,69 @@ def __init__( *, state: ConnectionState, data: InvitePayload, - guild: Optional[Union[PartialInviteGuild, Guild]] = None, - channel: Optional[Union[PartialInviteChannel, GuildChannel]] = None, + guild: PartialInviteGuild | Guild | None = None, + channel: PartialInviteChannel | GuildChannel | None = None, ): self._state: ConnectionState = state - self.max_age: Optional[int] = data.get("max_age") + self.max_age: int | None = data.get("max_age") self.code: str = data["code"] - self.guild: Optional[InviteGuildType] = self._resolve_guild(data.get("guild"), guild) - self.revoked: Optional[bool] = data.get("revoked") - self.created_at: Optional[datetime.datetime] = parse_time(data.get("created_at")) - self.temporary: Optional[bool] = data.get("temporary") - self.uses: Optional[int] = data.get("uses") - self.max_uses: Optional[int] = data.get("max_uses") - self.approximate_presence_count: Optional[int] = data.get("approximate_presence_count") - self.approximate_member_count: Optional[int] = data.get("approximate_member_count") + self.guild: InviteGuildType | None = self._resolve_guild( + data.get("guild"), guild + ) + self.revoked: bool | None = data.get("revoked") + self.created_at: datetime.datetime | None = parse_time(data.get("created_at")) + self.temporary: bool | None = data.get("temporary") + self.uses: int | None = data.get("uses") + self.max_uses: int | None = data.get("max_uses") + self.approximate_presence_count: int | None = data.get( + "approximate_presence_count" + ) + self.approximate_member_count: int | None = data.get("approximate_member_count") expires_at = data.get("expires_at", None) - self.expires_at: Optional[datetime.datetime] = parse_time(expires_at) if expires_at else None + self.expires_at: datetime.datetime | None = ( + parse_time(expires_at) if expires_at else None + ) inviter_data = data.get("inviter") - self.inviter: Optional[User] = None if inviter_data is None else self._state.create_user(inviter_data) + self.inviter: User | None = ( + None if inviter_data is None else self._state.create_user(inviter_data) + ) - self.channel: Optional[InviteChannelType] = self._resolve_channel(data.get("channel"), channel) + self.channel: InviteChannelType | None = self._resolve_channel( + data.get("channel"), channel + ) target_user_data = data.get("target_user") - self.target_user: Optional[User] = ( - None if target_user_data is None else self._state.create_user(target_user_data) + self.target_user: User | None = ( + None + if target_user_data is None + else self._state.create_user(target_user_data) ) - self.target_type: InviteTarget = try_enum(InviteTarget, data.get("target_type", 0)) + self.target_type: InviteTarget = try_enum( + InviteTarget, data.get("target_type", 0) + ) from .scheduled_events import ScheduledEvent scheduled_event: ScheduledEventPayload = data.get("guild_scheduled_event") - self.scheduled_event: Optional[ScheduledEvent] = ( - ScheduledEvent(state=state, data=scheduled_event) if scheduled_event else None + self.scheduled_event: ScheduledEvent | None = ( + ScheduledEvent(state=state, data=scheduled_event) + if scheduled_event + else None ) application = data.get("target_application") - self.target_application: Optional[PartialAppInfo] = ( + self.target_application: PartialAppInfo | None = ( PartialAppInfo(data=application, state=state) if application else None ) @classmethod - def from_incomplete(cls: Type[I], *, state: ConnectionState, data: InvitePayload) -> I: - guild: Optional[Union[Guild, PartialInviteGuild]] + def from_incomplete( + cls: type[I], *, state: ConnectionState, data: InvitePayload + ) -> I: + guild: Guild | PartialInviteGuild | None try: guild_data = data["guild"] except KeyError: @@ -405,7 +431,9 @@ def from_incomplete(cls: Type[I], *, state: ConnectionState, data: InvitePayload # As far as I know, invites always need a channel # So this should never raise. - channel: Union[PartialInviteChannel, GuildChannel] = PartialInviteChannel(data["channel"]) + channel: PartialInviteChannel | GuildChannel = PartialInviteChannel( + data["channel"] + ) if guild is not None and not isinstance(guild, PartialInviteGuild): # Upgrade the partial data if applicable channel = guild.get_channel(channel.id) or channel @@ -413,9 +441,11 @@ def from_incomplete(cls: Type[I], *, state: ConnectionState, data: InvitePayload return cls(state=state, data=data, guild=guild, channel=channel) @classmethod - def from_gateway(cls: Type[I], *, state: ConnectionState, data: GatewayInvitePayload) -> I: - guild_id: Optional[int] = _get_as_snowflake(data, "guild_id") - guild: Optional[Union[Guild, Object]] = state._get_guild(guild_id) + def from_gateway( + cls: type[I], *, state: ConnectionState, data: GatewayInvitePayload + ) -> I: + guild_id: int | None = _get_as_snowflake(data, "guild_id") + guild: Guild | Object | None = state._get_guild(guild_id) channel_id = int(data["channel_id"]) if guild is not None: channel = guild.get_channel(channel_id) or Object(id=channel_id) # type: ignore @@ -427,9 +457,9 @@ def from_gateway(cls: Type[I], *, state: ConnectionState, data: GatewayInvitePay def _resolve_guild( self, - data: Optional[InviteGuildPayload], - guild: Optional[Union[Guild, PartialInviteGuild]] = None, - ) -> Optional[InviteGuildType]: + data: InviteGuildPayload | None, + guild: Guild | PartialInviteGuild | None = None, + ) -> InviteGuildType | None: if guild is not None: return guild @@ -441,9 +471,9 @@ def _resolve_guild( def _resolve_channel( self, - data: Optional[InviteChannelPayload], - channel: Optional[Union[PartialInviteChannel, GuildChannel]] = None, - ) -> Optional[InviteChannelType]: + data: InviteChannelPayload | None, + channel: PartialInviteChannel | GuildChannel | None = None, + ) -> InviteChannelType | None: if channel is not None: return channel @@ -476,7 +506,7 @@ def url(self) -> str: """:class:`str`: A property that retrieves the invite URL.""" return f"{self.BASE}/{self.code}{f'?event={self.scheduled_event.id}' if self.scheduled_event else ''}" - async def delete(self, *, reason: Optional[str] = None): + async def delete(self, *, reason: str | None = None): """|coro| Revokes the instant invite. @@ -484,12 +514,12 @@ async def delete(self, *, reason: Optional[str] = None): You must have the :attr:`~Permissions.manage_channels` permission to do this. Parameters - ----------- + ---------- reason: Optional[:class:`str`] The reason for deleting this invite. Shows up on the audit log. Raises - ------- + ------ Forbidden You do not have permissions to revoke invites. NotFound @@ -513,7 +543,7 @@ def set_scheduled_event(self, event: ScheduledEvent) -> None: .. versionadded:: 2.0 Parameters - ----------- + ---------- event: :class:`ScheduledEvent` The scheduled event object to link. """ diff --git a/discord/iterators.py b/discord/iterators.py index 692ae61cae..181527f700 100644 --- a/discord/iterators.py +++ b/discord/iterators.py @@ -34,7 +34,6 @@ Awaitable, Callable, List, - Optional, TypeVar, Union, ) @@ -80,7 +79,7 @@ class _AsyncIterator(AsyncIterator[T]): async def next(self) -> T: raise NotImplementedError - def get(self, **attrs: Any) -> Awaitable[Optional[T]]: + def get(self, **attrs: Any) -> Awaitable[T | None]: def predicate(elem: T): for attr, val in attrs.items(): nested = attr.split("__") @@ -94,7 +93,7 @@ def predicate(elem: T): return self.find(predicate) - async def find(self, predicate: _Func[T, bool]) -> Optional[T]: + async def find(self, predicate: _Func[T, bool]) -> T | None: while True: try: elem = await self.next() @@ -116,7 +115,7 @@ def map(self, func: _Func[T, OT]) -> _MappedAsyncIterator[OT]: def filter(self, predicate: _Func[T, bool]) -> _FilteredAsyncIterator[T]: return _FilteredAsyncIterator(self, predicate) - async def flatten(self) -> List[T]: + async def flatten(self) -> list[T]: return [element async for element in self] async def __anext__(self) -> T: @@ -135,8 +134,8 @@ def __init__(self, iterator, max_size): self.iterator = iterator self.max_size = max_size - async def next(self) -> List[T]: - ret: List[T] = [] + async def next(self) -> list[T]: + ret: list[T] = [] n = 0 while n < self.max_size: try: @@ -195,7 +194,7 @@ def __init__(self, message, emoji, limit=100, after=None): self.channel_id = message.channel.id self.users = asyncio.Queue() - async def next(self) -> Union[User, Member]: + async def next(self) -> User | Member: if self.users.empty(): await self.fill_users() @@ -212,7 +211,7 @@ async def fill_users(self): retrieve = min(self.limit, 100) after = self.after.id if self.after else None - data: List[PartialUserPayload] = await self.getter( + data: list[PartialUserPayload] = await self.getter( self.channel_id, self.message.id, self.emoji, retrieve, after=after ) @@ -249,7 +248,7 @@ class HistoryIterator(_AsyncIterator["Message"]): messages endpoint. Parameters - ----------- + ---------- messageable: :class:`abc.Messageable` Messageable class to retrieve message history from. limit: :class:`int` @@ -299,7 +298,9 @@ def __init__( if self.limit is None: raise ValueError("history does not support around with limit=None") if self.limit > 101: - raise ValueError("history max limit 101 when specifying around parameter") + raise ValueError( + "history max limit 101 when specifying around parameter" + ) elif self.limit == 101: self.limit = 100 # Thanks discord @@ -355,16 +356,20 @@ async def fill_messages(self): channel = self.channel for element in data: - await self.messages.put(self.state.create_message(channel=channel, data=element)) + await self.messages.put( + self.state.create_message(channel=channel, data=element) + ) - async def _retrieve_messages(self, retrieve) -> List[Message]: + async def _retrieve_messages(self, retrieve) -> list[Message]: """Retrieve messages and update next parameters.""" raise NotImplementedError async def _retrieve_messages_before_strategy(self, retrieve): """Retrieve messages using before parameter.""" before = self.before.id if self.before else None - data: List[MessagePayload] = await self.logs_from(self.channel.id, retrieve, before=before) + data: list[MessagePayload] = await self.logs_from( + self.channel.id, retrieve, before=before + ) if len(data): if self.limit is not None: self.limit -= retrieve @@ -374,7 +379,9 @@ async def _retrieve_messages_before_strategy(self, retrieve): async def _retrieve_messages_after_strategy(self, retrieve): """Retrieve messages using after parameter.""" after = self.after.id if self.after else None - data: List[MessagePayload] = await self.logs_from(self.channel.id, retrieve, after=after) + data: list[MessagePayload] = await self.logs_from( + self.channel.id, retrieve, after=after + ) if len(data): if self.limit is not None: self.limit -= retrieve @@ -385,7 +392,9 @@ async def _retrieve_messages_around_strategy(self, retrieve): """Retrieve messages using around parameter.""" if self.around: around = self.around.id if self.around else None - data: List[MessagePayload] = await self.logs_from(self.channel.id, retrieve, around=around) + data: list[MessagePayload] = await self.logs_from( + self.channel.id, retrieve, around=around + ) self.around = None return data return [] @@ -505,7 +514,9 @@ async def _fill(self): if element["action_type"] is None: continue - await self.entries.put(AuditLogEntry(data=element, users=self._users, guild=self.guild)) + await self.entries.put( + AuditLogEntry(data=element, users=self._users, guild=self.guild) + ) class GuildIterator(_AsyncIterator["Guild"]): @@ -526,7 +537,7 @@ class GuildIterator(_AsyncIterator["Guild"]): guilds endpoint. Parameters - ----------- + ---------- bot: :class:`discord.Client` The client to retrieve the guilds from. limit: :class:`int` @@ -597,14 +608,14 @@ async def fill_guilds(self): for element in data: await self.guilds.put(self.create_guild(element)) - async def _retrieve_guilds(self, retrieve) -> List[Guild]: + async def _retrieve_guilds(self, retrieve) -> list[Guild]: """Retrieve guilds and update next parameters.""" raise NotImplementedError async def _retrieve_guilds_before_strategy(self, retrieve): """Retrieve guilds using before parameter.""" before = self.before.id if self.before else None - data: List[GuildPayload] = await self.get_guilds(retrieve, before=before) + data: list[GuildPayload] = await self.get_guilds(retrieve, before=before) if len(data): if self.limit is not None: self.limit -= retrieve @@ -614,7 +625,7 @@ async def _retrieve_guilds_before_strategy(self, retrieve): async def _retrieve_guilds_after_strategy(self, retrieve): """Retrieve guilds using after parameter.""" after = self.after.id if self.after else None - data: List[GuildPayload] = await self.get_guilds(retrieve, after=after) + data: list[GuildPayload] = await self.get_guilds(retrieve, after=after) if len(data): if self.limit is not None: self.limit -= retrieve @@ -736,7 +747,9 @@ def create_ban(self, data): from .guild import BanEntry from .user import User - return BanEntry(reason=data["reason"], user=User(state=self.state, data=data["user"])) + return BanEntry( + reason=data["reason"], user=User(state=self.state, data=data["user"]) + ) class ArchivedThreadIterator(_AsyncIterator["Thread"]): @@ -744,10 +757,10 @@ def __init__( self, channel_id: int, guild: Guild, - limit: Optional[int], + limit: int | None, joined: bool, private: bool, - before: Optional[Union[Snowflake, datetime.datetime]] = None, + before: Snowflake | datetime.datetime | None = None, ): self.channel_id = channel_id self.guild = guild @@ -759,7 +772,7 @@ def __init__( if joined and not private: raise ValueError("Cannot iterate over joined public archived threads") - self.before: Optional[str] + self.before: str | None if before is None: self.before = None elif isinstance(before, datetime.datetime): @@ -811,7 +824,7 @@ async def fill_queue(self) -> None: data = await self.endpoint(self.channel_id, before=self.before, limit=limit) # This stuff is obviously WIP because 'members' is always empty - threads: List[ThreadPayload] = data.get("threads", []) + threads: list[ThreadPayload] = data.get("threads", []) for d in reversed(threads): self.queue.put_nowait(self.create_thread(d)) @@ -836,8 +849,8 @@ def __init__( event: ScheduledEvent, limit: int, with_member: bool = False, - before: Union[datetime.datetime, int] = None, - after: Union[datetime.datetime, int] = None, + before: datetime.datetime | int = None, + after: datetime.datetime | int = None, ): if isinstance(before, datetime.datetime): before = Object(id=time_snowflake(before, high=False)) @@ -853,7 +866,7 @@ def __init__( self.subscribers = asyncio.Queue() self.get_subscribers = self.event._state.http.get_scheduled_event_users - async def next(self) -> Union[User, Member]: + async def next(self) -> User | Member: if self.subscribers.empty(): await self.fill_subs() diff --git a/discord/member.py b/discord/member.py index 9ec2a9f6e2..9766ff73ae 100644 --- a/discord/member.py +++ b/discord/member.py @@ -30,18 +30,7 @@ import itertools import sys from operator import attrgetter -from typing import ( - TYPE_CHECKING, - Any, - Dict, - List, - Literal, - Optional, - Tuple, - Type, - TypeVar, - Union, -) +from typing import TYPE_CHECKING, Any, Literal, TypeVar, Union import discord.abc @@ -82,7 +71,7 @@ class VoiceState: """Represents a Discord user's voice state. Attributes - ------------ + ---------- deaf: :class:`bool` Indicates if the user is currently deafened by the guild. mute: :class:`bool` @@ -135,11 +124,13 @@ class VoiceState: "suppress", ) - def __init__(self, *, data: VoiceStatePayload, channel: Optional[VocalGuildChannel] = None): + def __init__( + self, *, data: VoiceStatePayload, channel: VocalGuildChannel | None = None + ): self.session_id: str = data.get("session_id") self._update(data, channel) - def _update(self, data: VoiceStatePayload, channel: Optional[VocalGuildChannel]): + def _update(self, data: VoiceStatePayload, channel: VocalGuildChannel | None): self.self_mute: bool = data.get("self_mute", False) self.self_deaf: bool = data.get("self_deaf", False) self.self_stream: bool = data.get("self_stream", False) @@ -148,10 +139,10 @@ def _update(self, data: VoiceStatePayload, channel: Optional[VocalGuildChannel]) self.mute: bool = data.get("mute", False) self.deaf: bool = data.get("deaf", False) self.suppress: bool = data.get("suppress", False) - self.requested_to_speak_at: Optional[datetime.datetime] = utils.parse_time( + self.requested_to_speak_at: datetime.datetime | None = utils.parse_time( data.get("request_to_speak_timestamp") ) - self.channel: Optional[VocalGuildChannel] = channel + self.channel: VocalGuildChannel | None = channel def __repr__(self) -> str: attrs = [ @@ -167,7 +158,9 @@ def __repr__(self) -> str: def flatten_user(cls): - for attr, value in itertools.chain(BaseUser.__dict__.items(), User.__dict__.items()): + for attr, value in itertools.chain( + BaseUser.__dict__.items(), User.__dict__.items() + ): # ignore private/special methods if attr.startswith("_"): continue @@ -180,7 +173,9 @@ def flatten_user(cls): # slotted members are implemented as member_descriptors in Type.__dict__ if not hasattr(value, "__annotations__"): getter = attrgetter(f"_user.{attr}") - setattr(cls, attr, property(getter, doc=f"Equivalent to :attr:`User.{attr}`")) + setattr( + cls, attr, property(getter, doc=f"Equivalent to :attr:`User.{attr}`") + ) else: # Technically, this can also use attrgetter # However I'm not sure how I feel about "functions" returning properties @@ -291,29 +286,35 @@ class Member(discord.abc.Messageable, _UserTag): system: bool created_at: datetime.datetime default_avatar: Asset - avatar: Optional[Asset] - dm_channel: Optional[DMChannel] + avatar: Asset | None + dm_channel: DMChannel | None create_dm = User.create_dm - mutual_guilds: List[Guild] + mutual_guilds: list[Guild] public_flags: PublicUserFlags - banner: Optional[Asset] - accent_color: Optional[Colour] - accent_colour: Optional[Colour] - communication_disabled_until: Optional[datetime.datetime] - - def __init__(self, *, data: MemberWithUserPayload, guild: Guild, state: ConnectionState): + banner: Asset | None + accent_color: Colour | None + accent_colour: Colour | None + communication_disabled_until: datetime.datetime | None + + def __init__( + self, *, data: MemberWithUserPayload, guild: Guild, state: ConnectionState + ): self._state: ConnectionState = state self._user: User = state.store_user(data["user"]) self.guild: Guild = guild - self.joined_at: Optional[datetime.datetime] = utils.parse_time(data.get("joined_at")) - self.premium_since: Optional[datetime.datetime] = utils.parse_time(data.get("premium_since")) + self.joined_at: datetime.datetime | None = utils.parse_time( + data.get("joined_at") + ) + self.premium_since: datetime.datetime | None = utils.parse_time( + data.get("premium_since") + ) self._roles: utils.SnowflakeList = utils.SnowflakeList(map(int, data["roles"])) - self._client_status: Dict[Optional[str], str] = {None: "offline"} - self.activities: Tuple[ActivityTypes, ...] = tuple() - self.nick: Optional[str] = data.get("nick", None) + self._client_status: dict[str | None, str] = {None: "offline"} + self.activities: tuple[ActivityTypes, ...] = tuple() + self.nick: str | None = data.get("nick", None) self.pending: bool = data.get("pending", False) - self._avatar: Optional[str] = data.get("avatar") - self.communication_disabled_until: Optional[datetime.datetime] = utils.parse_time( + self._avatar: str | None = data.get("avatar") + self.communication_disabled_until: datetime.datetime | None = utils.parse_time( data.get("communication_disabled_until") ) @@ -336,7 +337,7 @@ def __hash__(self) -> int: return hash(self._user) @classmethod - def _from_message(cls: Type[M], *, message: Message, data: MemberPayload) -> M: + def _from_message(cls: type[M], *, message: Message, data: MemberPayload) -> M: author = message.author data["user"] = author._to_minimal_user_json() # type: ignore return cls(data=data, guild=message.guild, state=message._state) # type: ignore @@ -350,12 +351,12 @@ def _update_from_message(self, data: MemberPayload) -> None: @classmethod def _try_upgrade( - cls: Type[M], + cls: type[M], *, data: UserWithMemberPayload, guild: Guild, state: ConnectionState, - ) -> Union[User, M]: + ) -> User | M: # A User object with a 'member' key try: member_data = data.pop("member") @@ -366,7 +367,7 @@ def _try_upgrade( return cls(data=member_data, guild=guild, state=state) # type: ignore @classmethod - def _copy(cls: Type[M], member: M) -> M: + def _copy(cls: type[M], member: M) -> M: self: M = cls.__new__(cls) # to bypass __init__ self._roles = utils.SnowflakeList(member._roles, is_sorted=True) @@ -406,9 +407,13 @@ def _update(self, data: MemberPayload) -> None: self.premium_since = utils.parse_time(data.get("premium_since")) self._roles = utils.SnowflakeList(map(int, data["roles"])) self._avatar = data.get("avatar") - self.communication_disabled_until = utils.parse_time(data.get("communication_disabled_until")) + self.communication_disabled_until = utils.parse_time( + data.get("communication_disabled_until") + ) - def _presence_update(self, data: PartialPresenceUpdate, user: UserPayload) -> Optional[Tuple[User, User]]: + def _presence_update( + self, data: PartialPresenceUpdate, user: UserPayload + ) -> tuple[User, User] | None: self.activities = tuple(map(create_activity, data["activities"])) self._client_status = { sys.intern(key): sys.intern(value) for key, value in data.get("client_status", {}).items() # type: ignore @@ -419,7 +424,7 @@ def _presence_update(self, data: PartialPresenceUpdate, user: UserPayload) -> Op return self._update_inner_user(user) return None - def _update_inner_user(self, user: UserPayload) -> Optional[Tuple[User, User]]: + def _update_inner_user(self, user: UserPayload) -> tuple[User, User] | None: u = self._user original = (u.name, u._avatar, u.discriminator, u._public_flags) # These keys seem to always be available @@ -504,7 +509,7 @@ def color(self) -> Colour: return self.colour @property - def roles(self) -> List[Role]: + def roles(self) -> list[Role]: """List[:class:`Role`]: A :class:`list` of :class:`Role` that the member belongs to. Note that the first element of this list is always the default '@everyone' role. @@ -549,7 +554,7 @@ def display_avatar(self) -> Asset: return self.guild_avatar or self._user.avatar or self._user.default_avatar @property - def guild_avatar(self) -> Optional[Asset]: + def guild_avatar(self) -> Asset | None: """Optional[:class:`Asset`]: Returns an :class:`Asset` for the guild avatar the member has. If unavailable, ``None`` is returned. @@ -557,10 +562,12 @@ def guild_avatar(self) -> Optional[Asset]: """ if self._avatar is None: return None - return Asset._from_guild_avatar(self._state, self.guild.id, self.id, self._avatar) + return Asset._from_guild_avatar( + self._state, self.guild.id, self.id, self._avatar + ) @property - def activity(self) -> Optional[ActivityTypes]: + def activity(self) -> ActivityTypes | None: """Optional[Union[:class:`BaseActivity`, :class:`Spotify`]]: Returns the primary activity the user is currently doing. Could be ``None`` if no activity is being done. @@ -581,7 +588,7 @@ def mentioned_in(self, message: Message) -> bool: """Checks if the member is mentioned in the specified message. Parameters - ----------- + ---------- message: :class:`Message` The message to check if you're mentioned in. @@ -637,7 +644,7 @@ def guild_permissions(self) -> Permissions: return base @property - def voice(self) -> Optional[VoiceState]: + def voice(self) -> VoiceState | None: """Optional[:class:`VoiceState`]: Returns the member's current voice state.""" return self.guild._voice_state_for(self._user.id) @@ -649,29 +656,32 @@ def timed_out(self) -> bool: """ return ( self.communication_disabled_until is not None - and self.communication_disabled_until > datetime.datetime.now(datetime.timezone.utc) + and self.communication_disabled_until + > datetime.datetime.now(datetime.timezone.utc) ) async def ban( self, *, delete_message_days: Literal[0, 1, 2, 3, 4, 5, 6, 7] = 1, - reason: Optional[str] = None, + reason: str | None = None, ) -> None: """|coro| Bans this member. Equivalent to :meth:`Guild.ban`. """ - await self.guild.ban(self, reason=reason, delete_message_days=delete_message_days) + await self.guild.ban( + self, reason=reason, delete_message_days=delete_message_days + ) - async def unban(self, *, reason: Optional[str] = None) -> None: + async def unban(self, *, reason: str | None = None) -> None: """|coro| Unbans this member. Equivalent to :meth:`Guild.unban`. """ await self.guild.unban(self, reason=reason) - async def kick(self, *, reason: Optional[str] = None) -> None: + async def kick(self, *, reason: str | None = None) -> None: """|coro| Kicks this member. Equivalent to :meth:`Guild.kick`. @@ -681,15 +691,15 @@ async def kick(self, *, reason: Optional[str] = None) -> None: async def edit( self, *, - nick: Optional[str] = MISSING, + nick: str | None = MISSING, mute: bool = MISSING, deafen: bool = MISSING, suppress: bool = MISSING, - roles: List[discord.abc.Snowflake] = MISSING, - voice_channel: Optional[VocalGuildChannel] = MISSING, - reason: Optional[str] = None, - communication_disabled_until: Optional[datetime.datetime] = MISSING, - ) -> Optional[Member]: + roles: list[discord.abc.Snowflake] = MISSING, + voice_channel: VocalGuildChannel | None = MISSING, + reason: str | None = None, + communication_disabled_until: datetime.datetime | None = MISSING, + ) -> Member | None: """|coro| Edits the member's data. @@ -721,7 +731,7 @@ async def edit( The newly member is now optionally returned, if applicable. Parameters - ----------- + ---------- nick: Optional[:class:`str`] The member's new nickname. Use ``None`` to remove the nickname. mute: :class:`bool` @@ -745,23 +755,24 @@ async def edit( from timeout. .. versionadded:: 2.0 - Raises - ------- - Forbidden - You do not have the proper permissions to the action requested. - HTTPException - The operation failed. Returns - -------- + ------- Optional[:class:`.Member`] The newly updated member, if applicable. This is only returned when certain fields are updated. + + Raises + ------ + Forbidden + You do not have the proper permissions to the action requested. + HTTPException + The operation failed. """ http = self._state.http guild_id = self.guild.id me = self._state.self_id == self.id - payload: Dict[str, Any] = {} + payload: dict[str, Any] = {} if nick is not MISSING: nick = nick or "" @@ -789,7 +800,9 @@ async def edit( await http.edit_my_voice_state(guild_id, voice_state_payload) else: if not suppress: - voice_state_payload["request_to_speak_timestamp"] = datetime.datetime.utcnow().isoformat() + voice_state_payload[ + "request_to_speak_timestamp" + ] = datetime.datetime.utcnow().isoformat() await http.edit_voice_state(guild_id, self.id, voice_state_payload) if voice_channel is not MISSING: @@ -800,7 +813,9 @@ async def edit( if communication_disabled_until is not MISSING: if communication_disabled_until is not None: - payload["communication_disabled_until"] = communication_disabled_until.isoformat() + payload[ + "communication_disabled_until" + ] = communication_disabled_until.isoformat() else: payload["communication_disabled_until"] = communication_disabled_until @@ -808,7 +823,9 @@ async def edit( data = await http.edit_member(guild_id, self.id, reason=reason, **payload) return Member(data=data, guild=self.guild, state=self._state) - async def timeout(self, until: Optional[datetime.datetime], *, reason: Optional[str] = None) -> None: + async def timeout( + self, until: datetime.datetime | None, *, reason: str | None = None + ) -> None: """|coro| Applies a timeout to a member in the guild until a set datetime. @@ -816,14 +833,14 @@ async def timeout(self, until: Optional[datetime.datetime], *, reason: Optional[ You must have the :attr:`~Permissions.moderate_members` permission to timeout a member. Parameters - ----------- + ---------- until: :class:`datetime.datetime` The date and time to timeout the member for. If this is ``None`` then the member is removed from timeout. reason: Optional[:class:`str`] The reason for doing this action. Shows up on the audit log. Raises - ------- + ------ Forbidden You do not have permissions to timeout members. HTTPException @@ -831,7 +848,9 @@ async def timeout(self, until: Optional[datetime.datetime], *, reason: Optional[ """ await self.edit(communication_disabled_until=until, reason=reason) - async def timeout_for(self, duration: datetime.timedelta, *, reason: Optional[str] = None) -> None: + async def timeout_for( + self, duration: datetime.timedelta, *, reason: str | None = None + ) -> None: """|coro| Applies a timeout to a member in the guild for a set duration. A shortcut method for :meth:`~.timeout`, and @@ -841,22 +860,24 @@ async def timeout_for(self, duration: datetime.timedelta, *, reason: Optional[st timeout a member. Parameters - ----------- + ---------- duration: :class:`datetime.timedelta` The duration to timeout the member for. reason: Optional[:class:`str`] The reason for doing this action. Shows up on the audit log. Raises - ------- + ------ Forbidden You do not have permissions to timeout members. HTTPException An error occurred doing the request. """ - await self.timeout(datetime.datetime.now(datetime.timezone.utc) + duration, reason=reason) + await self.timeout( + datetime.datetime.now(datetime.timezone.utc) + duration, reason=reason + ) - async def remove_timeout(self, *, reason: Optional[str] = None) -> None: + async def remove_timeout(self, *, reason: str | None = None) -> None: """|coro| Removes the timeout from a member. @@ -867,12 +888,12 @@ async def remove_timeout(self, *, reason: Optional[str] = None) -> None: This is equivalent to calling :meth:`~.timeout` and passing ``None`` to the ``until`` parameter. Parameters - ----------- + ---------- reason: Optional[:class:`str`] The reason for doing this action. Shows up on the audit log. Raises - ------- + ------ Forbidden You do not have permissions to remove the timeout. HTTPException @@ -895,7 +916,7 @@ async def request_to_speak(self) -> None: .. versionadded:: 1.7 Raises - ------- + ------ Forbidden You do not have the proper permissions to the action requested. HTTPException @@ -912,7 +933,9 @@ async def request_to_speak(self) -> None: else: await self._state.http.edit_my_voice_state(self.guild.id, payload) - async def move_to(self, channel: VocalGuildChannel, *, reason: Optional[str] = None) -> None: + async def move_to( + self, channel: VocalGuildChannel, *, reason: str | None = None + ) -> None: """|coro| Moves a member to a new voice channel (they must be connected first). @@ -926,7 +949,7 @@ async def move_to(self, channel: VocalGuildChannel, *, reason: Optional[str] = N Can now pass ``None`` to kick a member from voice. Parameters - ----------- + ---------- channel: Optional[:class:`VoiceChannel`] The new voice channel to move the member to. Pass ``None`` to kick them from voice. @@ -935,7 +958,9 @@ async def move_to(self, channel: VocalGuildChannel, *, reason: Optional[str] = N """ await self.edit(voice_channel=channel, reason=reason) - async def add_roles(self, *roles: Snowflake, reason: Optional[str] = None, atomic: bool = True) -> None: + async def add_roles( + self, *roles: Snowflake, reason: str | None = None, atomic: bool = True + ) -> None: r"""|coro| Gives the member a number of :class:`Role`\s. @@ -965,7 +990,9 @@ async def add_roles(self, *roles: Snowflake, reason: Optional[str] = None, atomi """ if not atomic: - new_roles = utils._unique(Object(id=r.id) for s in (self.roles[1:], roles) for r in s) + new_roles = utils._unique( + Object(id=r.id) for s in (self.roles[1:], roles) for r in s + ) await self.edit(roles=new_roles, reason=reason) else: req = self._state.http.add_role @@ -974,7 +1001,9 @@ async def add_roles(self, *roles: Snowflake, reason: Optional[str] = None, atomi for role in roles: await req(guild_id, user_id, role.id, reason=reason) - async def remove_roles(self, *roles: Snowflake, reason: Optional[str] = None, atomic: bool = True) -> None: + async def remove_roles( + self, *roles: Snowflake, reason: str | None = None, atomic: bool = True + ) -> None: r"""|coro| Removes :class:`Role`\s from this member. @@ -1019,18 +1048,18 @@ async def remove_roles(self, *roles: Snowflake, reason: Optional[str] = None, at for role in roles: await req(guild_id, user_id, role.id, reason=reason) - def get_role(self, role_id: int, /) -> Optional[Role]: + def get_role(self, role_id: int, /) -> Role | None: """Returns a role with the given ID from roles which the member has. .. versionadded:: 2.0 Parameters - ----------- + ---------- role_id: :class:`int` The ID to search for. Returns - -------- + ------- Optional[:class:`Role`] The role or ``None`` if not found in the member's roles. """ diff --git a/discord/mentions.py b/discord/mentions.py index 968b4ae5b0..3323c67856 100644 --- a/discord/mentions.py +++ b/discord/mentions.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, List, Type, TypeVar, Union +from typing import TYPE_CHECKING, Any, TypeVar __all__ = ("AllowedMentions",) @@ -58,7 +58,7 @@ class AllowedMentions: via :meth:`abc.Messageable.send` for more fine-grained control. Attributes - ------------ + ---------- everyone: :class:`bool` Whether to allow everyone and here mentions. Defaults to ``True``. users: Union[:class:`bool`, List[:class:`abc.Snowflake`]] @@ -86,8 +86,8 @@ def __init__( self, *, everyone: bool = default, - users: Union[bool, List[Snowflake]] = default, - roles: Union[bool, List[Snowflake]] = default, + users: bool | list[Snowflake] = default, + roles: bool | list[Snowflake] = default, replied_user: bool = default, ): self.everyone = everyone @@ -96,7 +96,7 @@ def __init__( self.replied_user = replied_user @classmethod - def all(cls: Type[A]) -> A: + def all(cls: type[A]) -> A: """A factory method that returns a :class:`AllowedMentions` with all fields explicitly set to ``True`` .. versionadded:: 1.5 @@ -104,7 +104,7 @@ def all(cls: Type[A]) -> A: return cls(everyone=True, users=True, roles=True, replied_user=True) @classmethod - def none(cls: Type[A]) -> A: + def none(cls: type[A]) -> A: """A factory method that returns a :class:`AllowedMentions` with all fields set to ``False`` .. versionadded:: 1.5 @@ -144,8 +144,12 @@ def merge(self, other: AllowedMentions) -> AllowedMentions: everyone = self.everyone if other.everyone is default else other.everyone users = self.users if other.users is default else other.users roles = self.roles if other.roles is default else other.roles - replied_user = self.replied_user if other.replied_user is default else other.replied_user - return AllowedMentions(everyone=everyone, roles=roles, users=users, replied_user=replied_user) + replied_user = ( + self.replied_user if other.replied_user is default else other.replied_user + ) + return AllowedMentions( + everyone=everyone, roles=roles, users=users, replied_user=replied_user + ) def __repr__(self) -> str: return ( diff --git a/discord/message.py b/discord/message.py index ac2cd2cf1d..e98378a264 100644 --- a/discord/message.py +++ b/discord/message.py @@ -34,12 +34,7 @@ Any, Callable, ClassVar, - Dict, - List, - Optional, Sequence, - Tuple, - Type, TypeVar, Union, overload, @@ -114,7 +109,9 @@ def convert_emoji_reaction(emoji): # No existing emojis have <> in them, so this should be okay. return emoji.strip("<>") - raise InvalidArgument(f"emoji argument must be str, Emoji, or Reaction not {emoji.__class__.__name__}.") + raise InvalidArgument( + f"emoji argument must be str, Emoji, or Reaction not {emoji.__class__.__name__}." + ) class Attachment(Hashable): @@ -142,7 +139,7 @@ class Attachment(Hashable): Attachment can now be cast to :class:`str` and is hashable. Attributes - ------------ + ---------- id: :class:`int` The attachment ID. size: :class:`int` @@ -190,15 +187,15 @@ class Attachment(Hashable): def __init__(self, *, data: AttachmentPayload, state: ConnectionState): self.id: int = int(data["id"]) self.size: int = data["size"] - self.height: Optional[int] = data.get("height") - self.width: Optional[int] = data.get("width") + self.height: int | None = data.get("height") + self.width: int | None = data.get("width") self.filename: str = data["filename"] self.url: str = data.get("url") self.proxy_url: str = data.get("proxy_url") self._http = state.http - self.content_type: Optional[str] = data.get("content_type") + self.content_type: str | None = data.get("content_type") self.ephemeral: bool = data.get("ephemeral", False) - self.description: Optional[str] = data.get("description") + self.description: str | None = data.get("description") def is_spoiler(self) -> bool: """:class:`bool`: Whether this attachment contains a spoiler.""" @@ -212,7 +209,7 @@ def __str__(self) -> str: async def save( self, - fp: Union[io.BufferedIOBase, PathLike], + fp: io.BufferedIOBase | PathLike, *, seek_begin: bool = True, use_cached: bool = False, @@ -222,7 +219,7 @@ async def save( Saves this attachment into a file-like object. Parameters - ----------- + ---------- fp: Union[:class:`io.BufferedIOBase`, :class:`os.PathLike`] The file-like object to save this attachment to or the filename to use. If a filename is passed then a file is created with that @@ -238,17 +235,17 @@ async def save( deleted attachments if too much time has passed, and it does not work on some types of attachments. + Returns + ------- + :class:`int` + The number of bytes written. + Raises - -------- + ------ HTTPException Saving the attachment failed. NotFound The attachment was deleted. - - Returns - -------- - :class:`int` - The number of bytes written. """ data = await self.read(use_cached=use_cached) if isinstance(fp, io.BufferedIOBase): @@ -268,7 +265,7 @@ async def read(self, *, use_cached: bool = False) -> bytes: .. versionadded:: 1.1 Parameters - ----------- + ---------- use_cached: :class:`bool` Whether to use :attr:`proxy_url` rather than :attr:`url` when downloading the attachment. This will allow attachments to be saved after deletion @@ -277,6 +274,11 @@ async def read(self, *, use_cached: bool = False) -> bytes: deleted attachments if too much time has passed, and it does not work on some types of attachments. + Returns + ------- + :class:`bytes` + The contents of the attachment. + Raises ------ HTTPException @@ -285,11 +287,6 @@ async def read(self, *, use_cached: bool = False) -> bytes: You do not have permissions to access this attachment NotFound The attachment was deleted. - - Returns - ------- - :class:`bytes` - The contents of the attachment. """ url = self.proxy_url if use_cached else self.url data = await self._http.get_from_cdn(url) @@ -304,7 +301,7 @@ async def to_file(self, *, use_cached: bool = False, spoiler: bool = False) -> F .. versionadded:: 1.3 Parameters - ----------- + ---------- use_cached: :class:`bool` Whether to use :attr:`proxy_url` rather than :attr:`url` when downloading the attachment. This will allow attachments to be saved after deletion @@ -319,6 +316,11 @@ async def to_file(self, *, use_cached: bool = False, spoiler: bool = False) -> F .. versionadded:: 1.4 + Returns + ------- + :class:`File` + The attachment as a file suitable for sending. + Raises ------ HTTPException @@ -327,11 +329,6 @@ async def to_file(self, *, use_cached: bool = False, spoiler: bool = False) -> F You do not have permissions to access this attachment NotFound The attachment was deleted. - - Returns - ------- - :class:`File` - The attachment as a file suitable for sending. """ data = await self.read(use_cached=use_cached) @@ -392,7 +389,7 @@ def channel_id(self) -> int: return self._parent.channel_id @property - def guild_id(self) -> Optional[int]: + def guild_id(self) -> int | None: """Optional[:class:`int`]: The guild ID of the deleted referenced message.""" return self._parent.guild_id @@ -406,7 +403,7 @@ class MessageReference: This class can now be constructed by users. Attributes - ----------- + ---------- message_id: Optional[:class:`int`] The id of the message referenced. channel_id: :class:`int` @@ -445,18 +442,20 @@ def __init__( *, message_id: int, channel_id: int, - guild_id: Optional[int] = None, + guild_id: int | None = None, fail_if_not_exists: bool = True, ): - self._state: Optional[ConnectionState] = None - self.resolved: Optional[Union[Message, DeletedReferencedMessage]] = None - self.message_id: Optional[int] = message_id + self._state: ConnectionState | None = None + self.resolved: Message | DeletedReferencedMessage | None = None + self.message_id: int | None = message_id self.channel_id: int = channel_id - self.guild_id: Optional[int] = guild_id + self.guild_id: int | None = guild_id self.fail_if_not_exists: bool = fail_if_not_exists @classmethod - def with_state(cls: Type[MR], state: ConnectionState, data: MessageReferencePayload) -> MR: + def with_state( + cls: type[MR], state: ConnectionState, data: MessageReferencePayload + ) -> MR: self = cls.__new__(cls) self.message_id = utils._get_as_snowflake(data, "message_id") self.channel_id = int(data.pop("channel_id")) @@ -467,7 +466,9 @@ def with_state(cls: Type[MR], state: ConnectionState, data: MessageReferencePayl return self @classmethod - def from_message(cls: Type[MR], message: Message, *, fail_if_not_exists: bool = True) -> MR: + def from_message( + cls: type[MR], message: Message, *, fail_if_not_exists: bool = True + ) -> MR: """Creates a :class:`MessageReference` from an existing :class:`~discord.Message`. .. versionadded:: 1.6 @@ -497,7 +498,7 @@ def from_message(cls: Type[MR], message: Message, *, fail_if_not_exists: bool = return self @property - def cached_message(self) -> Optional[Message]: + def cached_message(self) -> Message | None: """Optional[:class:`~discord.Message`]: The cached message, if found in the internal message cache.""" return self._state and self._state._get_message(self.message_id) @@ -511,11 +512,15 @@ def jump_url(self) -> str: return f"https://discord.com/channels/{guild_id}/{self.channel_id}/{self.message_id}" def __repr__(self) -> str: - return (f"") + return ( + f"" + ) def to_dict(self) -> MessageReferencePayload: - result: MessageReferencePayload = {"message_id": self.message_id} if self.message_id is not None else {} + result: MessageReferencePayload = ( + {"message_id": self.message_id} if self.message_id is not None else {} + ) result["channel_id"] = self.channel_id if self.guild_id is not None: result["guild_id"] = self.guild_id @@ -700,13 +705,13 @@ class Message(Hashable): ) if TYPE_CHECKING: - _HANDLERS: ClassVar[List[Tuple[str, Callable[..., None]]]] - _CACHED_SLOTS: ClassVar[List[str]] - guild: Optional[Guild] - reference: Optional[MessageReference] - mentions: List[Union[User, Member]] - author: Union[User, Member] - role_mentions: List[Role] + _HANDLERS: ClassVar[list[tuple[str, Callable[..., None]]]] + _CACHED_SLOTS: ClassVar[list[str]] + guild: Guild | None + reference: MessageReference | None + mentions: list[User | Member] + author: User | Member + role_mentions: list[Role] def __init__( self, @@ -717,23 +722,33 @@ def __init__( ): self._state: ConnectionState = state self.id: int = int(data["id"]) - self.webhook_id: Optional[int] = utils._get_as_snowflake(data, "webhook_id") - self.reactions: List[Reaction] = [Reaction(message=self, data=d) for d in data.get("reactions", [])] - self.attachments: List[Attachment] = [Attachment(data=a, state=self._state) for a in data["attachments"]] - self.embeds: List[Embed] = [Embed.from_dict(a) for a in data["embeds"]] - self.application: Optional[MessageApplicationPayload] = data.get("application") - self.activity: Optional[MessageActivityPayload] = data.get("activity") + self.webhook_id: int | None = utils._get_as_snowflake(data, "webhook_id") + self.reactions: list[Reaction] = [ + Reaction(message=self, data=d) for d in data.get("reactions", []) + ] + self.attachments: list[Attachment] = [ + Attachment(data=a, state=self._state) for a in data["attachments"] + ] + self.embeds: list[Embed] = [Embed.from_dict(a) for a in data["embeds"]] + self.application: MessageApplicationPayload | None = data.get("application") + self.activity: MessageActivityPayload | None = data.get("activity") self.channel: MessageableChannel = channel - self._edited_timestamp: Optional[datetime.datetime] = utils.parse_time(data["edited_timestamp"]) + self._edited_timestamp: datetime.datetime | None = utils.parse_time( + data["edited_timestamp"] + ) self.type: MessageType = try_enum(MessageType, data["type"]) self.pinned: bool = data["pinned"] self.flags: MessageFlags = MessageFlags._from_value(data.get("flags", 0)) self.mention_everyone: bool = data["mention_everyone"] self.tts: bool = data["tts"] self.content: str = data["content"] - self.nonce: Optional[Union[int, str]] = data.get("nonce") - self.stickers: List[StickerItem] = [StickerItem(data=d, state=state) for d in data.get("sticker_items", [])] - self.components: List[Component] = [_component_factory(d) for d in data.get("components", [])] + self.nonce: int | str | None = data.get("nonce") + self.stickers: list[StickerItem] = [ + StickerItem(data=d, state=state) for d in data.get("sticker_items", []) + ] + self.components: list[Component] = [ + _component_factory(d) for d in data.get("components", []) + ] try: # if the channel doesn't have a guild attribute, we handle that @@ -759,22 +774,26 @@ def __init__( if ref.channel_id == channel.id: chan = channel else: - chan, _ = state._get_guild_channel(resolved, guild_id=self.guild.id) + chan, _ = state._get_guild_channel( + resolved, guild_id=self.guild.id + ) # the channel will be the correct type here ref.resolved = self.__class__(channel=chan, data=resolved, state=state) # type: ignore from .interactions import MessageInteraction - self.interaction: Optional[MessageInteraction] + self.interaction: MessageInteraction | None try: self.interaction = MessageInteraction(data=data["interaction"], state=state) except KeyError: self.interaction = None - self.thread: Optional[Thread] + self.thread: Thread | None try: - self.thread = Thread(guild=self.guild, state=self._state, data=data["thread"]) + self.thread = Thread( + guild=self.guild, state=self._state, data=data["thread"] + ) except KeyError: self.thread = None @@ -786,8 +805,10 @@ def __init__( def __repr__(self) -> str: name = self.__class__.__name__ - return (f"<{name} id={self.id} channel={self.channel!r} type={self.type!r}" - f" author={self.author!r} flags={self.flags!r}>") + return ( + f"<{name} id={self.id} channel={self.channel!r} type={self.type!r}" + f" author={self.author!r} flags={self.flags!r}>" + ) def _try_patch(self, data, key, transform=None) -> None: try: @@ -814,7 +835,9 @@ def _add_reaction(self, data, emoji, user_id) -> Reaction: return reaction - def _remove_reaction(self, data: ReactionPayload, emoji: EmojiInputType, user_id: int) -> Reaction: + def _remove_reaction( + self, data: ReactionPayload, emoji: EmojiInputType, user_id: int + ) -> Reaction: reaction = utils.find(lambda r: r.emoji == emoji, self.reactions) if reaction is None: @@ -833,7 +856,7 @@ def _remove_reaction(self, data: ReactionPayload, emoji: EmojiInputType, user_id return reaction - def _clear_emoji(self, emoji) -> Optional[Reaction]: + def _clear_emoji(self, emoji) -> Reaction | None: to_check = str(emoji) for index, reaction in enumerate(self.reactions): if str(reaction.emoji) == to_check: @@ -892,13 +915,13 @@ def _handle_type(self, value: int) -> None: def _handle_content(self, value: str) -> None: self.content = value - def _handle_attachments(self, value: List[AttachmentPayload]) -> None: + def _handle_attachments(self, value: list[AttachmentPayload]) -> None: self.attachments = [Attachment(data=a, state=self._state) for a in value] - def _handle_embeds(self, value: List[EmbedPayload]) -> None: + def _handle_embeds(self, value: list[EmbedPayload]) -> None: self.embeds = [Embed.from_dict(data) for data in value] - def _handle_nonce(self, value: Union[str, int]) -> None: + def _handle_nonce(self, value: str | int) -> None: self.nonce = value def _handle_author(self, author: UserPayload) -> None: @@ -924,7 +947,7 @@ def _handle_member(self, member: MemberPayload) -> None: # TODO: consider adding to cache here self.author = Member._from_message(message=self, data=member) - def _handle_mentions(self, mentions: List[UserWithMemberPayload]) -> None: + def _handle_mentions(self, mentions: list[UserWithMemberPayload]) -> None: self.mentions = r = [] guild = self.guild state = self._state @@ -940,7 +963,7 @@ def _handle_mentions(self, mentions: List[UserWithMemberPayload]) -> None: else: r.append(Member._try_upgrade(data=mention, guild=guild, state=state)) - def _handle_mention_roles(self, role_mentions: List[int]) -> None: + def _handle_mention_roles(self, role_mentions: list[int]) -> None: self.role_mentions = [] if isinstance(self.guild, Guild): for role_id in map(int, role_mentions): @@ -948,15 +971,17 @@ def _handle_mention_roles(self, role_mentions: List[int]) -> None: if role is not None: self.role_mentions.append(role) - def _handle_components(self, components: List[ComponentPayload]): + def _handle_components(self, components: list[ComponentPayload]): self.components = [_component_factory(d) for d in components] - def _rebind_cached_references(self, new_guild: Guild, new_channel: Union[TextChannel, Thread]) -> None: + def _rebind_cached_references( + self, new_guild: Guild, new_channel: TextChannel | Thread + ) -> None: self.guild = new_guild self.channel = new_channel @utils.cached_slot_property("_cs_raw_mentions") - def raw_mentions(self) -> List[int]: + def raw_mentions(self) -> list[int]: """List[:class:`int`]: A property that returns an array of user IDs matched with the syntax of ``<@user_id>`` in the message content. @@ -966,21 +991,21 @@ def raw_mentions(self) -> List[int]: return [int(x) for x in re.findall(r"<@!?([0-9]{15,20})>", self.content)] @utils.cached_slot_property("_cs_raw_channel_mentions") - def raw_channel_mentions(self) -> List[int]: + def raw_channel_mentions(self) -> list[int]: """List[:class:`int`]: A property that returns an array of channel IDs matched with the syntax of ``<#channel_id>`` in the message content. """ return [int(x) for x in re.findall(r"<#([0-9]{15,20})>", self.content)] @utils.cached_slot_property("_cs_raw_role_mentions") - def raw_role_mentions(self) -> List[int]: + def raw_role_mentions(self) -> list[int]: """List[:class:`int`]: A property that returns an array of role IDs matched with the syntax of ``<@&role_id>`` in the message content. """ return [int(x) for x in re.findall(r"<@&([0-9]{15,20})>", self.content)] @utils.cached_slot_property("_cs_channel_mentions") - def channel_mentions(self) -> List[GuildChannel]: + def channel_mentions(self) -> list[GuildChannel]: if self.guild is None: return [] it = filter(None, map(self.guild.get_channel, self.raw_channel_mentions)) @@ -1003,20 +1028,30 @@ def clean_content(self) -> str: respectively, along with this function. """ - transformations = {re.escape(f"<#{channel.id}>"): f"#{channel.name}" for channel in self.channel_mentions} + transformations = { + re.escape(f"<#{channel.id}>"): f"#{channel.name}" + for channel in self.channel_mentions + } - mention_transforms = {re.escape(f"<@{member.id}>"): f"@{member.display_name}" for member in self.mentions} + mention_transforms = { + re.escape(f"<@{member.id}>"): f"@{member.display_name}" + for member in self.mentions + } # add the <@!user_id> cases as well.. second_mention_transforms = { - re.escape(f"<@!{member.id}>"): f"@{member.display_name}" for member in self.mentions + re.escape(f"<@!{member.id}>"): f"@{member.display_name}" + for member in self.mentions } transformations.update(mention_transforms) transformations.update(second_mention_transforms) if self.guild is not None: - role_transforms = {re.escape(f"<@&{role.id}>"): f"@{role.name}" for role in self.role_mentions} + role_transforms = { + re.escape(f"<@&{role.id}>"): f"@{role.name}" + for role in self.role_mentions + } transformations.update(role_transforms) def repl(obj): @@ -1032,7 +1067,7 @@ def created_at(self) -> datetime.datetime: return utils.snowflake_time(self.id) @property - def edited_at(self) -> Optional[datetime.datetime]: + def edited_at(self) -> datetime.datetime | None: """Optional[:class:`datetime.datetime`]: An aware UTC datetime object containing the edited time of the message. """ @@ -1076,7 +1111,9 @@ def system_content(self): if self.channel.type is ChannelType.group: return f"{self.author.name} added {self.mentions[0].name} to the group." else: - return f"{self.author.name} added {self.mentions[0].name} to the thread." + return ( + f"{self.author.name} added {self.mentions[0].name} to the thread." + ) if self.type is MessageType.recipient_remove: if self.channel.type is ChannelType.group: @@ -1123,22 +1160,28 @@ def system_content(self): if not self.content: return f"{self.author.name} just boosted the server! {self.guild} has achieved **Level 1!**" else: - return (f"{self.author.name} just boosted the server **{self.content}** times!" - f" {self.guild} has achieved **Level 1!**") + return ( + f"{self.author.name} just boosted the server **{self.content}** times!" + f" {self.guild} has achieved **Level 1!**" + ) if self.type is MessageType.premium_guild_tier_2: if not self.content: return f"{self.author.name} just boosted the server! {self.guild} has achieved **Level 2!**" else: - return (f"{self.author.name} just boosted the server **{self.content}** times!" - f" {self.guild} has achieved **Level 2!**") + return ( + f"{self.author.name} just boosted the server **{self.content}** times!" + f" {self.guild} has achieved **Level 2!**" + ) if self.type is MessageType.premium_guild_tier_3: if not self.content: return f"{self.author.name} just boosted the server! {self.guild} has achieved **Level 3!**" else: - return (f"{self.author.name} just boosted the server **{self.content}** times!" - f" {self.guild} has achieved **Level 3!**") + return ( + f"{self.author.name} just boosted the server **{self.content}** times!" + f" {self.guild} has achieved **Level 3!**" + ) if self.type is MessageType.channel_follow_add: return f"{self.author.name} has added {self.content} to this channel" @@ -1148,19 +1191,25 @@ def system_content(self): return f"{self.author.name} is live! Now streaming {self.author.activity.name}" # type: ignore if self.type is MessageType.guild_discovery_disqualified: - return ("This server has been removed from Server Discovery because it no longer passes all the" - " requirements. Check Server Settings for more details.") + return ( + "This server has been removed from Server Discovery because it no longer passes all the" + " requirements. Check Server Settings for more details." + ) if self.type is MessageType.guild_discovery_requalified: return "This server is eligible for Server Discovery again and has been automatically relisted!" if self.type is MessageType.guild_discovery_grace_period_initial_warning: - return ("This server has failed Discovery activity requirements for 1 week. If this server fails for" - " 4 weeks in a row, it will be automatically removed from Discovery.") + return ( + "This server has failed Discovery activity requirements for 1 week. If this server fails for" + " 4 weeks in a row, it will be automatically removed from Discovery." + ) if self.type is MessageType.guild_discovery_grace_period_final_warning: - return ("This server has failed Discovery activity requirements for 3 weeks in a row. If this server fails" - " for 1 more week, it will be removed from Discovery.") + return ( + "This server has failed Discovery activity requirements for 3 weeks in a row. If this server fails" + " for 1 more week, it will be removed from Discovery." + ) if self.type is MessageType.thread_created: return f"{self.author.name} started a thread: **{self.content}**. See all **threads**." @@ -1178,7 +1227,9 @@ def system_content(self): if self.type is MessageType.guild_invite_reminder: return "Wondering who to invite?\nStart by inviting anyone who can help you build the server!" - async def delete(self, *, delay: Optional[float] = None, reason: Optional[str] = None) -> None: + async def delete( + self, *, delay: float | None = None, reason: str | None = None + ) -> None: """|coro| Deletes the message. @@ -1191,7 +1242,7 @@ async def delete(self, *, delay: Optional[float] = None, reason: Optional[str] = Added the new ``delay`` keyword-only parameter. Parameters - ----------- + ---------- delay: Optional[:class:`float`] If provided, the number of seconds to wait in the background before deleting the message. If the deletion fails then it is silently ignored. @@ -1207,7 +1258,9 @@ async def delete(self, *, delay: Optional[float] = None, reason: Optional[str] = HTTPException Deleting the message failed. """ - del_func = self._state.http.delete_message(self.channel.id, self.id, reason=reason) + del_func = self._state.http.delete_message( + self.channel.id, self.id, reason=reason + ) if delay is not None: utils.delay_task(delay, del_func) else: @@ -1217,31 +1270,31 @@ async def delete(self, *, delay: Optional[float] = None, reason: Optional[str] = async def edit( self, *, - content: Optional[str] = ..., - embed: Optional[Embed] = ..., - embeds: List[Embed] = ..., - file: Optional[File] = ..., - files: Optional[List[File]] = ..., - attachments: List[Attachment] = ..., + content: str | None = ..., + embed: Embed | None = ..., + embeds: list[Embed] = ..., + file: File | None = ..., + files: list[File] | None = ..., + attachments: list[Attachment] = ..., suppress: bool = ..., - delete_after: Optional[float] = ..., - allowed_mentions: Optional[AllowedMentions] = ..., - view: Optional[View] = ..., + delete_after: float | None = ..., + allowed_mentions: AllowedMentions | None = ..., + view: View | None = ..., ) -> Message: ... async def edit( self, - content: Optional[str] = MISSING, - embed: Optional[Embed] = MISSING, - embeds: List[Embed] = MISSING, + content: str | None = MISSING, + embed: Embed | None = MISSING, + embeds: list[Embed] = MISSING, file: Sequence[File] = MISSING, - files: List[Sequence[File]] = MISSING, - attachments: List[Attachment] = MISSING, + files: list[Sequence[File]] = MISSING, + attachments: list[Attachment] = MISSING, suppress: bool = MISSING, - delete_after: Optional[float] = None, - allowed_mentions: Optional[AllowedMentions] = MISSING, - view: Optional[View] = MISSING, + delete_after: float | None = None, + allowed_mentions: AllowedMentions | None = MISSING, + view: View | None = MISSING, ) -> Message: """|coro| @@ -1253,7 +1306,7 @@ async def edit( The ``suppress`` keyword-only parameter was added. Parameters - ----------- + ---------- content: Optional[:class:`str`] The new content to replace the message with. Could be ``None`` to remove the content. @@ -1295,7 +1348,7 @@ async def edit( the view is removed. Raises - ------- + ------ HTTPException Editing the message failed. Forbidden @@ -1307,11 +1360,13 @@ async def edit( or ``files`` were of the wrong type. """ - payload: Dict[str, Any] = {} + payload: dict[str, Any] = {} if content is not MISSING: payload["content"] = str(content) if content is not None else None if embed is not MISSING and embeds is not MISSING: - raise InvalidArgument("cannot pass both embed and embeds parameter to edit()") + raise InvalidArgument( + "cannot pass both embed and embeds parameter to edit()" + ) if embed is not MISSING: payload["embeds"] = [] if embed is None else [embed.to_dict()] @@ -1324,11 +1379,16 @@ async def edit( payload["flags"] = flags.value if allowed_mentions is MISSING: - if self._state.allowed_mentions is not None and self.author.id == self._state.self_id: + if ( + self._state.allowed_mentions is not None + and self.author.id == self._state.self_id + ): payload["allowed_mentions"] = self._state.allowed_mentions.to_dict() elif allowed_mentions is not None: if self._state.allowed_mentions is not None: - payload["allowed_mentions"] = self._state.allowed_mentions.merge(allowed_mentions).to_dict() + payload["allowed_mentions"] = self._state.allowed_mentions.merge( + allowed_mentions + ).to_dict() else: payload["allowed_mentions"] = allowed_mentions.to_dict() @@ -1348,10 +1408,12 @@ async def edit( files = [file] else: if len(files) > 10: - raise InvalidArgument("files parameter must be a list of up to 10 elements") + raise InvalidArgument( + "files parameter must be a list of up to 10 elements" + ) elif not all(isinstance(file, File) for file in files): raise InvalidArgument("files parameter must be a list of File") - + if "attachments" not in payload: # don't want it to remove any attachments when we just add a new file payload["attachments"] = [a.to_dict() for a in self.attachments] @@ -1366,7 +1428,9 @@ async def edit( for f in files: f.close() else: - data = await self._state.http.edit_message(self.channel.id, self.id, **payload) + data = await self._state.http.edit_message( + self.channel.id, self.id, **payload + ) message = Message(state=self._state, channel=self.channel, data=data) if view and not view.is_finished(): @@ -1388,7 +1452,7 @@ async def publish(self) -> None: permission is also needed. Raises - ------- + ------ Forbidden You do not have the proper permissions to publish this message. HTTPException @@ -1397,7 +1461,7 @@ async def publish(self) -> None: await self._state.http.publish_message(self.channel.id, self.id) - async def pin(self, *, reason: Optional[str] = None) -> None: + async def pin(self, *, reason: str | None = None) -> None: """|coro| Pins the message. @@ -1406,14 +1470,14 @@ async def pin(self, *, reason: Optional[str] = None) -> None: this in a non-private channel context. Parameters - ----------- + ---------- reason: Optional[:class:`str`] The reason for pinning the message. Shows up on the audit log. .. versionadded:: 1.4 Raises - ------- + ------ Forbidden You do not have permissions to pin the message. NotFound @@ -1426,7 +1490,7 @@ async def pin(self, *, reason: Optional[str] = None) -> None: await self._state.http.pin_message(self.channel.id, self.id, reason=reason) self.pinned = True - async def unpin(self, *, reason: Optional[str] = None) -> None: + async def unpin(self, *, reason: str | None = None) -> None: """|coro| Unpins the message. @@ -1435,14 +1499,14 @@ async def unpin(self, *, reason: Optional[str] = None) -> None: this in a non-private channel context. Parameters - ----------- + ---------- reason: Optional[:class:`str`] The reason for unpinning the message. Shows up on the audit log. .. versionadded:: 1.4 Raises - ------- + ------ Forbidden You do not have permissions to unpin the message. NotFound @@ -1466,12 +1530,12 @@ async def add_reaction(self, emoji: EmojiInputType) -> None: emoji, the :attr:`~Permissions.add_reactions` permission is required. Parameters - ------------ + ---------- emoji: Union[:class:`Emoji`, :class:`Reaction`, :class:`PartialEmoji`, :class:`str`] The emoji to react with. Raises - -------- + ------ HTTPException Adding the reaction failed. Forbidden @@ -1485,7 +1549,9 @@ async def add_reaction(self, emoji: EmojiInputType) -> None: emoji = convert_emoji_reaction(emoji) await self._state.http.add_reaction(self.channel.id, self.id, emoji) - async def remove_reaction(self, emoji: Union[EmojiInputType, Reaction], member: Snowflake) -> None: + async def remove_reaction( + self, emoji: EmojiInputType | Reaction, member: Snowflake + ) -> None: """|coro| Remove a reaction by the member from the message. @@ -1499,14 +1565,14 @@ async def remove_reaction(self, emoji: Union[EmojiInputType, Reaction], member: the :class:`abc.Snowflake` abc. Parameters - ------------ + ---------- emoji: Union[:class:`Emoji`, :class:`Reaction`, :class:`PartialEmoji`, :class:`str`] The emoji to remove. member: :class:`abc.Snowflake` The member for which to remove the reaction. Raises - -------- + ------ HTTPException Removing the reaction failed. Forbidden @@ -1522,9 +1588,11 @@ async def remove_reaction(self, emoji: Union[EmojiInputType, Reaction], member: if member.id == self._state.self_id: await self._state.http.remove_own_reaction(self.channel.id, self.id, emoji) else: - await self._state.http.remove_reaction(self.channel.id, self.id, emoji, member.id) + await self._state.http.remove_reaction( + self.channel.id, self.id, emoji, member.id + ) - async def clear_reaction(self, emoji: Union[EmojiInputType, Reaction]) -> None: + async def clear_reaction(self, emoji: EmojiInputType | Reaction) -> None: """|coro| Clears a specific reaction from the message. @@ -1536,12 +1604,12 @@ async def clear_reaction(self, emoji: Union[EmojiInputType, Reaction]) -> None: .. versionadded:: 1.3 Parameters - ----------- + ---------- emoji: Union[:class:`Emoji`, :class:`Reaction`, :class:`PartialEmoji`, :class:`str`] The emoji to clear. Raises - -------- + ------ HTTPException Clearing the reaction failed. Forbidden @@ -1563,7 +1631,7 @@ async def clear_reactions(self) -> None: You need the :attr:`~Permissions.manage_messages` permission to use this. Raises - -------- + ------ HTTPException Removing the reactions failed. Forbidden @@ -1571,7 +1639,9 @@ async def clear_reactions(self) -> None: """ await self._state.http.clear_reactions(self.channel.id, self.id) - async def create_thread(self, *, name: str, auto_archive_duration: ThreadArchiveDuration = MISSING) -> Thread: + async def create_thread( + self, *, name: str, auto_archive_duration: ThreadArchiveDuration = MISSING + ) -> Thread: """|coro| Creates a public thread from this message. @@ -1584,26 +1654,26 @@ async def create_thread(self, *, name: str, auto_archive_duration: ThreadArchive .. versionadded:: 2.0 Parameters - ----------- + ---------- name: :class:`str` The name of the thread. auto_archive_duration: :class:`int` The duration in minutes before a thread is automatically archived for inactivity. If not provided, the channel's default auto archive duration is used. - Raises + Returns ------- + :class:`.Thread` + The created thread. + + Raises + ------ Forbidden You do not have permissions to create a thread. HTTPException Creating the thread failed. InvalidArgument This message does not have guild info attached. - - Returns - -------- - :class:`.Thread` - The created thread. """ if self.guild is None: raise InvalidArgument("This message does not have guild info attached.") @@ -1616,13 +1686,14 @@ async def create_thread(self, *, name: str, auto_archive_duration: ThreadArchive self.channel.id, self.id, name=name, - auto_archive_duration=auto_archive_duration or default_auto_archive_duration, + auto_archive_duration=auto_archive_duration + or default_auto_archive_duration, ) self.thread = Thread(guild=self.guild, state=self._state, data=data) return self.thread - async def reply(self, content: Optional[str] = None, **kwargs) -> Message: + async def reply(self, content: str | None = None, **kwargs) -> Message: """|coro| A shortcut method to :meth:`.abc.Messageable.send` to reply to the @@ -1630,8 +1701,13 @@ async def reply(self, content: Optional[str] = None, **kwargs) -> Message: .. versionadded:: 1.6 + Returns + ------- + :class:`.Message` + The message that was sent. + Raises - -------- + ------ ~discord.HTTPException Sending the message failed. ~discord.Forbidden @@ -1639,11 +1715,6 @@ async def reply(self, content: Optional[str] = None, **kwargs) -> Message: ~discord.InvalidArgument The ``files`` list is not of the appropriate size, or you specified both ``file`` and ``files``. - - Returns - --------- - :class:`.Message` - The message that was sent. """ return await self.channel.send(content, reference=self, **kwargs) @@ -1662,12 +1733,14 @@ def to_reference(self, *, fail_if_not_exists: bool = True) -> MessageReference: .. versionadded:: 1.7 Returns - --------- + ------- :class:`~discord.MessageReference` The reference to this message. """ - return MessageReference.from_message(self, fail_if_not_exists=fail_if_not_exists) + return MessageReference.from_message( + self, fail_if_not_exists=fail_if_not_exists + ) def to_message_reference_dict(self) -> MessageReferencePayload: data: MessageReferencePayload = { @@ -1711,7 +1784,7 @@ class PartialMessage(Hashable): Returns the partial message's hash. Attributes - ----------- + ---------- channel: Union[:class:`TextChannel`, :class:`Thread`, :class:`DMChannel`] The channel associated with this partial message. id: :class:`int` @@ -1743,7 +1816,9 @@ def __init__(self, *, channel: PartialMessageableChannel, id: int): ChannelType.public_thread, ChannelType.private_thread, ): - raise TypeError(f"Expected TextChannel, VoiceChannel, DMChannel or Thread not {type(channel)!r}") + raise TypeError( + f"Expected TextChannel, VoiceChannel, DMChannel or Thread not {type(channel)!r}" + ) self.channel: PartialMessageableChannel = channel self._state: ConnectionState = channel._state @@ -1767,7 +1842,7 @@ def created_at(self) -> datetime.datetime: return utils.snowflake_time(self.id) @utils.cached_slot_property("_cs_guild") - def guild(self) -> Optional[Guild]: + def guild(self) -> Guild | None: """Optional[:class:`Guild`]: The guild that the partial message belongs to, if applicable.""" return getattr(self.channel, "guild", None) @@ -1776,25 +1851,25 @@ async def fetch(self) -> Message: Fetches the partial message to a full :class:`Message`. + Returns + ------- + :class:`Message` + The full message. + Raises - -------- + ------ NotFound The message was not found. Forbidden You do not have the permissions required to get a message. HTTPException Retrieving the message failed. - - Returns - -------- - :class:`Message` - The full message. """ data = await self._state.http.get_message(self.channel.id, self.id) return self._state.create_message(channel=self.channel, data=data) - async def edit(self, **fields: Any) -> Optional[Message]: + async def edit(self, **fields: Any) -> Message | None: """|coro| Edits the message. @@ -1803,7 +1878,7 @@ async def edit(self, **fields: Any) -> Optional[Message]: :class:`discord.Message` is returned instead of ``None`` if an edit took place. Parameters - ----------- + ---------- content: Optional[:class:`str`] The new content to replace the message with. Could be ``None`` to remove the content. @@ -1836,8 +1911,13 @@ async def edit(self, **fields: Any) -> Optional[Message]: .. versionadded:: 2.0 - Raises + Returns ------- + Optional[:class:`Message`] + The message that was edited. + + Raises + ------ NotFound The message was not found. HTTPException @@ -1845,11 +1925,6 @@ async def edit(self, **fields: Any) -> Optional[Message]: Forbidden Tried to suppress a message without permissions or edited a message's content or embed that isn't yours. - - Returns - --------- - Optional[:class:`Message`] - The message that was edited. """ content = fields.pop("content", MISSING) @@ -1878,13 +1953,17 @@ async def edit(self, **fields: Any) -> Optional[Message]: allowed_mentions = fields.get("allowed_mentions", MISSING) if allowed_mentions is not MISSING: if self._state.allowed_mentions is not None: - allowed_mentions = self._state.allowed_mentions.merge(allowed_mentions).to_dict() + allowed_mentions = self._state.allowed_mentions.merge( + allowed_mentions + ).to_dict() else: allowed_mentions = allowed_mentions.to_dict() fields["allowed_mentions"] = allowed_mentions else: fields["allowed_mentions"] = ( - self._state.allowed_mentions.to_dict() if self._state.allowed_mentions else None + self._state.allowed_mentions.to_dict() + if self._state.allowed_mentions + else None ) view = fields.pop("view", MISSING) @@ -1893,7 +1972,9 @@ async def edit(self, **fields: Any) -> Optional[Message]: fields["components"] = view.to_components() if view else [] if fields: - data = await self._state.http.edit_message(self.channel.id, self.id, **fields) + data = await self._state.http.edit_message( + self.channel.id, self.id, **fields + ) if delete_after is not None: await self.delete(delay=delete_after) diff --git a/discord/object.py b/discord/object.py index cdda934165..a189b44d77 100644 --- a/discord/object.py +++ b/discord/object.py @@ -67,7 +67,7 @@ class Object(Hashable): Returns the object's hash. Attributes - ----------- + ---------- id: :class:`int` The ID of the object. """ @@ -76,7 +76,9 @@ def __init__(self, id: SupportsIntCast): try: id = int(id) except ValueError: - raise TypeError(f"id parameter must be convertible to int not {id.__class__!r}") from None + raise TypeError( + f"id parameter must be convertible to int not {id.__class__!r}" + ) from None else: self.id = id diff --git a/discord/oggparse.py b/discord/oggparse.py index 2fd60ccbad..9cc0025bf1 100644 --- a/discord/oggparse.py +++ b/discord/oggparse.py @@ -26,7 +26,7 @@ from __future__ import annotations import struct -from typing import IO, TYPE_CHECKING, ClassVar, Generator, Optional, Tuple +from typing import IO, TYPE_CHECKING, ClassVar, Generator from .errors import DiscordException @@ -40,8 +40,6 @@ class OggError(DiscordException): """An exception that is thrown for Ogg stream parsing errors.""" - pass - # https://tools.ietf.org/html/rfc3533 # https://tools.ietf.org/html/rfc7845 @@ -76,7 +74,7 @@ def __init__(self, stream: IO[bytes]) -> None: except Exception: raise OggError("bad data stream") from None - def iter_packets(self) -> Generator[Tuple[bytes, bool], None, None]: + def iter_packets(self) -> Generator[tuple[bytes, bool], None, None]: packetlen = offset = 0 partial = True @@ -99,7 +97,7 @@ class OggStream: def __init__(self, stream: IO[bytes]) -> None: self.stream: IO[bytes] = stream - def _next_page(self) -> Optional[OggPage]: + def _next_page(self) -> OggPage | None: head = self.stream.read(4) if head == b"OggS": return OggPage(self.stream) diff --git a/discord/opus.py b/discord/opus.py index 80f3373c73..42867e502b 100644 --- a/discord/opus.py +++ b/discord/opus.py @@ -36,16 +36,7 @@ import sys import threading import time -from typing import ( - TYPE_CHECKING, - Any, - Callable, - List, - Literal, - Tuple, - TypedDict, - TypeVar, -) +from typing import TYPE_CHECKING, Any, Callable, Literal, TypedDict, TypeVar from .errors import DiscordException from .sinks import RawData @@ -134,14 +125,14 @@ class DecoderStruct(ctypes.Structure): } -def _err_lt(result: int, func: Callable, args: List) -> int: +def _err_lt(result: int, func: Callable, args: list) -> int: if result < OK: _log.info("error has happened in %s", func.__name__) raise OpusError(result) return result -def _err_ne(result: T, func: Callable, args: List) -> T: +def _err_ne(result: T, func: Callable, args: list) -> T: ret = args[-1]._obj if ret.value != OK: _log.info("error has happened in %s", func.__name__) @@ -154,7 +145,7 @@ def _err_ne(result: T, func: Callable, args: List) -> T: # The second one are the types of arguments it takes. # The third is the result type. # The fourth is the error handler. -exported_functions: List[Tuple[Any, ...]] = [ +exported_functions: list[tuple[Any, ...]] = [ # Generic ("opus_get_version_string", None, ctypes.c_char_p, None), ("opus_strerror", [ctypes.c_int], ctypes.c_char_p, None), @@ -355,8 +346,6 @@ def __init__(self, code: int): class OpusNotLoaded(DiscordException): """An exception that is thrown for when libopus is not loaded.""" - pass - class _OpusStruct: SAMPLING_RATE = 48000 @@ -395,7 +384,9 @@ def __del__(self) -> None: def _create_state(self) -> EncoderStruct: ret = ctypes.c_int() - return _lib.opus_encoder_create(self.SAMPLING_RATE, self.CHANNELS, self.application, ctypes.byref(ret)) + return _lib.opus_encoder_create( + self.SAMPLING_RATE, self.CHANNELS, self.application, ctypes.byref(ret) + ) def set_bitrate(self, kbps: int) -> int: kbps = min(512, max(16, int(kbps))) @@ -405,14 +396,18 @@ def set_bitrate(self, kbps: int) -> int: def set_bandwidth(self, req: BAND_CTL) -> None: if req not in band_ctl: - raise KeyError(f'{req!r} is not a valid bandwidth setting. Try one of: {",".join(band_ctl)}') + raise KeyError( + f'{req!r} is not a valid bandwidth setting. Try one of: {",".join(band_ctl)}' + ) k = band_ctl[req] _lib.opus_encoder_ctl(self._state, CTL_SET_BANDWIDTH, k) def set_signal_type(self, req: SIGNAL_CTL) -> None: if req not in signal_ctl: - raise KeyError(f'{req!r} is not a valid bandwidth setting. Try one of: {",".join(signal_ctl)}') + raise KeyError( + f'{req!r} is not a valid bandwidth setting. Try one of: {",".join(signal_ctl)}' + ) k = signal_ctl[req] _lib.opus_encoder_ctl(self._state, CTL_SET_SIGNAL, k) @@ -448,7 +443,9 @@ def __del__(self): def _create_state(self): ret = ctypes.c_int() - return _lib.opus_decoder_create(self.SAMPLING_RATE, self.CHANNELS, ctypes.byref(ret)) + return _lib.opus_decoder_create( + self.SAMPLING_RATE, self.CHANNELS, ctypes.byref(ret) + ) @staticmethod def packet_get_nb_frames(data): @@ -506,10 +503,15 @@ def decode(self, data, *, fec=False): samples_per_frame = self.packet_get_samples_per_frame(data) frame_size = frames * samples_per_frame - pcm = (ctypes.c_int16 * (frame_size * channel_count * ctypes.sizeof(ctypes.c_int16)))() + pcm = ( + ctypes.c_int16 + * (frame_size * channel_count * ctypes.sizeof(ctypes.c_int16)) + )() pcm_ptr = ctypes.cast(pcm, c_int16_ptr) - ret = _lib.opus_decode(self._state, data, len(data) if data else 0, pcm_ptr, frame_size, fec) + ret = _lib.opus_decode( + self._state, data, len(data) if data else 0, pcm_ptr, frame_size, fec + ) return array.array("h", pcm[: ret * channel_count]).tobytes() @@ -542,7 +544,9 @@ def run(self): if data.decrypted_data is None: continue else: - data.decoded_data = self.get_decoder(data.ssrc).decode(data.decrypted_data) + data.decoded_data = self.get_decoder(data.ssrc).decode( + data.decrypted_data + ) except OpusError: print("Error occurred while decoding opus frame.") continue diff --git a/discord/partial_emoji.py b/discord/partial_emoji.py index 18f6b06673..32fd013e97 100644 --- a/discord/partial_emoji.py +++ b/discord/partial_emoji.py @@ -26,7 +26,7 @@ from __future__ import annotations import re -from typing import TYPE_CHECKING, Any, Dict, Optional, Type, TypeVar, Union +from typing import TYPE_CHECKING, Any, TypeVar from . import utils from .asset import Asset, AssetMixin @@ -80,7 +80,7 @@ class PartialEmoji(_EmojiTag, AssetMixin): Returns the emoji rendered for discord. Attributes - ----------- + ---------- name: Optional[:class:`str`] The custom emoji name, if applicable, or the unicode codepoint of the non-custom emoji. This can be ``None`` if the emoji @@ -93,19 +93,21 @@ class PartialEmoji(_EmojiTag, AssetMixin): __slots__ = ("animated", "name", "id", "_state") - _CUSTOM_EMOJI_RE = re.compile(r"a)?:?(?P\w+):(?P[0-9]{13,20})>?") + _CUSTOM_EMOJI_RE = re.compile( + r"a)?:?(?P\w+):(?P[0-9]{13,20})>?" + ) if TYPE_CHECKING: - id: Optional[int] + id: int | None - def __init__(self, *, name: str, animated: bool = False, id: Optional[int] = None): + def __init__(self, *, name: str, animated: bool = False, id: int | None = None): self.animated = animated self.name = name self.id = id - self._state: Optional[ConnectionState] = None + self._state: ConnectionState | None = None @classmethod - def from_dict(cls: Type[PE], data: Union[PartialEmojiPayload, Dict[str, Any]]) -> PE: + def from_dict(cls: type[PE], data: PartialEmojiPayload | dict[str, Any]) -> PE: return cls( animated=data.get("animated", False), id=utils._get_as_snowflake(data, "id"), @@ -113,7 +115,7 @@ def from_dict(cls: Type[PE], data: Union[PartialEmojiPayload, Dict[str, Any]]) - ) @classmethod - def from_str(cls: Type[PE], value: str) -> PE: + def from_str(cls: type[PE], value: str) -> PE: """Converts a Discord string representation of an emoji to a :class:`PartialEmoji`. The formats accepted are: @@ -128,12 +130,12 @@ def from_str(cls: Type[PE], value: str) -> PE: .. versionadded:: 2.0 Parameters - ------------ + ---------- value: :class:`str` The string representation of an emoji. Returns - -------- + ------- :class:`PartialEmoji` The partial emoji from this string. """ @@ -147,8 +149,8 @@ def from_str(cls: Type[PE], value: str) -> PE: return cls(name=value, id=None, animated=False) - def to_dict(self) -> Dict[str, Any]: - o: Dict[str, Any] = {"name": self.name} + def to_dict(self) -> dict[str, Any]: + o: dict[str, Any] = {"name": self.name} if self.id: o["id"] = self.id if self.animated: @@ -160,12 +162,12 @@ def _to_partial(self) -> PartialEmoji: @classmethod def with_state( - cls: Type[PE], + cls: type[PE], state: ConnectionState, *, name: str, animated: bool = False, - id: Optional[int] = None, + id: int | None = None, ) -> PE: self = cls(name=name, animated=animated, id=id) self._state = state @@ -209,7 +211,7 @@ def _as_reaction(self) -> str: return f"{self.name}:{self.id}" @property - def created_at(self) -> Optional[datetime]: + def created_at(self) -> datetime | None: """Optional[:class:`datetime.datetime`]: Returns the emoji's creation time in UTC, or None if Unicode emoji. .. versionadded:: 1.6 diff --git a/discord/permissions.py b/discord/permissions.py index 9cd0f834eb..1753235a6a 100644 --- a/discord/permissions.py +++ b/discord/permissions.py @@ -25,19 +25,7 @@ from __future__ import annotations -from typing import ( - TYPE_CHECKING, - Any, - Callable, - ClassVar, - Dict, - Iterator, - Optional, - Set, - Tuple, - Type, - TypeVar, -) +from typing import TYPE_CHECKING, Any, Callable, ClassVar, Iterator, TypeVar from .flags import BaseFlags, alias_flag_value, fill_with_flags, flag_value @@ -125,7 +113,7 @@ class Permissions(BaseFlags): Note that aliases are not shown. Attributes - ----------- + ---------- value: :class:`int` The raw value. This value is a bit array field of a 53-bit integer representing the currently available permissions. You should query @@ -136,7 +124,9 @@ class Permissions(BaseFlags): def __init__(self, permissions: int = 0, **kwargs: bool): if not isinstance(permissions, int): - raise TypeError(f"Expected int parameter, received {permissions.__class__.__name__} instead.") + raise TypeError( + f"Expected int parameter, received {permissions.__class__.__name__} instead." + ) self.value = permissions for key, value in kwargs.items(): @@ -149,14 +139,18 @@ def is_subset(self, other: Permissions) -> bool: if isinstance(other, Permissions): return (self.value & other.value) == self.value else: - raise TypeError(f"cannot compare {self.__class__.__name__} with {other.__class__.__name__}") + raise TypeError( + f"cannot compare {self.__class__.__name__} with {other.__class__.__name__}" + ) def is_superset(self, other: Permissions) -> bool: """Returns ``True`` if self has the same or more permissions as other.""" if isinstance(other, Permissions): return (self.value | other.value) == self.value else: - raise TypeError(f"cannot compare {self.__class__.__name__} with {other.__class__.__name__}") + raise TypeError( + f"cannot compare {self.__class__.__name__} with {other.__class__.__name__}" + ) def is_strict_subset(self, other: Permissions) -> bool: """Returns ``True`` if the permissions on other are a strict subset of those on self.""" @@ -172,20 +166,21 @@ def is_strict_superset(self, other: Permissions) -> bool: __gt__: Callable[[Permissions], bool] = is_strict_superset @classmethod - def none(cls: Type[P]) -> P: + def none(cls: type[P]) -> P: """A factory method that creates a :class:`Permissions` with all - permissions set to ``False``.""" + permissions set to ``False``. + """ return cls(0) @classmethod - def all(cls: Type[P]) -> P: + def all(cls: type[P]) -> P: """A factory method that creates a :class:`Permissions` with all permissions set to ``True``. """ return cls(0b11111111111111111111111111111111111111111) @classmethod - def all_channel(cls: Type[P]) -> P: + def all_channel(cls: type[P]) -> P: """A :class:`Permissions` with all channel-specific permissions set to ``True`` and the guild-specific ones set to ``False``. The guild-specific permissions are currently: @@ -211,7 +206,7 @@ def all_channel(cls: Type[P]) -> P: return cls(0b111110110110011111101111111111101010001) @classmethod - def general(cls: Type[P]) -> P: + def general(cls: type[P]) -> P: """A factory method that creates a :class:`Permissions` with all "General" permissions from the official Discord UI set to ``True``. @@ -224,7 +219,7 @@ def general(cls: Type[P]) -> P: return cls(0b01110000000010000000010010110000) @classmethod - def membership(cls: Type[P]) -> P: + def membership(cls: type[P]) -> P: """A factory method that creates a :class:`Permissions` with all "Membership" permissions from the official Discord UI set to ``True``. @@ -233,7 +228,7 @@ def membership(cls: Type[P]) -> P: return cls(0b00001100000000000000000000000111) @classmethod - def text(cls: Type[P]) -> P: + def text(cls: type[P]) -> P: """A factory method that creates a :class:`Permissions` with all "Text" permissions from the official Discord UI set to ``True``. @@ -248,13 +243,14 @@ def text(cls: Type[P]) -> P: return cls(0b111110010000000000001111111100001000000) @classmethod - def voice(cls: Type[P]) -> P: + def voice(cls: type[P]) -> P: """A factory method that creates a :class:`Permissions` with all - "Voice" permissions from the official Discord UI set to ``True``.""" + "Voice" permissions from the official Discord UI set to ``True``. + """ return cls(0b00000011111100000000001100000000) @classmethod - def stage(cls: Type[P]) -> P: + def stage(cls: type[P]) -> P: """A factory method that creates a :class:`Permissions` with all "Stage Channel" permissions from the official Discord UI set to ``True``. @@ -263,7 +259,7 @@ def stage(cls: Type[P]) -> P: return cls(1 << 32) @classmethod - def stage_moderator(cls: Type[P]) -> P: + def stage_moderator(cls: type[P]) -> P: """A factory method that creates a :class:`Permissions` with all "Stage Moderator" permissions from the official Discord UI set to ``True``. @@ -272,7 +268,7 @@ def stage_moderator(cls: Type[P]) -> P: return cls(0b100000001010000000000000000000000) @classmethod - def advanced(cls: Type[P]) -> P: + def advanced(cls: type[P]) -> P: """A factory method that creates a :class:`Permissions` with all "Advanced" permissions from the official Discord UI set to ``True``. @@ -338,7 +334,8 @@ def administrator(self) -> int: def manage_channels(self) -> int: """:class:`bool`: Returns ``True`` if a user can edit, delete, or create channels in the guild. - This also corresponds to the "Manage Channel" channel-specific override.""" + This also corresponds to the "Manage Channel" channel-specific override. + """ return 1 << 4 @flag_value @@ -678,58 +675,58 @@ class PermissionOverwrite: __slots__ = ("_values",) if TYPE_CHECKING: - VALID_NAMES: ClassVar[Set[str]] - PURE_FLAGS: ClassVar[Set[str]] + VALID_NAMES: ClassVar[set[str]] + PURE_FLAGS: ClassVar[set[str]] # I wish I didn't have to do this - create_instant_invite: Optional[bool] - kick_members: Optional[bool] - ban_members: Optional[bool] - administrator: Optional[bool] - manage_channels: Optional[bool] - manage_guild: Optional[bool] - add_reactions: Optional[bool] - view_audit_log: Optional[bool] - priority_speaker: Optional[bool] - stream: Optional[bool] - read_messages: Optional[bool] - view_channel: Optional[bool] - send_messages: Optional[bool] - send_tts_messages: Optional[bool] - manage_messages: Optional[bool] - embed_links: Optional[bool] - attach_files: Optional[bool] - read_message_history: Optional[bool] - mention_everyone: Optional[bool] - external_emojis: Optional[bool] - use_external_emojis: Optional[bool] - view_guild_insights: Optional[bool] - connect: Optional[bool] - speak: Optional[bool] - mute_members: Optional[bool] - deafen_members: Optional[bool] - move_members: Optional[bool] - use_voice_activation: Optional[bool] - change_nickname: Optional[bool] - manage_nicknames: Optional[bool] - manage_roles: Optional[bool] - manage_permissions: Optional[bool] - manage_webhooks: Optional[bool] - manage_emojis: Optional[bool] - manage_emojis_and_stickers: Optional[bool] - use_slash_commands: Optional[bool] - request_to_speak: Optional[bool] - manage_events: Optional[bool] - manage_threads: Optional[bool] - create_public_threads: Optional[bool] - create_private_threads: Optional[bool] - send_messages_in_threads: Optional[bool] - external_stickers: Optional[bool] - use_external_stickers: Optional[bool] - start_embedded_activities: Optional[bool] - moderate_members: Optional[bool] - - def __init__(self, **kwargs: Optional[bool]): - self._values: Dict[str, Optional[bool]] = {} + create_instant_invite: bool | None + kick_members: bool | None + ban_members: bool | None + administrator: bool | None + manage_channels: bool | None + manage_guild: bool | None + add_reactions: bool | None + view_audit_log: bool | None + priority_speaker: bool | None + stream: bool | None + read_messages: bool | None + view_channel: bool | None + send_messages: bool | None + send_tts_messages: bool | None + manage_messages: bool | None + embed_links: bool | None + attach_files: bool | None + read_message_history: bool | None + mention_everyone: bool | None + external_emojis: bool | None + use_external_emojis: bool | None + view_guild_insights: bool | None + connect: bool | None + speak: bool | None + mute_members: bool | None + deafen_members: bool | None + move_members: bool | None + use_voice_activation: bool | None + change_nickname: bool | None + manage_nicknames: bool | None + manage_roles: bool | None + manage_permissions: bool | None + manage_webhooks: bool | None + manage_emojis: bool | None + manage_emojis_and_stickers: bool | None + use_slash_commands: bool | None + request_to_speak: bool | None + manage_events: bool | None + manage_threads: bool | None + create_public_threads: bool | None + create_private_threads: bool | None + send_messages_in_threads: bool | None + external_stickers: bool | None + use_external_stickers: bool | None + start_embedded_activities: bool | None + moderate_members: bool | None + + def __init__(self, **kwargs: bool | None): + self._values: dict[str, bool | None] = {} for key, value in kwargs.items(): if key not in self.VALID_NAMES: @@ -740,16 +737,18 @@ def __init__(self, **kwargs: Optional[bool]): def __eq__(self, other: Any) -> bool: return isinstance(other, PermissionOverwrite) and self._values == other._values - def _set(self, key: str, value: Optional[bool]) -> None: + def _set(self, key: str, value: bool | None) -> None: if value not in (True, None, False): - raise TypeError(f"Expected bool or NoneType, received {value.__class__.__name__}") + raise TypeError( + f"Expected bool or NoneType, received {value.__class__.__name__}" + ) if value is None: self._values.pop(key, None) else: self._values[key] = value - def pair(self) -> Tuple[Permissions, Permissions]: + def pair(self) -> tuple[Permissions, Permissions]: """Tuple[:class:`Permissions`, :class:`Permissions`]: Returns the (allow, deny) pair from this overwrite.""" allow = Permissions.none() @@ -764,7 +763,7 @@ def pair(self) -> Tuple[Permissions, Permissions]: return allow, deny @classmethod - def from_pair(cls: Type[PO], allow: Permissions, deny: Permissions) -> PO: + def from_pair(cls: type[PO], allow: Permissions, deny: Permissions) -> PO: """Creates an overwrite from an allow/deny pair of :class:`Permissions`.""" ret = cls() for key, value in allow: @@ -808,6 +807,6 @@ def update(self, **kwargs: bool) -> None: setattr(self, key, value) - def __iter__(self) -> Iterator[Tuple[str, Optional[bool]]]: + def __iter__(self) -> Iterator[tuple[str, bool | None]]: for key in self.PURE_FLAGS: yield key, self._values.get(key) diff --git a/discord/player.py b/discord/player.py index ff1c8e8c83..9ba376616c 100644 --- a/discord/player.py +++ b/discord/player.py @@ -36,18 +36,7 @@ import threading import time import traceback -from typing import ( - IO, - TYPE_CHECKING, - Any, - Callable, - Generic, - Optional, - Tuple, - Type, - TypeVar, - Union, -) +from typing import IO, TYPE_CHECKING, Any, Callable, Generic, TypeVar from .errors import ClientException from .oggparse import OggStream @@ -105,7 +94,7 @@ def read(self) -> bytes: per frame (20ms worth of audio). Returns - -------- + ------- :class:`bytes` A bytes like object that represents the PCM or Opus data. """ @@ -121,7 +110,6 @@ def cleanup(self) -> None: Useful for clearing buffer data or processes after it is done playing audio. """ - pass def __del__(self) -> None: self.cleanup() @@ -131,7 +119,7 @@ class PCMAudio(AudioSource): """Represents raw 16-bit 48KHz stereo PCM audio source. Attributes - ----------- + ---------- stream: :term:`py:file object` A file-like object that reads byte data representing raw PCM. """ @@ -157,7 +145,7 @@ class FFmpegAudio(AudioSource): def __init__( self, - source: Union[str, io.BufferedIOBase], + source: str | io.BufferedIOBase, *, executable: str = "ffmpeg", args: Any, @@ -165,7 +153,9 @@ def __init__( ): piping = subprocess_kwargs.get("stdin") == subprocess.PIPE if piping and isinstance(source, str): - raise TypeError("parameter conflict: 'source' parameter cannot be a string when piping to stdin") + raise TypeError( + "parameter conflict: 'source' parameter cannot be a string when piping to stdin" + ) args = [executable, *args] kwargs = {"stdout": subprocess.PIPE} @@ -173,23 +163,29 @@ def __init__( self._process: subprocess.Popen = self._spawn_process(args, **kwargs) self._stdout: IO[bytes] = self._process.stdout # type: ignore - self._stdin: Optional[IO[bytes]] = None - self._pipe_thread: Optional[threading.Thread] = None + self._stdin: IO[bytes] | None = None + self._pipe_thread: threading.Thread | None = None if piping: n = f"popen-stdin-writer:{id(self):#x}" self._stdin = self._process.stdin - self._pipe_thread = threading.Thread(target=self._pipe_writer, args=(source,), daemon=True, name=n) + self._pipe_thread = threading.Thread( + target=self._pipe_writer, args=(source,), daemon=True, name=n + ) self._pipe_thread.start() def _spawn_process(self, args: Any, **subprocess_kwargs: Any) -> subprocess.Popen: try: - process = subprocess.Popen(args, creationflags=CREATE_NO_WINDOW, **subprocess_kwargs) + process = subprocess.Popen( + args, creationflags=CREATE_NO_WINDOW, **subprocess_kwargs + ) except FileNotFoundError: executable = args.partition(" ")[0] if isinstance(args, str) else args[0] raise ClientException(f"{executable} was not found.") from None except subprocess.SubprocessError as exc: - raise ClientException(f"Popen failed: {exc.__class__.__name__}: {exc}") from exc + raise ClientException( + f"Popen failed: {exc.__class__.__name__}: {exc}" + ) from exc else: return process @@ -203,7 +199,9 @@ def _kill_process(self) -> None: try: proc.kill() except Exception: - _log.exception("Ignoring error attempting to kill ffmpeg process %s", proc.pid) + _log.exception( + "Ignoring error attempting to kill ffmpeg process %s", proc.pid + ) if proc.poll() is None: _log.info( @@ -258,7 +256,7 @@ class FFmpegPCMAudio(FFmpegAudio): variable in order for this to work. Parameters - ------------ + ---------- source: Union[:class:`str`, :class:`io.BufferedIOBase`] The input that ffmpeg will take and convert to PCM bytes. If ``pipe`` is ``True`` then this is a file-like object that is @@ -277,20 +275,20 @@ class FFmpegPCMAudio(FFmpegAudio): Extra command line arguments to pass to ffmpeg after the ``-i`` flag. Raises - -------- + ------ ClientException The subprocess failed to be created. """ def __init__( self, - source: Union[str, io.BufferedIOBase], + source: str | io.BufferedIOBase, *, executable: str = "ffmpeg", pipe: bool = False, - stderr: Optional[IO[str]] = None, - before_options: Optional[str] = None, - options: Optional[str] = None, + stderr: IO[str] | None = None, + before_options: str | None = None, + options: str | None = None, ) -> None: args = [] subprocess_kwargs = { @@ -344,7 +342,7 @@ class FFmpegOpusAudio(FFmpegAudio): variable in order for this to work. Parameters - ------------ + ---------- source: Union[:class:`str`, :class:`io.BufferedIOBase`] The input that ffmpeg will take and convert to Opus bytes. If ``pipe`` is ``True`` then this is a file-like object that is @@ -378,17 +376,17 @@ class FFmpegOpusAudio(FFmpegAudio): Extra command line arguments to pass to ffmpeg after the ``-i`` flag. Raises - -------- + ------ ClientException The subprocess failed to be created. """ def __init__( self, - source: Union[str, io.BufferedIOBase], + source: str | io.BufferedIOBase, *, bitrate: int = 128, - codec: Optional[str] = None, + codec: str | None = None, executable: str = "ffmpeg", pipe=False, stderr=None, @@ -439,10 +437,10 @@ def __init__( @classmethod async def from_probe( - cls: Type[FT], + cls: type[FT], source: str, *, - method: Optional[Union[str, Callable[[str, str], Tuple[Optional[str], Optional[int]]]]] = None, + method: str | Callable[[str, str], tuple[str | None, int | None]] | None = None, **kwargs: Any, ) -> FT: """|coro| @@ -450,8 +448,34 @@ async def from_probe( A factory method that creates a :class:`FFmpegOpusAudio` after probing the input source for audio codec and bitrate information. - Examples + Parameters ---------- + source + Identical to the ``source`` parameter for the constructor. + method: Optional[Union[:class:`str`, Callable[:class:`str`, :class:`str`]]] + The probing method used to determine bitrate and codec information. As a string, valid + values are ``native`` to use ffprobe (or avprobe) and ``fallback`` to use ffmpeg + (or avconv). As a callable, it must take two string arguments, ``source`` and + ``executable``. Both parameters are the same values passed to this factory function. + ``executable`` will default to ``ffmpeg`` if not provided as a keyword argument. + kwargs + The remaining parameters to be passed to the :class:`FFmpegOpusAudio` constructor, + excluding ``bitrate`` and ``codec``. + + Returns + ------- + :class:`FFmpegOpusAudio` + An instance of this class. + + Raises + ------ + AttributeError + Invalid probe method, must be ``'native'`` or ``'fallback'``. + TypeError + Invalid value for ``probe`` parameter, must be :class:`str` or a callable. + + Examples + -------- Use this function to create an :class:`FFmpegOpusAudio` instance instead of the constructor: :: @@ -472,32 +496,6 @@ def custom_probe(source, executable): source = await discord.FFmpegOpusAudio.from_probe("song.webm", method=custom_probe) voice_client.play(source) - - Parameters - ------------ - source - Identical to the ``source`` parameter for the constructor. - method: Optional[Union[:class:`str`, Callable[:class:`str`, :class:`str`]]] - The probing method used to determine bitrate and codec information. As a string, valid - values are ``native`` to use ffprobe (or avprobe) and ``fallback`` to use ffmpeg - (or avconv). As a callable, it must take two string arguments, ``source`` and - ``executable``. Both parameters are the same values passed to this factory function. - ``executable`` will default to ``ffmpeg`` if not provided as a keyword argument. - kwargs - The remaining parameters to be passed to the :class:`FFmpegOpusAudio` constructor, - excluding ``bitrate`` and ``codec``. - - Raises - -------- - AttributeError - Invalid probe method, must be ``'native'`` or ``'fallback'``. - TypeError - Invalid value for ``probe`` parameter, must be :class:`str` or a callable. - - Returns - -------- - :class:`FFmpegOpusAudio` - An instance of this class. """ executable = kwargs.get("executable") @@ -509,15 +507,15 @@ async def probe( cls, source: str, *, - method: Optional[Union[str, Callable[[str, str], Tuple[Optional[str], Optional[int]]]]] = None, - executable: Optional[str] = None, - ) -> Tuple[Optional[str], Optional[int]]: + method: str | Callable[[str, str], tuple[str | None, int | None]] | None = None, + executable: str | None = None, + ) -> tuple[str | None, int | None]: """|coro| Probes the input source for bitrate and codec information. Parameters - ------------ + ---------- source Identical to the ``source`` parameter for :class:`FFmpegOpusAudio`. method @@ -525,17 +523,17 @@ async def probe( executable: :class:`str` Identical to the ``executable`` parameter for :class:`FFmpegOpusAudio`. + Returns + ------- + Optional[Tuple[Optional[:class:`str`], Optional[:class:`int`]]] + A 2-tuple with the codec and bitrate of the input source. + Raises - -------- + ------ AttributeError Invalid probe method, must be ``'native'`` or ``'fallback'``. TypeError Invalid value for ``probe`` parameter, must be :class:`str` or a callable. - - Returns - --------- - Optional[Tuple[Optional[:class:`str`], Optional[:class:`int`]]] - A 2-tuple with the codec and bitrate of the input source. """ method = method or "native" @@ -554,7 +552,10 @@ async def probe( probefunc = method fallback = cls._probe_codec_fallback else: - raise TypeError("Expected str or callable for parameter 'probe', " f"not '{method.__class__.__name__}'") + raise TypeError( + "Expected str or callable for parameter 'probe', " + f"not '{method.__class__.__name__}'" + ) codec = bitrate = None loop = asyncio.get_event_loop() @@ -565,7 +566,9 @@ async def probe( _log.exception("Probe '%s' using '%s' failed", method, executable) return # type: ignore - _log.exception("Probe '%s' using '%s' failed, trying fallback", method, executable) + _log.exception( + "Probe '%s' using '%s' failed, trying fallback", method, executable + ) try: codec, bitrate = await loop.run_in_executor(None, lambda: fallback(source, executable)) # type: ignore except Exception: @@ -578,8 +581,14 @@ async def probe( return codec, bitrate @staticmethod - def _probe_codec_native(source, executable: str = "ffmpeg") -> Tuple[Optional[str], Optional[int]]: - exe = f"{executable[:2]}probe" if executable in {"ffmpeg", "avconv"} else executable + def _probe_codec_native( + source, executable: str = "ffmpeg" + ) -> tuple[str | None, int | None]: + exe = ( + f"{executable[:2]}probe" + if executable in {"ffmpeg", "avconv"} + else executable + ) args = [ exe, @@ -606,7 +615,9 @@ def _probe_codec_native(source, executable: str = "ffmpeg") -> Tuple[Optional[st return codec, bitrate @staticmethod - def _probe_codec_fallback(source, executable: str = "ffmpeg") -> Tuple[Optional[str], Optional[int]]: + def _probe_codec_fallback( + source, executable: str = "ffmpeg" + ) -> tuple[str | None, int | None]: args = [executable, "-hide_banner", "-i", source] proc = subprocess.Popen( args, @@ -642,7 +653,7 @@ class PCMVolumeTransformer(AudioSource, Generic[AT]): set to ``True``. Parameters - ------------ + ---------- original: :class:`AudioSource` The original AudioSource to transform. volume: :class:`float` @@ -650,7 +661,7 @@ class PCMVolumeTransformer(AudioSource, Generic[AT]): See :attr:`volume` for more info. Raises - ------- + ------ TypeError Not an audio source. ClientException @@ -692,12 +703,12 @@ def __init__(self, source: AudioSource, client: VoiceClient, *, after=None): self.daemon: bool = True self.source: AudioSource = source self.client: VoiceClient = client - self.after: Optional[Callable[[Optional[Exception]], Any]] = after + self.after: Callable[[Exception | None], Any] | None = after self._end: threading.Event = threading.Event() self._resumed: threading.Event = threading.Event() self._resumed.set() # we are not paused - self._current_error: Optional[Exception] = None + self._current_error: Exception | None = None self._connected: threading.Event = client._connected self._lock: threading.Lock = threading.Lock() @@ -796,6 +807,8 @@ def _set_source(self, source: AudioSource) -> None: def _speak(self, speaking: bool) -> None: try: - asyncio.run_coroutine_threadsafe(self.client.ws.speak(speaking), self.client.loop) + asyncio.run_coroutine_threadsafe( + self.client.ws.speak(speaking), self.client.loop + ) except Exception as e: _log.info("Speaking call in player failed: %s", e) diff --git a/discord/raw_models.py b/discord/raw_models.py index 5b1bd9060c..65f9d8f151 100644 --- a/discord/raw_models.py +++ b/discord/raw_models.py @@ -26,7 +26,7 @@ from __future__ import annotations import datetime -from typing import TYPE_CHECKING, List, Optional, Set +from typing import TYPE_CHECKING from .automod import AutoModAction from .enums import ChannelType, try_enum @@ -39,8 +39,8 @@ from .partial_emoji import PartialEmoji from .state import ConnectionState from .threads import Thread + from .types.raw_models import AutoModActionExecutionEvent as AutoModActionExecution from .types.raw_models import ( - AutoModActionExecutionEvent as AutoModActionExecution, BulkMessageDeleteEvent, IntegrationDeleteEvent, MessageDeleteEvent, @@ -79,7 +79,7 @@ class RawMessageDeleteEvent(_RawReprMixin): """Represents the event payload for a :func:`on_raw_message_delete` event. Attributes - ------------ + ---------- channel_id: :class:`int` The channel ID where the deletion took place. guild_id: Optional[:class:`int`] @@ -95,18 +95,18 @@ class RawMessageDeleteEvent(_RawReprMixin): def __init__(self, data: MessageDeleteEvent) -> None: self.message_id: int = int(data["id"]) self.channel_id: int = int(data["channel_id"]) - self.cached_message: Optional[Message] = None + self.cached_message: Message | None = None try: - self.guild_id: Optional[int] = int(data["guild_id"]) + self.guild_id: int | None = int(data["guild_id"]) except KeyError: - self.guild_id: Optional[int] = None + self.guild_id: int | None = None class RawBulkMessageDeleteEvent(_RawReprMixin): """Represents the event payload for a :func:`on_raw_bulk_message_delete` event. Attributes - ----------- + ---------- message_ids: Set[:class:`int`] A :class:`set` of the message IDs that were deleted. channel_id: :class:`int` @@ -120,21 +120,21 @@ class RawBulkMessageDeleteEvent(_RawReprMixin): __slots__ = ("message_ids", "channel_id", "guild_id", "cached_messages") def __init__(self, data: BulkMessageDeleteEvent) -> None: - self.message_ids: Set[int] = {int(x) for x in data.get("ids", [])} + self.message_ids: set[int] = {int(x) for x in data.get("ids", [])} self.channel_id: int = int(data["channel_id"]) - self.cached_messages: List[Message] = [] + self.cached_messages: list[Message] = [] try: - self.guild_id: Optional[int] = int(data["guild_id"]) + self.guild_id: int | None = int(data["guild_id"]) except KeyError: - self.guild_id: Optional[int] = None + self.guild_id: int | None = None class RawMessageUpdateEvent(_RawReprMixin): """Represents the payload for a :func:`on_raw_message_edit` event. Attributes - ----------- + ---------- message_id: :class:`int` The message ID that got updated. channel_id: :class:`int` @@ -159,12 +159,12 @@ def __init__(self, data: MessageUpdateEvent) -> None: self.message_id: int = int(data["id"]) self.channel_id: int = int(data["channel_id"]) self.data: MessageUpdateEvent = data - self.cached_message: Optional[Message] = None + self.cached_message: Message | None = None try: - self.guild_id: Optional[int] = int(data["guild_id"]) + self.guild_id: int | None = int(data["guild_id"]) except KeyError: - self.guild_id: Optional[int] = None + self.guild_id: int | None = None class RawReactionActionEvent(_RawReprMixin): @@ -172,7 +172,7 @@ class RawReactionActionEvent(_RawReprMixin): :func:`on_raw_reaction_remove` event. Attributes - ----------- + ---------- message_id: :class:`int` The message ID that got or lost a reaction. user_id: :class:`int` @@ -207,25 +207,27 @@ class RawReactionActionEvent(_RawReprMixin): "member", ) - def __init__(self, data: ReactionActionEvent, emoji: PartialEmoji, event_type: str) -> None: + def __init__( + self, data: ReactionActionEvent, emoji: PartialEmoji, event_type: str + ) -> None: self.message_id: int = int(data["message_id"]) self.channel_id: int = int(data["channel_id"]) self.user_id: int = int(data["user_id"]) self.emoji: PartialEmoji = emoji self.event_type: str = event_type - self.member: Optional[Member] = None + self.member: Member | None = None try: - self.guild_id: Optional[int] = int(data["guild_id"]) + self.guild_id: int | None = int(data["guild_id"]) except KeyError: - self.guild_id: Optional[int] = None + self.guild_id: int | None = None class RawReactionClearEvent(_RawReprMixin): """Represents the payload for a :func:`on_raw_reaction_clear` event. Attributes - ----------- + ---------- message_id: :class:`int` The message ID that got its reactions cleared. channel_id: :class:`int` @@ -241,9 +243,9 @@ def __init__(self, data: ReactionClearEvent) -> None: self.channel_id: int = int(data["channel_id"]) try: - self.guild_id: Optional[int] = int(data["guild_id"]) + self.guild_id: int | None = int(data["guild_id"]) except KeyError: - self.guild_id: Optional[int] = None + self.guild_id: int | None = None class RawReactionClearEmojiEvent(_RawReprMixin): @@ -252,7 +254,7 @@ class RawReactionClearEmojiEvent(_RawReprMixin): .. versionadded:: 1.3 Attributes - ----------- + ---------- message_id: :class:`int` The message ID that got its reactions cleared. channel_id: :class:`int` @@ -271,9 +273,9 @@ def __init__(self, data: ReactionClearEmojiEvent, emoji: PartialEmoji) -> None: self.channel_id: int = int(data["channel_id"]) try: - self.guild_id: Optional[int] = int(data["guild_id"]) + self.guild_id: int | None = int(data["guild_id"]) except KeyError: - self.guild_id: Optional[int] = None + self.guild_id: int | None = None class RawIntegrationDeleteEvent(_RawReprMixin): @@ -282,7 +284,7 @@ class RawIntegrationDeleteEvent(_RawReprMixin): .. versionadded:: 2.0 Attributes - ----------- + ---------- integration_id: :class:`int` The ID of the integration that got deleted. application_id: Optional[:class:`int`] @@ -298,9 +300,9 @@ def __init__(self, data: IntegrationDeleteEvent) -> None: self.guild_id: int = int(data["guild_id"]) try: - self.application_id: Optional[int] = int(data["application_id"]) + self.application_id: int | None = int(data["application_id"]) except KeyError: - self.application_id: Optional[int] = None + self.application_id: int | None = None class RawThreadDeleteEvent(_RawReprMixin): @@ -330,7 +332,7 @@ def __init__(self, data: ThreadDeleteEvent) -> None: self.thread_type: ChannelType = try_enum(ChannelType, int(data["type"])) self.guild_id: int = int(data["guild_id"]) self.parent_id: int = int(data["parent_id"]) - self.thread: Optional[Thread] = None + self.thread: Thread | None = None class RawTypingEvent(_RawReprMixin): @@ -339,7 +341,7 @@ class RawTypingEvent(_RawReprMixin): .. versionadded:: 2.0 Attributes - ----------- + ---------- channel_id: :class:`int` The channel ID where the typing originated from. user_id: :class:`int` @@ -357,13 +359,15 @@ class RawTypingEvent(_RawReprMixin): def __init__(self, data: TypingEvent) -> None: self.channel_id: int = int(data["channel_id"]) self.user_id: int = int(data["user_id"]) - self.when: datetime.datetime = datetime.datetime.fromtimestamp(data.get("timestamp"), tz=datetime.timezone.utc) - self.member: Optional[Member] = None + self.when: datetime.datetime = datetime.datetime.fromtimestamp( + data.get("timestamp"), tz=datetime.timezone.utc + ) + self.member: Member | None = None try: - self.guild_id: Optional[int] = int(data["guild_id"]) + self.guild_id: int | None = int(data["guild_id"]) except KeyError: - self.guild_id: Optional[int] = None + self.guild_id: int | None = None class RawScheduledEventSubscription(_RawReprMixin): @@ -373,7 +377,7 @@ class RawScheduledEventSubscription(_RawReprMixin): .. versionadded:: 2.0 Attributes - ----------- + ---------- event_id: :class:`int` The event ID where the typing originated from. user_id: :class:`int` @@ -390,17 +394,17 @@ class RawScheduledEventSubscription(_RawReprMixin): def __init__(self, data: ScheduledEventSubscription, event_type: str): self.event_id: int = int(data["guild_scheduled_event_id"]) self.user_id: int = int(data["user_id"]) - self.guild: Optional[Guild] = None + self.guild: Guild | None = None self.event_type: str = event_type class AutoModActionExecutionEvent: """Represents the payload for an :func:`on_auto_moderation_action_execution` - + .. versionadded:: 2.0 - + Attributes - ----------- + ---------- action: :class:`AutoModAction` The action that was executed. rule_id: :class:`int` @@ -418,13 +422,13 @@ class AutoModActionExecutionEvent: channel: Optional[Union[:class:`TextChannel`, :class:`Thread`, :class:`VoiceChannel`]] The channel in which the member's content was posted, if cached. message_id: Optional[:class:`int`] - The ID of the message that triggered the action. This is only available if the + The ID of the message that triggered the action. This is only available if the message was not blocked. message: Optional[:class:`Message`] The message that triggered the action, if cached. alert_system_message_id: Optional[:class:`int`] The ID of the system auto moderation message that was posted as a result - of the action. + of the action. alert_system_message: Optional[:class:`Message`] The system auto moderation message that was posted as a result of the action, if cached. @@ -458,40 +462,46 @@ def __init__(self, state: ConnectionState, data: AutoModActionExecution) -> None self.action: AutoModAction = AutoModAction.from_dict(data["action"]) self.rule_id: int = int(data["rule_id"]) self.guild_id: int = int(data["guild_id"]) - self.guild: Optional[Guild] = state._get_guild(self.guild_id) + self.guild: Guild | None = state._get_guild(self.guild_id) self.user_id: int = int(data["user_id"]) - self.content: Optional[str] = data.get("content", None) + self.content: str | None = data.get("content", None) self.matched_keyword: str = data["matched_keyword"] - self.matched_content: Optional[str] = data.get("matched_content", None) - + self.matched_content: str | None = data.get("matched_content", None) + if self.guild: - self.member: Optional[Member] = self.guild.get_member(self.user_id) + self.member: Member | None = self.guild.get_member(self.user_id) else: - self.member: Optional[Member] = None - + self.member: Member | None = None + try: # I don't see why this would be optional, but it's documented # as such, so we should treat it that way - self.channel_id: Optional[int] = int(data["channel_id"]) - self.channel: Optional[MessageableChannel] = self.guild.get_channel_or_thread(self.channel_id) + self.channel_id: int | None = int(data["channel_id"]) + self.channel: MessageableChannel | None = self.guild.get_channel_or_thread( + self.channel_id + ) except KeyError: - self.channel_id: Optional[int] = None - self.channel: Optional[MessageableChannel] = None - + self.channel_id: int | None = None + self.channel: MessageableChannel | None = None + try: - self.message_id: Optional[int] = int(data["message_id"]) - self.message: Optional[Message] = state._get_message(self.message_id) + self.message_id: int | None = int(data["message_id"]) + self.message: Message | None = state._get_message(self.message_id) except KeyError: - self.message_id: Optional[int] = None - self.message: Optional[Message] = None - + self.message_id: int | None = None + self.message: Message | None = None + try: - self.alert_system_message_id: Optional[int] = int(data["alert_system_message_id"]) - self.alert_system_message: Optional[Message] = state._get_message(self.alert_system_message_id) + self.alert_system_message_id: int | None = int( + data["alert_system_message_id"] + ) + self.alert_system_message: Message | None = state._get_message( + self.alert_system_message_id + ) except KeyError: - self.alert_system_message_id: Optional[int] = None - self.alert_system_message: Optional[Message] = None - + self.alert_system_message_id: int | None = None + self.alert_system_message: Message | None = None + def __repr__(self) -> str: return ( f" None: the :class:`abc.Snowflake` abc. Parameters - ----------- + ---------- user: :class:`abc.Snowflake` The user or member from which to remove the reaction. Raises - ------- + ------ HTTPException Removing the reaction failed. Forbidden @@ -151,7 +153,7 @@ async def clear(self) -> None: .. versionadded:: 1.3 Raises - -------- + ------ HTTPException Clearing the reaction failed. Forbidden @@ -163,30 +165,16 @@ async def clear(self) -> None: """ await self.message.clear_reaction(self.emoji) - def users(self, *, limit: Optional[int] = None, after: Optional[Snowflake] = None) -> ReactionIterator: + def users( + self, *, limit: int | None = None, after: Snowflake | None = None + ) -> ReactionIterator: """Returns an :class:`AsyncIterator` representing the users that have reacted to the message. The ``after`` parameter must represent a member and meet the :class:`abc.Snowflake` abc. - Examples - --------- - - Usage :: - - # I do not actually recommend doing this. - async for user in reaction.users(): - await channel.send(f'{user} has reacted with {reaction.emoji}!') - - Flattening into a list: :: - - users = await reaction.users().flatten() - # users is now a list of User... - winner = random.choice(users) - await channel.send(f'{winner} has won the raffle.') - Parameters - ------------ + ---------- limit: Optional[:class:`int`] The maximum number of results to return. If not provided, returns all the users who @@ -194,18 +182,34 @@ def users(self, *, limit: Optional[int] = None, after: Optional[Snowflake] = Non after: Optional[:class:`abc.Snowflake`] For pagination, reactions are sorted by member. - Raises - -------- - HTTPException - Getting the users for the reaction failed. - Yields - -------- + ------ Union[:class:`User`, :class:`Member`] The member (if retrievable) or the user that has reacted to this message. The case where it can be a :class:`Member` is in a guild message context. Sometimes it can be a :class:`User` if the member has left the guild. + + Raises + ------ + HTTPException + Getting the users for the reaction failed. + + Examples + -------- + + Usage :: + + # I do not actually recommend doing this. + async for user in reaction.users(): + await channel.send(f'{user} has reacted with {reaction.emoji}!') + + Flattening into a list: :: + + users = await reaction.users().flatten() + # users is now a list of User... + winner = random.choice(users) + await channel.send(f'{winner} has won the raffle.') """ if not isinstance(self.emoji, str): diff --git a/discord/role.py b/discord/role.py index 6c7ed33b86..6a9b3038a0 100644 --- a/discord/role.py +++ b/discord/role.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Dict, List, Optional, TypeVar, Union +from typing import TYPE_CHECKING, Any, TypeVar from .asset import Asset from .colour import Colour @@ -62,7 +62,7 @@ class RoleTags: .. versionadded:: 1.6 Attributes - ------------ + ---------- bot_id: Optional[:class:`int`] The bot's user ID that manages this role. integration_id: Optional[:class:`int`] @@ -76,13 +76,13 @@ class RoleTags: ) def __init__(self, data: RoleTagPayload): - self.bot_id: Optional[int] = _get_as_snowflake(data, "bot_id") - self.integration_id: Optional[int] = _get_as_snowflake(data, "integration_id") + self.bot_id: int | None = _get_as_snowflake(data, "bot_id") + self.integration_id: int | None = _get_as_snowflake(data, "integration_id") # NOTE: The API returns "null" for this if it's valid, which corresponds to None. # This is different from other fields where "null" means "not there". # So in this case, a value of None is the same as True. # Which means we would need a different sentinel. - self._premium_subscriber: Optional[Any] = data.get("premium_subscriber", MISSING) + self._premium_subscriber: Any | None = data.get("premium_subscriber", MISSING) def is_bot_managed(self) -> bool: """:class:`bool`: Whether the role is associated with a bot.""" @@ -251,9 +251,9 @@ def _update(self, data: RolePayload): self.hoist: bool = data.get("hoist", False) self.managed: bool = data.get("managed", False) self.mentionable: bool = data.get("mentionable", False) - self._icon: Optional[str] = data.get("icon") - self.unicode_emoji: Optional[str] = data.get("unicode_emoji") - self.tags: Optional[RoleTags] + self._icon: str | None = data.get("icon") + self.unicode_emoji: str | None = data.get("unicode_emoji") + self.tags: RoleTags | None try: self.tags = RoleTags(data["tags"]) @@ -291,7 +291,11 @@ def is_assignable(self) -> bool: .. versionadded:: 2.0 """ me = self.guild.me - return not self.is_default() and not self.managed and (me.top_role > self or me.id == self.guild.owner_id) + return ( + not self.is_default() + and not self.managed + and (me.top_role > self or me.id == self.guild.owner_id) + ) @property def permissions(self) -> Permissions: @@ -319,7 +323,7 @@ def mention(self) -> str: return f"<@&{self.id}>" @property - def members(self) -> List[Member]: + def members(self) -> list[Member]: """List[:class:`Member`]: Returns all the members with this role.""" all_members = self.guild.members if self.is_default(): @@ -329,7 +333,7 @@ def members(self) -> List[Member]: return [member for member in all_members if member._roles.has(role_id)] @property - def icon(self) -> Optional[Asset]: + def icon(self) -> Asset | None: """Optional[:class:`Asset`]: Returns the role's icon asset, if available. .. versionadded:: 2.0 @@ -339,7 +343,7 @@ def icon(self) -> Optional[Asset]: return Asset._from_icon(self._state, self.id, self._icon, "role") - async def _move(self, position: int, reason: Optional[str]) -> None: + async def _move(self, position: int, reason: str | None) -> None: if position <= 0: raise InvalidArgument("Cannot move role to position 0 or below") @@ -351,15 +355,23 @@ async def _move(self, position: int, reason: Optional[str]) -> None: http = self._state.http - change_range = range(min(self.position, position), max(self.position, position) + 1) - roles = [r.id for r in self.guild.roles[1:] if r.position in change_range and r.id != self.id] + change_range = range( + min(self.position, position), max(self.position, position) + 1 + ) + roles = [ + r.id + for r in self.guild.roles[1:] + if r.position in change_range and r.id != self.id + ] if self.position > position: roles.insert(0, self.id) else: roles.append(self.id) - payload: List[RolePositionUpdate] = [{"id": z[0], "position": z[1]} for z in zip(roles, change_range)] + payload: list[RolePositionUpdate] = [ + {"id": z[0], "position": z[1]} for z in zip(roles, change_range) + ] await http.move_role_position(self.guild.id, payload, reason=reason) async def edit( @@ -367,15 +379,15 @@ async def edit( *, name: str = MISSING, permissions: Permissions = MISSING, - colour: Union[Colour, int] = MISSING, - color: Union[Colour, int] = MISSING, + colour: Colour | int = MISSING, + color: Colour | int = MISSING, hoist: bool = MISSING, mentionable: bool = MISSING, position: int = MISSING, - reason: Optional[str] = MISSING, - icon: Optional[bytes] = MISSING, - unicode_emoji: Optional[str] = MISSING, - ) -> Optional[Role]: + reason: str | None = MISSING, + icon: bytes | None = MISSING, + unicode_emoji: str | None = MISSING, + ) -> Role | None: """|coro| Edits the role. @@ -393,7 +405,7 @@ async def edit( Added ``icon`` and ``unicode_emoji``. Parameters - ----------- + ---------- name: :class:`str` The new role name to change to. permissions: :class:`Permissions` @@ -418,8 +430,13 @@ async def edit( The role's unicode emoji. If this argument is passed, ``icon`` is set to None. Only available to guilds that contain ``ROLE_ICONS`` in :attr:`Guild.features`. - Raises + Returns ------- + :class:`Role` + The newly edited role. + + Raises + ------ Forbidden You do not have permissions to change the role. HTTPException @@ -427,16 +444,11 @@ async def edit( InvalidArgument An invalid position was given or the default role was asked to be moved. - - Returns - -------- - :class:`Role` - The newly edited role. """ if position is not MISSING: await self._move(position, reason=reason) - payload: Dict[str, Any] = {} + payload: dict[str, Any] = {} if color is not MISSING: colour = color @@ -465,10 +477,12 @@ async def edit( payload["unicode_emoji"] = unicode_emoji payload["icon"] = None - data = await self._state.http.edit_role(self.guild.id, self.id, reason=reason, **payload) + data = await self._state.http.edit_role( + self.guild.id, self.id, reason=reason, **payload + ) return Role(guild=self.guild, data=data, state=self._state) - async def delete(self, *, reason: Optional[str] = None) -> None: + async def delete(self, *, reason: str | None = None) -> None: """|coro| Deletes the role. @@ -477,12 +491,12 @@ async def delete(self, *, reason: Optional[str] = None) -> None: use this. Parameters - ----------- + ---------- reason: Optional[:class:`str`] The reason for deleting this role. Shows up on the audit log. Raises - -------- + ------ Forbidden You do not have permissions to delete the role. HTTPException diff --git a/discord/scheduled_events.py b/discord/scheduled_events.py index b7614f2a48..8dd09f5454 100644 --- a/discord/scheduled_events.py +++ b/discord/scheduled_events.py @@ -25,7 +25,7 @@ from __future__ import annotations import datetime -from typing import TYPE_CHECKING, Any, Dict, Optional, Union +from typing import TYPE_CHECKING, Any from . import utils from .asset import Asset @@ -89,10 +89,10 @@ def __init__( self, *, state: ConnectionState, - value: Union[str, int, StageChannel, VoiceChannel], + value: str | int | StageChannel | VoiceChannel, ): self._state = state - self.value: Union[str, StageChannel, VoiceChannel, Object] + self.value: str | StageChannel | VoiceChannel | Object if isinstance(value, int): self.value = self._state.get_channel(id=int(value)) or Object(id=int(value)) else: @@ -189,7 +189,7 @@ def __init__( *, state: ConnectionState, guild: Guild, - creator: Optional[Member], + creator: Member | None, data: ScheduledEventPayload, ): self._state: ConnectionState = state @@ -197,21 +197,27 @@ def __init__( self.id: int = int(data.get("id")) self.guild: Guild = guild self.name: str = data.get("name") - self.description: Optional[str] = data.get("description", None) - self._cover: Optional[str] = data.get("image", None) - self.start_time: datetime.datetime = datetime.datetime.fromisoformat(data.get("scheduled_start_time")) + self.description: str | None = data.get("description", None) + self._cover: str | None = data.get("image", None) + self.start_time: datetime.datetime = datetime.datetime.fromisoformat( + data.get("scheduled_start_time") + ) if end_time := data.get("scheduled_end_time", None): end_time = datetime.datetime.fromisoformat(end_time) - self.end_time: Optional[datetime.datetime] = end_time - self.status: ScheduledEventStatus = try_enum(ScheduledEventStatus, data.get("status")) - self.subscriber_count: Optional[int] = data.get("user_count", None) + self.end_time: datetime.datetime | None = end_time + self.status: ScheduledEventStatus = try_enum( + ScheduledEventStatus, data.get("status") + ) + self.subscriber_count: int | None = data.get("user_count", None) self.creator_id = data.get("creator_id", None) - self.creator: Optional[Member] = creator + self.creator: Member | None = creator entity_metadata = data.get("entity_metadata") channel_id = data.get("channel_id", None) if channel_id is None: - self.location = ScheduledEventLocation(state=state, value=entity_metadata["location"]) + self.location = ScheduledEventLocation( + state=state, value=entity_metadata["location"] + ) else: self.location = ScheduledEventLocation(state=state, value=int(channel_id)) @@ -237,7 +243,7 @@ def created_at(self) -> datetime.datetime: return utils.snowflake_time(self.id) @property - def interested(self) -> Optional[int]: + def interested(self) -> int | None: """An alias to :attr:`.subscriber_count`""" return self.subscriber_count @@ -247,7 +253,7 @@ def url(self) -> str: return f"https://discord.com/events/{self.guild.id}/{self.id}" @property - def cover(self) -> Optional[Asset]: + def cover(self) -> Asset | None: """Optional[:class:`Asset`]: Returns the scheduled event cover image asset, if available.""" if self._cover is None: return None @@ -260,16 +266,20 @@ def cover(self) -> Optional[Asset]: async def edit( self, *, - reason: Optional[str] = None, + reason: str | None = None, name: str = MISSING, description: str = MISSING, - status: Union[int, ScheduledEventStatus] = MISSING, - location: Union[str, int, VoiceChannel, StageChannel, ScheduledEventLocation] = MISSING, + status: int | ScheduledEventStatus = MISSING, + location: str + | int + | VoiceChannel + | StageChannel + | ScheduledEventLocation = MISSING, start_time: datetime.datetime = MISSING, end_time: datetime.datetime = MISSING, - cover: Optional[bytes] = MISSING, + cover: bytes | None = MISSING, privacy_level: ScheduledEventPrivacyLevel = ScheduledEventPrivacyLevel.guild_only, - ) -> Optional[ScheduledEvent]: + ) -> ScheduledEvent | None: """|coro| Edits the Scheduled Event's data @@ -281,7 +291,7 @@ async def edit( Will return a new :class:`.ScheduledEvent` object if applicable. Parameters - ----------- + ---------- name: :class:`str` The new name of the event. description: :class:`str` @@ -305,20 +315,20 @@ async def edit( cover: Optional[:class:`Asset`] The cover image of the scheduled event. - Raises + Returns ------- + Optional[:class:`.ScheduledEvent`] + The newly updated scheduled event object. This is only returned when certain + fields are updated. + + Raises + ------ Forbidden You do not have the Manage Events permission. HTTPException The operation failed. - - Returns - -------- - Optional[:class:`.ScheduledEvent`] - The newly updated scheduled event object. This is only returned when certain - fields are updated. """ - payload: Dict[str, Any] = {} + payload: dict[str, Any] = {} if name is not MISSING: payload["name"] = name @@ -337,7 +347,9 @@ async def edit( payload["image"] = utils._bytes_to_base64_data(cover) if location is not MISSING: - if not isinstance(location, (ScheduledEventLocation, utils._MissingSentinel)): + if not isinstance( + location, (ScheduledEventLocation, utils._MissingSentinel) + ): location = ScheduledEventLocation(state=self._state, value=location) if location.type is ScheduledEventLocationType.external: @@ -351,7 +363,9 @@ async def edit( if end_time is MISSING and location.type is ScheduledEventLocationType.external: end_time = self.end_time if end_time is None: - raise ValidationError("end_time needs to be passed if location type is external.") + raise ValidationError( + "end_time needs to be passed if location type is external." + ) if start_time is not MISSING: payload["scheduled_start_time"] = start_time.isoformat() @@ -360,8 +374,12 @@ async def edit( payload["scheduled_end_time"] = end_time.isoformat() if payload != {}: - data = await self._state.http.edit_scheduled_event(self.guild.id, self.id, **payload, reason=reason) - return ScheduledEvent(data=data, guild=self.guild, creator=self.creator, state=self._state) + data = await self._state.http.edit_scheduled_event( + self.guild.id, self.id, **payload, reason=reason + ) + return ScheduledEvent( + data=data, guild=self.guild, creator=self.creator, state=self._state + ) async def delete(self) -> None: """|coro| @@ -369,7 +387,7 @@ async def delete(self) -> None: Deletes the scheduled event. Raises - ------- + ------ Forbidden You do not have the Manage Events permission. HTTPException @@ -377,7 +395,7 @@ async def delete(self) -> None: """ await self._state.http.delete_scheduled_event(self.guild.id, self.id) - async def start(self, *, reason: Optional[str] = None) -> None: + async def start(self, *, reason: str | None = None) -> None: """|coro| Starts the scheduled event. Shortcut from :meth:`.edit`. @@ -387,25 +405,25 @@ async def start(self, *, reason: Optional[str] = None) -> None: This method can only be used if :attr:`.status` is :attr:`ScheduledEventStatus.scheduled`. Parameters - ----------- + ---------- reason: Optional[:class:`str`] The reason to show in the audit log. - Raises + Returns ------- + Optional[:class:`.ScheduledEvent`] + The newly updated scheduled event object. + + Raises + ------ Forbidden You do not have the Manage Events permission. HTTPException The operation failed. - - Returns - -------- - Optional[:class:`.ScheduledEvent`] - The newly updated scheduled event object. """ return await self.edit(status=ScheduledEventStatus.active, reason=reason) - async def complete(self, *, reason: Optional[str] = None) -> None: + async def complete(self, *, reason: str | None = None) -> None: """|coro| Ends/completes the scheduled event. Shortcut from :meth:`.edit`. @@ -415,25 +433,25 @@ async def complete(self, *, reason: Optional[str] = None) -> None: This method can only be used if :attr:`.status` is :attr:`ScheduledEventStatus.active`. Parameters - ----------- + ---------- reason: Optional[:class:`str`] The reason to show in the audit log. - Raises + Returns ------- + Optional[:class:`.ScheduledEvent`] + The newly updated scheduled event object. + + Raises + ------ Forbidden You do not have the Manage Events permission. HTTPException The operation failed. - - Returns - -------- - Optional[:class:`.ScheduledEvent`] - The newly updated scheduled event object. """ return await self.edit(status=ScheduledEventStatus.completed, reason=reason) - async def cancel(self, *, reason: Optional[str] = None) -> None: + async def cancel(self, *, reason: str | None = None) -> None: """|coro| Cancels the scheduled event. Shortcut from :meth:`.edit`. @@ -443,21 +461,21 @@ async def cancel(self, *, reason: Optional[str] = None) -> None: This method can only be used if :attr:`.status` is :attr:`ScheduledEventStatus.scheduled`. Parameters - ----------- + ---------- reason: Optional[:class:`str`] The reason to show in the audit log. - Raises + Returns ------- + Optional[:class:`.ScheduledEvent`] + The newly updated scheduled event object. + + Raises + ------ Forbidden You do not have the Manage Events permission. HTTPException The operation failed. - - Returns - -------- - Optional[:class:`.ScheduledEvent`] - The newly updated scheduled event object. """ return await self.edit(status=ScheduledEventStatus.canceled, reason=reason) @@ -466,8 +484,8 @@ def subscribers( *, limit: int = 100, as_member: bool = False, - before: Optional[Union[Snowflake, datetime.datetime]] = None, - after: Optional[Union[Snowflake, datetime.datetime]] = None, + before: Snowflake | datetime.datetime | None = None, + after: Snowflake | datetime.datetime | None = None, ) -> AsyncIterator: """Returns an :class:`AsyncIterator` representing the users or members subscribed to the event. @@ -479,26 +497,8 @@ def subscribers( Even is ``as_member`` is set to ``True``, if the user is outside the guild, it will be a :class:`User` object. - Examples - --------- - - Usage :: - - async for user in event.subscribers(limit=100): - print(user.name) - - Flattening into a list: :: - - users = await event.subscribers(limit=100).flatten() - # users is now a list of User... - - Getting members instead of user objects: :: - - async for member in event.subscribers(limit=100, as_member=True): - print(member.display_name) - Parameters - ----------- + ---------- limit: Optional[:class:`int`] The maximum number of results to return. as_member: Optional[:class:`bool`] @@ -514,17 +514,35 @@ def subscribers( it is recommended to use a UTC aware datetime. If the datetime is naive, it is assumed to be local time. - Raises - ------- - HTTPException - Fetching the subscribed users failed. - Yields - ------- + ------ Union[:class:`User`, :class:`Member`] The subscribed :class:`Member`. If ``as_member`` is set to ``False`` or the user is outside the guild, it will be a :class:`User` object. + + Raises + ------ + HTTPException + Fetching the subscribed users failed. + + Examples + -------- + + Usage :: + + async for user in event.subscribers(limit=100): + print(user.name) + + Flattening into a list: :: + + users = await event.subscribers(limit=100).flatten() + # users is now a list of User... + + Getting members instead of user objects: :: + + async for member in event.subscribers(limit=100, as_member=True): + print(member.display_name) """ return ScheduledEventSubscribersIterator( event=self, limit=limit, with_member=as_member, before=before, after=after diff --git a/discord/shard.py b/discord/shard.py index 2213f5b59a..476e0b85d8 100644 --- a/discord/shard.py +++ b/discord/shard.py @@ -27,17 +27,7 @@ import asyncio import logging -from typing import ( - TYPE_CHECKING, - Any, - Callable, - Dict, - List, - Optional, - Tuple, - Type, - TypeVar, -) +from typing import TYPE_CHECKING, Any, Callable, TypeVar import aiohttp @@ -80,10 +70,12 @@ class EventType: class EventItem: __slots__ = ("type", "shard", "error") - def __init__(self, etype: int, shard: Optional["Shard"], error: Optional[Exception]) -> None: + def __init__( + self, etype: int, shard: Shard | None, error: Exception | None + ) -> None: self.type: int = etype - self.shard: Optional["Shard"] = shard - self.error: Optional[Exception] = error + self.shard: Shard | None = shard + self.error: Exception | None = error def __lt__(self: EI, other: EI) -> bool: if not isinstance(other, EventItem): @@ -114,8 +106,8 @@ def __init__( self._disconnect: bool = False self._reconnect = client._reconnect self._backoff: ExponentialBackoff = ExponentialBackoff() - self._task: Optional[asyncio.Task] = None - self._handled_exceptions: Tuple[Type[Exception], ...] = ( + self._task: asyncio.Task | None = None + self._handled_exceptions: tuple[type[Exception], ...] = ( OSError, HTTPException, GatewayNotFound, @@ -162,7 +154,11 @@ async def _handle_disconnect(self, e: Exception) -> None: if isinstance(e, ConnectionClosed): if e.code == 4014: - self._queue_put(EventItem(EventType.terminate, self, PrivilegedIntentsRequired(self.id))) + self._queue_put( + EventItem( + EventType.terminate, self, PrivilegedIntentsRequired(self.id) + ) + ) return if e.code != 1000: self._queue_put(EventItem(EventType.close, self, e)) @@ -246,7 +242,7 @@ class ShardInfo: .. versionadded:: 1.4 Attributes - ------------ + ---------- id: :class:`int` The shard ID for this shard. shard_count: Optional[:class:`int`] @@ -255,10 +251,10 @@ class ShardInfo: __slots__ = ("_parent", "id", "shard_count") - def __init__(self, parent: Shard, shard_count: Optional[int]) -> None: + def __init__(self, parent: Shard, shard_count: int | None) -> None: self._parent: Shard = parent self.id: int = parent.id - self.shard_count: Optional[int] = shard_count + self.shard_count: int | None = shard_count def is_closed(self) -> bool: """:class:`bool`: Whether the shard connection is currently closed.""" @@ -334,7 +330,7 @@ class AutoShardedClient(Client): 0 to ``shard_count - 1``. Attributes - ------------ + ---------- shard_ids: Optional[List[:class:`int`]] An optional list of shard_ids to launch the shards with. """ @@ -345,16 +341,18 @@ class AutoShardedClient(Client): def __init__( self, *args: Any, - loop: Optional[asyncio.AbstractEventLoop] = None, + loop: asyncio.AbstractEventLoop | None = None, **kwargs: Any, ) -> None: kwargs.pop("shard_id", None) - self.shard_ids: Optional[List[int]] = kwargs.pop("shard_ids", None) + self.shard_ids: list[int] | None = kwargs.pop("shard_ids", None) super().__init__(*args, loop=loop, **kwargs) if self.shard_ids is not None: if self.shard_count is None: - raise ClientException("When passing manual shard_ids, you must provide a shard_count.") + raise ClientException( + "When passing manual shard_ids, you must provide a shard_count." + ) elif not isinstance(self.shard_ids, (list, tuple)): raise ClientException("shard_ids parameter must be a list or a tuple.") @@ -365,7 +363,9 @@ def __init__( self._connection._get_client = lambda: self self.__queue = asyncio.PriorityQueue() - def _get_websocket(self, guild_id: Optional[int] = None, *, shard_id: Optional[int] = None) -> DiscordWebSocket: + def _get_websocket( + self, guild_id: int | None = None, *, shard_id: int | None = None + ) -> DiscordWebSocket: if shard_id is None: # guild_id won't be None if shard_id is None and shard_count won't be None here shard_id = (guild_id >> 22) % self.shard_count # type: ignore @@ -394,15 +394,17 @@ def latency(self) -> float: return sum(latency for _, latency in self.latencies) / len(self.__shards) @property - def latencies(self) -> List[Tuple[int, float]]: + def latencies(self) -> list[tuple[int, float]]: """List[Tuple[:class:`int`, :class:`float`]]: A list of latencies between a HEARTBEAT and a HEARTBEAT_ACK in seconds. This returns a list of tuples with elements ``(shard_id, latency)``. """ - return [(shard_id, shard.ws.latency) for shard_id, shard in self.__shards.items()] + return [ + (shard_id, shard.ws.latency) for shard_id, shard in self.__shards.items() + ] - def get_shard(self, shard_id: int) -> Optional[ShardInfo]: + def get_shard(self, shard_id: int) -> ShardInfo | None: """Optional[:class:`ShardInfo`]: Gets the shard information at a given shard ID or ``None`` if not found.""" try: parent = self.__shards[shard_id] @@ -412,13 +414,20 @@ def get_shard(self, shard_id: int) -> Optional[ShardInfo]: return ShardInfo(parent, self.shard_count) @property - def shards(self) -> Dict[int, ShardInfo]: + def shards(self) -> dict[int, ShardInfo]: """Mapping[:class:`int`, :class:`ShardInfo`]: Returns a mapping of shard IDs to their respective info object.""" - return {shard_id: ShardInfo(parent, self.shard_count) for shard_id, parent in self.__shards.items()} + return { + shard_id: ShardInfo(parent, self.shard_count) + for shard_id, parent in self.__shards.items() + } - async def launch_shard(self, gateway: str, shard_id: int, *, initial: bool = False) -> None: + async def launch_shard( + self, gateway: str, shard_id: int, *, initial: bool = False + ) -> None: try: - coro = DiscordWebSocket.from_client(self, initial=initial, gateway=gateway, shard_id=shard_id) + coro = DiscordWebSocket.from_client( + self, initial=initial, gateway=gateway, shard_id=shard_id + ) ws = await asyncio.wait_for(coro, timeout=180.0) except Exception: _log.exception("Failed to connect for shard_id: %s. Retrying...", shard_id) @@ -483,7 +492,10 @@ async def close(self) -> None: except Exception: pass - to_close = [asyncio.ensure_future(shard.close(), loop=self.loop) for shard in self.__shards.values()] + to_close = [ + asyncio.ensure_future(shard.close(), loop=self.loop) + for shard in self.__shards.values() + ] if to_close: await asyncio.wait(to_close) @@ -493,8 +505,8 @@ async def close(self) -> None: async def change_presence( self, *, - activity: Optional[BaseActivity] = None, - status: Optional[Status] = None, + activity: BaseActivity | None = None, + status: Status | None = None, shard_id: int = None, ) -> None: """|coro| diff --git a/discord/sinks/core.py b/discord/sinks/core.py index 9685262e1b..479acbe179 100644 --- a/discord/sinks/core.py +++ b/discord/sinks/core.py @@ -109,7 +109,9 @@ def __init__(self, data, client): unpacker = struct.Struct(">xxHII") self.sequence, self.timestamp, self.ssrc = unpacker.unpack_from(self.header) - self.decrypted_data = getattr(self.client, f"_decrypt_{self.client.mode}")(self.header, self.data) + self.decrypted_data = getattr(self.client, f"_decrypt_{self.client.mode}")( + self.header, self.data + ) self.decoded_data = None self.user_id = None diff --git a/discord/sinks/errors.py b/discord/sinks/errors.py index 8d12cf4a16..da1a928566 100644 --- a/discord/sinks/errors.py +++ b/discord/sinks/errors.py @@ -38,8 +38,6 @@ class RecordingException(SinkException): .. versionadded:: 2.0 """ - pass - class MP3SinkError(SinkException): """Exception thrown when an exception occurs with :class:`MP3Sink` diff --git a/discord/sinks/m4a.py b/discord/sinks/m4a.py index 3547cf3639..497a22225f 100644 --- a/discord/sinks/m4a.py +++ b/discord/sinks/m4a.py @@ -57,7 +57,9 @@ def format_audio(self, audio): Formatting the audio failed. """ if self.vc.recording: - raise M4ASinkError("Audio may only be formatted after recording is finished.") + raise M4ASinkError( + "Audio may only be formatted after recording is finished." + ) m4a_file = f"{time.time()}.tmp" args = [ "ffmpeg", @@ -74,13 +76,19 @@ def format_audio(self, audio): m4a_file, ] if os.path.exists(m4a_file): - os.remove(m4a_file) # process will get stuck asking whether to overwrite, if file already exists. + os.remove( + m4a_file + ) # process will get stuck asking whether to overwrite, if file already exists. try: - process = subprocess.Popen(args, creationflags=CREATE_NO_WINDOW, stdin=subprocess.PIPE) + process = subprocess.Popen( + args, creationflags=CREATE_NO_WINDOW, stdin=subprocess.PIPE + ) except FileNotFoundError: raise M4ASinkError("ffmpeg was not found.") from None except subprocess.SubprocessError as exc: - raise M4ASinkError("Popen failed: {0.__class__.__name__}: {0}".format(exc)) from exc + raise M4ASinkError( + "Popen failed: {0.__class__.__name__}: {0}".format(exc) + ) from exc process.communicate(audio.file.read()) diff --git a/discord/sinks/mka.py b/discord/sinks/mka.py index f3786251fa..830954a9d3 100644 --- a/discord/sinks/mka.py +++ b/discord/sinks/mka.py @@ -55,7 +55,9 @@ def format_audio(self, audio): Formatting the audio failed. """ if self.vc.recording: - raise MKASinkError("Audio may only be formatted after recording is finished.") + raise MKASinkError( + "Audio may only be formatted after recording is finished." + ) args = [ "ffmpeg", "-f", @@ -80,7 +82,9 @@ def format_audio(self, audio): except FileNotFoundError: raise MKASinkError("ffmpeg was not found.") from None except subprocess.SubprocessError as exc: - raise MKASinkError("Popen failed: {0.__class__.__name__}: {0}".format(exc)) from exc + raise MKASinkError( + "Popen failed: {0.__class__.__name__}: {0}".format(exc) + ) from exc out = process.communicate(audio.file.read())[0] out = io.BytesIO(out) diff --git a/discord/sinks/mkv.py b/discord/sinks/mkv.py index 33cb5675b2..e1ed4ca78e 100644 --- a/discord/sinks/mkv.py +++ b/discord/sinks/mkv.py @@ -55,7 +55,9 @@ def format_audio(self, audio): Formatting the audio failed. """ if self.vc.recording: - raise MKVSinkError("Audio may only be formatted after recording is finished.") + raise MKVSinkError( + "Audio may only be formatted after recording is finished." + ) args = [ "ffmpeg", "-f", @@ -79,7 +81,9 @@ def format_audio(self, audio): except FileNotFoundError: raise MKVSinkError("ffmpeg was not found.") from None except subprocess.SubprocessError as exc: - raise MKVSinkError("Popen failed: {0.__class__.__name__}: {0}".format(exc)) from exc + raise MKVSinkError( + "Popen failed: {0.__class__.__name__}: {0}".format(exc) + ) from exc out = process.communicate(audio.file.read())[0] out = io.BytesIO(out) diff --git a/discord/sinks/mp3.py b/discord/sinks/mp3.py index 71fa2e3fe3..96fa8a562d 100644 --- a/discord/sinks/mp3.py +++ b/discord/sinks/mp3.py @@ -55,7 +55,9 @@ def format_audio(self, audio): Formatting the audio failed. """ if self.vc.recording: - raise MP3SinkError("Audio may only be formatted after recording is finished.") + raise MP3SinkError( + "Audio may only be formatted after recording is finished." + ) args = [ "ffmpeg", "-f", @@ -80,7 +82,9 @@ def format_audio(self, audio): except FileNotFoundError: raise MP3SinkError("ffmpeg was not found.") from None except subprocess.SubprocessError as exc: - raise MP3SinkError("Popen failed: {0.__class__.__name__}: {0}".format(exc)) from exc + raise MP3SinkError( + "Popen failed: {0.__class__.__name__}: {0}".format(exc) + ) from exc out = process.communicate(audio.file.read())[0] out = io.BytesIO(out) diff --git a/discord/sinks/mp4.py b/discord/sinks/mp4.py index 5cebcac104..3168452e47 100644 --- a/discord/sinks/mp4.py +++ b/discord/sinks/mp4.py @@ -57,7 +57,9 @@ def format_audio(self, audio): Formatting the audio failed. """ if self.vc.recording: - raise MP4SinkError("Audio may only be formatted after recording is finished.") + raise MP4SinkError( + "Audio may only be formatted after recording is finished." + ) mp4_file = f"{time.time()}.tmp" args = [ "ffmpeg", @@ -74,13 +76,19 @@ def format_audio(self, audio): mp4_file, ] if os.path.exists(mp4_file): - os.remove(mp4_file) # process will get stuck asking whether to overwrite, if file already exists. + os.remove( + mp4_file + ) # process will get stuck asking whether to overwrite, if file already exists. try: - process = subprocess.Popen(args, creationflags=CREATE_NO_WINDOW, stdin=subprocess.PIPE) + process = subprocess.Popen( + args, creationflags=CREATE_NO_WINDOW, stdin=subprocess.PIPE + ) except FileNotFoundError: raise MP4SinkError("ffmpeg was not found.") from None except subprocess.SubprocessError as exc: - raise MP4SinkError("Popen failed: {0.__class__.__name__}: {0}".format(exc)) from exc + raise MP4SinkError( + "Popen failed: {0.__class__.__name__}: {0}".format(exc) + ) from exc process.communicate(audio.file.read()) diff --git a/discord/sinks/ogg.py b/discord/sinks/ogg.py index ac76f7105b..759b97d53a 100644 --- a/discord/sinks/ogg.py +++ b/discord/sinks/ogg.py @@ -55,7 +55,9 @@ def format_audio(self, audio): Formatting the audio failed. """ if self.vc.recording: - raise OGGSinkError("Audio may only be formatted after recording is finished.") + raise OGGSinkError( + "Audio may only be formatted after recording is finished." + ) args = [ "ffmpeg", "-f", @@ -80,7 +82,9 @@ def format_audio(self, audio): except FileNotFoundError: raise OGGSinkError("ffmpeg was not found.") from None except subprocess.SubprocessError as exc: - raise OGGSinkError("Popen failed: {0.__class__.__name__}: {0}".format(exc)) from exc + raise OGGSinkError( + "Popen failed: {0.__class__.__name__}: {0}".format(exc) + ) from exc out = process.communicate(audio.file.read())[0] out = io.BytesIO(out) diff --git a/discord/sinks/wave.py b/discord/sinks/wave.py index 5ccf1b7419..90645bd602 100644 --- a/discord/sinks/wave.py +++ b/discord/sinks/wave.py @@ -54,7 +54,9 @@ def format_audio(self, audio): Formatting the audio failed. """ if self.vc.recording: - raise WaveSinkError("Audio may only be formatted after recording is finished.") + raise WaveSinkError( + "Audio may only be formatted after recording is finished." + ) data = audio.file with wave.open(data, "wb") as f: diff --git a/discord/stage_instance.py b/discord/stage_instance.py index 0bf2b8050d..fa30bd72a8 100644 --- a/discord/stage_instance.py +++ b/discord/stage_instance.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from .enums import StagePrivacyLevel, try_enum from .errors import InvalidArgument @@ -61,7 +61,7 @@ class StageInstance(Hashable): Returns the stage instance's hash. Attributes - ----------- + ---------- id: :class:`int` The stage instance's ID. guild: :class:`Guild` @@ -90,7 +90,9 @@ class StageInstance(Hashable): "_cs_channel", ) - def __init__(self, *, state: ConnectionState, guild: Guild, data: StageInstancePayload) -> None: + def __init__( + self, *, state: ConnectionState, guild: Guild, data: StageInstancePayload + ) -> None: self._state = state self.guild = guild self._update(data) @@ -99,7 +101,9 @@ def _update(self, data: StageInstancePayload): self.id: int = int(data["id"]) self.channel_id: int = int(data["channel_id"]) self.topic: str = data["topic"] - self.privacy_level: StagePrivacyLevel = try_enum(StagePrivacyLevel, data["privacy_level"]) + self.privacy_level: StagePrivacyLevel = try_enum( + StagePrivacyLevel, data["privacy_level"] + ) self.discoverable_disabled: bool = data.get("discoverable_disabled", False) self.scheduled_event = None @@ -110,7 +114,7 @@ def __repr__(self) -> str: return f"" @cached_slot_property("_cs_channel") - def channel(self) -> Optional[StageChannel]: + def channel(self) -> StageChannel | None: """Optional[:class:`StageChannel`]: The channel that stage instance is running in.""" # the returned channel will always be a StageChannel or None return self._state.get_channel(self.channel_id) # type: ignore @@ -123,7 +127,7 @@ async def edit( *, topic: str = MISSING, privacy_level: StagePrivacyLevel = MISSING, - reason: Optional[str] = None, + reason: str | None = None, ) -> None: """|coro| @@ -133,7 +137,7 @@ async def edit( use this. Parameters - ----------- + ---------- topic: :class:`str` The stage instance's new topic. privacy_level: :class:`StagePrivacyLevel` @@ -158,14 +162,18 @@ async def edit( if privacy_level is not MISSING: if not isinstance(privacy_level, StagePrivacyLevel): - raise InvalidArgument("privacy_level field must be of type PrivacyLevel") + raise InvalidArgument( + "privacy_level field must be of type PrivacyLevel" + ) payload["privacy_level"] = privacy_level.value if payload: - await self._state.http.edit_stage_instance(self.channel_id, **payload, reason=reason) + await self._state.http.edit_stage_instance( + self.channel_id, **payload, reason=reason + ) - async def delete(self, *, reason: Optional[str] = None) -> None: + async def delete(self, *, reason: str | None = None) -> None: """|coro| Deletes the stage instance. @@ -174,7 +182,7 @@ async def delete(self, *, reason: Optional[str] = None) -> None: use this. Parameters - ----------- + ---------- reason: :class:`str` The reason the stage instance was deleted. Shows up on the audit log. diff --git a/discord/state.py b/discord/state.py index 4cca09f61e..ef9d8de752 100644 --- a/discord/state.py +++ b/discord/state.py @@ -38,11 +38,7 @@ Callable, Coroutine, Deque, - Dict, - List, - Optional, Sequence, - Tuple, TypeVar, Union, ) @@ -109,10 +105,10 @@ def __init__( self.loop: asyncio.AbstractEventLoop = loop self.cache: bool = cache self.nonce: str = os.urandom(16).hex() - self.buffer: List[Member] = [] - self.waiters: List[asyncio.Future[List[Member]]] = [] + self.buffer: list[Member] = [] + self.waiters: list[asyncio.Future[list[Member]]] = [] - def add_members(self, members: List[Member]) -> None: + def add_members(self, members: list[Member]) -> None: self.buffer.extend(members) if self.cache: guild = self.resolver(self.guild_id) @@ -124,7 +120,7 @@ def add_members(self, members: List[Member]) -> None: if existing is None or existing.joined_at is None: guild._add_member(member) - async def wait(self) -> List[Member]: + async def wait(self) -> list[Member]: future = self.loop.create_future() self.waiters.append(future) try: @@ -132,7 +128,7 @@ async def wait(self) -> List[Member]: finally: self.waiters.remove(future) - def get_future(self) -> asyncio.Future[List[Member]]: + def get_future(self) -> asyncio.Future[list[Member]]: future = self.loop.create_future() self.waiters.append(future) return future @@ -146,7 +142,9 @@ def done(self) -> None: _log = logging.getLogger(__name__) -async def logging_coroutine(coroutine: Coroutine[Any, Any, T], *, info: str) -> Optional[T]: +async def logging_coroutine( + coroutine: Coroutine[Any, Any, T], *, info: str +) -> T | None: try: await coroutine except Exception: @@ -157,30 +155,32 @@ class ConnectionState: if TYPE_CHECKING: _get_websocket: Callable[..., DiscordWebSocket] _get_client: Callable[..., Client] - _parsers: Dict[str, Callable[[Dict[str, Any]], None]] + _parsers: dict[str, Callable[[dict[str, Any]], None]] def __init__( self, *, dispatch: Callable, - handlers: Dict[str, Callable], - hooks: Dict[str, Callable], + handlers: dict[str, Callable], + hooks: dict[str, Callable], http: HTTPClient, loop: asyncio.AbstractEventLoop, **options: Any, ) -> None: self.loop: asyncio.AbstractEventLoop = loop self.http: HTTPClient = http - self.max_messages: Optional[int] = options.get("max_messages", 1000) + self.max_messages: int | None = options.get("max_messages", 1000) if self.max_messages is not None and self.max_messages <= 0: self.max_messages = 1000 self.dispatch: Callable = dispatch - self.handlers: Dict[str, Callable] = handlers - self.hooks: Dict[str, Callable] = hooks - self.shard_count: Optional[int] = None - self._ready_task: Optional[asyncio.Task] = None - self.application_id: Optional[int] = utils._get_as_snowflake(options, "application_id") + self.handlers: dict[str, Callable] = handlers + self.hooks: dict[str, Callable] = hooks + self.shard_count: int | None = None + self._ready_task: asyncio.Task | None = None + self.application_id: int | None = utils._get_as_snowflake( + options, "application_id" + ) self.heartbeat_timeout: float = options.get("heartbeat_timeout", 60.0) self.guild_ready_timeout: float = options.get("guild_ready_timeout", 2.0) if self.guild_ready_timeout < 0: @@ -188,11 +188,13 @@ def __init__( allowed_mentions = options.get("allowed_mentions") - if allowed_mentions is not None and not isinstance(allowed_mentions, AllowedMentions): + if allowed_mentions is not None and not isinstance( + allowed_mentions, AllowedMentions + ): raise TypeError("allowed_mentions parameter must be AllowedMentions") - self.allowed_mentions: Optional[AllowedMentions] = allowed_mentions - self._chunk_requests: Dict[Union[int, str], ChunkRequest] = {} + self.allowed_mentions: AllowedMentions | None = allowed_mentions + self._chunk_requests: dict[int | str, ChunkRequest] = {} activity = options.get("activity", None) if activity: @@ -211,26 +213,34 @@ def __init__( elif not isinstance(intents, Intents): raise TypeError(f"intents parameter must be Intent not {type(intents)!r}") if not intents.guilds: - _log.warning("Guilds intent seems to be disabled. This may cause state related issues.") + _log.warning( + "Guilds intent seems to be disabled. This may cause state related issues." + ) - self._chunk_guilds: bool = options.get("chunk_guilds_at_startup", intents.members) + self._chunk_guilds: bool = options.get( + "chunk_guilds_at_startup", intents.members + ) # Ensure these two are set properly if not intents.members and self._chunk_guilds: - raise ValueError("Intents.members must be enabled to chunk guilds at startup.") + raise ValueError( + "Intents.members must be enabled to chunk guilds at startup." + ) cache_flags = options.get("member_cache_flags", None) if cache_flags is None: cache_flags = MemberCacheFlags.from_intents(intents) elif not isinstance(cache_flags, MemberCacheFlags): - raise TypeError(f"member_cache_flags parameter must be MemberCacheFlags not {type(cache_flags)!r}") + raise TypeError( + f"member_cache_flags parameter must be MemberCacheFlags not {type(cache_flags)!r}" + ) else: cache_flags._verify_intents(intents) self.member_cache_flags: MemberCacheFlags = cache_flags - self._activity: Optional[ActivityPayload] = activity - self._status: Optional[str] = status + self._activity: ActivityPayload | None = activity + self._status: str | None = status self._intents: Intents = intents if not intents.members or cache_flags._empty: @@ -245,7 +255,7 @@ def __init__( self.clear() def clear(self, *, views: bool = True) -> None: - self.user: Optional[ClientUser] = None + self.user: ClientUser | None = None # Originally, this code used WeakValueDictionary to maintain references to the # global user mapping. @@ -258,26 +268,26 @@ def clear(self, *, views: bool = True) -> None: # references now using a regular dictionary with eviction being done # using __del__. Testing this for memory leaks led to no discernible leaks, # though more testing will have to be done. - self._users: Dict[int, User] = {} - self._emojis: Dict[int, Emoji] = {} - self._stickers: Dict[int, GuildSticker] = {} - self._guilds: Dict[int, Guild] = {} + self._users: dict[int, User] = {} + self._emojis: dict[int, Emoji] = {} + self._stickers: dict[int, GuildSticker] = {} + self._guilds: dict[int, Guild] = {} if views: self._view_store: ViewStore = ViewStore(self) self._modal_store: ModalStore = ModalStore(self) - self._voice_clients: Dict[int, VoiceProtocol] = {} + self._voice_clients: dict[int, VoiceProtocol] = {} # LRU of max size 128 self._private_channels: OrderedDict[int, PrivateChannel] = OrderedDict() # extra dict to look up private channels by user id - self._private_channels_by_user: Dict[int, DMChannel] = {} + self._private_channels_by_user: dict[int, DMChannel] = {} if self.max_messages is not None: - self._messages: Optional[Deque[Message]] = deque(maxlen=self.max_messages) + self._messages: Deque[Message] | None = deque(maxlen=self.max_messages) else: - self._messages: Optional[Deque[Message]] = None + self._messages: Deque[Message] | None = None def process_chunk_requests( - self, guild_id: int, nonce: Optional[str], members: List[Member], complete: bool + self, guild_id: int, nonce: str | None, members: list[Member], complete: bool ) -> None: removed = [] for key, request in self._chunk_requests.items(): @@ -307,7 +317,7 @@ async def call_hooks(self, key: str, *args: Any, **kwargs: Any) -> None: await coro(*args, **kwargs) @property - def self_id(self) -> Optional[int]: + def self_id(self) -> int | None: u = self.user return u.id if u else None @@ -318,10 +328,10 @@ def intents(self) -> Intents: return ret @property - def voice_clients(self) -> List[VoiceProtocol]: + def voice_clients(self) -> list[VoiceProtocol]: return list(self._voice_clients.values()) - def _get_voice_client(self, guild_id: Optional[int]) -> Optional[VoiceProtocol]: + def _get_voice_client(self, guild_id: int | None) -> VoiceProtocol | None: # the keys of self._voice_clients are ints return self._voice_clients.get(guild_id) # type: ignore @@ -355,7 +365,7 @@ def create_user(self, data: UserPayload) -> User: def deref_user_no_intents(self, user_id: int) -> None: return - def get_user(self, id: Optional[int]) -> Optional[User]: + def get_user(self, id: int | None) -> User | None: # the keys of self._users are ints return self._users.get(id) # type: ignore @@ -370,13 +380,13 @@ def store_sticker(self, guild: Guild, data: GuildStickerPayload) -> GuildSticker self._stickers[sticker_id] = sticker = GuildSticker(state=self, data=data) return sticker - def store_view(self, view: View, message_id: Optional[int] = None) -> None: + def store_view(self, view: View, message_id: int | None = None) -> None: self._view_store.add_view(view, message_id) def store_modal(self, modal: Modal, message_id: int) -> None: self._modal_store.add_modal(modal, message_id) - def prevent_view_updates_for(self, message_id: int) -> Optional[View]: + def prevent_view_updates_for(self, message_id: int) -> View | None: return self._view_store.remove_message_tracking(message_id) @property @@ -384,10 +394,10 @@ def persistent_views(self) -> Sequence[View]: return self._view_store.persistent_views @property - def guilds(self) -> List[Guild]: + def guilds(self) -> list[Guild]: return list(self._guilds.values()) - def _get_guild(self, guild_id: Optional[int]) -> Optional[Guild]: + def _get_guild(self, guild_id: int | None) -> Guild | None: # the keys of self._guilds are ints return self._guilds.get(guild_id) # type: ignore @@ -406,26 +416,26 @@ def _remove_guild(self, guild: Guild) -> None: del guild @property - def emojis(self) -> List[Emoji]: + def emojis(self) -> list[Emoji]: return list(self._emojis.values()) @property - def stickers(self) -> List[GuildSticker]: + def stickers(self) -> list[GuildSticker]: return list(self._stickers.values()) - def get_emoji(self, emoji_id: Optional[int]) -> Optional[Emoji]: + def get_emoji(self, emoji_id: int | None) -> Emoji | None: # the keys of self._emojis are ints return self._emojis.get(emoji_id) # type: ignore - def get_sticker(self, sticker_id: Optional[int]) -> Optional[GuildSticker]: + def get_sticker(self, sticker_id: int | None) -> GuildSticker | None: # the keys of self._stickers are ints return self._stickers.get(sticker_id) # type: ignore @property - def private_channels(self) -> List[PrivateChannel]: + def private_channels(self) -> list[PrivateChannel]: return list(self._private_channels.values()) - def _get_private_channel(self, channel_id: Optional[int]) -> Optional[PrivateChannel]: + def _get_private_channel(self, channel_id: int | None) -> PrivateChannel | None: try: # the keys of self._private_channels are ints value = self._private_channels[channel_id] # type: ignore @@ -435,7 +445,7 @@ def _get_private_channel(self, channel_id: Optional[int]) -> Optional[PrivateCha self._private_channels.move_to_end(channel_id) # type: ignore return value - def _get_private_channel_by_user(self, user_id: Optional[int]) -> Optional[DMChannel]: + def _get_private_channel_by_user(self, user_id: int | None) -> DMChannel | None: # the keys of self._private_channels are ints return self._private_channels_by_user.get(user_id) # type: ignore @@ -464,8 +474,12 @@ def _remove_private_channel(self, channel: PrivateChannel) -> None: if recipient is not None: self._private_channels_by_user.pop(recipient.id, None) - def _get_message(self, msg_id: Optional[int]) -> Optional[Message]: - return utils.find(lambda m: m.id == msg_id, reversed(self._messages)) if self._messages else None + def _get_message(self, msg_id: int | None) -> Message | None: + return ( + utils.find(lambda m: m.id == msg_id, reversed(self._messages)) + if self._messages + else None + ) def _add_guild_from_data(self, data: GuildPayload) -> Guild: guild = Guild(data=data, state=self) @@ -474,9 +488,15 @@ def _add_guild_from_data(self, data: GuildPayload) -> Guild: def _guild_needs_chunking(self, guild: Guild) -> bool: # If presences are enabled then we get back the old guild.large behaviour - return self._chunk_guilds and not guild.chunked and not (self._intents.presences and not guild.large) - - def _get_guild_channel(self, data: MessagePayload, guild_id: Optional[int] = None) -> Tuple[Union[Channel, Thread], Optional[Guild]]: + return ( + self._chunk_guilds + and not guild.chunked + and not (self._intents.presences and not guild.large) + ) + + def _get_guild_channel( + self, data: MessagePayload, guild_id: int | None = None + ) -> tuple[Channel | Thread, Guild | None]: channel_id = int(data["channel_id"]) try: guild = self._get_guild(int(guild_id or data["guild_id"])) @@ -495,17 +515,19 @@ async def chunker( limit: int = 0, presences: bool = False, *, - nonce: Optional[str] = None, + nonce: str | None = None, ) -> None: ws = self._get_websocket(guild_id) # This is ignored upstream - await ws.request_chunks(guild_id, query=query, limit=limit, presences=presences, nonce=nonce) + await ws.request_chunks( + guild_id, query=query, limit=limit, presences=presences, nonce=nonce + ) async def query_members( self, guild: Guild, query: str, limit: int, - user_ids: List[int], + user_ids: list[int], cache: bool, presences: bool, ): @@ -544,7 +566,9 @@ async def _delay_ready(self) -> None: # this snippet of code is basically waiting N seconds # until the last GUILD_CREATE was sent try: - guild = await asyncio.wait_for(self._ready_state.get(), timeout=self.guild_ready_timeout) + guild = await asyncio.wait_for( + self._ready_state.get(), timeout=self.guild_ready_timeout + ) except asyncio.TimeoutError: break else: @@ -617,20 +641,20 @@ def parse_resumed(self, data) -> None: def parse_application_command_permissions_update(self, data) -> None: # unsure what the implementation would be like pass - + def parse_auto_moderation_rule_create(self, data) -> None: rule = AutoModRule(state=self, data=data) self.dispatch("auto_moderation_rule_create", rule) - + def parse_auto_moderation_rule_update(self, data) -> None: # somehow get a 'before' object? rule = AutoModRule(state=self, data=data) self.dispatch("auto_moderation_rule_update", rule) - + def parse_auto_moderation_rule_delete(self, data) -> None: rule = AutoModRule(state=self, data=data) self.dispatch("auto_moderation_rule_delete", rule) - + def parse_auto_moderation_action_execution(self, data) -> None: event = AutoModActionExecutionEvent(self, data) self.dispatch("auto_moderation_action_execution", event) @@ -658,7 +682,9 @@ def parse_message_delete(self, data) -> None: def parse_message_delete_bulk(self, data) -> None: raw = RawBulkMessageDeleteEvent(data) if self._messages: - found_messages = [message for message in self._messages if message.id in raw.message_ids] + found_messages = [ + message for message in self._messages if message.id in raw.message_ids + ] else: found_messages = [] raw.cached_messages = found_messages @@ -690,7 +716,9 @@ def parse_message_update(self, data) -> None: def parse_message_reaction_add(self, data) -> None: emoji = data["emoji"] emoji_id = utils._get_as_snowflake(emoji, "id") - emoji = PartialEmoji.with_state(self, id=emoji_id, animated=emoji.get("animated", False), name=emoji["name"]) + emoji = PartialEmoji.with_state( + self, id=emoji_id, animated=emoji.get("animated", False), name=emoji["name"] + ) raw = RawReactionActionEvent(data, emoji, "REACTION_ADD") member_data = data.get("member") @@ -771,7 +799,9 @@ def parse_interaction_create(self, data) -> None: interaction.user.id, interaction.data["custom_id"], ) - asyncio.create_task(self._modal_store.dispatch(user_id, custom_id, interaction)) + asyncio.create_task( + self._modal_store.dispatch(user_id, custom_id, interaction) + ) self.dispatch("interaction", interaction) @@ -898,7 +928,11 @@ def parse_channel_pins_update(self, data) -> None: ) return - last_pin = utils.parse_time(data["last_pin_timestamp"]) if data["last_pin_timestamp"] else None + last_pin = ( + utils.parse_time(data["last_pin_timestamp"]) + if data["last_pin_timestamp"] + else None + ) if guild is None: self.dispatch("private_channel_pins_update", channel, last_pin) @@ -907,7 +941,7 @@ def parse_channel_pins_update(self, data) -> None: def parse_thread_create(self, data) -> None: guild_id = int(data["guild_id"]) - guild: Optional[Guild] = self._get_guild(guild_id) + guild: Guild | None = self._get_guild(guild_id) if guild is None: _log.debug( "THREAD_CREATE referencing an unknown guild ID: %s. Discarding", @@ -971,7 +1005,7 @@ def parse_thread_delete(self, data) -> None: def parse_thread_list_sync(self, data) -> None: guild_id = int(data["guild_id"]) - guild: Optional[Guild] = self._get_guild(guild_id) + guild: Guild | None = self._get_guild(guild_id) if guild is None: _log.debug( "THREAD_LIST_SYNC referencing an unknown guild ID: %s. Discarding", @@ -1010,7 +1044,7 @@ def parse_thread_list_sync(self, data) -> None: def parse_thread_member_update(self, data) -> None: guild_id = int(data["guild_id"]) - guild: Optional[Guild] = self._get_guild(guild_id) + guild: Guild | None = self._get_guild(guild_id) if guild is None: _log.debug( "THREAD_MEMBER_UPDATE referencing an unknown guild ID: %s. Discarding", @@ -1019,7 +1053,7 @@ def parse_thread_member_update(self, data) -> None: return thread_id = int(data["id"]) - thread: Optional[Thread] = guild.get_thread(thread_id) + thread: Thread | None = guild.get_thread(thread_id) if thread is None: _log.debug( "THREAD_MEMBER_UPDATE referencing an unknown thread ID: %s. Discarding", @@ -1032,7 +1066,7 @@ def parse_thread_member_update(self, data) -> None: def parse_thread_members_update(self, data) -> None: guild_id = int(data["guild_id"]) - guild: Optional[Guild] = self._get_guild(guild_id) + guild: Guild | None = self._get_guild(guild_id) if guild is None: _log.debug( "THREAD_MEMBERS_UPDATE referencing an unknown guild ID: %s. Discarding", @@ -1041,7 +1075,7 @@ def parse_thread_members_update(self, data) -> None: return thread_id = int(data["id"]) - thread: Optional[Thread] = guild.get_thread(thread_id) + thread: Thread | None = guild.get_thread(thread_id) if thread is None: _log.debug( "THREAD_MEMBERS_UPDATE referencing an unknown thread ID: %s. Discarding", @@ -1194,7 +1228,9 @@ async def chunk_guild(self, guild, *, wait=True, cache=None): cache = cache or self.member_cache_flags.joined request = self._chunk_requests.get(guild.id) if request is None: - self._chunk_requests[guild.id] = request = ChunkRequest(guild.id, self.loop, self._get_guild, cache=cache) + self._chunk_requests[guild.id] = request = ChunkRequest( + guild.id, self.loop, self._get_guild, cache=cache + ) await self.chunker(guild.id, nonce=request.nonce) if wait: @@ -1270,7 +1306,7 @@ def parse_guild_delete(self, data) -> None: # do a cleanup of the messages cache if self._messages is not None: - self._messages: Optional[Deque[Message]] = deque( + self._messages: Deque[Message] | None = deque( (msg for msg in self._messages if msg.guild != guild), maxlen=self.max_messages, ) @@ -1353,7 +1389,9 @@ def parse_guild_members_chunk(self, data) -> None: # the guild won't be None here members = [Member(guild=guild, data=member, state=self) for member in data.get("members", [])] # type: ignore - _log.debug("Processed a chunk for %s members in guild ID %s.", len(members), guild_id) + _log.debug( + "Processed a chunk for %s members in guild ID %s.", len(members), guild_id + ) if presences: member_dict = {str(member.id): member for member in members} @@ -1376,8 +1414,14 @@ def parse_guild_scheduled_event_create(self, data) -> None: ) return - creator = None if not data.get("creator", None) else guild.get_member(data.get("creator_id")) - scheduled_event = ScheduledEvent(state=self, guild=guild, creator=creator, data=data) + creator = ( + None + if not data.get("creator", None) + else guild.get_member(data.get("creator_id")) + ) + scheduled_event = ScheduledEvent( + state=self, guild=guild, creator=creator, data=data + ) guild._add_scheduled_event(scheduled_event) self.dispatch("scheduled_event_create", scheduled_event) @@ -1390,8 +1434,14 @@ def parse_guild_scheduled_event_update(self, data) -> None: ) return - creator = None if not data.get("creator", None) else guild.get_member(data.get("creator_id")) - scheduled_event = ScheduledEvent(state=self, guild=guild, creator=creator, data=data) + creator = ( + None + if not data.get("creator", None) + else guild.get_member(data.get("creator_id")) + ) + scheduled_event = ScheduledEvent( + state=self, guild=guild, creator=creator, data=data + ) old_event = guild.get_scheduled_event(int(data["id"])) guild._add_scheduled_event(scheduled_event) self.dispatch("scheduled_event_update", old_event, scheduled_event) @@ -1405,8 +1455,14 @@ def parse_guild_scheduled_event_delete(self, data) -> None: ) return - creator = None if not data.get("creator", None) else guild.get_member(data.get("creator_id")) - scheduled_event = ScheduledEvent(state=self, guild=guild, creator=creator, data=data) + creator = ( + None + if not data.get("creator", None) + else guild.get_member(data.get("creator_id")) + ) + scheduled_event = ScheduledEvent( + state=self, guild=guild, creator=creator, data=data + ) scheduled_event.status = ScheduledEventStatus.canceled guild._remove_scheduled_event(scheduled_event) self.dispatch("scheduled_event_delete", scheduled_event) @@ -1538,7 +1594,9 @@ def parse_stage_instance_update(self, data) -> None: if stage_instance is not None: old_stage_instance = copy.copy(stage_instance) stage_instance._update(data) - self.dispatch("stage_instance_update", old_stage_instance, stage_instance) + self.dispatch( + "stage_instance_update", old_stage_instance, stage_instance + ) else: _log.debug( "STAGE_INSTANCE_UPDATE referencing unknown stage instance ID: %s. Discarding.", @@ -1576,11 +1634,19 @@ def parse_voice_state_update(self, data) -> None: voice = self._get_voice_client(guild.id) if voice is not None: coro = voice.on_voice_state_update(data) - asyncio.create_task(logging_coroutine(coro, info="Voice Protocol voice state update handler")) + asyncio.create_task( + logging_coroutine( + coro, info="Voice Protocol voice state update handler" + ) + ) member, before, after = guild._update_voice_state(data, channel_id) # type: ignore if member is not None: if flags.voice: - if channel_id is None and flags._voice_only and member.id != self_id: + if ( + channel_id is None + and flags._voice_only + and member.id != self_id + ): # Only remove from cache if we only have the voice flag enabled # Member doesn't meet the Snowflake protocol currently guild._remove_member(member) # type: ignore @@ -1603,7 +1669,11 @@ def parse_voice_server_update(self, data) -> None: vc = self._get_voice_client(key_id) if vc is not None: coro = vc.on_voice_server_update(data) - asyncio.create_task(logging_coroutine(coro, info="Voice Protocol voice server update handler")) + asyncio.create_task( + logging_coroutine( + coro, info="Voice Protocol voice server update handler" + ) + ) def parse_typing_start(self, data) -> None: raw = RawTypingEvent(data) @@ -1626,7 +1696,9 @@ def parse_typing_start(self, data) -> None: if user is not None: self.dispatch("typing", channel, user, raw.when) - def _get_typing_user(self, channel: Optional[MessageableChannel], user_id: int) -> Optional[Union[User, Member]]: + def _get_typing_user( + self, channel: MessageableChannel | None, user_id: int + ) -> User | Member | None: if isinstance(channel, DMChannel): return channel.recipient or self.get_user(user_id) @@ -1638,12 +1710,14 @@ def _get_typing_user(self, channel: Optional[MessageableChannel], user_id: int) return self.get_user(user_id) - def _get_reaction_user(self, channel: MessageableChannel, user_id: int) -> Optional[Union[User, Member]]: + def _get_reaction_user( + self, channel: MessageableChannel, user_id: int + ) -> User | Member | None: if isinstance(channel, TextChannel): return channel.guild.get_member(user_id) return self.get_user(user_id) - def get_reaction_emoji(self, data) -> Union[Emoji, PartialEmoji]: + def get_reaction_emoji(self, data) -> Emoji | PartialEmoji: emoji_id = utils._get_as_snowflake(data, "id") if not emoji_id: @@ -1659,7 +1733,7 @@ def get_reaction_emoji(self, data) -> Union[Emoji, PartialEmoji]: name=data["name"], ) - def _upgrade_partial_emoji(self, emoji: PartialEmoji) -> Union[Emoji, PartialEmoji, str]: + def _upgrade_partial_emoji(self, emoji: PartialEmoji) -> Emoji | PartialEmoji | str: emoji_id = emoji.id if not emoji_id: return emoji.name @@ -1668,7 +1742,7 @@ def _upgrade_partial_emoji(self, emoji: PartialEmoji) -> Union[Emoji, PartialEmo except KeyError: return emoji - def get_channel(self, id: Optional[int]) -> Optional[Union[Channel, Thread]]: + def get_channel(self, id: int | None) -> Channel | Thread | None: if id is None: return None @@ -1693,7 +1767,7 @@ def create_message( class AutoShardedConnectionState(ConnectionState): def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) - self.shard_ids: Union[List[int], range] = [] + self.shard_ids: list[int] | range = [] self.shards_launched: asyncio.Event = asyncio.Event() def _update_message_references(self) -> None: @@ -1705,7 +1779,9 @@ def _update_message_references(self) -> None: new_guild = self._get_guild(msg.guild.id) if new_guild is not None and new_guild is not msg.guild: channel_id = msg.channel.id - channel = new_guild._resolve_channel(channel_id) or Object(id=channel_id) + channel = new_guild._resolve_channel(channel_id) or Object( + id=channel_id + ) # channel will either be a TextChannel, Thread or Object msg._rebind_cached_references(new_guild, channel) # type: ignore @@ -1716,11 +1792,13 @@ async def chunker( limit: int = 0, presences: bool = False, *, - shard_id: Optional[int] = None, - nonce: Optional[str] = None, + shard_id: int | None = None, + nonce: str | None = None, ) -> None: ws = self._get_websocket(guild_id, shard_id=shard_id) - await ws.request_chunks(guild_id, query=query, limit=limit, presences=presences, nonce=nonce) + await ws.request_chunks( + guild_id, query=query, limit=limit, presences=presences, nonce=nonce + ) async def _delay_ready(self) -> None: await self.shards_launched.wait() @@ -1731,7 +1809,9 @@ async def _delay_ready(self) -> None: # this snippet of code is basically waiting N seconds # until the last GUILD_CREATE was sent try: - guild = await asyncio.wait_for(self._ready_state.get(), timeout=self.guild_ready_timeout) + guild = await asyncio.wait_for( + self._ready_state.get(), timeout=self.guild_ready_timeout + ) except asyncio.TimeoutError: break else: @@ -1742,7 +1822,9 @@ async def _delay_ready(self) -> None: ) if len(current_bucket) >= max_concurrency: try: - await utils.sane_wait_for(current_bucket, timeout=max_concurrency * 70.0) + await utils.sane_wait_for( + current_bucket, timeout=max_concurrency * 70.0 + ) except asyncio.TimeoutError: fmt = "Shard ID %s failed to wait for chunks from a sub-bucket with length %d" _log.warning(fmt, guild.shard_id, len(current_bucket)) @@ -1808,7 +1890,9 @@ def parse_ready(self, data) -> None: pass else: self.application_id = utils._get_as_snowflake(application, "id") - self.application_flags = ApplicationFlags._from_value(application["flags"]) + self.application_flags = ApplicationFlags._from_value( + application["flags"] + ) for guild_data in data["guilds"]: self._add_guild_from_data(guild_data) diff --git a/discord/sticker.py b/discord/sticker.py index 70f99ab45a..8a9fe92af1 100644 --- a/discord/sticker.py +++ b/discord/sticker.py @@ -26,7 +26,7 @@ from __future__ import annotations import unicodedata -from typing import TYPE_CHECKING, List, Literal, Optional, Tuple, Type, Union +from typing import TYPE_CHECKING, Literal from .asset import Asset, AssetMixin from .enums import StickerFormatType, StickerType, try_enum @@ -77,7 +77,7 @@ class StickerPack(Hashable): Checks if the sticker pack is not equal to another sticker pack. Attributes - ----------- + ---------- name: :class:`str` The name of the sticker pack. description: :class:`str` @@ -113,7 +113,7 @@ def __init__(self, *, state: ConnectionState, data: StickerPackPayload) -> None: def _from_data(self, data: StickerPackPayload) -> None: self.id: int = int(data["id"]) stickers = data["stickers"] - self.stickers: List[StandardSticker] = [ + self.stickers: list[StandardSticker] = [ StandardSticker(state=self._state, data=sticker) for sticker in stickers ] self.name: str = data["name"] @@ -150,6 +150,11 @@ async def read(self) -> bytes: Stickers that use the :attr:`StickerFormatType.lottie` format cannot be read. + Returns + ------- + :class:`bytes` + The content of the asset. + Raises ------ HTTPException @@ -158,11 +163,6 @@ async def read(self) -> bytes: The asset was deleted. TypeError The sticker is a lottie type. - - Returns - ------- - :class:`bytes` - The content of the asset. """ if self.format is StickerFormatType.lottie: raise TypeError('Cannot read stickers of format "lottie".') @@ -189,7 +189,7 @@ class StickerItem(_StickerTag): Checks if the sticker item is not equal to another sticker item. Attributes - ----------- + ---------- name: :class:`str` The sticker's name. id: :class:`int` @@ -206,7 +206,9 @@ def __init__(self, *, state: ConnectionState, data: StickerItemPayload): self._state: ConnectionState = state self.name: str = data["name"] self.id: int = int(data["id"]) - self.format: StickerFormatType = try_enum(StickerFormatType, data["format_type"]) + self.format: StickerFormatType = try_enum( + StickerFormatType, data["format_type"] + ) self.url: str = f"{Asset.BASE}/stickers/{self.id}.{self.format.file_extension}" def __repr__(self) -> str: @@ -215,20 +217,20 @@ def __repr__(self) -> str: def __str__(self) -> str: return self.name - async def fetch(self) -> Union[Sticker, StandardSticker, GuildSticker]: + async def fetch(self) -> Sticker | StandardSticker | GuildSticker: """|coro| Attempts to retrieve the full sticker data of the sticker item. - Raises - -------- - HTTPException - Retrieving the sticker failed. - Returns - -------- + ------- Union[:class:`StandardSticker`, :class:`GuildSticker`] The retrieved sticker. + + Raises + ------ + HTTPException + Retrieving the sticker failed. """ data: StickerPayload = await self._state.http.get_sticker(self.id) cls, _ = _sticker_factory(data["type"]) # type: ignore @@ -280,7 +282,9 @@ def _from_data(self, data: StickerPayload) -> None: self.id: int = int(data["id"]) self.name: str = data["name"] self.description: str = data["description"] - self.format: StickerFormatType = try_enum(StickerFormatType, data["format_type"]) + self.format: StickerFormatType = try_enum( + StickerFormatType, data["format_type"] + ) self.url: str = f"{Asset.BASE}/stickers/{self.id}.{self.format.file_extension}" def __repr__(self) -> str: @@ -341,31 +345,35 @@ def _from_data(self, data: StandardStickerPayload) -> None: self.type: StickerType = StickerType.standard try: - self.tags: List[str] = [tag.strip() for tag in data["tags"].split(",")] + self.tags: list[str] = [tag.strip() for tag in data["tags"].split(",")] except KeyError: self.tags = [] def __repr__(self) -> str: - return f"" + return ( + f"" + ) async def pack(self) -> StickerPack: """|coro| Retrieves the sticker pack that this sticker belongs to. + Returns + ------- + :class:`StickerPack` + The retrieved sticker pack. + Raises - -------- + ------ InvalidData The corresponding sticker pack was not found. HTTPException Retrieving the sticker pack failed. - - Returns - -------- - :class:`StickerPack` - The retrieved sticker pack. """ - data: ListPremiumStickerPacksPayload = await self._state.http.list_premium_sticker_packs() + data: ListPremiumStickerPacksPayload = ( + await self._state.http.list_premium_sticker_packs() + ) packs = data["sticker_packs"] pack = find(lambda d: int(d["id"]) == self.pack_id, packs) @@ -421,7 +429,7 @@ def _from_data(self, data: GuildStickerPayload) -> None: self.available: bool = data["available"] self.guild_id: int = int(data["guild_id"]) user = data.get("user") - self.user: Optional[User] = self._state.store_user(user) if user else None + self.user: User | None = self._state.store_user(user) if user else None self.emoji: str = data["tags"] self.type: StickerType = StickerType.guild @@ -429,7 +437,7 @@ def __repr__(self) -> str: return f"" @cached_slot_property("_cs_guild") - def guild(self) -> Optional[Guild]: + def guild(self) -> Guild | None: """Optional[:class:`Guild`]: The guild that this sticker is from. Could be ``None`` if the bot is not in the guild. @@ -443,14 +451,14 @@ async def edit( name: str = MISSING, description: str = MISSING, emoji: str = MISSING, - reason: Optional[str] = None, + reason: str | None = None, ) -> GuildSticker: """|coro| Edits a :class:`GuildSticker` for the guild. Parameters - ----------- + ---------- name: :class:`str` The sticker's new name. Must be at least 2 characters. description: Optional[:class:`str`] @@ -460,17 +468,17 @@ async def edit( reason: :class:`str` The reason for editing this sticker. Shows up on the audit log. - Raises + Returns ------- + :class:`GuildSticker` + The newly modified sticker. + + Raises + ------ Forbidden You are not allowed to edit stickers. HTTPException An error occurred editing the sticker. - - Returns - -------- - :class:`GuildSticker` - The newly modified sticker. """ payload: EditGuildSticker = {} @@ -490,10 +498,12 @@ async def edit( payload["tags"] = emoji - data: GuildStickerPayload = await self._state.http.modify_guild_sticker(self.guild_id, self.id, payload, reason) + data: GuildStickerPayload = await self._state.http.modify_guild_sticker( + self.guild_id, self.id, payload, reason + ) return GuildSticker(state=self._state, data=data) - async def delete(self, *, reason: Optional[str] = None) -> None: + async def delete(self, *, reason: str | None = None) -> None: """|coro| Deletes the custom :class:`Sticker` from the guild. @@ -502,12 +512,12 @@ async def delete(self, *, reason: Optional[str] = None) -> None: do this. Parameters - ----------- + ---------- reason: Optional[:class:`str`] The reason for deleting this sticker. Shows up on the audit log. Raises - ------- + ------ Forbidden You are not allowed to delete stickers. HTTPException @@ -518,7 +528,7 @@ async def delete(self, *, reason: Optional[str] = None) -> None: def _sticker_factory( sticker_type: Literal[1, 2] -) -> Tuple[Type[Union[StandardSticker, GuildSticker, Sticker]], StickerType]: +) -> tuple[type[StandardSticker | GuildSticker | Sticker], StickerType]: value = try_enum(StickerType, sticker_type) if value == StickerType.standard: return StandardSticker, value diff --git a/discord/team.py b/discord/team.py index d1087bfce4..a054836754 100644 --- a/discord/team.py +++ b/discord/team.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List, Optional +from typing import TYPE_CHECKING from . import utils from .asset import Asset @@ -47,7 +47,7 @@ class Team: """Represents an application team for a bot provided by Discord. Attributes - ------------- + ---------- id: :class:`int` The team ID. name: :class:`str` @@ -67,22 +67,24 @@ def __init__(self, state: ConnectionState, data: TeamPayload): self.id: int = int(data["id"]) self.name: str = data["name"] - self._icon: Optional[str] = data["icon"] - self.owner_id: Optional[int] = utils._get_as_snowflake(data, "owner_user_id") - self.members: List[TeamMember] = [TeamMember(self, self._state, member) for member in data["members"]] + self._icon: str | None = data["icon"] + self.owner_id: int | None = utils._get_as_snowflake(data, "owner_user_id") + self.members: list[TeamMember] = [ + TeamMember(self, self._state, member) for member in data["members"] + ] def __repr__(self) -> str: return f"<{self.__class__.__name__} id={self.id} name={self.name}>" @property - def icon(self) -> Optional[Asset]: + def icon(self) -> Asset | None: """Optional[:class:`.Asset`]: Retrieves the team's icon asset, if any.""" if self._icon is None: return None return Asset._from_icon(self._state, self.id, self._icon, path="team") @property - def owner(self) -> Optional[TeamMember]: + def owner(self) -> TeamMember | None: """Optional[:class:`TeamMember`]: The team's owner.""" return utils.get(self.members, id=self.owner_id) @@ -111,7 +113,7 @@ class TeamMember(BaseUser): .. versionadded:: 1.3 Attributes - ------------- + ---------- name: :class:`str` The team member's username. id: :class:`int` @@ -132,8 +134,10 @@ class TeamMember(BaseUser): def __init__(self, team: Team, state: ConnectionState, data: TeamMemberPayload): self.team: Team = team - self.membership_state: TeamMembershipState = try_enum(TeamMembershipState, data["membership_state"]) - self.permissions: List[str] = data["permissions"] + self.membership_state: TeamMembershipState = try_enum( + TeamMembershipState, data["membership_state"] + ) + self.permissions: list[str] = data["permissions"] super().__init__(state=state, data=data["user"]) def __repr__(self) -> str: diff --git a/discord/template.py b/discord/template.py index e52e2d582d..c13b70d106 100644 --- a/discord/template.py +++ b/discord/template.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any from .guild import Guild from .utils import MISSING, _bytes_to_base64_data, parse_time @@ -93,7 +93,7 @@ class Template: .. versionadded:: 1.4 Attributes - ----------- + ---------- code: :class:`str` The template code. uses: :class:`int` @@ -138,15 +138,17 @@ def _store(self, data: TemplatePayload) -> None: self.code: str = data["code"] self.uses: int = data["usage_count"] self.name: str = data["name"] - self.description: Optional[str] = data["description"] + self.description: str | None = data["description"] creator_data = data.get("creator") - self.creator: Optional[User] = None if creator_data is None else self._state.create_user(creator_data) + self.creator: User | None = ( + None if creator_data is None else self._state.create_user(creator_data) + ) - self.created_at: Optional[datetime.datetime] = parse_time(data.get("created_at")) - self.updated_at: Optional[datetime.datetime] = parse_time(data.get("updated_at")) + self.created_at: datetime.datetime | None = parse_time(data.get("created_at")) + self.updated_at: datetime.datetime | None = parse_time(data.get("updated_at")) guild_id = int(data["source_guild_id"]) - guild: Optional[Guild] = self._state._get_guild(guild_id) + guild: Guild | None = self._state._get_guild(guild_id) self.source_guild: Guild if guild is None: @@ -158,7 +160,7 @@ def _store(self, data: TemplatePayload) -> None: else: self.source_guild = guild - self.is_dirty: Optional[bool] = data.get("is_dirty", None) + self.is_dirty: bool | None = data.get("is_dirty", None) def __repr__(self) -> str: return ( @@ -181,18 +183,18 @@ async def create_guild(self, name: str, icon: Any = None) -> Guild: The :term:`py:bytes-like object` representing the icon. See :meth:`.ClientUser.edit` for more details on what is expected. + Returns + ------- + :class:`.Guild` + The guild created. This is not the same guild that is + added to cache. + Raises ------ HTTPException Guild creation failed. InvalidArgument Invalid icon image format given. Must be PNG or JPG. - - Returns - ------- - :class:`.Guild` - The guild created. This is not the same guild that is - added to cache. """ if icon is not None: icon = _bytes_to_base64_data(icon) @@ -213,19 +215,19 @@ async def sync(self) -> Template: .. versionchanged:: 2.0 The template is no longer synced in-place, instead it is returned. - Raises + Returns ------- + :class:`Template` + The newly synced template. + + Raises + ------ HTTPException Syncing the template failed. Forbidden You don't have permissions to sync the template. NotFound This template does not exist. - - Returns - -------- - :class:`Template` - The newly synced template. """ data = await self._state.http.sync_template(self.source_guild.id, self.code) @@ -235,7 +237,7 @@ async def edit( self, *, name: str = MISSING, - description: Optional[str] = MISSING, + description: str | None = MISSING, ) -> Template: """|coro| @@ -250,25 +252,25 @@ async def edit( The template is no longer edited in-place, instead it is returned. Parameters - ------------ + ---------- name: :class:`str` The template's new name. description: Optional[:class:`str`] The template's new description. - Raises + Returns ------- + :class:`Template` + The newly edited template. + + Raises + ------ HTTPException Editing the template failed. Forbidden You don't have permissions to edit the template. NotFound This template does not exist. - - Returns - -------- - :class:`Template` - The newly edited template. """ payload = {} @@ -277,7 +279,9 @@ async def edit( if description is not MISSING: payload["description"] = description - data = await self._state.http.edit_template(self.source_guild.id, self.code, payload) + data = await self._state.http.edit_template( + self.source_guild.id, self.code, payload + ) return Template(state=self._state, data=data) async def delete(self) -> None: @@ -291,7 +295,7 @@ async def delete(self) -> None: .. versionadded:: 1.7 Raises - ------- + ------ HTTPException Deleting the template failed. Forbidden diff --git a/discord/threads.py b/discord/threads.py index 60acdd314d..cf34e854e9 100644 --- a/discord/threads.py +++ b/discord/threads.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Optional, Union +from typing import TYPE_CHECKING, Callable, Iterable from .abc import Messageable, _purge_messages_helper from .enums import ChannelType, try_enum @@ -79,7 +79,7 @@ class Thread(Messageable, Hashable): .. versionadded:: 2.0 Attributes - ----------- + ---------- name: :class:`str` The thread name. guild: :class:`Guild` @@ -156,7 +156,7 @@ class Thread(Messageable, Hashable): def __init__(self, *, guild: Guild, state: ConnectionState, data: ThreadPayload): self._state: ConnectionState = state self.guild = guild - self._members: Dict[int, ThreadMember] = {} + self._members: dict[int, ThreadMember] = {} self._from_data(data) async def _get_channel(self): @@ -179,7 +179,11 @@ def _from_data(self, data: ThreadPayload): self._type = try_enum(ChannelType, data["type"]) # This data may be missing depending on how this object is being created - self.owner_id = int(data.get("owner_id")) if data.get("owner_id", None) is not None else None + self.owner_id = ( + int(data.get("owner_id")) + if data.get("owner_id", None) is not None + else None + ) self.last_message_id = _get_as_snowflake(data, "last_message_id") self.slowmode_delay = data.get("rate_limit_per_user", 0) self.message_count = data.get("message_count", None) @@ -189,9 +193,19 @@ def _from_data(self, data: ThreadPayload): # Here, we try to fill in potentially missing data if thread := self.guild.get_thread(self.id) and data.pop("_invoke_flag", False): self.owner_id = thread.owner_id if self.owner_id is None else self.owner_id - self.last_message_id = thread.last_message_id if self.last_message_id is None else self.last_message_id - self.message_count = thread.message_count if self.message_count is None else self.message_count - self.member_count = thread.member_count if self.member_count is None else self.member_count + self.last_message_id = ( + thread.last_message_id + if self.last_message_id is None + else self.last_message_id + ) + self.message_count = ( + thread.message_count + if self.message_count is None + else self.message_count + ) + self.member_count = ( + thread.member_count if self.member_count is None else self.member_count + ) self._unroll_metadata(data["thread_metadata"]) @@ -230,12 +244,12 @@ def type(self) -> ChannelType: return self._type @property - def parent(self) -> Optional[Union[TextChannel, ForumChannel]]: + def parent(self) -> TextChannel | ForumChannel | None: """Optional[:class:`TextChannel`]: The parent channel this thread belongs to.""" return self.guild.get_channel(self.parent_id) # type: ignore @property - def owner(self) -> Optional[Member]: + def owner(self) -> Member | None: """Optional[:class:`Member`]: The member this thread belongs to.""" return self.guild.get_member(self.owner_id) @@ -253,7 +267,7 @@ def jump_url(self) -> str: return f"https://discord.com/channels/{self.guild.id}/{self.id}" @property - def members(self) -> List[ThreadMember]: + def members(self) -> list[ThreadMember]: """List[:class:`ThreadMember`]: A list of thread members in this thread. This requires :attr:`Intents.members` to be properly filled. Most of the time however, @@ -263,7 +277,7 @@ def members(self) -> List[ThreadMember]: return list(self._members.values()) @property - def last_message(self) -> Optional[Message]: + def last_message(self) -> Message | None: """Returns the last message from this thread in cache. The message might not be valid or point to an existing message. @@ -277,25 +291,29 @@ def last_message(self) -> Optional[Message]: attribute. Returns - -------- + ------- Optional[:class:`Message`] The last message in this channel or ``None`` if not found. """ - return self._state._get_message(self.last_message_id) if self.last_message_id else None + return ( + self._state._get_message(self.last_message_id) + if self.last_message_id + else None + ) @property - def category(self) -> Optional[CategoryChannel]: + def category(self) -> CategoryChannel | None: """The category channel the parent channel belongs to, if applicable. - Raises - ------- - ClientException - The parent channel was not cached and returned ``None``. - Returns - -------- + ------- Optional[:class:`CategoryChannel`] The parent channel's category. + + Raises + ------ + ClientException + The parent channel was not cached and returned ``None``. """ parent = self.parent @@ -304,18 +322,18 @@ def category(self) -> Optional[CategoryChannel]: return parent.category @property - def category_id(self) -> Optional[int]: + def category_id(self) -> int | None: """The category channel ID the parent channel belongs to, if applicable. - Raises - ------- - ClientException - The parent channel was not cached and returned ``None``. - Returns - -------- + ------- Optional[:class:`int`] The parent channel's category ID. + + Raises + ------ + ClientException + The parent channel was not cached and returned ``None``. """ parent = self.parent @@ -324,7 +342,7 @@ def category_id(self) -> Optional[int]: return parent.category_id @property - def starting_message(self) -> Optional[Message]: + def starting_message(self) -> Message | None: """Returns the message that started this thread. The message might not be valid or point to an existing message. @@ -333,7 +351,7 @@ def starting_message(self) -> Optional[Message]: The ID for this message is the same as the thread ID. Returns - -------- + ------- Optional[:class:`Message`] The message that started this thread or ``None`` if not found in the cache. """ @@ -364,7 +382,7 @@ def is_nsfw(self) -> bool: parent = self.parent return parent is not None and parent.is_nsfw() - def permissions_for(self, obj: Union[Member, Role], /) -> Permissions: + def permissions_for(self, obj: Member | Role, /) -> Permissions: """Handles permission resolution for the :class:`~discord.Member` or :class:`~discord.Role`. @@ -380,15 +398,15 @@ def permissions_for(self, obj: Union[Member, Role], /) -> Permissions: a member or a role. If it's a role then member overwrites are not computed. - Raises - ------- - ClientException - The parent channel was not cached and returned ``None`` - Returns ------- :class:`~discord.Permissions` The resolved permissions for the member or role. + + Raises + ------ + ClientException + The parent channel was not cached and returned ``None`` """ parent = self.parent @@ -396,7 +414,9 @@ def permissions_for(self, obj: Union[Member, Role], /) -> Permissions: raise ClientException("Parent channel not found") return parent.permissions_for(obj) - async def delete_messages(self, messages: Iterable[Snowflake], *, reason: Optional[str] = None) -> None: + async def delete_messages( + self, messages: Iterable[Snowflake], *, reason: str | None = None + ) -> None: """|coro| Deletes a list of messages. This is similar to :meth:`Message.delete` @@ -415,7 +435,7 @@ async def delete_messages(self, messages: Iterable[Snowflake], *, reason: Option Usable only by bot accounts. Parameters - ----------- + ---------- messages: Iterable[:class:`abc.Snowflake`] An iterable of messages denoting which ones to bulk delete. reason: Optional[:class:`str`] @@ -453,15 +473,15 @@ async def delete_messages(self, messages: Iterable[Snowflake], *, reason: Option async def purge( self, *, - limit: Optional[int] = 100, + limit: int | None = 100, check: Callable[[Message], bool] = MISSING, - before: Optional[SnowflakeTime] = None, - after: Optional[SnowflakeTime] = None, - around: Optional[SnowflakeTime] = None, - oldest_first: Optional[bool] = False, + before: SnowflakeTime | None = None, + after: SnowflakeTime | None = None, + around: SnowflakeTime | None = None, + oldest_first: bool | None = False, bulk: bool = True, - reason: Optional[str] = None, - ) -> List[Message]: + reason: str | None = None, + ) -> list[Message]: """|coro| Purges a list of messages that meet the criteria given by the predicate @@ -473,19 +493,8 @@ async def purge( account). The :attr:`~Permissions.read_message_history` permission is also needed to retrieve message history. - Examples - --------- - - Deleting bot's messages :: - - def is_me(m): - return m.author == client.user - - deleted = await thread.purge(limit=100, check=is_me) - await thread.send(f'Deleted {len(deleted)} message(s)') - Parameters - ----------- + ---------- limit: Optional[:class:`int`] The number of messages to search through. This is not the number of messages that will be deleted, though it can be. @@ -507,17 +516,28 @@ def is_me(m): reason: Optional[:class:`str`] The reason for deleting the messages. Shows up on the audit log. - Raises + Returns ------- + List[:class:`.Message`] + The list of messages that were deleted. + + Raises + ------ Forbidden You do not have proper permissions to do the actions required. HTTPException Purging the messages failed. - Returns + Examples -------- - List[:class:`.Message`] - The list of messages that were deleted. + + Deleting bot's messages :: + + def is_me(m): + return m.author == client.user + + deleted = await thread.purge(limit=100, check=is_me) + await thread.send(f'Deleted {len(deleted)} message(s)') """ return await _purge_messages_helper( self, @@ -541,7 +561,7 @@ async def edit( slowmode_delay: int = MISSING, auto_archive_duration: ThreadArchiveDuration = MISSING, pinned: bool = MISSING, - reason: Optional[str] = None, + reason: str | None = None, ) -> Thread: """|coro| @@ -555,7 +575,7 @@ async def edit( The thread must be unarchived to be edited. Parameters - ------------ + ---------- name: :class:`str` The new name of the thread. archived: :class:`bool` @@ -576,17 +596,17 @@ async def edit( pinned: :class:`bool` Whether to pin the thread or not. This only works if the thread is part of a forum. - Raises + Returns ------- + :class:`Thread` + The newly edited thread. + + Raises + ------ Forbidden You do not have permissions to edit the thread. HTTPException Editing the thread failed. - - Returns - -------- - :class:`Thread` - The newly edited thread. """ payload = {} if name is not MISSING: @@ -617,13 +637,12 @@ async def archive(self, locked: bool = MISSING) -> Thread: Archives the thread. This is a shorthand of :meth:`.edit`. Parameters - ------------ + ---------- locked: :class:`bool` Whether to lock the thread on archive, Defaults to ``False``. - Returns - -------- + ------- :class:`.Thread` The updated thread. """ @@ -635,7 +654,7 @@ async def unarchive(self) -> Thread: Unarchives the thread. This is a shorthand of :meth:`.edit`. Returns - -------- + ------- :class:`.Thread` The updated thread. """ @@ -650,7 +669,7 @@ async def join(self): If the thread is private, :attr:`~Permissions.manage_threads` is also needed. Raises - ------- + ------ Forbidden You do not have permissions to join the thread. HTTPException @@ -664,7 +683,7 @@ async def leave(self): Leaves this thread. Raises - ------- + ------ HTTPException Leaving the thread failed. """ @@ -681,12 +700,12 @@ async def add_user(self, user: Snowflake): :attr:`~Permissions.manage_threads` is required. Parameters - ----------- + ---------- user: :class:`abc.Snowflake` The user to add to the thread. Raises - ------- + ------ Forbidden You do not have permissions to add the user to the thread. HTTPException @@ -702,12 +721,12 @@ async def remove_user(self, user: Snowflake): You must have :attr:`~Permissions.manage_threads` or be the creator of the thread to remove a user. Parameters - ----------- + ---------- user: :class:`abc.Snowflake` The user to remove from the thread. Raises - ------- + ------ Forbidden You do not have permissions to remove the user from the thread. HTTPException @@ -715,7 +734,7 @@ async def remove_user(self, user: Snowflake): """ await self._state.http.remove_user_from_thread(self.id, user.id) - async def fetch_members(self) -> List[ThreadMember]: + async def fetch_members(self) -> list[ThreadMember]: """|coro| Retrieves all :class:`ThreadMember` that are in this thread. @@ -723,15 +742,15 @@ async def fetch_members(self) -> List[ThreadMember]: This requires :attr:`Intents.members` to get information about members other than yourself. - Raises - ------- - HTTPException - Retrieving the members failed. - Returns - -------- + ------- List[:class:`ThreadMember`] All thread members in the thread. + + Raises + ------ + HTTPException + Retrieving the members failed. """ members = await self._state.http.get_thread_members(self.id) @@ -751,7 +770,7 @@ async def delete(self): You must have :attr:`~Permissions.manage_threads` to delete threads. Raises - ------- + ------ Forbidden You do not have permissions to delete this thread. HTTPException @@ -768,12 +787,12 @@ def get_partial_message(self, message_id: int, /) -> PartialMessage: .. versionadded:: 2.0 Parameters - ------------ + ---------- message_id: :class:`int` The message ID to create a partial message for. Returns - --------- + ------- :class:`PartialMessage` The partial message. """ @@ -785,7 +804,7 @@ def get_partial_message(self, message_id: int, /) -> PartialMessage: def _add_member(self, member: ThreadMember) -> None: self._members[member.id] = member - def _pop_member(self, member_id: int) -> Optional[ThreadMember]: + def _pop_member(self, member_id: int) -> ThreadMember | None: return self._members.pop(member_id, None) @@ -813,7 +832,7 @@ class ThreadMember(Hashable): .. versionadded:: 2.0 Attributes - ----------- + ---------- id: :class:`int` The thread member's ID. thread_id: :class:`int` diff --git a/discord/types/activity.py b/discord/types/activity.py index f7805985ad..af959ef5a8 100644 --- a/discord/types/activity.py +++ b/discord/types/activity.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import List, Literal, Optional, TypedDict +from typing import Literal, TypedDict from .snowflake import Snowflake from .user import PartialUser @@ -37,7 +37,7 @@ class PartialPresenceUpdate(TypedDict): user: PartialUser guild_id: Snowflake status: StatusType - activities: List[Activity] + activities: list[Activity] client_status: ClientStatus @@ -54,7 +54,7 @@ class ActivityTimestamps(TypedDict, total=False): class ActivityParty(TypedDict, total=False): id: str - size: List[int] + size: list[int] class ActivityAssets(TypedDict, total=False): @@ -85,7 +85,7 @@ class ActivityButton(TypedDict): class _SendableActivityOptional(TypedDict, total=False): - url: Optional[str] + url: str | None ActivityType = Literal[0, 1, 2, 4, 5] @@ -101,15 +101,15 @@ class _BaseActivity(SendableActivity): class Activity(_BaseActivity, total=False): - state: Optional[str] - details: Optional[str] + state: str | None + details: str | None timestamps: ActivityTimestamps assets: ActivityAssets party: ActivityParty application_id: Snowflake flags: int - emoji: Optional[ActivityEmoji] + emoji: ActivityEmoji | None secrets: ActivitySecrets - session_id: Optional[str] + session_id: str | None instance: bool - buttons: List[str] + buttons: list[str] diff --git a/discord/types/appinfo.py b/discord/types/appinfo.py index 9c0157556e..f11db4fd9f 100644 --- a/discord/types/appinfo.py +++ b/discord/types/appinfo.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import List, Optional, TypedDict +from typing import TypedDict from .snowflake import Snowflake from .team import Team @@ -36,7 +36,7 @@ class BaseAppInfo(TypedDict): id: Snowflake name: str verify_key: str - icon: Optional[str] + icon: str | None summary: str description: str @@ -53,14 +53,14 @@ class _AppInfoOptional(TypedDict, total=False): class AppInfo(BaseAppInfo, _AppInfoOptional): - rpc_origins: List[str] + rpc_origins: list[str] owner: User bot_public: bool bot_require_code_grant: bool class _PartialAppInfoOptional(TypedDict, total=False): - rpc_origins: List[str] + rpc_origins: list[str] cover_image: str hook: bool terms_of_service_url: str diff --git a/discord/types/audit_log.py b/discord/types/audit_log.py index 1057e80a2f..615b4325a2 100644 --- a/discord/types/audit_log.py +++ b/discord/types/audit_log.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import List, Literal, Optional, TypedDict, Union +from typing import Literal, TypedDict, Union from .automod import AutoModRule from .channel import ChannelType, PermissionOverwrite, VideoQualityMode @@ -187,8 +187,8 @@ class _AuditLogChange_Int(TypedDict): class _AuditLogChange_ListRole(TypedDict): key: Literal["$add", "$remove"] - new_value: List[Role] - old_value: List[Role] + new_value: list[Role] + old_value: list[Role] class _AuditLogChange_MFALevel(TypedDict): @@ -235,8 +235,8 @@ class _AuditLogChange_VideoQualityMode(TypedDict): class _AuditLogChange_Overwrites(TypedDict): key: Literal["permission_overwrites"] - new_value: List[PermissionOverwrite] - old_value: List[PermissionOverwrite] + new_value: list[PermissionOverwrite] + old_value: list[PermissionOverwrite] AuditLogChange = Union[ @@ -269,23 +269,23 @@ class AuditEntryInfo(TypedDict): class _AuditLogEntryOptional(TypedDict, total=False): - changes: List[AuditLogChange] + changes: list[AuditLogChange] options: AuditEntryInfo reason: str class AuditLogEntry(_AuditLogEntryOptional): - target_id: Optional[str] - user_id: Optional[Snowflake] + target_id: str | None + user_id: Snowflake | None id: Snowflake action_type: AuditLogEvent class AuditLog(TypedDict): - webhooks: List[Webhook] - users: List[User] - audit_log_entries: List[AuditLogEntry] - integrations: List[PartialIntegration] - threads: List[Thread] - scheduled_events: List[ScheduledEvent] - auto_moderation_rules: List[AutoModRule] + webhooks: list[Webhook] + users: list[User] + audit_log_entries: list[AuditLogEntry] + integrations: list[PartialIntegration] + threads: list[Thread] + scheduled_events: list[ScheduledEvent] + auto_moderation_rules: list[AutoModRule] diff --git a/discord/types/automod.py b/discord/types/automod.py index 98e17f2732..166849045a 100644 --- a/discord/types/automod.py +++ b/discord/types/automod.py @@ -22,7 +22,7 @@ from __future__ import annotations -from typing import List, Literal, TypedDict +from typing import Literal, TypedDict from .snowflake import Snowflake @@ -36,19 +36,19 @@ class AutoModTriggerMetadata(TypedDict, total=False): - keyword_filter: List[str] - presets: List[AutoModKeywordPresetType] + keyword_filter: list[str] + presets: list[AutoModKeywordPresetType] + - class AutoModActionMetadata(TypedDict, total=False): channel_id: Snowflake duration_seconds: int - - + + class AutoModAction(TypedDict): type: AutoModActionType metadata: AutoModActionMetadata - + class AutoModRule(TypedDict): id: Snowflake @@ -58,31 +58,31 @@ class AutoModRule(TypedDict): event_type: AutoModEventType trigger_type: AutoModTriggerType trigger_metadata: AutoModTriggerMetadata - actions: List[AutoModAction] + actions: list[AutoModAction] enabled: bool - exempt_roles: List[Snowflake] - exempt_channels: List[Snowflake] - - + exempt_roles: list[Snowflake] + exempt_channels: list[Snowflake] + + class _CreateAutoModRuleOptional(TypedDict, total=False): enabled: bool - exempt_roles: List[Snowflake] - exempt_channels: List[Snowflake] - - + exempt_roles: list[Snowflake] + exempt_channels: list[Snowflake] + + class CreateAutoModRule(_CreateAutoModRuleOptional): name: str event_type: AutoModEventType trigger_type: AutoModTriggerType trigger_metadata: AutoModTriggerMetadata - actions: List[AutoModAction] - + actions: list[AutoModAction] + class EditAutoModRule(TypedDict, total=False): name: str event_type: AutoModEventType trigger_metadata: AutoModTriggerMetadata - actions: List[AutoModAction] + actions: list[AutoModAction] enabled: bool - exempt_roles: List[Snowflake] - exempt_channels: List[Snowflake] + exempt_roles: list[Snowflake] + exempt_channels: list[Snowflake] diff --git a/discord/types/components.py b/discord/types/components.py index d12e96aa02..4a9b4aa95b 100644 --- a/discord/types/components.py +++ b/discord/types/components.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import List, Literal, TypedDict, Union +from typing import Literal, TypedDict, Union from .emoji import PartialEmoji @@ -36,7 +36,7 @@ class ActionRow(TypedDict): type: Literal[1] - components: List[Component] + components: list[Component] class _ButtonComponentOptional(TypedDict, total=False): @@ -88,7 +88,7 @@ class SelectOption(_SelectOptionsOptional): class SelectMenu(_SelectMenuOptional): type: Literal[3] custom_id: str - options: List[SelectOption] + options: list[SelectOption] Component = Union[ActionRow, ButtonComponent, SelectMenu, InputText] diff --git a/discord/types/embed.py b/discord/types/embed.py index 8e64c946f5..f4de616ab9 100644 --- a/discord/types/embed.py +++ b/discord/types/embed.py @@ -77,7 +77,9 @@ class EmbedAuthor(TypedDict, total=False): proxy_icon_url: str -EmbedType = Literal["rich", "image", "video", "gifv", "article", "link", "auto_moderation_message"] +EmbedType = Literal[ + "rich", "image", "video", "gifv", "article", "link", "auto_moderation_message" +] class Embed(TypedDict, total=False): diff --git a/discord/types/integration.py b/discord/types/integration.py index 7c144013b3..4eda85a6f4 100644 --- a/discord/types/integration.py +++ b/discord/types/integration.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import Literal, Optional, TypedDict, Union +from typing import Literal, TypedDict, Union from .snowflake import Snowflake from .user import User @@ -38,7 +38,7 @@ class _IntegrationApplicationOptional(TypedDict, total=False): class IntegrationApplication(_IntegrationApplicationOptional): id: Snowflake name: str - icon: Optional[str] + icon: str | None description: str summary: str @@ -71,7 +71,7 @@ class BaseIntegration(PartialIntegration): class StreamIntegration(BaseIntegration): - role_id: Optional[Snowflake] + role_id: Snowflake | None enable_emoticons: bool subscriber_count: int revoked: bool diff --git a/discord/types/interactions.py b/discord/types/interactions.py index 77096ebf47..292580b6d7 100644 --- a/discord/types/interactions.py +++ b/discord/types/interactions.py @@ -25,8 +25,9 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Dict, List, Literal, Optional, TypedDict, Union +from typing import TYPE_CHECKING, Literal, TypedDict, Union +from ..permissions import Permissions from .channel import ChannelType from .components import Component, ComponentType from .embed import Embed @@ -35,7 +36,6 @@ from .role import Role from .snowflake import Snowflake from .user import User -from ..permissions import Permissions if TYPE_CHECKING: from .message import AllowedMentions, Message @@ -45,12 +45,12 @@ class _ApplicationCommandOptional(TypedDict, total=False): - options: List[ApplicationCommandOption] + options: list[ApplicationCommandOption] type: ApplicationCommandType name_localized: str - name_localizations: Dict[str, str] + name_localizations: dict[str, str] description_localized: str - description_localizations: Dict[str, str] + description_localizations: dict[str, str] class ApplicationCommand(_ApplicationCommandOptional): @@ -61,10 +61,10 @@ class ApplicationCommand(_ApplicationCommandOptional): class _ApplicationCommandOptionOptional(TypedDict, total=False): - choices: List[ApplicationCommandOptionChoice] - options: List[ApplicationCommandOption] - name_localizations: Dict[str, str] - description_localizations: Dict[str, str] + choices: list[ApplicationCommandOptionChoice] + options: list[ApplicationCommandOption] + name_localizations: dict[str, str] + description_localizations: dict[str, str] ApplicationCommandOptionType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] @@ -78,12 +78,12 @@ class ApplicationCommandOption(_ApplicationCommandOptionOptional): class _ApplicationCommandOptionChoiceOptional(TypedDict, total=False): - name_localizations: Dict[str, str] + name_localizations: dict[str, str] class ApplicationCommandOptionChoice(_ApplicationCommandOptionChoiceOptional): name: str - value: Union[str, int] + value: str | int ApplicationCommandPermissionType = Literal[1, 2, 3] @@ -96,7 +96,7 @@ class ApplicationCommandPermissions(TypedDict): class BaseGuildApplicationCommandPermissions(TypedDict): - permissions: List[ApplicationCommandPermissions] + permissions: list[ApplicationCommandPermissions] class PartialGuildApplicationCommandPermissions(BaseGuildApplicationCommandPermissions): @@ -115,32 +115,44 @@ class _ApplicationCommandInteractionDataOption(TypedDict): name: str -class _ApplicationCommandInteractionDataOptionSubcommand(_ApplicationCommandInteractionDataOption): +class _ApplicationCommandInteractionDataOptionSubcommand( + _ApplicationCommandInteractionDataOption +): type: Literal[1, 2] - options: List[ApplicationCommandInteractionDataOption] + options: list[ApplicationCommandInteractionDataOption] -class _ApplicationCommandInteractionDataOptionString(_ApplicationCommandInteractionDataOption): +class _ApplicationCommandInteractionDataOptionString( + _ApplicationCommandInteractionDataOption +): type: Literal[3] value: str -class _ApplicationCommandInteractionDataOptionInteger(_ApplicationCommandInteractionDataOption): +class _ApplicationCommandInteractionDataOptionInteger( + _ApplicationCommandInteractionDataOption +): type: Literal[4] value: int -class _ApplicationCommandInteractionDataOptionBoolean(_ApplicationCommandInteractionDataOption): +class _ApplicationCommandInteractionDataOptionBoolean( + _ApplicationCommandInteractionDataOption +): type: Literal[5] value: bool -class _ApplicationCommandInteractionDataOptionSnowflake(_ApplicationCommandInteractionDataOption): +class _ApplicationCommandInteractionDataOptionSnowflake( + _ApplicationCommandInteractionDataOption +): type: Literal[6, 7, 8, 9, 11] value: Snowflake -class _ApplicationCommandInteractionDataOptionNumber(_ApplicationCommandInteractionDataOption): +class _ApplicationCommandInteractionDataOptionNumber( + _ApplicationCommandInteractionDataOption +): type: Literal[10] value: float @@ -163,15 +175,15 @@ class ApplicationCommandResolvedPartialChannel(TypedDict): class ApplicationCommandInteractionDataResolved(TypedDict, total=False): - users: Dict[Snowflake, User] - members: Dict[Snowflake, Member] - roles: Dict[Snowflake, Role] - channels: Dict[Snowflake, ApplicationCommandResolvedPartialChannel] - attachments: Dict[Snowflake, Attachment] + users: dict[Snowflake, User] + members: dict[Snowflake, Member] + roles: dict[Snowflake, Role] + channels: dict[Snowflake, ApplicationCommandResolvedPartialChannel] + attachments: dict[Snowflake, Attachment] class _ApplicationCommandInteractionDataOptional(TypedDict, total=False): - options: List[ApplicationCommandInteractionDataOption] + options: list[ApplicationCommandInteractionDataOption] resolved: ApplicationCommandInteractionDataResolved target_id: Snowflake type: ApplicationCommandType @@ -183,7 +195,7 @@ class ApplicationCommandInteractionData(_ApplicationCommandInteractionDataOption class _ComponentInteractionDataOptional(TypedDict, total=False): - values: List[str] + values: list[str] class ComponentInteractionData(_ComponentInteractionDataOptional): @@ -217,10 +229,10 @@ class Interaction(_InteractionOptional): class InteractionApplicationCommandCallbackData(TypedDict, total=False): tts: bool content: str - embeds: List[Embed] + embeds: list[Embed] allowed_mentions: AllowedMentions flags: int - components: List[Component] + components: list[Component] InteractionResponseType = Literal[1, 4, 5, 6, 7] @@ -243,7 +255,7 @@ class MessageInteraction(TypedDict): class _EditApplicationCommandOptional(TypedDict, total=False): description: str - options: Optional[List[ApplicationCommandOption]] + options: list[ApplicationCommandOption] | None type: ApplicationCommandType diff --git a/discord/types/invite.py b/discord/types/invite.py index 3cd29f9d5b..575a58bf0a 100644 --- a/discord/types/invite.py +++ b/discord/types/invite.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import Literal, Optional, TypedDict, Union +from typing import Literal, TypedDict, Union from .appinfo import PartialAppInfo from .channel import PartialChannel @@ -52,11 +52,11 @@ class _InviteMetadata(TypedDict, total=False): max_age: int temporary: bool created_at: str - expires_at: Optional[str] + expires_at: str | None class VanityInvite(_InviteMetadata): - code: Optional[str] + code: str | None class IncompleteInvite(_InviteMetadata): diff --git a/discord/types/message.py b/discord/types/message.py index 7e0adcd515..940f3ae1f0 100644 --- a/discord/types/message.py +++ b/discord/types/message.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List, Literal, Optional, TypedDict, Union +from typing import TYPE_CHECKING, Literal, TypedDict from .channel import ChannelType from .components import Component @@ -55,8 +55,8 @@ class Reaction(TypedDict): class _AttachmentOptional(TypedDict, total=False): - height: Optional[int] - width: Optional[int] + height: int | None + width: int | None content_type: str spoiler: bool @@ -84,7 +84,7 @@ class _MessageApplicationOptional(TypedDict, total=False): class MessageApplication(_MessageApplicationOptional): id: Snowflake description: str - icon: Optional[str] + icon: str | None name: str @@ -98,23 +98,25 @@ class MessageReference(TypedDict, total=False): class _MessageOptional(TypedDict, total=False): guild_id: Snowflake member: Member - mention_channels: List[ChannelMention] - reactions: List[Reaction] - nonce: Union[int, str] + mention_channels: list[ChannelMention] + reactions: list[Reaction] + nonce: int | str webhook_id: Snowflake activity: MessageActivity application: MessageApplication application_id: Snowflake message_reference: MessageReference flags: int - sticker_items: List[StickerItem] - referenced_message: Optional[Message] + sticker_items: list[StickerItem] + referenced_message: Message | None interaction: MessageInteraction - components: List[Component] - thread: Optional[Thread] + components: list[Component] + thread: Thread | None -MessageType = Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 18, 19, 20, 21, 22, 23, 24] +MessageType = Literal[ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 18, 19, 20, 21, 22, 23, 24 +] class Message(_MessageOptional): @@ -123,13 +125,13 @@ class Message(_MessageOptional): author: User content: str timestamp: str - edited_timestamp: Optional[str] + edited_timestamp: str | None tts: bool mention_everyone: bool - mentions: List[UserWithMember] + mentions: list[UserWithMember] mention_roles: SnowflakeList - attachments: List[Attachment] - embeds: List[Embed] + attachments: list[Attachment] + embeds: list[Embed] pinned: bool type: MessageType @@ -138,7 +140,7 @@ class Message(_MessageOptional): class AllowedMentions(TypedDict): - parse: List[AllowedMentionType] + parse: list[AllowedMentionType] roles: SnowflakeList users: SnowflakeList replied_user: bool diff --git a/discord/types/raw_models.py b/discord/types/raw_models.py index 94dd780113..069a1e90c1 100644 --- a/discord/types/raw_models.py +++ b/discord/types/raw_models.py @@ -120,8 +120,8 @@ class _AutoModActionExecutionEventOptional(TypedDict, total=False): alert_system_message_id: Snowflake matched_keyword: str matched_content: str - - + + class AutoModActionExecutionEvent(_AutoModActionExecutionEventOptional): guild_id: Snowflake action: AutoModAction diff --git a/discord/types/scheduled_events.py b/discord/types/scheduled_events.py index 37f612acb2..85d77d7313 100644 --- a/discord/types/scheduled_events.py +++ b/discord/types/scheduled_events.py @@ -24,7 +24,7 @@ from __future__ import annotations -from typing import Literal, Optional, TypedDict +from typing import Literal, TypedDict from .member import Member from .snowflake import Snowflake @@ -42,16 +42,16 @@ class ScheduledEvent(TypedDict): creator_id: Snowflake name: str description: str - image: Optional[str] + image: str | None scheduled_start_time: str - scheduled_end_time: Optional[str] + scheduled_end_time: str | None privacy_level: ScheduledEventPrivacyLevel status: ScheduledEventStatus entity_type: ScheduledEventLocationType entity_id: Snowflake entity_metadata: ScheduledEventEntityMetadata creator: User - user_count: Optional[int] + user_count: int | None class ScheduledEventEntityMetadata(TypedDict): @@ -61,4 +61,4 @@ class ScheduledEventEntityMetadata(TypedDict): class ScheduledEventSubscriber(TypedDict): guild_scheduled_event_id: Snowflake user: User - member: Optional[Member] + member: Member | None diff --git a/discord/types/sticker.py b/discord/types/sticker.py index 2131d8a6b4..05c85a1e95 100644 --- a/discord/types/sticker.py +++ b/discord/types/sticker.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import List, Literal, TypedDict, Union +from typing import Literal, TypedDict, Union from .snowflake import Snowflake from .user import User @@ -68,7 +68,7 @@ class GuildSticker(BaseSticker, _GuildStickerOptional): class StickerPack(TypedDict): id: Snowflake - stickers: List[StandardSticker] + stickers: list[StandardSticker] name: str sku_id: Snowflake cover_sticker_id: Snowflake @@ -92,4 +92,4 @@ class EditGuildSticker(TypedDict, total=False): class ListPremiumStickerPacks(TypedDict): - sticker_packs: List[StickerPack] + sticker_packs: list[StickerPack] diff --git a/discord/types/team.py b/discord/types/team.py index 0237dbaa78..3fe78aad86 100644 --- a/discord/types/team.py +++ b/discord/types/team.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import List, Optional, TypedDict +from typing import TypedDict from .snowflake import Snowflake from .user import PartialUser @@ -34,7 +34,7 @@ class TeamMember(TypedDict): user: PartialUser membership_state: int - permissions: List[str] + permissions: list[str] team_id: Snowflake @@ -42,5 +42,5 @@ class Team(TypedDict): id: Snowflake name: str owner_id: Snowflake - members: List[TeamMember] - icon: Optional[str] + members: list[TeamMember] + icon: str | None diff --git a/discord/types/template.py b/discord/types/template.py index a27e02b2bb..8561836ecc 100644 --- a/discord/types/template.py +++ b/discord/types/template.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import Optional, TypedDict +from typing import TypedDict from .guild import Guild from .snowflake import Snowflake @@ -34,13 +34,13 @@ class CreateTemplate(TypedDict): name: str - icon: Optional[bytes] + icon: bytes | None class Template(TypedDict): code: str name: str - description: Optional[str] + description: str | None usage_count: int creator_id: Snowflake creator: User @@ -48,4 +48,4 @@ class Template(TypedDict): updated_at: str source_guild_id: Snowflake serialized_source_guild: Guild - is_dirty: Optional[bool] + is_dirty: bool | None diff --git a/discord/types/threads.py b/discord/types/threads.py index 9ee62c00ce..ec84ba8713 100644 --- a/discord/types/threads.py +++ b/discord/types/threads.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import List, Literal, Optional, TypedDict +from typing import Literal, TypedDict from .snowflake import Snowflake @@ -54,8 +54,8 @@ class ThreadMetadata(_ThreadMetadataOptional): class _ThreadOptional(TypedDict, total=False): member: ThreadMember - last_message_id: Optional[Snowflake] - last_pin_timestamp: Optional[Snowflake] + last_message_id: Snowflake | None + last_pin_timestamp: Snowflake | None class Thread(_ThreadOptional): @@ -72,6 +72,6 @@ class Thread(_ThreadOptional): class ThreadPaginationPayload(TypedDict): - threads: List[Thread] - members: List[ThreadMember] + threads: list[Thread] + members: list[ThreadMember] has_more: bool diff --git a/discord/types/voice.py b/discord/types/voice.py index 3a4ce86813..e4dbe8c4cf 100644 --- a/discord/types/voice.py +++ b/discord/types/voice.py @@ -28,7 +28,9 @@ from .member import MemberWithUser from .snowflake import Snowflake -SupportedModes = Literal["xsalsa20_poly1305_lite", "xsalsa20_poly1305_suffix", "xsalsa20_poly1305"] +SupportedModes = Literal[ + "xsalsa20_poly1305_lite", "xsalsa20_poly1305_suffix", "xsalsa20_poly1305" +] class _PartialVoiceStateOptional(TypedDict, total=False): diff --git a/discord/types/webhook.py b/discord/types/webhook.py index a3c5a03acd..cb96791db4 100644 --- a/discord/types/webhook.py +++ b/discord/types/webhook.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import Literal, Optional, TypedDict +from typing import Literal, TypedDict from .channel import PartialChannel from .snowflake import Snowflake @@ -63,10 +63,10 @@ class PartialWebhook(_WebhookOptional): class _FullWebhook(TypedDict, total=False): - name: Optional[str] - avatar: Optional[str] + name: str | None + avatar: str | None channel_id: Snowflake - application_id: Optional[Snowflake] + application_id: Snowflake | None class Webhook(PartialWebhook, _FullWebhook): diff --git a/discord/types/welcome_screen.py b/discord/types/welcome_screen.py index acd7ed33d6..48dc4cbf93 100644 --- a/discord/types/welcome_screen.py +++ b/discord/types/welcome_screen.py @@ -25,18 +25,18 @@ from __future__ import annotations -from typing import List, Optional, TypedDict +from typing import TypedDict from .snowflake import Snowflake class WelcomeScreen(TypedDict): description: str - welcome_channels: List[WelcomeScreenChannel] + welcome_channels: list[WelcomeScreenChannel] class WelcomeScreenChannel(TypedDict): channel_id: Snowflake description: str - emoji_id: Optional[Snowflake] - emoji_name: Optional[str] + emoji_id: Snowflake | None + emoji_name: str | None diff --git a/discord/ui/__init__.py b/discord/ui/__init__.py index 2514e07920..fa1767d220 100644 --- a/discord/ui/__init__.py +++ b/discord/ui/__init__.py @@ -6,7 +6,6 @@ :copyright: (c) 2015-2021 Rapptz & (c) 2021-present Pycord Development :license: MIT, see LICENSE for more details. - """ from .button import * diff --git a/discord/ui/button.py b/discord/ui/button.py index 6f12a4d7da..eb6d54e9aa 100644 --- a/discord/ui/button.py +++ b/discord/ui/button.py @@ -27,7 +27,7 @@ import inspect import os -from typing import TYPE_CHECKING, Callable, Optional, Tuple, Type, TypeVar, Union +from typing import TYPE_CHECKING, Callable, TypeVar from ..components import Button as ButtonComponent from ..enums import ButtonStyle, ComponentType @@ -53,7 +53,7 @@ class Button(Item[V]): .. versionadded:: 2.0 Parameters - ------------ + ---------- style: :class:`discord.ButtonStyle` The style of the button. custom_id: Optional[:class:`str`] @@ -75,7 +75,7 @@ class Button(Item[V]): ordering. The row number must be between 0 and 4 (i.e. zero indexed). """ - __item_repr_attributes__: Tuple[str, ...] = ( + __item_repr_attributes__: tuple[str, ...] = ( "style", "url", "disabled", @@ -88,12 +88,12 @@ def __init__( self, *, style: ButtonStyle = ButtonStyle.secondary, - label: Optional[str] = None, + label: str | None = None, disabled: bool = False, - custom_id: Optional[str] = None, - url: Optional[str] = None, - emoji: Optional[Union[str, Emoji, PartialEmoji]] = None, - row: Optional[int] = None, + custom_id: str | None = None, + url: str | None = None, + emoji: str | Emoji | PartialEmoji | None = None, + row: int | None = None, ): super().__init__() if label and len(str(label)) > 80: @@ -104,7 +104,9 @@ def __init__( raise TypeError("cannot mix both url and custom_id with Button") if not isinstance(custom_id, str) and custom_id is not None: - raise TypeError(f"expected custom_id to be str, not {custom_id.__class__.__name__}") + raise TypeError( + f"expected custom_id to be str, not {custom_id.__class__.__name__}" + ) self._provided_custom_id = custom_id is not None if url is None and custom_id is None: @@ -119,7 +121,9 @@ def __init__( elif isinstance(emoji, _EmojiTag): emoji = emoji._to_partial() else: - raise TypeError(f"expected emoji to be str, Emoji, or PartialEmoji not {emoji.__class__}") + raise TypeError( + f"expected emoji to be str, Emoji, or PartialEmoji not {emoji.__class__}" + ) self._underlying = ButtonComponent._raw_construct( type=ComponentType.button, @@ -142,7 +146,7 @@ def style(self, value: ButtonStyle): self._underlying.style = value @property - def custom_id(self) -> Optional[str]: + def custom_id(self) -> str | None: """Optional[:class:`str`]: The ID of the button that gets received during an interaction. If this button is for a URL, it does not have a custom ID. @@ -150,7 +154,7 @@ def custom_id(self) -> Optional[str]: return self._underlying.custom_id @custom_id.setter - def custom_id(self, value: Optional[str]): + def custom_id(self, value: str | None): if value is not None and not isinstance(value, str): raise TypeError("custom_id must be None or str") if value and len(value) > 100: @@ -158,12 +162,12 @@ def custom_id(self, value: Optional[str]): self._underlying.custom_id = value @property - def url(self) -> Optional[str]: + def url(self) -> str | None: """Optional[:class:`str`]: The URL this button sends you to.""" return self._underlying.url @url.setter - def url(self, value: Optional[str]): + def url(self, value: str | None): if value is not None and not isinstance(value, str): raise TypeError("url must be None or str") self._underlying.url = value @@ -178,23 +182,23 @@ def disabled(self, value: bool): self._underlying.disabled = bool(value) @property - def label(self) -> Optional[str]: + def label(self) -> str | None: """Optional[:class:`str`]: The label of the button, if available.""" return self._underlying.label @label.setter - def label(self, value: Optional[str]): + def label(self, value: str | None): if value and len(str(value)) > 80: raise ValueError("label must be 80 characters or fewer") self._underlying.label = str(value) if value is not None else value @property - def emoji(self) -> Optional[PartialEmoji]: + def emoji(self) -> PartialEmoji | None: """Optional[:class:`.PartialEmoji`]: The emoji of the button, if available.""" return self._underlying.emoji @emoji.setter - def emoji(self, value: Optional[Union[str, Emoji, PartialEmoji]]): # type: ignore + def emoji(self, value: str | Emoji | PartialEmoji | None): # type: ignore if value is None: self._underlying.emoji = None elif isinstance(value, str): @@ -202,10 +206,12 @@ def emoji(self, value: Optional[Union[str, Emoji, PartialEmoji]]): # type: igno elif isinstance(value, _EmojiTag): self._underlying.emoji = value._to_partial() else: - raise TypeError(f"expected str, Emoji, or PartialEmoji, received {value.__class__} instead") + raise TypeError( + f"expected str, Emoji, or PartialEmoji, received {value.__class__} instead" + ) @classmethod - def from_component(cls: Type[B], button: ButtonComponent) -> B: + def from_component(cls: type[B], button: ButtonComponent) -> B: return cls( style=button.style, label=button.label, @@ -237,12 +243,12 @@ def refresh_component(self, button: ButtonComponent) -> None: def button( *, - label: Optional[str] = None, - custom_id: Optional[str] = None, + label: str | None = None, + custom_id: str | None = None, disabled: bool = False, style: ButtonStyle = ButtonStyle.secondary, - emoji: Optional[Union[str, Emoji, PartialEmoji]] = None, - row: Optional[int] = None, + emoji: str | Emoji | PartialEmoji | None = None, + row: int | None = None, ) -> Callable[[ItemCallbackType], ItemCallbackType]: """A decorator that attaches a button to a component. @@ -259,7 +265,7 @@ def button( with it. Parameters - ------------ + ---------- label: Optional[:class:`str`] The label of the button, if any. custom_id: Optional[:class:`str`] diff --git a/discord/ui/input_text.py b/discord/ui/input_text.py index 6f6c63426a..ce552fd2fe 100644 --- a/discord/ui/input_text.py +++ b/discord/ui/input_text.py @@ -1,7 +1,7 @@ from __future__ import annotations import os -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from ..components import InputText as InputTextComponent from ..enums import ComponentType, InputTextStyle @@ -52,14 +52,14 @@ def __init__( self, *, style: InputTextStyle = InputTextStyle.short, - custom_id: Optional[str] = None, + custom_id: str | None = None, label: str, - placeholder: Optional[str] = None, - min_length: Optional[int] = None, - max_length: Optional[int] = None, - required: Optional[bool] = True, - value: Optional[str] = None, - row: Optional[int] = None, + placeholder: str | None = None, + min_length: int | None = None, + max_length: int | None = None, + required: bool | None = True, + value: str | None = None, + row: int | None = None, ): super().__init__() if len(str(label)) > 45: @@ -73,7 +73,9 @@ def __init__( if placeholder and len(str(placeholder)) > 100: raise ValueError("placeholder must be 100 characters or fewer") if not isinstance(custom_id, str) and custom_id is not None: - raise TypeError(f"expected custom_id to be str, not {custom_id.__class__.__name__}") + raise TypeError( + f"expected custom_id to be str, not {custom_id.__class__.__name__}" + ) custom_id = os.urandom(16).hex() if custom_id is None else custom_id self._underlying = InputTextComponent._raw_construct( @@ -89,7 +91,7 @@ def __init__( ) self._input_value = False self.row = row - self._rendered_row: Optional[int] = None + self._rendered_row: int | None = None @property def type(self) -> ComponentType: @@ -103,7 +105,9 @@ def style(self) -> InputTextStyle: @style.setter def style(self, value: InputTextStyle): if not isinstance(value, InputTextStyle): - raise TypeError(f"style must be of type InputTextStyle not {value.__class__.__name__}") + raise TypeError( + f"style must be of type InputTextStyle not {value.__class__.__name__}" + ) self._underlying.style = value @property @@ -114,7 +118,9 @@ def custom_id(self) -> str: @custom_id.setter def custom_id(self, value: str): if not isinstance(value, str): - raise TypeError(f"custom_id must be None or str not {value.__class__.__name__}") + raise TypeError( + f"custom_id must be None or str not {value.__class__.__name__}" + ) self._underlying.custom_id = value @property @@ -131,12 +137,12 @@ def label(self, value: str): self._underlying.label = value @property - def placeholder(self) -> Optional[str]: + def placeholder(self) -> str | None: """Optional[:class:`str`]: The placeholder text that is shown before anything is entered, if any.""" return self._underlying.placeholder @placeholder.setter - def placeholder(self, value: Optional[str]): + def placeholder(self, value: str | None): if value and not isinstance(value, str): raise TypeError(f"placeholder must be None or str not {value.__class__.__name__}") # type: ignore if value and len(value) > 100: @@ -144,12 +150,12 @@ def placeholder(self, value: Optional[str]): self._underlying.placeholder = value @property - def min_length(self) -> Optional[int]: + def min_length(self) -> int | None: """Optional[:class:`int`]: The minimum number of characters that must be entered. Defaults to `0`.""" return self._underlying.min_length @min_length.setter - def min_length(self, value: Optional[int]): + def min_length(self, value: int | None): if value and not isinstance(value, int): raise TypeError(f"min_length must be None or int not {value.__class__.__name__}") # type: ignore if value and (value < 0 or value) > 4000: @@ -157,12 +163,12 @@ def min_length(self, value: Optional[int]): self._underlying.min_length = value @property - def max_length(self) -> Optional[int]: + def max_length(self) -> int | None: """Optional[:class:`int`]: The maximum number of characters that can be entered.""" return self._underlying.max_length @max_length.setter - def max_length(self, value: Optional[int]): + def max_length(self, value: int | None): if value and not isinstance(value, int): raise TypeError(f"min_length must be None or int not {value.__class__.__name__}") # type: ignore if value and (value <= 0 or value > 4000): @@ -170,18 +176,18 @@ def max_length(self, value: Optional[int]): self._underlying.max_length = value @property - def required(self) -> Optional[bool]: + def required(self) -> bool | None: """Optional[:class:`bool`]: Whether the input text field is required or not. Defaults to `True`.""" return self._underlying.required @required.setter - def required(self, value: Optional[bool]): + def required(self, value: bool | None): if not isinstance(value, bool): raise TypeError(f"required must be bool not {value.__class__.__name__}") # type: ignore self._underlying.required = bool(value) @property - def value(self) -> Optional[str]: + def value(self) -> str | None: """Optional[:class:`str`]: The value entered in the text field.""" if self._input_value is not False: # only False on init, otherwise the value was either set or cleared @@ -189,7 +195,7 @@ def value(self) -> Optional[str]: return self._underlying.value @value.setter - def value(self, value: Optional[str]): + def value(self, value: str | None): if value and not isinstance(value, str): raise TypeError(f"value must be None or str not {value.__class__.__name__}") # type: ignore if value and len(str(value)) > 4000: diff --git a/discord/ui/item.py b/discord/ui/item.py index 86c2bc9a7d..ea70fea230 100644 --- a/discord/ui/item.py +++ b/discord/ui/item.py @@ -25,18 +25,7 @@ from __future__ import annotations -from typing import ( - TYPE_CHECKING, - Any, - Callable, - Coroutine, - Dict, - Generic, - Optional, - Tuple, - Type, - TypeVar, -) +from typing import TYPE_CHECKING, Any, Callable, Coroutine, Generic, TypeVar from ..interactions import Interaction @@ -63,12 +52,12 @@ class Item(Generic[V]): .. versionadded:: 2.0 """ - __item_repr_attributes__: Tuple[str, ...] = ("row",) + __item_repr_attributes__: tuple[str, ...] = ("row",) def __init__(self): - self._view: Optional[V] = None - self._row: Optional[int] = None - self._rendered_row: Optional[int] = None + self._view: V | None = None + self._row: int | None = None + self._rendered_row: int | None = None # This works mostly well but there is a gotcha with # the interaction with from_component, since that technically provides # a custom_id most dispatchable items would get this set to True even though @@ -77,7 +66,7 @@ def __init__(self): # only called upon edit and we're mainly interested during initial creation time. self._provided_custom_id: bool = False - def to_component_dict(self) -> Dict[str, Any]: + def to_component_dict(self) -> dict[str, Any]: raise NotImplementedError def refresh_component(self, component: Component) -> None: @@ -87,7 +76,7 @@ def refresh_state(self, interaction: Interaction) -> None: return None @classmethod - def from_component(cls: Type[I], component: Component) -> I: + def from_component(cls: type[I], component: Component) -> I: return cls() @property @@ -101,15 +90,17 @@ def is_persistent(self) -> bool: return self._provided_custom_id def __repr__(self) -> str: - attrs = " ".join(f"{key}={getattr(self, key)!r}" for key in self.__item_repr_attributes__) + attrs = " ".join( + f"{key}={getattr(self, key)!r}" for key in self.__item_repr_attributes__ + ) return f"<{self.__class__.__name__} {attrs}>" @property - def row(self) -> Optional[int]: + def row(self) -> int | None: return self._row @row.setter - def row(self, value: Optional[int]): + def row(self, value: int | None): if value is None: self._row = None elif 5 > value >= 0: @@ -122,7 +113,7 @@ def width(self) -> int: return 1 @property - def view(self) -> Optional[V]: + def view(self) -> V | None: """Optional[:class:`View`]: The underlying view for this item.""" return self._view @@ -134,8 +125,7 @@ async def callback(self, interaction: Interaction): This can be overridden by subclasses. Parameters - ----------- + ---------- interaction: :class:`.Interaction` The interaction that triggered this UI item. """ - pass diff --git a/discord/ui/modal.py b/discord/ui/modal.py index 2f74948db9..9d985c4a9f 100644 --- a/discord/ui/modal.py +++ b/discord/ui/modal.py @@ -3,11 +3,11 @@ import asyncio import os import sys -import traceback import time +import traceback from functools import partial from itertools import groupby -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Callable +from typing import TYPE_CHECKING, Any, Callable from .input_text import InputText @@ -44,22 +44,29 @@ class Modal: If ``None`` then there is no timeout. """ - def __init__(self, *children: InputText, title: str, custom_id: Optional[str] = None, - timeout: Optional[float] = None) -> None: - self.timeout: Optional[float] = timeout + def __init__( + self, + *children: InputText, + title: str, + custom_id: str | None = None, + timeout: float | None = None, + ) -> None: + self.timeout: float | None = timeout if not isinstance(custom_id, str) and custom_id is not None: - raise TypeError(f"expected custom_id to be str, not {custom_id.__class__.__name__}") - self._custom_id: Optional[str] = custom_id or os.urandom(16).hex() + raise TypeError( + f"expected custom_id to be str, not {custom_id.__class__.__name__}" + ) + self._custom_id: str | None = custom_id or os.urandom(16).hex() if len(title) > 45: raise ValueError("title must be 45 characters or fewer") self._title = title - self._children: List[InputText] = list(children) + self._children: list[InputText] = list(children) self._weights = _ModalWeights(self._children) loop = asyncio.get_running_loop() self._stopped: asyncio.Future[bool] = loop.create_future() - self.__cancel_callback: Optional[Callable[[Modal], None]] = None - self.__timeout_expiry: Optional[float] = None - self.__timeout_task: Optional[asyncio.Task[None]] = None + self.__cancel_callback: Callable[[Modal], None] | None = None + self.__timeout_expiry: float | None = None + self.__timeout_task: asyncio.Task[None] | None = None self.loop = asyncio.get_event_loop() def _start_listening_from_store(self, store: ModalStore) -> None: @@ -90,7 +97,7 @@ async def __timeout_task_impl(self) -> None: await asyncio.sleep(self.__timeout_expiry - now) @property - def _expires_at(self) -> Optional[float]: + def _expires_at(self) -> float | None: if self.timeout: return time.monotonic() + self.timeout return None @@ -100,7 +107,9 @@ def _dispatch_timeout(self): return self._stopped.set_result(True) - self.loop.create_task(self.on_timeout(), name=f"discord-ui-view-timeout-{self.custom_id}") + self.loop.create_task( + self.on_timeout(), name=f"discord-ui-view-timeout-{self.custom_id}" + ) @property def title(self) -> str: @@ -116,15 +125,17 @@ def title(self, value: str): self._title = value @property - def children(self) -> List[InputText]: + def children(self) -> list[InputText]: """List[:class:`InputText`]: The child components associated with the modal dialog.""" return self._children @children.setter - def children(self, value: List[InputText]): + def children(self, value: list[InputText]): for item in value: if not isinstance(item, InputText): - raise TypeError(f"all Modal children must be InputText, not {item.__class__.__name__}") + raise TypeError( + f"all Modal children must be InputText, not {item.__class__.__name__}" + ) self._weights = _ModalWeights(self._children) self._children = value @@ -136,7 +147,9 @@ def custom_id(self) -> str: @custom_id.setter def custom_id(self, value: str): if not isinstance(value, str): - raise TypeError(f"expected custom_id to be str, not {value.__class__.__name__}") + raise TypeError( + f"expected custom_id to be str, not {value.__class__.__name__}" + ) if len(value) > 100: raise ValueError("custom_id must be 100 characters or fewer") self._custom_id = value @@ -148,18 +161,18 @@ async def callback(self, interaction: Interaction): Should be overridden to handle the values submitted by the user. Parameters - ----------- + ---------- interaction: :class:`~discord.Interaction` The interaction that submitted the modal dialog. """ self.stop() - def to_components(self) -> List[Dict[str, Any]]: + def to_components(self) -> list[dict[str, Any]]: def key(item: InputText) -> int: return item._rendered_row or 0 children = sorted(self._children, key=key) - components: List[Dict[str, Any]] = [] + components: list[dict[str, Any]] = [] for _, group in groupby(children, key=key): children = [item.to_component_dict() for item in group] if not children: @@ -233,28 +246,29 @@ async def on_error(self, error: Exception, interaction: Interaction) -> None: The default implementation prints the traceback to stderr. Parameters - ----------- + ---------- error: :class:`Exception` The exception that was raised. interaction: :class:`~discord.Interaction` The interaction that led to the failure. """ print(f"Ignoring exception in modal {self}:", file=sys.stderr) - traceback.print_exception(error.__class__, error, error.__traceback__, file=sys.stderr) + traceback.print_exception( + error.__class__, error, error.__traceback__, file=sys.stderr + ) async def on_timeout(self) -> None: """|coro| A callback that is called when a modal's timeout elapses without being explicitly stopped. """ - pass class _ModalWeights: __slots__ = ("weights",) - def __init__(self, children: List[InputText]): - self.weights: List[int] = [0, 0, 0, 0, 0] + def __init__(self, children: list[InputText]): + self.weights: list[int] = [0, 0, 0, 0, 0] key = lambda i: sys.maxsize if i.row is None else i.row children = sorted(children, key=key) @@ -273,7 +287,9 @@ def add_item(self, item: InputText) -> None: if item.row is not None: total = self.weights[item.row] + item.width if total > 5: - raise ValueError(f"item would not fit at row {item.row} ({total} > 5 width)") + raise ValueError( + f"item would not fit at row {item.row} ({total} > 5 width)" + ) self.weights[item.row] = total item._rendered_row = item.row else: @@ -293,7 +309,7 @@ def clear(self) -> None: class ModalStore: def __init__(self, state: ConnectionState) -> None: # (user_id, custom_id) : Modal - self._modals: Dict[Tuple[int, str], Modal] = {} + self._modals: dict[tuple[int, str], Modal] = {} self._state: ConnectionState = state def add_modal(self, modal: Modal, user_id: int): diff --git a/discord/ui/select.py b/discord/ui/select.py index 833a6bb588..c99d5f8098 100644 --- a/discord/ui/select.py +++ b/discord/ui/select.py @@ -27,7 +27,7 @@ import inspect import os -from typing import TYPE_CHECKING, Callable, List, Optional, Tuple, Type, TypeVar, Union +from typing import TYPE_CHECKING, Callable, TypeVar from ..components import SelectMenu, SelectOption from ..emoji import Emoji @@ -61,7 +61,7 @@ class Select(Item[V]): .. versionadded:: 2.0 Parameters - ------------ + ---------- custom_id: :class:`str` The ID of the select menu that gets received during an interaction. If not given then one is generated for you. @@ -85,7 +85,7 @@ class Select(Item[V]): ordering. The row number must be between 0 and 4 (i.e. zero indexed). """ - __item_repr_attributes__: Tuple[str, ...] = ( + __item_repr_attributes__: tuple[str, ...] = ( "placeholder", "min_values", "max_values", @@ -96,16 +96,16 @@ class Select(Item[V]): def __init__( self, *, - custom_id: Optional[str] = None, - placeholder: Optional[str] = None, + custom_id: str | None = None, + placeholder: str | None = None, min_values: int = 1, max_values: int = 1, - options: List[SelectOption] = MISSING, + options: list[SelectOption] = MISSING, disabled: bool = False, - row: Optional[int] = None, + row: int | None = None, ) -> None: super().__init__() - self._selected_values: List[str] = [] + self._selected_values: list[str] = [] if min_values < 0 or min_values > 25: raise ValueError("min_values must be between 0 and 25") if max_values < 1 or max_values > 25: @@ -113,7 +113,9 @@ def __init__( if placeholder and len(placeholder) > 150: raise ValueError("placeholder must be 150 characters or fewer") if not isinstance(custom_id, str) and custom_id is not None: - raise TypeError(f"expected custom_id to be str, not {custom_id.__class__.__name__}") + raise TypeError( + f"expected custom_id to be str, not {custom_id.__class__.__name__}" + ) self._provided_custom_id = custom_id is not None custom_id = os.urandom(16).hex() if custom_id is None else custom_id @@ -143,12 +145,12 @@ def custom_id(self, value: str): self._underlying.custom_id = value @property - def placeholder(self) -> Optional[str]: + def placeholder(self) -> str | None: """Optional[:class:`str`]: The placeholder text that is shown if nothing is selected, if any.""" return self._underlying.placeholder @placeholder.setter - def placeholder(self, value: Optional[str]): + def placeholder(self, value: str | None): if value is not None and not isinstance(value, str): raise TypeError("placeholder must be None or str") if value and len(value) > 150: @@ -179,12 +181,12 @@ def max_values(self, value: int): self._underlying.max_values = int(value) @property - def options(self) -> List[SelectOption]: + def options(self) -> list[SelectOption]: """List[:class:`discord.SelectOption`]: A list of options that can be selected in this menu.""" return self._underlying.options @options.setter - def options(self, value: List[SelectOption]): + def options(self, value: list[SelectOption]): if not isinstance(value, list): raise TypeError("options must be a list of SelectOption") if not all(isinstance(obj, SelectOption) for obj in value): @@ -197,8 +199,8 @@ def add_option( *, label: str, value: str = MISSING, - description: Optional[str] = None, - emoji: Optional[Union[str, Emoji, PartialEmoji]] = None, + description: str | None = None, + emoji: str | Emoji | PartialEmoji | None = None, default: bool = False, ): """Adds an option to the select menu. @@ -207,7 +209,7 @@ def add_option( :meth:`append_option` method instead. Parameters - ----------- + ---------- label: :class:`str` The label of the option. This is displayed to users. Can only be up to 100 characters. @@ -224,7 +226,7 @@ def add_option( Whether this option is selected by default. Raises - ------- + ------ ValueError The number of options exceeds 25. """ @@ -243,12 +245,12 @@ def append_option(self, option: SelectOption): """Appends an option to the select menu. Parameters - ----------- + ---------- option: :class:`discord.SelectOption` The option to append to the select menu. Raises - ------- + ------ ValueError The number of options exceeds 25. """ @@ -268,7 +270,7 @@ def disabled(self, value: bool): self._underlying.disabled = bool(value) @property - def values(self) -> List[str]: + def values(self) -> list[str]: """List[:class:`str`]: A list of values that have been selected by the user.""" return self._selected_values @@ -287,7 +289,7 @@ def refresh_state(self, interaction: Interaction) -> None: self._selected_values = data.get("values", []) @classmethod - def from_component(cls: Type[S], component: SelectMenu) -> S: + def from_component(cls: type[S], component: SelectMenu) -> S: return cls( custom_id=component.custom_id, placeholder=component.placeholder, @@ -308,13 +310,13 @@ def is_dispatchable(self) -> bool: def select( *, - placeholder: Optional[str] = None, - custom_id: Optional[str] = None, + placeholder: str | None = None, + custom_id: str | None = None, min_values: int = 1, max_values: int = 1, - options: List[SelectOption] = MISSING, + options: list[SelectOption] = MISSING, disabled: bool = False, - row: Optional[int] = None, + row: int | None = None, ) -> Callable[[ItemCallbackType], ItemCallbackType]: """A decorator that attaches a select menu to a component. @@ -326,7 +328,7 @@ def select( use :attr:`Select.values`. Parameters - ------------ + ---------- placeholder: Optional[:class:`str`] The placeholder text that is shown if nothing is selected, if any. custom_id: :class:`str` diff --git a/discord/ui/view.py b/discord/ui/view.py index a3461259c5..20e2b291e4 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -32,27 +32,15 @@ import traceback from functools import partial from itertools import groupby -from typing import ( - TYPE_CHECKING, - Any, - Callable, - ClassVar, - Dict, - Iterator, - List, - Optional, - Sequence, - Tuple, - Union, -) +from typing import TYPE_CHECKING, Any, Callable, ClassVar, Iterator, Sequence from ..components import ActionRow as ActionRowComponent from ..components import Button as ButtonComponent from ..components import Component from ..components import SelectMenu as SelectComponent from ..components import _component_factory -from .item import Item, ItemCallbackType from ..utils import get +from .item import Item, ItemCallbackType __all__ = ("View",) @@ -64,7 +52,7 @@ from ..types.components import Component as ComponentPayload -def _walk_all_components(components: List[Component]) -> Iterator[Component]: +def _walk_all_components(components: list[Component]) -> Iterator[Component]: for item in components: if isinstance(item, ActionRowComponent): yield from item.children @@ -87,8 +75,8 @@ def _component_to_item(component: Component) -> Item: class _ViewWeights: __slots__ = ("weights",) - def __init__(self, children: List[Item]): - self.weights: List[int] = [0, 0, 0, 0, 0] + def __init__(self, children: list[Item]): + self.weights: list[int] = [0, 0, 0, 0, 0] key = lambda i: sys.maxsize if i.row is None else i.row children = sorted(children, key=key) @@ -107,7 +95,9 @@ def add_item(self, item: Item) -> None: if item.row is not None: total = self.weights[item.row] + item.width if total > 5: - raise ValueError(f"item would not fit at row {item.row} ({total} > 5 width)") + raise ValueError( + f"item would not fit at row {item.row} ({total} > 5 width)" + ) self.weights[item.row] = total item._rendered_row = item.row else: @@ -132,7 +122,7 @@ class View: .. versionadded:: 2.0 Parameters - ----------- + ---------- *items: :class:`Item` The initial items attached to this view. timeout: Optional[:class:`float`] @@ -140,7 +130,7 @@ class View: If ``None`` then there is no timeout. Attributes - ------------ + ---------- timeout: Optional[:class:`float`] Timeout from last interaction with the UI before no longer accepting input. If ``None`` then there is no timeout. @@ -149,15 +139,15 @@ class View: disable_on_timeout: :class:`bool` Whether to disable the view when the timeout is reached. Defaults to ``False``. message: Optional[:class:`.Message`] - The message that this view is attached to. + The message that this view is attached to. If ``None`` then the view has not been sent with a message. """ __discord_ui_view__: ClassVar[bool] = True - __view_children_items__: ClassVar[List[ItemCallbackType]] = [] + __view_children_items__: ClassVar[list[ItemCallbackType]] = [] def __init_subclass__(cls) -> None: - children: List[ItemCallbackType] = [] + children: list[ItemCallbackType] = [] for base in reversed(cls.__mro__): for member in base.__dict__.values(): if hasattr(member, "__discord_ui_model_type__"): @@ -168,12 +158,19 @@ def __init_subclass__(cls) -> None: cls.__view_children_items__ = children - def __init__(self, *items: Item, timeout: Optional[float] = 180.0, disable_on_timeout: bool = False): + def __init__( + self, + *items: Item, + timeout: float | None = 180.0, + disable_on_timeout: bool = False, + ): self.timeout = timeout self.disable_on_timeout = disable_on_timeout - self.children: List[Item] = [] + self.children: list[Item] = [] for func in self.__view_children_items__: - item: Item = func.__discord_ui_model_type__(**func.__discord_ui_model_kwargs__) + item: Item = func.__discord_ui_model_type__( + **func.__discord_ui_model_kwargs__ + ) item.callback = partial(func, self, item) item._view = self setattr(self, func.__name__, item) @@ -185,11 +182,11 @@ def __init__(self, *items: Item, timeout: Optional[float] = 180.0, disable_on_ti loop = asyncio.get_running_loop() self.id: str = os.urandom(16).hex() - self.__cancel_callback: Optional[Callable[[View], None]] = None - self.__timeout_expiry: Optional[float] = None - self.__timeout_task: Optional[asyncio.Task[None]] = None + self.__cancel_callback: Callable[[View], None] | None = None + self.__timeout_expiry: float | None = None + self.__timeout_task: asyncio.Task[None] | None = None self.__stopped: asyncio.Future[bool] = loop.create_future() - self._message: Optional[Union[Message, InteractionMessage]] = None + self._message: Message | InteractionMessage | None = None def __repr__(self) -> str: return f"<{self.__class__.__name__} timeout={self.timeout} children={len(self.children)}>" @@ -211,12 +208,12 @@ async def __timeout_task_impl(self) -> None: # Wait N seconds to see if timeout data has been refreshed await asyncio.sleep(self.__timeout_expiry - now) - def to_components(self) -> List[Dict[str, Any]]: + def to_components(self) -> list[dict[str, Any]]: def key(item: Item) -> int: return item._rendered_row or 0 children = sorted(self.children, key=key) - components: List[Dict[str, Any]] = [] + components: list[dict[str, Any]] = [] for _, group in groupby(children, key=key): children = [item.to_component_dict() for item in group] if not children: @@ -232,7 +229,9 @@ def key(item: Item) -> int: return components @classmethod - def from_message(cls, message: Message, /, *, timeout: Optional[float] = 180.0) -> View: + def from_message( + cls, message: Message, /, *, timeout: float | None = 180.0 + ) -> View: """Converts a message's components into a :class:`View`. The :attr:`.Message.components` of a message are read-only @@ -241,14 +240,14 @@ def from_message(cls, message: Message, /, *, timeout: Optional[float] = 180.0) converted into a :class:`View` first. Parameters - ----------- + ---------- message: :class:`.Message` The message with components to convert into a view. timeout: Optional[:class:`float`] The timeout of the converted view. Returns - -------- + ------- :class:`View` The converted view. This always returns a :class:`View` and not one of its subclasses. @@ -259,7 +258,7 @@ def from_message(cls, message: Message, /, *, timeout: Optional[float] = 180.0) return view @property - def _expires_at(self) -> Optional[float]: + def _expires_at(self) -> float | None: if self.timeout: return time.monotonic() + self.timeout return None @@ -268,12 +267,12 @@ def add_item(self, item: Item) -> None: """Adds an item to the view. Parameters - ----------- + ---------- item: :class:`Item` The item to add to the view. Raises - -------- + ------ TypeError An :class:`Item` was not passed. ValueError @@ -296,7 +295,7 @@ def remove_item(self, item: Item) -> None: """Removes an item from the view. Parameters - ----------- + ---------- item: :class:`Item` The item to remove from the view. """ @@ -313,16 +312,16 @@ def clear_items(self) -> None: self.children.clear() self.__weights.clear() - def get_item(self, custom_id: str) -> Optional[Item]: + def get_item(self, custom_id: str) -> Item | None: """Get an item from the view with the given custom ID. Alias for `utils.get(view.children, custom_id=custom_id)`. Parameters - ----------- + ---------- custom_id: :class:`str` The custom_id of the item to get Returns - -------- + ------- Optional[:class:`Item`] The item with the matching ``custom_id`` if it exists. """ @@ -347,12 +346,12 @@ async def interaction_check(self, interaction: Interaction) -> bool: is considered a failure and :meth:`on_error` is called. Parameters - ----------- + ---------- interaction: :class:`~discord.Interaction` The interaction that occurred. Returns - --------- + ------- :class:`bool` Whether the view children's callbacks should be called. """ @@ -374,13 +373,14 @@ async def on_check_failure(self, interaction: Interaction) -> None: This can be used to send a response when a check failure occurs. Parameters - ----------- + ---------- interaction: :class:`~discord.Interaction` The interaction that occurred. """ - pass - async def on_error(self, error: Exception, item: Item, interaction: Interaction) -> None: + async def on_error( + self, error: Exception, item: Item, interaction: Interaction + ) -> None: """|coro| A callback that is called when an item's callback or :meth:`interaction_check` @@ -389,7 +389,7 @@ async def on_error(self, error: Exception, item: Item, interaction: Interaction) The default implementation prints the traceback to stderr. Parameters - ----------- + ---------- error: :class:`Exception` The exception that was raised. item: :class:`Item` @@ -398,7 +398,9 @@ async def on_error(self, error: Exception, item: Item, interaction: Interaction) The interaction that led to the failure. """ print(f"Ignoring exception in view {self} for item {item}:", file=sys.stderr) - traceback.print_exception(error.__class__, error, error.__traceback__, file=sys.stderr) + traceback.print_exception( + error.__class__, error, error.__traceback__, file=sys.stderr + ) async def _scheduled_task(self, item: Item, interaction: Interaction): try: @@ -428,7 +430,9 @@ def _dispatch_timeout(self): return self.__stopped.set_result(True) - asyncio.create_task(self.on_timeout(), name=f"discord-ui-view-timeout-{self.id}") + asyncio.create_task( + self.on_timeout(), name=f"discord-ui-view-timeout-{self.id}" + ) def _dispatch_item(self, item: Item, interaction: Interaction): if self.__stopped.done(): @@ -439,12 +443,14 @@ def _dispatch_item(self, item: Item, interaction: Interaction): name=f"discord-ui-view-dispatch-{self.id}", ) - def refresh(self, components: List[Component]): + def refresh(self, components: list[Component]): # This is pretty hacky at the moment - old_state: Dict[Tuple[int, str], Item] = { + old_state: dict[tuple[int, str], Item] = { (item.type.value, item.custom_id): item for item in self.children if item.is_dispatchable() # type: ignore } - children: List[Item] = [item for item in self.children if not item.is_dispatchable()] + children: list[Item] = [ + item for item in self.children if not item.is_dispatchable() + ] for component in _walk_all_components(components): try: older = old_state[(component.type.value, component.custom_id)] # type: ignore @@ -490,7 +496,9 @@ def is_persistent(self) -> bool: A persistent view has all their components with a set ``custom_id`` and a :attr:`timeout` set to ``None``. """ - return self.timeout is None and all(item.is_persistent() for item in self.children) + return self.timeout is None and all( + item.is_persistent() for item in self.children + ) async def wait(self) -> bool: """Waits until the view has finished interacting. @@ -499,19 +507,19 @@ async def wait(self) -> bool: is called, or it times out. Returns - -------- + ------- :class:`bool` If ``True``, then the view timed out. If ``False`` then the view finished normally. """ return await self.__stopped - def disable_all_items(self, *, exclusions: Optional[List[Item]] = None) -> None: + def disable_all_items(self, *, exclusions: list[Item] | None = None) -> None: """ Disables all items in the view. Parameters - ----------- + ---------- exclusions: Optional[List[:class:`Item`]] A list of items in `self.children` to not disable from the view. """ @@ -519,12 +527,12 @@ def disable_all_items(self, *, exclusions: Optional[List[Item]] = None) -> None: if exclusions is None or child not in exclusions: child.disabled = True - def enable_all_items(self, *, exclusions: Optional[List[Item]] = None) -> None: + def enable_all_items(self, *, exclusions: list[Item] | None = None) -> None: """ Enables all items in the view. Parameters - ----------- + ---------- exclusions: Optional[List[:class:`Item`]] A list of items in `self.children` to not enable from the view. """ @@ -535,7 +543,7 @@ def enable_all_items(self, *, exclusions: Optional[List[Item]] = None) -> None: @property def message(self): return self._message - + @message.setter def message(self, value): self._message = value @@ -544,18 +552,22 @@ def message(self, value): class ViewStore: def __init__(self, state: ConnectionState): # (component_type, message_id, custom_id): (View, Item) - self._views: Dict[Tuple[int, Optional[int], str], Tuple[View, Item]] = {} + self._views: dict[tuple[int, int | None, str], tuple[View, Item]] = {} # message_id: View - self._synced_message_views: Dict[int, View] = {} + self._synced_message_views: dict[int, View] = {} self._state: ConnectionState = state @property def persistent_views(self) -> Sequence[View]: - views = {view.id: view for (_, (view, _)) in self._views.items() if view.is_persistent()} + views = { + view.id: view + for (_, (view, _)) in self._views.items() + if view.is_persistent() + } return list(views.values()) def __verify_integrity(self): - to_remove: List[Tuple[int, Optional[int], str]] = [] + to_remove: list[tuple[int, int | None, str]] = [] for (k, (view, _)) in self._views.items(): if view.is_finished(): to_remove.append(k) @@ -563,7 +575,7 @@ def __verify_integrity(self): for k in to_remove: del self._views[k] - def add_view(self, view: View, message_id: Optional[int] = None): + def add_view(self, view: View, message_id: int | None = None): self.__verify_integrity() view._start_listening_from_store(self) @@ -586,11 +598,13 @@ def remove_view(self, view: View): def dispatch(self, component_type: int, custom_id: str, interaction: Interaction): self.__verify_integrity() - message_id: Optional[int] = interaction.message and interaction.message.id + message_id: int | None = interaction.message and interaction.message.id key = (component_type, message_id, custom_id) # Fallback to None message_id searches in case a persistent view # was added without an associated message_id - value = self._views.get(key) or self._views.get((component_type, None, custom_id)) + value = self._views.get(key) or self._views.get( + (component_type, None, custom_id) + ) if value is None: return @@ -601,10 +615,10 @@ def dispatch(self, component_type: int, custom_id: str, interaction: Interaction def is_message_tracked(self, message_id: int): return message_id in self._synced_message_views - def remove_message_tracking(self, message_id: int) -> Optional[View]: + def remove_message_tracking(self, message_id: int) -> View | None: return self._synced_message_views.pop(message_id, None) - def update_from_message(self, message_id: int, components: List[ComponentPayload]): + def update_from_message(self, message_id: int, components: list[ComponentPayload]): # pre-req: is_message_tracked == true view = self._synced_message_views[message_id] view.refresh([_component_factory(d) for d in components]) diff --git a/discord/user.py b/discord/user.py index 8dab1e91b1..3541c33823 100644 --- a/discord/user.py +++ b/discord/user.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, TypeVar +from typing import TYPE_CHECKING, Any, TypeVar import discord.abc @@ -80,9 +80,9 @@ class BaseUser(_UserTag): bot: bool system: bool _state: ConnectionState - _avatar: Optional[str] - _banner: Optional[str] - _accent_colour: Optional[int] + _avatar: str | None + _banner: str | None + _accent_colour: int | None _public_flags: int def __init__(self, *, state: ConnectionState, data: UserPayload) -> None: @@ -119,7 +119,7 @@ def _update(self, data: UserPayload) -> None: self.system = data.get("system", False) @classmethod - def _copy(cls: Type[BU], user: BU) -> BU: + def _copy(cls: type[BU], user: BU) -> BU: self = cls.__new__(cls) # bypass __init__ self.name = user.name @@ -134,7 +134,7 @@ def _copy(cls: Type[BU], user: BU) -> BU: return self - def _to_minimal_user_json(self) -> Dict[str, Any]: + def _to_minimal_user_json(self) -> dict[str, Any]: return { "username": self.name, "id": self.id, @@ -150,14 +150,14 @@ def jump_url(self) -> str: .. versionadded:: 2.0 """ return f"https://discord.com/users/{self.id}" - + @property def public_flags(self) -> PublicUserFlags: """:class:`PublicUserFlags`: The publicly available flags the user has.""" return PublicUserFlags._from_value(self._public_flags) @property - def avatar(self) -> Optional[Asset]: + def avatar(self) -> Asset | None: """Optional[:class:`Asset`]: Returns an :class:`Asset` for the avatar the user has. If the user does not have a traditional avatar, ``None`` is returned. @@ -172,7 +172,9 @@ def default_avatar(self) -> Asset: """:class:`Asset`: Returns the default avatar for a given user. This is calculated by the user's discriminator. """ - return Asset._from_default_avatar(self._state, int(self.discriminator) % len(DefaultAvatar)) + return Asset._from_default_avatar( + self._state, int(self.discriminator) % len(DefaultAvatar) + ) @property def display_avatar(self) -> Asset: @@ -185,12 +187,11 @@ def display_avatar(self) -> Asset: return self.avatar or self.default_avatar @property - def banner(self) -> Optional[Asset]: + def banner(self) -> Asset | None: """Optional[:class:`Asset`]: Returns the user's banner asset, if available. .. versionadded:: 2.0 - .. note:: This information is only available via :meth:`Client.fetch_user`. """ @@ -199,7 +200,7 @@ def banner(self) -> Optional[Asset]: return Asset._from_user_banner(self._state, self.id, self._banner) @property - def accent_colour(self) -> Optional[Colour]: + def accent_colour(self) -> Colour | None: """Optional[:class:`Colour`]: Returns the user's accent colour, if applicable. There is an alias for this named :attr:`accent_color`. @@ -215,7 +216,7 @@ def accent_colour(self) -> Optional[Colour]: return Colour(self._accent_colour) @property - def accent_color(self) -> Optional[Colour]: + def accent_color(self) -> Colour | None: """Optional[:class:`Colour`]: Returns the user's accent color, if applicable. There is an alias for this named :attr:`accent_colour`. @@ -273,7 +274,7 @@ def mentioned_in(self, message: Message) -> bool: """Checks if the user is mentioned in the specified message. Parameters - ----------- + ---------- message: :class:`Message` The message to check if you're mentioned in. @@ -311,7 +312,7 @@ class ClientUser(BaseUser): Returns the user's name with discriminator. Attributes - ----------- + ---------- name: :class:`str` The user's username. id: :class:`int` @@ -337,7 +338,7 @@ class ClientUser(BaseUser): if TYPE_CHECKING: verified: bool - locale: Optional[str] + locale: str | None mfa_enabled: bool _flags: int @@ -358,7 +359,9 @@ def _update(self, data: UserPayload) -> None: self._flags = data.get("flags", 0) self.mfa_enabled = data.get("mfa_enabled", False) - async def edit(self, *, username: str = MISSING, avatar: bytes = MISSING) -> ClientUser: + async def edit( + self, *, username: str = MISSING, avatar: bytes = MISSING + ) -> ClientUser: """|coro| Edits the current profile of the client. @@ -376,26 +379,26 @@ async def edit(self, *, username: str = MISSING, avatar: bytes = MISSING) -> Cli The edit is no longer in-place, instead the newly edited client user is returned. Parameters - ----------- + ---------- username: :class:`str` The new username you wish to change to. avatar: :class:`bytes` A :term:`py:bytes-like object` representing the image to upload. Could be ``None`` to denote no avatar. + Returns + ------- + :class:`ClientUser` + The newly edited client user. + Raises ------ HTTPException Editing your profile failed. InvalidArgument Wrong image format passed for ``avatar``. - - Returns - --------- - :class:`ClientUser` - The newly edited client user. """ - payload: Dict[str, Any] = {} + payload: dict[str, Any] = {} if username is not MISSING: payload["username"] = username @@ -428,7 +431,7 @@ class User(BaseUser, discord.abc.Messageable): Returns the user's name with discriminator. Attributes - ----------- + ---------- name: :class:`str` The user's username. id: :class:`int` @@ -468,7 +471,7 @@ async def _get_channel(self) -> DMChannel: return ch @property - def dm_channel(self) -> Optional[DMChannel]: + def dm_channel(self) -> DMChannel | None: """Optional[:class:`DMChannel`]: Returns the channel associated with this user if it exists. If this returns ``None``, you can create a DM channel by calling the @@ -477,7 +480,7 @@ def dm_channel(self) -> Optional[DMChannel]: return self._state._get_private_channel_by_user(self.id) @property - def mutual_guilds(self) -> List[Guild]: + def mutual_guilds(self) -> list[Guild]: """List[:class:`Guild`]: The guilds that the user shares with the client. .. note:: @@ -486,7 +489,9 @@ def mutual_guilds(self) -> List[Guild]: .. versionadded:: 1.7 """ - return [guild for guild in self._state._guilds.values() if guild.get_member(self.id)] + return [ + guild for guild in self._state._guilds.values() if guild.get_member(self.id) + ] async def create_dm(self) -> DMChannel: """|coro| diff --git a/discord/utils.py b/discord/utils.py index 780d6cd75e..cf59e979d1 100644 --- a/discord/utils.py +++ b/discord/utils.py @@ -48,20 +48,15 @@ Awaitable, Callable, Coroutine, - Dict, ForwardRef, Generic, Iterable, Iterator, - List, Literal, Mapping, NewType, - Optional, Protocol, Sequence, - Tuple, - Type, TypeVar, Union, overload, @@ -163,14 +158,14 @@ def __init__(self, name: str, function: Callable[[T], T_co]) -> None: self.__doc__ = getattr(function, "__doc__") @overload - def __get__(self, instance: None, owner: Type[T]) -> CachedSlotProperty[T, T_co]: + def __get__(self, instance: None, owner: type[T]) -> CachedSlotProperty[T, T_co]: ... @overload - def __get__(self, instance: T, owner: Type[T]) -> T_co: + def __get__(self, instance: T, owner: type[T]) -> T_co: ... - def __get__(self, instance: Optional[T], owner: Type[T]) -> Any: + def __get__(self, instance: T | None, owner: type[T]) -> Any: if instance is None: return self @@ -186,7 +181,7 @@ class classproperty(Generic[T_co]): def __init__(self, fget: Callable[[Any], T_co]) -> None: self.fget = fget - def __get__(self, instance: Optional[Any], owner: Type[Any]) -> T_co: + def __get__(self, instance: Any | None, owner: type[Any]) -> T_co: return self.fget(owner) def __set__(self, instance, value) -> None: @@ -252,11 +247,11 @@ def parse_time(timestamp: str) -> datetime.datetime: @overload -def parse_time(timestamp: Optional[str]) -> Optional[datetime.datetime]: +def parse_time(timestamp: str | None) -> datetime.datetime | None: ... -def parse_time(timestamp: Optional[str]) -> Optional[datetime.datetime]: +def parse_time(timestamp: str | None) -> datetime.datetime | None: if timestamp: return datetime.datetime.fromisoformat(timestamp) return None @@ -273,10 +268,10 @@ def decorator(overridden: T) -> T: def warn_deprecated( name: str, - instead: Optional[str] = None, - since: Optional[str] = None, - removed: Optional[str] = None, - reference: Optional[str] = None, + instead: str | None = None, + since: str | None = None, + removed: str | None = None, + reference: str | None = None, ) -> None: """Warn about a deprecated function, with the ability to specify details about the deprecation. Emits a DeprecationWarning. @@ -314,10 +309,10 @@ def warn_deprecated( def deprecated( - instead: Optional[str] = None, - since: Optional[str] = None, - removed: Optional[str] = None, - reference: Optional[str] = None, + instead: str | None = None, + since: str | None = None, + removed: str | None = None, + reference: str | None = None, *, use_qualname: bool = True, ) -> Callable[[Callable[[P], T]], Callable[[P], T]]: @@ -342,6 +337,7 @@ def deprecated( the function will be used instead. For example, __qualname__ will display as ``Client.login`` while __name__ will display as ``login``. Defaults to ``True``. """ + def actual_decorator(func: Callable[[P], T]) -> Callable[[P], T]: @functools.wraps(func) def decorated(*args: P.args, **kwargs: P.kwargs) -> T: @@ -360,7 +356,7 @@ def decorated(*args: P.args, **kwargs: P.kwargs) -> T: def oauth_url( - client_id: Union[int, str], + client_id: int | str, *, permissions: Permissions = MISSING, guild: Snowflake = MISSING, @@ -372,7 +368,7 @@ def oauth_url( into guilds. Parameters - ----------- + ---------- client_id: Union[:class:`int`, :class:`str`] The client ID for your bot. permissions: :class:`~discord.Permissions` @@ -392,7 +388,7 @@ def oauth_url( .. versionadded:: 2.0 Returns - -------- + ------- :class:`str` The OAuth2 URL for inviting the bot into guilds. """ @@ -414,12 +410,12 @@ def oauth_url( def snowflake_time(id: int) -> datetime.datetime: """ Parameters - ----------- + ---------- id: :class:`int` The snowflake ID. Returns - -------- + ------- :class:`datetime.datetime` An aware datetime in UTC representing the creation time of the snowflake. """ @@ -437,7 +433,7 @@ def time_snowflake(dt: datetime.datetime, high: bool = False) -> int: to be inclusive, ``high=False`` to be exclusive Parameters - ----------- + ---------- dt: :class:`datetime.datetime` A datetime object to convert to a snowflake. If naive, the timezone is assumed to be local time. @@ -445,7 +441,7 @@ def time_snowflake(dt: datetime.datetime, high: bool = False) -> int: Whether to set the lower 22 bit to high or low. Returns - -------- + ------- :class:`int` The snowflake representing the time given. """ @@ -453,7 +449,7 @@ def time_snowflake(dt: datetime.datetime, high: bool = False) -> int: return (discord_millis << 22) + (2**22 - 1 if high else 0) -def find(predicate: Callable[[T], Any], seq: Iterable[T]) -> Optional[T]: +def find(predicate: Callable[[T], Any], seq: Iterable[T]) -> T | None: """A helper to return the first element found in the sequence that meets the predicate. For example: :: @@ -466,7 +462,7 @@ def find(predicate: Callable[[T], Any], seq: Iterable[T]) -> Optional[T]: a valid entry. Parameters - ----------- + ---------- predicate A function that returns a boolean-like result. seq: :class:`collections.abc.Iterable` @@ -479,7 +475,7 @@ def find(predicate: Callable[[T], Any], seq: Iterable[T]) -> Optional[T]: return None -def get(iterable: Iterable[T], **attrs: Any) -> Optional[T]: +def get(iterable: Iterable[T], **attrs: Any) -> T | None: r"""A helper that returns the first element in the iterable that meets all the traits passed in ``attrs``. This is an alternative for :func:`~discord.utils.find`. @@ -536,7 +532,9 @@ def get(iterable: Iterable[T], **attrs: Any) -> Optional[T]: return elem return None - converted = [(attrget(attr.replace("__", ".")), value) for attr, value in attrs.items()] + converted = [ + (attrget(attr.replace("__", ".")), value) for attr, value in attrs.items() + ] for elem in iterable: if _all(pred(elem) == value for pred, value in converted): @@ -562,11 +560,11 @@ async def get_or_fetch(obj, attr: str, id: int, *, default: Any = MISSING): return getter -def _unique(iterable: Iterable[T]) -> List[T]: +def _unique(iterable: Iterable[T]) -> list[T]: return [x for x in dict.fromkeys(iterable)] -def _get_as_snowflake(data: Any, key: str) -> Optional[int]: +def _get_as_snowflake(data: Any, key: str) -> int | None: try: value = data[key] except KeyError: @@ -611,12 +609,14 @@ def _to_json(obj: Any) -> str: def _parse_ratelimit_header(request: Any, *, use_clock: bool = False) -> float: - reset_after: Optional[str] = request.headers.get("X-Ratelimit-Reset-After") + reset_after: str | None = request.headers.get("X-Ratelimit-Reset-After") if not use_clock and reset_after: return float(reset_after) utc = datetime.timezone.utc now = datetime.datetime.now(utc) - reset = datetime.datetime.fromtimestamp(float(request.headers["X-Ratelimit-Reset"]), utc) + reset = datetime.datetime.fromtimestamp( + float(request.headers["X-Ratelimit-Reset"]), utc + ) return (reset - now).total_seconds() @@ -639,7 +639,9 @@ async def async_all(gen, *, check=_isawaitable): async def sane_wait_for(futures, *, timeout): ensured = [asyncio.ensure_future(fut) for fut in futures] - done, pending = await asyncio.wait(ensured, timeout=timeout, return_when=asyncio.ALL_COMPLETED) + done, pending = await asyncio.wait( + ensured, timeout=timeout, return_when=asyncio.ALL_COMPLETED + ) if len(pending) != 0: raise asyncio.TimeoutError() @@ -647,7 +649,7 @@ async def sane_wait_for(futures, *, timeout): return done -def get_slots(cls: Type[Any]) -> Iterator[str]: +def get_slots(cls: type[Any]) -> Iterator[str]: for mro in reversed(cls.__mro__): try: yield from mro.__slots__ @@ -662,7 +664,7 @@ def compute_timedelta(dt: datetime.datetime): return max((dt - now).total_seconds(), 0) -async def sleep_until(when: datetime.datetime, result: Optional[T] = None) -> Optional[T]: +async def sleep_until(when: datetime.datetime, result: T | None = None) -> T | None: """|coro| Sleep until a specified time. @@ -672,7 +674,7 @@ async def sleep_until(when: datetime.datetime, result: Optional[T] = None) -> Op .. versionadded:: 1.3 Parameters - ----------- + ---------- when: :class:`datetime.datetime` The timestamp in which to sleep until. If the datetime is naive then it is assumed to be local time. @@ -692,7 +694,7 @@ def utcnow() -> datetime.datetime: .. versionadded:: 2.0 Returns - -------- + ------- :class:`datetime.datetime` The current aware datetime in UTC. """ @@ -730,7 +732,7 @@ def add(self, element: int) -> None: i = bisect_left(self, element) self.insert(i, element) - def get(self, element: int) -> Optional[int]: + def get(self, element: int) -> int | None: i = bisect_left(self, element) return self[i] if i != len(self) and self[i] == element else None @@ -753,17 +755,17 @@ def _string_width(string: str, *, _IS_ASCII=_IS_ASCII) -> int: return sum(2 if func(char) in UNICODE_WIDE_CHAR_TYPE else 1 for char in string) -def resolve_invite(invite: Union[Invite, str]) -> str: +def resolve_invite(invite: Invite | str) -> str: """ Resolves an invite from a :class:`~discord.Invite`, URL or code. Parameters - ----------- + ---------- invite: Union[:class:`~discord.Invite`, :class:`str`] The invite. Returns - -------- + ------- :class:`str` The invite code. """ @@ -778,19 +780,19 @@ def resolve_invite(invite: Union[Invite, str]) -> str: return invite -def resolve_template(code: Union[Template, str]) -> str: +def resolve_template(code: Template | str) -> str: """ Resolves a template code from a :class:`~discord.Template`, URL or code. .. versionadded:: 1.4 Parameters - ----------- + ---------- code: Union[:class:`~discord.Template`, :class:`str`] The code. Returns - -------- + ------- :class:`str` The template code. """ @@ -805,7 +807,9 @@ def resolve_template(code: Union[Template, str]) -> str: return code -_MARKDOWN_ESCAPE_SUBREGEX = "|".join(r"\{0}(?=([\s\S]*((? str: if the input contains ``10 * 5`` then it will be converted into ``10 5``. Parameters - ----------- + ---------- text: :class:`str` The text to remove markdown from. ignore_links: :class:`bool` @@ -851,7 +855,7 @@ def remove_markdown(text: str, *, ignore_links: bool = True) -> str: be left alone. Defaults to ``True``. Returns - -------- + ------- :class:`str` The text with the markdown special characters removed. """ @@ -866,7 +870,9 @@ def replacement(match): return re.sub(regex, replacement, text, 0, re.MULTILINE) -def escape_markdown(text: str, *, as_needed: bool = False, ignore_links: bool = True) -> str: +def escape_markdown( + text: str, *, as_needed: bool = False, ignore_links: bool = True +) -> str: r"""A helper function that escapes Discord's markdown. Parameters @@ -923,63 +929,67 @@ def escape_mentions(text: str) -> str: class. Parameters - ----------- + ---------- text: :class:`str` The text to escape mentions from. Returns - -------- + ------- :class:`str` The text with the mentions removed. """ return re.sub(r"@(everyone|here|[!&]?[0-9]{17,20})", "@\u200b\\1", text) -def raw_mentions(text: str) -> List[int]: + +def raw_mentions(text: str) -> list[int]: """Returns a list of user IDs matching ``<@user_id>`` in the string. Parameters - ----------- + ---------- text: :class:`str` The string to get user mentions from. Returns - -------- + ------- List[:class:`int`] A list of user IDs found in the string. """ return [int(x) for x in re.findall(r"<@!?([0-9]+)>", text)] -def raw_channel_mentions(text: str) -> List[int]: + +def raw_channel_mentions(text: str) -> list[int]: """Returns a list of channel IDs matching ``<@#channel_id>`` in the string. Parameters - ----------- + ---------- text: :class:`str` The string to get channel mentions from. Returns - -------- + ------- List[:class:`int`] A list of channel IDs found in the string. """ return [int(x) for x in re.findall(r"<#([0-9]+)>", text)] -def raw_role_mentions(text: str) -> List[int]: + +def raw_role_mentions(text: str) -> list[int]: """Returns a list of role IDs matching ``<@&role_id>`` in the string. Parameters - ----------- + ---------- text: :class:`str` The string to get role mentions from. Returns - -------- + ------- List[:class:`int`] A list of role IDs found in the string. """ return [int(x) for x in re.findall(r"<@&([0-9]+)>", text)] -def _chunk(iterator: Iterator[T], max_size: int) -> Iterator[List[T]]: + +def _chunk(iterator: Iterator[T], max_size: int) -> Iterator[list[T]]: ret = [] n = 0 for item in iterator: @@ -993,7 +1003,7 @@ def _chunk(iterator: Iterator[T], max_size: int) -> Iterator[List[T]]: yield ret -async def _achunk(iterator: AsyncIterator[T], max_size: int) -> AsyncIterator[List[T]]: +async def _achunk(iterator: AsyncIterator[T], max_size: int) -> AsyncIterator[list[T]]: ret = [] n = 0 async for item in iterator: @@ -1008,16 +1018,16 @@ async def _achunk(iterator: AsyncIterator[T], max_size: int) -> AsyncIterator[Li @overload -def as_chunks(iterator: Iterator[T], max_size: int) -> Iterator[List[T]]: +def as_chunks(iterator: Iterator[T], max_size: int) -> Iterator[list[T]]: ... @overload -def as_chunks(iterator: AsyncIterator[T], max_size: int) -> AsyncIterator[List[T]]: +def as_chunks(iterator: AsyncIterator[T], max_size: int) -> AsyncIterator[list[T]]: ... -def as_chunks(iterator: _Iter[T], max_size: int) -> _Iter[List[T]]: +def as_chunks(iterator: _Iter[T], max_size: int) -> _Iter[list[T]]: """A helper function that collects an iterator into chunks of a given size. .. versionadded:: 2.0 @@ -1029,13 +1039,12 @@ def as_chunks(iterator: _Iter[T], max_size: int) -> _Iter[List[T]]: max_size: :class:`int` The maximum chunk size. - .. warning:: The last chunk collected may not be as large as ``max_size``. Returns - -------- + ------- Union[:class:`collections.abc.Iterator`, :class:`collections.abc.AsyncIterator`] A new iterator which yields chunks of a given size. """ @@ -1050,7 +1059,7 @@ def as_chunks(iterator: _Iter[T], max_size: int) -> _Iter[List[T]]: PY_310 = sys.version_info >= (3, 10) -def flatten_literal_params(parameters: Iterable[Any]) -> Tuple[Any, ...]: +def flatten_literal_params(parameters: Iterable[Any]) -> tuple[Any, ...]: params = [] literal_cls = type(Literal[0]) for p in parameters: @@ -1061,16 +1070,16 @@ def flatten_literal_params(parameters: Iterable[Any]) -> Tuple[Any, ...]: return tuple(params) -def normalise_optional_params(parameters: Iterable[Any]) -> Tuple[Any, ...]: +def normalise_optional_params(parameters: Iterable[Any]) -> tuple[Any, ...]: none_cls = type(None) return tuple(p for p in parameters if p is not none_cls) + (none_cls,) def evaluate_annotation( tp: Any, - globals: Dict[str, Any], - locals: Dict[str, Any], - cache: Dict[str, Any], + globals: dict[str, Any], + locals: dict[str, Any], + cache: dict[str, Any], *, implicit_str: bool = True, ): @@ -1109,11 +1118,16 @@ def evaluate_annotation( is_literal = True evaluated_args = tuple( - evaluate_annotation(arg, globals, locals, cache, implicit_str=implicit_str) for arg in args + evaluate_annotation(arg, globals, locals, cache, implicit_str=implicit_str) + for arg in args ) - if is_literal and not all(isinstance(x, (str, int, bool, type(None))) for x in evaluated_args): - raise TypeError("Literal arguments must be of type str, int, bool, or NoneType.") + if is_literal and not all( + isinstance(x, (str, int, bool, type(None))) for x in evaluated_args + ): + raise TypeError( + "Literal arguments must be of type str, int, bool, or NoneType." + ) if evaluated_args == args: return tp @@ -1128,9 +1142,9 @@ def evaluate_annotation( def resolve_annotation( annotation: Any, - globalns: Dict[str, Any], - localns: Optional[Dict[str, Any]], - cache: Optional[Dict[str, Any]], + globalns: dict[str, Any], + localns: dict[str, Any] | None, + cache: dict[str, Any] | None, ) -> Any: if annotation is None: return type(None) @@ -1146,7 +1160,7 @@ def resolve_annotation( TimestampStyle = Literal["f", "F", "d", "D", "t", "T", "R"] -def format_dt(dt: datetime.datetime, /, style: Optional[TimestampStyle] = None) -> str: +def format_dt(dt: datetime.datetime, /, style: TimestampStyle | None = None) -> str: """A helper function to format a :class:`datetime.datetime` for presentation within Discord. This allows for a locale-independent way of presenting data using Discord specific Markdown. @@ -1175,14 +1189,14 @@ def format_dt(dt: datetime.datetime, /, style: Optional[TimestampStyle] = None) .. versionadded:: 2.0 Parameters - ----------- + ---------- dt: :class:`datetime.datetime` The datetime to format. style: :class:`str` The style to format the datetime with. Returns - -------- + ------- :class:`str` The formatted string. """ @@ -1191,18 +1205,18 @@ def format_dt(dt: datetime.datetime, /, style: Optional[TimestampStyle] = None) return f"" -def generate_snowflake(dt: Optional[datetime.datetime] = None) -> int: +def generate_snowflake(dt: datetime.datetime | None = None) -> int: """Returns a numeric snowflake pretending to be created at the given date but more accurate and random than :func:`time_snowflake`. If dt is not passed, it makes one from the current time using utcnow. Parameters - ----------- + ---------- dt: :class:`datetime.datetime` A datetime object to convert to a snowflake. If naive, the timezone is assumed to be local time. Returns - -------- + ------- :class:`int` The snowflake representing the time given. """ @@ -1224,12 +1238,23 @@ def basic_autocomplete(values: Values) -> AutocompleteFunc: This is meant to be passed into the :attr:`discord.Option.autocomplete` attribute. + Parameters + ---------- + values: Union[Union[Iterable[:class:`.OptionChoice`], Iterable[:class:`str`], Iterable[:class:`int`], Iterable[:class:`float`]], Callable[[:class:`.AutocompleteContext`], Union[Union[Iterable[:class:`str`], Iterable[:class:`int`], Iterable[:class:`float`]], Awaitable[Union[Iterable[:class:`str`], Iterable[:class:`int`], Iterable[:class:`float`]]]]], Awaitable[Union[Iterable[:class:`str`], Iterable[:class:`int`], Iterable[:class:`float`]]]] + Possible values for the option. Accepts an iterable of :class:`str`, a callable (sync or async) that takes a + single argument of :class:`.AutocompleteContext`, or a coroutine. Must resolve to an iterable of :class:`str`. + + Returns + ------- + Callable[[:class:`.AutocompleteContext`], Awaitable[Union[Iterable[:class:`.OptionChoice`], Iterable[:class:`str`], Iterable[:class:`int`], Iterable[:class:`float`]]]] + A wrapped callback for the autocomplete. + Note - ----- + ---- Autocomplete cannot be used for options that have specified choices. Example - -------- + ------- .. code-block:: python3 @@ -1242,19 +1267,7 @@ async def autocomplete(ctx): Option(str, "name", autocomplete=basic_autocomplete(autocomplete)) - .. versionadded:: 2.0 - - Parameters - ----------- - values: Union[Union[Iterable[:class:`.OptionChoice`], Iterable[:class:`str`], Iterable[:class:`int`], Iterable[:class:`float`]], Callable[[:class:`.AutocompleteContext`], Union[Union[Iterable[:class:`str`], Iterable[:class:`int`], Iterable[:class:`float`]], Awaitable[Union[Iterable[:class:`str`], Iterable[:class:`int`], Iterable[:class:`float`]]]]], Awaitable[Union[Iterable[:class:`str`], Iterable[:class:`int`], Iterable[:class:`float`]]]] - Possible values for the option. Accepts an iterable of :class:`str`, a callable (sync or async) that takes a - single argument of :class:`.AutocompleteContext`, or a coroutine. Must resolve to an iterable of :class:`str`. - - Returns - -------- - Callable[[:class:`.AutocompleteContext`], Awaitable[Union[Iterable[:class:`.OptionChoice`], Iterable[:class:`str`], Iterable[:class:`int`], Iterable[:class:`float`]]]] - A wrapped callback for the autocomplete. """ async def autocomplete_callback(ctx: AutocompleteContext) -> V: @@ -1279,7 +1292,7 @@ def filter_params(params, **kwargs): """A helper function to filter out and replace certain keyword parameters Parameters - ----------- + ---------- params: Dict[str, Any] The initial parameters to filter. **kwargs: Dict[str, Optional[str]] @@ -1295,10 +1308,9 @@ def filter_params(params, **kwargs): {'param3': 12} # values of 'param1' is moved to 'param3' # and values of 'param2' are completely removed. - """ for old_param, new_param in kwargs.items(): - if old_param in params: + if old_param in params: if new_param is None: params.pop(old_param) else: diff --git a/discord/voice_client.py b/discord/voice_client.py index 2d5b1d3b71..edf2d37419 100644 --- a/discord/voice_client.py +++ b/discord/voice_client.py @@ -22,7 +22,6 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - Some documentation to refer to: - Our main web socket (mWS) sends opcode 4 with a guild ID and channel ID. @@ -47,7 +46,7 @@ import struct import threading import time -from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple +from typing import TYPE_CHECKING, Any, Callable from . import opus, utils from .backoff import ExponentialBackoff @@ -101,7 +100,7 @@ class VoiceProtocol: .. _Lavalink: https://github.com/freyacodes/Lavalink Parameters - ------------ + ---------- client: :class:`Client` The client (or its subclasses) that started the connection request. channel: :class:`abc.Connectable` @@ -119,7 +118,7 @@ async def on_voice_state_update(self, data: GuildVoiceStatePayload) -> None: has changed. This corresponds to ``VOICE_STATE_UPDATE``. Parameters - ------------ + ---------- data: :class:`dict` The raw `voice state payload`__. @@ -136,7 +135,7 @@ async def on_voice_server_update(self, data: VoiceServerUpdatePayload) -> None: This corresponds to ``VOICE_SERVER_UPDATE``. Parameters - ------------ + ---------- data: :class:`dict` The raw `voice server update payload`__. @@ -161,7 +160,7 @@ async def connect(self, *, timeout: float, reconnect: bool) -> None: The order that these two are called is unspecified. Parameters - ------------ + ---------- timeout: :class:`float` The timeout for the connection. reconnect: :class:`bool` @@ -177,7 +176,7 @@ async def disconnect(self, *, force: bool) -> None: See :meth:`cleanup`. Parameters - ------------ + ---------- force: :class:`bool` Whether the disconnection was forced. """ @@ -203,15 +202,8 @@ class VoiceClient(VoiceProtocol): You do not create these, you typically get them from e.g. :meth:`VoiceChannel.connect`. - Warning - -------- - In order to use PCM based AudioSources, you must have the opus library - installed on your system and loaded through :func:`opus.load_opus`. - Otherwise, your AudioSources must be opus encoded (e.g. using :class:`FFmpegOpusAudio`) - or the library will not be able to transmit audio. - Attributes - ----------- + ---------- session_id: :class:`str` The voice connection session ID. token: :class:`str` @@ -222,11 +214,18 @@ class VoiceClient(VoiceProtocol): The voice channel connected to. loop: :class:`asyncio.AbstractEventLoop` The event loop that the voice client is running on. + + Warning + ------- + In order to use PCM based AudioSources, you must have the opus library + installed on your system and loaded through :func:`opus.load_opus`. + Otherwise, your AudioSources must be opus encoded (e.g. using :class:`FFmpegOpusAudio`) + or the library will not be able to transmit audio. """ endpoint_ip: str voice_port: int - secret_key: List[int] + secret_key: list[int] ssrc: int def __init__(self, client: Client, channel: abc.Connectable): @@ -253,7 +252,7 @@ def __init__(self, client: Client, channel: abc.Connectable): self.timestamp: int = 0 self.timeout: float = 0 self._runner: asyncio.Task = MISSING - self._player: Optional[AudioPlayer] = None + self._player: AudioPlayer | None = None self.encoder: Encoder = MISSING self.decoder = None self._lite_nonce: int = 0 @@ -267,14 +266,14 @@ def __init__(self, client: Client, channel: abc.Connectable): self.stopping_time = None warn_nacl = not has_nacl - supported_modes: Tuple[SupportedModes, ...] = ( + supported_modes: tuple[SupportedModes, ...] = ( "xsalsa20_poly1305_lite", "xsalsa20_poly1305_suffix", "xsalsa20_poly1305", ) @property - def guild(self) -> Optional[Guild]: + def guild(self) -> Guild | None: """Optional[:class:`Guild`]: The guild we're connected to, if applicable.""" return getattr(self.channel, "guild", None) @@ -358,7 +357,9 @@ def prepare_handshake(self) -> None: self._voice_state_complete.clear() self._voice_server_complete.clear() self._handshaking = True - _log.info("Starting voice handshake... (connection attempt %d)", self._connections + 1) + _log.info( + "Starting voice handshake... (connection attempt %d)", self._connections + 1 + ) self._connections += 1 def finish_handshake(self) -> None: @@ -421,7 +422,9 @@ async def potential_reconnect(self) -> bool: self._potentially_reconnecting = True try: # We only care about VOICE_SERVER_UPDATE since VOICE_STATE_UPDATE can come before we get disconnected - await asyncio.wait_for(self._voice_server_complete.wait(), timeout=self.timeout) + await asyncio.wait_for( + self._voice_server_complete.wait(), timeout=self.timeout + ) except asyncio.TimeoutError: self._potentially_reconnecting = False await self.disconnect(force=True) @@ -476,12 +479,16 @@ async def poll_voice_ws(self, reconnect: bool) -> None: await self.disconnect() break if exc.code == 4014: - _log.info("Disconnected from voice by force... potentially reconnecting.") + _log.info( + "Disconnected from voice by force... potentially reconnecting." + ) successful = await self.potential_reconnect() if successful: continue - _log.info("Reconnect was unsuccessful, disconnecting from voice normally...") + _log.info( + "Reconnect was unsuccessful, disconnecting from voice normally..." + ) await self.disconnect() break if not reconnect: @@ -489,7 +496,9 @@ async def poll_voice_ws(self, reconnect: bool) -> None: raise retry = backoff.delay() - _log.exception("Disconnected from voice... Reconnecting in %.2fs.", retry) + _log.exception( + "Disconnected from voice... Reconnecting in %.2fs.", retry + ) self._connected.clear() await asyncio.sleep(retry) await self.voice_disconnect() @@ -527,7 +536,7 @@ async def move_to(self, channel: abc.Snowflake) -> None: Moves you to a different voice channel. Parameters - ----------- + ---------- channel: :class:`abc.Snowflake` The channel to move to. Must be a voice channel. """ @@ -608,9 +617,13 @@ def strip_header_ext(data): return data def get_ssrc(self, user_id): - return {info["user_id"]: ssrc for ssrc, info in self.ws.ssrc_map.items()}[user_id] + return {info["user_id"]: ssrc for ssrc, info in self.ws.ssrc_map.items()}[ + user_id + ] - def play(self, source: AudioSource, *, after: Callable[[Optional[Exception]], Any] = None) -> None: + def play( + self, source: AudioSource, *, after: Callable[[Exception | None], Any] = None + ) -> None: """Plays an :class:`AudioSource`. The finalizer, ``after`` is called after the source has been exhausted @@ -621,7 +634,7 @@ def play(self, source: AudioSource, *, after: Callable[[Optional[Exception]], An passed, any caught exception will be displayed as if it were raised. Parameters - ----------- + ---------- source: :class:`AudioSource` The audio source we're reading from. after: Callable[[Optional[:class:`Exception`]], Any] @@ -630,7 +643,7 @@ def play(self, source: AudioSource, *, after: Callable[[Optional[Exception]], An denotes an optional exception that was raised during playing. Raises - ------- + ------ ClientException Already playing audio or not connected. TypeError @@ -646,7 +659,9 @@ def play(self, source: AudioSource, *, after: Callable[[Optional[Exception]], An raise ClientException("Already playing audio.") if not isinstance(source, AudioSource): - raise TypeError(f"source must be an AudioSource not {source.__class__.__name__}") + raise TypeError( + f"source must be an AudioSource not {source.__class__.__name__}" + ) if not self.encoder and not source.is_opus(): self.encoder = opus.Encoder() @@ -663,7 +678,7 @@ def unpack_audio(self, data): .. versionadded:: 2.0 Parameters - --------- + ---------- data: :class:`bytes` Bytes received by Discord via the UDP connection used for sending and receiving voice data. """ @@ -798,7 +813,9 @@ def recv_audio(self, sink, callback, *args): self.stopping_time = time.perf_counter() self.sink.cleanup() - callback = asyncio.run_coroutine_threadsafe(callback(self.sink, *args), self.loop) + callback = asyncio.run_coroutine_threadsafe( + callback(self.sink, *args), self.loop + ) result = callback.result() if result is not None: @@ -813,7 +830,10 @@ def recv_decoded_audio(self, data): silence = data.timestamp - self.user_timestamps[data.ssrc] - 960 self.user_timestamps[data.ssrc] = data.timestamp - data.decoded_data = struct.pack(" None: self._player.resume() @property - def source(self) -> Optional[AudioSource]: + def source(self) -> AudioSource | None: """Optional[:class:`AudioSource`]: The audio source being played, if playing. This property can also be used to change the audio source currently being played. @@ -873,7 +893,7 @@ def send_audio_packet(self, data: bytes, *, encode: bool = True) -> None: Indicates if ``data`` should be encoded into Opus. Raises - ------- + ------ ClientException You are not connected. opus.OpusError diff --git a/discord/webhook/__init__.py b/discord/webhook/__init__.py index cf93c1f327..adf7aa06c4 100644 --- a/discord/webhook/__init__.py +++ b/discord/webhook/__init__.py @@ -6,7 +6,6 @@ :copyright: (c) 2015-2021 Rapptz & (c) 2021-present Pycord Development :license: MIT, see LICENSE for more details. - """ from .async_ import * diff --git a/discord/webhook/async_.py b/discord/webhook/async_.py index 3796a54e29..f708ddbd46 100644 --- a/discord/webhook/async_.py +++ b/discord/webhook/async_.py @@ -31,18 +31,7 @@ import re import weakref from contextvars import ContextVar -from typing import ( - TYPE_CHECKING, - Any, - Dict, - List, - Literal, - NamedTuple, - Optional, - Tuple, - Union, - overload, -) +from typing import TYPE_CHECKING, Any, Literal, NamedTuple, overload from urllib.parse import quote as urlquote import aiohttp @@ -95,7 +84,7 @@ class AsyncDeferredLock: def __init__(self, lock: asyncio.Lock): self.lock = lock - self.delta: Optional[float] = None + self.delta: float | None = None async def __aenter__(self): await self.lock.acquire() @@ -119,18 +108,18 @@ async def request( route: Route, session: aiohttp.ClientSession, *, - payload: Optional[Dict[str, Any]] = None, - multipart: Optional[List[Dict[str, Any]]] = None, - proxy: Optional[str] = None, - proxy_auth: Optional[aiohttp.BasicAuth] = None, - files: Optional[List[File]] = None, - reason: Optional[str] = None, - auth_token: Optional[str] = None, - params: Optional[Dict[str, Any]] = None, + payload: dict[str, Any] | None = None, + multipart: list[dict[str, Any]] | None = None, + proxy: str | None = None, + proxy_auth: aiohttp.BasicAuth | None = None, + files: list[File] | None = None, + reason: str | None = None, + auth_token: str | None = None, + params: dict[str, Any] | None = None, ) -> Any: - headers: Dict[str, str] = {} + headers: dict[str, str] = {} files = files or [] - to_send: Optional[Union[str, aiohttp.FormData]] = None + to_send: str | aiohttp.FormData | None = None bucket = (route.webhook_id, route.webhook_token) try: @@ -148,8 +137,8 @@ async def request( if reason is not None: headers["X-Audit-Log-Reason"] = urlquote(reason, safe="/ ") - response: Optional[aiohttp.ClientResponse] = None - data: Optional[Union[Dict[str, Any], str]] = None + response: aiohttp.ClientResponse | None = None + data: dict[str, Any] | str | None = None method = route.method url = route.url webhook_id = route.webhook_id @@ -166,13 +155,13 @@ async def request( to_send = form_data try: async with session.request( - method, - url, - data=to_send, - headers=headers, - params=params, - proxy=proxy, - proxy_auth=proxy_auth, + method, + url, + data=to_send, + headers=headers, + params=params, + proxy=proxy, + proxy_auth=proxy_auth, ) as response: _log.debug( "Webhook ID %s with %s %s has returned status code %s", @@ -182,7 +171,10 @@ async def request( response.status, ) data = (await response.text(encoding="utf-8")) or None - if data and response.headers["Content-Type"] == "application/json": + if ( + data + and response.headers["Content-Type"] == "application/json" + ): data = json.loads(data) remaining = response.headers.get("X-Ratelimit-Remaining") @@ -239,14 +231,21 @@ def delete_webhook( self, webhook_id: int, *, - token: Optional[str] = None, + token: str | None = None, session: aiohttp.ClientSession, - proxy: Optional[str] = None, - proxy_auth: Optional[aiohttp.BasicAuth] = None, - reason: Optional[str] = None, + proxy: str | None = None, + proxy_auth: aiohttp.BasicAuth | None = None, + reason: str | None = None, ) -> Response[None]: route = Route("DELETE", "/webhooks/{webhook_id}", webhook_id=webhook_id) - return self.request(route, session=session, proxy=proxy, proxy_auth=proxy_auth, reason=reason, auth_token=token) + return self.request( + route, + session=session, + proxy=proxy, + proxy_auth=proxy_auth, + reason=reason, + auth_token=token, + ) def delete_webhook_with_token( self, @@ -254,9 +253,9 @@ def delete_webhook_with_token( token: str, *, session: aiohttp.ClientSession, - proxy: Optional[str] = None, - proxy_auth: Optional[aiohttp.BasicAuth] = None, - reason: Optional[str] = None, + proxy: str | None = None, + proxy_auth: aiohttp.BasicAuth | None = None, + reason: str | None = None, ) -> Response[None]: route = Route( "DELETE", @@ -264,34 +263,42 @@ def delete_webhook_with_token( webhook_id=webhook_id, webhook_token=token, ) - return self.request(route, session=session, proxy=proxy, proxy_auth=proxy_auth, reason=reason) + return self.request( + route, session=session, proxy=proxy, proxy_auth=proxy_auth, reason=reason + ) def edit_webhook( self, webhook_id: int, token: str, - payload: Dict[str, Any], + payload: dict[str, Any], *, session: aiohttp.ClientSession, - proxy: Optional[str] = None, - proxy_auth: Optional[aiohttp.BasicAuth] = None, - reason: Optional[str] = None, + proxy: str | None = None, + proxy_auth: aiohttp.BasicAuth | None = None, + reason: str | None = None, ) -> Response[WebhookPayload]: route = Route("PATCH", "/webhooks/{webhook_id}", webhook_id=webhook_id) return self.request( - route, session=session, proxy=proxy, proxy_auth=proxy_auth, reason=reason, payload=payload, auth_token=token + route, + session=session, + proxy=proxy, + proxy_auth=proxy_auth, + reason=reason, + payload=payload, + auth_token=token, ) def edit_webhook_with_token( self, webhook_id: int, token: str, - payload: Dict[str, Any], + payload: dict[str, Any], *, session: aiohttp.ClientSession, - proxy: Optional[str] = None, - proxy_auth: Optional[aiohttp.BasicAuth] = None, - reason: Optional[str] = None, + proxy: str | None = None, + proxy_auth: aiohttp.BasicAuth | None = None, + reason: str | None = None, ) -> Response[WebhookPayload]: route = Route( "PATCH", @@ -299,7 +306,14 @@ def edit_webhook_with_token( webhook_id=webhook_id, webhook_token=token, ) - return self.request(route, session=session, proxy=proxy, proxy_auth=proxy_auth, reason=reason, payload=payload) + return self.request( + route, + session=session, + proxy=proxy, + proxy_auth=proxy_auth, + reason=reason, + payload=payload, + ) def execute_webhook( self, @@ -307,15 +321,15 @@ def execute_webhook( token: str, *, session: aiohttp.ClientSession, - proxy: Optional[str] = None, - proxy_auth: Optional[aiohttp.BasicAuth] = None, - payload: Optional[Dict[str, Any]] = None, - multipart: Optional[List[Dict[str, Any]]] = None, - files: Optional[List[File]] = None, - thread_id: Optional[int] = None, - thread_name: Optional[str] = None, + proxy: str | None = None, + proxy_auth: aiohttp.BasicAuth | None = None, + payload: dict[str, Any] | None = None, + multipart: list[dict[str, Any]] | None = None, + files: list[File] | None = None, + thread_id: int | None = None, + thread_name: str | None = None, wait: bool = False, - ) -> Response[Optional[MessagePayload]]: + ) -> Response[MessagePayload | None]: params = {"wait": int(wait)} if thread_id: params["thread_id"] = thread_id @@ -347,9 +361,9 @@ def get_webhook_message( message_id: int, *, session: aiohttp.ClientSession, - proxy: Optional[str] = None, - proxy_auth: Optional[aiohttp.BasicAuth] = None, - thread_id: Optional[int] = None, + proxy: str | None = None, + proxy_auth: aiohttp.BasicAuth | None = None, + thread_id: int | None = None, ) -> Response[MessagePayload]: params = {} @@ -363,7 +377,9 @@ def get_webhook_message( webhook_token=token, message_id=message_id, ) - return self.request(route, session=session, proxy=proxy, proxy_auth=proxy_auth, params=params) + return self.request( + route, session=session, proxy=proxy, proxy_auth=proxy_auth, params=params + ) def edit_webhook_message( self, @@ -372,12 +388,12 @@ def edit_webhook_message( message_id: int, *, session: aiohttp.ClientSession, - proxy: Optional[str] = None, - proxy_auth: Optional[aiohttp.BasicAuth] = None, - thread_id: Optional[int] = None, - payload: Optional[Dict[str, Any]] = None, - multipart: Optional[List[Dict[str, Any]]] = None, - files: Optional[List[File]] = None, + proxy: str | None = None, + proxy_auth: aiohttp.BasicAuth | None = None, + thread_id: int | None = None, + payload: dict[str, Any] | None = None, + multipart: list[dict[str, Any]] | None = None, + files: list[File] | None = None, ) -> Response[Message]: params = {} @@ -409,9 +425,9 @@ def delete_webhook_message( message_id: int, *, session: aiohttp.ClientSession, - proxy: Optional[str] = None, - proxy_auth: Optional[aiohttp.BasicAuth] = None, - thread_id: Optional[int] = None, + proxy: str | None = None, + proxy_auth: aiohttp.BasicAuth | None = None, + thread_id: int | None = None, ) -> Response[None]: params = {} @@ -425,7 +441,9 @@ def delete_webhook_message( webhook_token=token, message_id=message_id, ) - return self.request(route, session=session, proxy=proxy, proxy_auth=proxy_auth, params=params) + return self.request( + route, session=session, proxy=proxy, proxy_auth=proxy_auth, params=params + ) def fetch_webhook( self, @@ -433,11 +451,13 @@ def fetch_webhook( token: str, *, session: aiohttp.ClientSession, - proxy: Optional[str] = None, - proxy_auth: Optional[aiohttp.BasicAuth] = None, + proxy: str | None = None, + proxy_auth: aiohttp.BasicAuth | None = None, ) -> Response[WebhookPayload]: route = Route("GET", "/webhooks/{webhook_id}", webhook_id=webhook_id) - return self.request(route, session=session, proxy=proxy, proxy_auth=proxy_auth, auth_token=token) + return self.request( + route, session=session, proxy=proxy, proxy_auth=proxy_auth, auth_token=token + ) def fetch_webhook_with_token( self, @@ -445,8 +465,8 @@ def fetch_webhook_with_token( token: str, *, session: aiohttp.ClientSession, - proxy: Optional[str] = None, - proxy_auth: Optional[aiohttp.BasicAuth] = None, + proxy: str | None = None, + proxy_auth: aiohttp.BasicAuth | None = None, ) -> Response[WebhookPayload]: route = Route( "GET", @@ -462,13 +482,13 @@ def create_interaction_response( token: str, *, session: aiohttp.ClientSession, - proxy: Optional[str] = None, - proxy_auth: Optional[aiohttp.BasicAuth] = None, + proxy: str | None = None, + proxy_auth: aiohttp.BasicAuth | None = None, type: int, - data: Optional[Dict[str, Any]] = None, - files: List[File] = None, + data: dict[str, Any] | None = None, + files: list[File] = None, ) -> Response[None]: - payload: Dict[str, Any] = { + payload: dict[str, Any] = { "type": type, } @@ -503,7 +523,14 @@ def create_interaction_response( webhook_token=token, ) - return self.request(route, session=session, proxy=proxy, proxy_auth=proxy_auth, files=files, multipart=form) + return self.request( + route, + session=session, + proxy=proxy, + proxy_auth=proxy_auth, + files=files, + multipart=form, + ) def get_original_interaction_response( self, @@ -511,8 +538,8 @@ def get_original_interaction_response( token: str, *, session: aiohttp.ClientSession, - proxy: Optional[str] = None, - proxy_auth: Optional[aiohttp.BasicAuth] = None, + proxy: str | None = None, + proxy_auth: aiohttp.BasicAuth | None = None, ) -> Response[MessagePayload]: r = Route( "GET", @@ -528,11 +555,11 @@ def edit_original_interaction_response( token: str, *, session: aiohttp.ClientSession, - proxy: Optional[str] = None, - proxy_auth: Optional[aiohttp.BasicAuth] = None, - payload: Optional[Dict[str, Any]] = None, - multipart: Optional[List[Dict[str, Any]]] = None, - files: Optional[List[File]] = None, + proxy: str | None = None, + proxy_auth: aiohttp.BasicAuth | None = None, + payload: dict[str, Any] | None = None, + multipart: list[dict[str, Any]] | None = None, + files: list[File] | None = None, ) -> Response[MessagePayload]: r = Route( "PATCH", @@ -541,7 +568,13 @@ def edit_original_interaction_response( webhook_token=token, ) return self.request( - r, session=session, proxy=proxy, proxy_auth=proxy_auth, payload=payload, multipart=multipart, files=files + r, + session=session, + proxy=proxy, + proxy_auth=proxy_auth, + payload=payload, + multipart=multipart, + files=files, ) def delete_original_interaction_response( @@ -550,8 +583,8 @@ def delete_original_interaction_response( token: str, *, session: aiohttp.ClientSession, - proxy: Optional[str] = None, - proxy_auth: Optional[aiohttp.BasicAuth] = None, + proxy: str | None = None, + proxy_auth: aiohttp.BasicAuth | None = None, ) -> Response[None]: r = Route( "DELETE", @@ -563,26 +596,26 @@ def delete_original_interaction_response( class ExecuteWebhookParameters(NamedTuple): - payload: Optional[Dict[str, Any]] - multipart: Optional[List[Dict[str, Any]]] - files: Optional[List[File]] + payload: dict[str, Any] | None + multipart: list[dict[str, Any]] | None + files: list[File] | None def handle_message_parameters( - content: Optional[str] = MISSING, + content: str | None = MISSING, *, username: str = MISSING, avatar_url: Any = MISSING, tts: bool = False, ephemeral: bool = False, file: File = MISSING, - files: List[File] = MISSING, - attachments: List[Attachment] = MISSING, - embed: Optional[Embed] = MISSING, - embeds: List[Embed] = MISSING, - view: Optional[View] = MISSING, - allowed_mentions: Optional[AllowedMentions] = MISSING, - previous_allowed_mentions: Optional[AllowedMentions] = None, + files: list[File] = MISSING, + attachments: list[Attachment] = MISSING, + embed: Embed | None = MISSING, + embeds: list[Embed] = MISSING, + view: View | None = MISSING, + allowed_mentions: AllowedMentions | None = MISSING, + previous_allowed_mentions: AllowedMentions | None = None, ) -> ExecuteWebhookParameters: if files is not MISSING and file is not MISSING: raise TypeError("Cannot mix file and files keyword arguments.") @@ -614,7 +647,9 @@ def handle_message_parameters( if allowed_mentions: if previous_allowed_mentions is not None: - payload["allowed_mentions"] = previous_allowed_mentions.merge(allowed_mentions).to_dict() + payload["allowed_mentions"] = previous_allowed_mentions.merge( + allowed_mentions + ).to_dict() else: payload["allowed_mentions"] = allowed_mentions.to_dict() elif previous_allowed_mentions is not None: @@ -651,7 +686,9 @@ def handle_message_parameters( return ExecuteWebhookParameters(payload=payload, multipart=multipart, files=files) -async_context: ContextVar[AsyncWebhookAdapter] = ContextVar("async_webhook_context", default=AsyncWebhookAdapter()) +async_context: ContextVar[AsyncWebhookAdapter] = ContextVar( + "async_webhook_context", default=AsyncWebhookAdapter() +) class PartialWebhookChannel(Hashable): @@ -662,7 +699,7 @@ class PartialWebhookChannel(Hashable): .. versionadded:: 2.0 Attributes - ----------- + ---------- id: :class:`int` The partial channel's ID. name: :class:`str` @@ -687,7 +724,7 @@ class PartialWebhookGuild(Hashable): .. versionadded:: 2.0 Attributes - ----------- + ---------- id: :class:`int` The partial guild's ID. name: :class:`str` @@ -706,7 +743,7 @@ def __repr__(self): return f"" @property - def icon(self) -> Optional[Asset]: + def icon(self) -> Asset | None: """Optional[:class:`Asset`]: Returns the guild's icon asset, if available.""" if self._icon is None: return None @@ -723,10 +760,10 @@ def __getattr__(self, attr): class _WebhookState: __slots__ = ("_parent", "_webhook") - def __init__(self, webhook: Any, parent: Optional[Union[ConnectionState, _WebhookState]]): + def __init__(self, webhook: Any, parent: ConnectionState | _WebhookState | None): self._webhook: Any = webhook - self._parent: Optional[ConnectionState] + self._parent: ConnectionState | None self._parent = None if isinstance(parent, _WebhookState) else parent def _get_guild(self, guild_id): @@ -776,14 +813,14 @@ class WebhookMessage(Message): async def edit( self, - content: Optional[str] = MISSING, - embeds: List[Embed] = MISSING, - embed: Optional[Embed] = MISSING, + content: str | None = MISSING, + embeds: list[Embed] = MISSING, + embed: Embed | None = MISSING, file: File = MISSING, - files: List[File] = MISSING, - attachments: List[Attachment] = MISSING, - view: Optional[View] = MISSING, - allowed_mentions: Optional[AllowedMentions] = None, + files: list[File] = MISSING, + attachments: list[Attachment] = MISSING, + view: View | None = MISSING, + allowed_mentions: AllowedMentions | None = None, ) -> WebhookMessage: """|coro| @@ -795,7 +832,7 @@ async def edit( The edit is no longer in-place, instead the newly edited message is returned. Parameters - ----------- + ---------- content: Optional[:class:`str`] The content to edit the message with or ``None`` to clear it. embeds: List[:class:`Embed`] @@ -826,6 +863,11 @@ async def edit( .. versionadded:: 2.0 + Returns + ------- + :class:`WebhookMessage` + The newly edited message. + Raises ------ HTTPException @@ -838,11 +880,6 @@ async def edit( The length of ``embeds`` was invalid InvalidArgument There was no token associated with this webhook. - - Returns - -------- - :class:`WebhookMessage` - The newly edited message. """ thread = MISSING if hasattr(self, "_thread_id"): @@ -866,13 +903,13 @@ async def edit( thread=thread, ) - async def delete(self, *, delay: Optional[float] = None) -> None: + async def delete(self, *, delay: float | None = None) -> None: """|coro| Deletes the message. Parameters - ----------- + ---------- delay: Optional[:class:`float`] If provided, the number of seconds to wait before deleting the message. The waiting is done in the background and deletion failures are ignored. @@ -886,7 +923,7 @@ async def delete(self, *, delay: Optional[float] = None) -> None: HTTPException Deleting the message failed. """ - thread_id: Optional[int] = None + thread_id: int | None = None if hasattr(self, "_thread_id"): thread_id = self._thread_id elif isinstance(self.channel, Thread): @@ -897,7 +934,9 @@ async def delete(self, *, delay: Optional[float] = None) -> None: async def inner_call(delay: float = delay): await asyncio.sleep(delay) try: - await self._state._webhook.delete_message(self.id, thread_id=thread_id) + await self._state._webhook.delete_message( + self.id, thread_id=thread_id + ) except HTTPException: pass @@ -907,7 +946,7 @@ async def inner_call(delay: float = delay): class BaseWebhook(Hashable): - __slots__: Tuple[str, ...] = ( + __slots__: tuple[str, ...] = ( "id", "type", "guild_id", @@ -925,11 +964,13 @@ class BaseWebhook(Hashable): def __init__( self, data: WebhookPayload, - token: Optional[str] = None, - state: Optional[ConnectionState] = None, + token: str | None = None, + state: ConnectionState | None = None, ): - self.auth_token: Optional[str] = token - self._state: Union[ConnectionState, _WebhookState] = state or _WebhookState(self, parent=state) + self.auth_token: str | None = token + self._state: ConnectionState | _WebhookState = state or _WebhookState( + self, parent=state + ) self._update(data) def _update(self, data: WebhookPayload): @@ -942,7 +983,7 @@ def _update(self, data: WebhookPayload): self.token = data.get("token") user = data.get("user") - self.user: Optional[Union[BaseUser, User]] = None + self.user: BaseUser | User | None = None if user is not None: # state parameter may be _WebhookState self.user = User(state=self._state, data=user) # type: ignore @@ -951,18 +992,19 @@ def _update(self, data: WebhookPayload): if source_channel: source_channel = PartialWebhookChannel(data=source_channel) - self.source_channel: Optional[PartialWebhookChannel] = source_channel + self.source_channel: PartialWebhookChannel | None = source_channel source_guild = data.get("source_guild") if source_guild: source_guild = PartialWebhookGuild(data=source_guild, state=self._state) - self.source_guild: Optional[PartialWebhookGuild] = source_guild + self.source_guild: PartialWebhookGuild | None = source_guild def is_partial(self) -> bool: """:class:`bool`: Whether the webhook is a "partial" webhook. - .. versionadded:: 2.0""" + .. versionadded:: 2.0 + """ return self.channel_id is None def is_authenticated(self) -> bool: @@ -973,7 +1015,7 @@ def is_authenticated(self) -> bool: return self.auth_token is not None @property - def guild(self) -> Optional[Guild]: + def guild(self) -> Guild | None: """Optional[:class:`Guild`]: The guild this webhook belongs to. If this is a partial webhook, then this will always return ``None``. @@ -981,7 +1023,7 @@ def guild(self) -> Optional[Guild]: return self._state and self._state._get_guild(self.guild_id) @property - def channel(self) -> Optional[TextChannel]: + def channel(self) -> TextChannel | None: """Optional[:class:`TextChannel`]: The text channel this webhook belongs to. If this is a partial webhook, then this will always return ``None``. @@ -1053,7 +1095,7 @@ async def foo(): Webhooks are now comparable and hashable. Attributes - ------------ + ---------- id: :class:`int` The webhook's ID type: :class:`WebhookType` @@ -1086,21 +1128,21 @@ async def foo(): .. versionadded:: 2.0 """ - __slots__: Tuple[str, ...] = ("session", "proxy", "proxy_auth") + __slots__: tuple[str, ...] = ("session", "proxy", "proxy_auth") def __init__( self, data: WebhookPayload, session: aiohttp.ClientSession, - proxy: Optional[str] = None, - proxy_auth: Optional[aiohttp.BasicAuth] = None, - token: Optional[str] = None, + proxy: str | None = None, + proxy_auth: aiohttp.BasicAuth | None = None, + token: str | None = None, state=None, ): super().__init__(data, token, state) self.session = session - self.proxy: Optional[str] = proxy - self.proxy_auth: Optional[aiohttp.BasicAuth] = proxy_auth + self.proxy: str | None = proxy + self.proxy_auth: aiohttp.BasicAuth | None = proxy_auth def __repr__(self): return f"" @@ -1117,14 +1159,14 @@ def partial( token: str, *, session: aiohttp.ClientSession, - proxy: Optional[str] = None, - proxy_auth: Optional[aiohttp.BasicAuth] = None, - bot_token: Optional[str] = None, + proxy: str | None = None, + proxy_auth: aiohttp.BasicAuth | None = None, + bot_token: str | None = None, ) -> Webhook: """Creates a partial :class:`Webhook`. Parameters - ----------- + ---------- id: :class:`int` The ID of the webhook. token: :class:`str` @@ -1142,7 +1184,7 @@ def partial( .. versionadded:: 2.0 Returns - -------- + ------- :class:`Webhook` A partial :class:`Webhook`. A partial webhook is just a webhook object with an ID and a token. @@ -1161,14 +1203,14 @@ def from_url( url: str, *, session: aiohttp.ClientSession, - proxy: Optional[str] = None, - proxy_auth: Optional[aiohttp.BasicAuth] = None, - bot_token: Optional[str] = None, + proxy: str | None = None, + proxy_auth: aiohttp.BasicAuth | None = None, + bot_token: str | None = None, ) -> Webhook: """Creates a partial :class:`Webhook` from a webhook URL. Parameters - ------------ + ---------- url: :class:`str` The URL of the webhook. session: :class:`aiohttp.ClientSession` @@ -1183,16 +1225,16 @@ def from_url( .. versionadded:: 2.0 - Raises - ------- - InvalidArgument - The URL is invalid. - Returns - -------- + ------- :class:`Webhook` A partial :class:`Webhook`. A partial webhook is just a webhook object with an ID and a token. + + Raises + ------ + InvalidArgument + The URL is invalid. """ m = re.search( r"discord(?:app)?.com/api/webhooks/(?P\d{17,20})/(?P[\w\.\-_]{60,68})", @@ -1201,7 +1243,7 @@ def from_url( if m is None: raise InvalidArgument("Invalid webhook URL given.") - data: Dict[str, Any] = m.groupdict() + data: dict[str, Any] = m.groupdict() data["type"] = 1 return cls(data, session, proxy=proxy, proxy_auth=proxy_auth, token=bot_token) # type: ignore @@ -1227,7 +1269,14 @@ def _as_follower(cls, data, *, channel, user) -> Webhook: session = http._HTTPClient__session proxy_auth = http.proxy_auth proxy = http.proxy - return cls(feed, session=session, state=state, proxy_auth=proxy_auth, proxy=proxy, token=state.http.token) + return cls( + feed, + session=session, + state=state, + proxy_auth=proxy_auth, + proxy=proxy, + token=state.http.token, + ) @classmethod def from_state(cls, data, state) -> Webhook: @@ -1235,7 +1284,14 @@ def from_state(cls, data, state) -> Webhook: session = http._HTTPClient__session proxy_auth = http.proxy_auth proxy = http.proxy - return cls(data, session=session, state=state, proxy_auth=proxy_auth, proxy=proxy, token=state.http.token) + return cls( + data, + session=session, + state=state, + proxy_auth=proxy_auth, + proxy=proxy, + token=state.http.token, + ) async def fetch(self, *, prefer_auth: bool = True) -> Webhook: """|coro| @@ -1253,24 +1309,24 @@ async def fetch(self, *, prefer_auth: bool = True) -> Webhook: returned webhook does not contain any user information. Parameters - ----------- + ---------- prefer_auth: :class:`bool` Whether to use the bot token over the webhook token if available. Defaults to ``True``. - Raises + Returns ------- + :class:`Webhook` + The fetched webhook. + + Raises + ------ HTTPException Could not fetch the webhook NotFound Could not find the webhook by this ID InvalidArgument This webhook does not have a token associated with it. - - Returns - -------- - :class:`Webhook` - The fetched webhook. """ adapter = async_context.get() @@ -1291,7 +1347,9 @@ async def fetch(self, *, prefer_auth: bool = True) -> Webhook: proxy_auth=self.proxy_auth, ) else: - raise InvalidArgument("This webhook does not have a token associated with it") + raise InvalidArgument( + "This webhook does not have a token associated with it" + ) return Webhook( data, @@ -1302,13 +1360,13 @@ async def fetch(self, *, prefer_auth: bool = True) -> Webhook: state=self._state, ) - async def delete(self, *, reason: Optional[str] = None, prefer_auth: bool = True): + async def delete(self, *, reason: str | None = None, prefer_auth: bool = True): """|coro| Deletes this Webhook. Parameters - ------------ + ---------- reason: Optional[:class:`str`] The reason for deleting this webhook. Shows up on the audit log. @@ -1320,7 +1378,7 @@ async def delete(self, *, reason: Optional[str] = None, prefer_auth: bool = True .. versionadded:: 2.0 Raises - ------- + ------ HTTPException Deleting the webhook failed. NotFound @@ -1331,7 +1389,9 @@ async def delete(self, *, reason: Optional[str] = None, prefer_auth: bool = True This webhook does not have a token associated with it. """ if self.token is None and self.auth_token is None: - raise InvalidArgument("This webhook does not have a token associated with it") + raise InvalidArgument( + "This webhook does not have a token associated with it" + ) adapter = async_context.get() @@ -1346,16 +1406,21 @@ async def delete(self, *, reason: Optional[str] = None, prefer_auth: bool = True ) elif self.token: await adapter.delete_webhook_with_token( - self.id, self.token, session=self.session, proxy=self.proxy, proxy_auth=self.proxy_auth, reason=reason, + self.id, + self.token, + session=self.session, + proxy=self.proxy, + proxy_auth=self.proxy_auth, + reason=reason, ) async def edit( self, *, - reason: Optional[str] = None, - name: Optional[str] = MISSING, - avatar: Optional[bytes] = MISSING, - channel: Optional[Snowflake] = None, + reason: str | None = None, + name: str | None = MISSING, + avatar: bytes | None = MISSING, + channel: Snowflake | None = None, prefer_auth: bool = True, ) -> Webhook: """|coro| @@ -1363,7 +1428,7 @@ async def edit( Edits this Webhook. Parameters - ----------- + ---------- name: Optional[:class:`str`] The webhook's new default name. avatar: Optional[:class:`bytes`] @@ -1393,18 +1458,22 @@ async def edit( it tried editing a channel without authentication. """ if self.token is None and self.auth_token is None: - raise InvalidArgument("This webhook does not have a token associated with it") + raise InvalidArgument( + "This webhook does not have a token associated with it" + ) payload = {} if name is not MISSING: payload["name"] = str(name) if name is not None else None if avatar is not MISSING: - payload["avatar"] = utils._bytes_to_base64_data(avatar) if avatar is not None else None + payload["avatar"] = ( + utils._bytes_to_base64_data(avatar) if avatar is not None else None + ) adapter = async_context.get() - data: Optional[WebhookPayload] = None + data: WebhookPayload | None = None # If a channel is given, always use the authenticated endpoint if channel is not None: if self.auth_token is None: @@ -1471,13 +1540,13 @@ async def send( tts: bool = MISSING, ephemeral: bool = MISSING, file: File = MISSING, - files: List[File] = MISSING, + files: list[File] = MISSING, embed: Embed = MISSING, - embeds: List[Embed] = MISSING, + embeds: list[Embed] = MISSING, allowed_mentions: AllowedMentions = MISSING, view: View = MISSING, thread: Snowflake = MISSING, - thread_name: Optional[str] = None, + thread_name: str | None = None, wait: Literal[True], ) -> WebhookMessage: ... @@ -1492,13 +1561,13 @@ async def send( tts: bool = MISSING, ephemeral: bool = MISSING, file: File = MISSING, - files: List[File] = MISSING, + files: list[File] = MISSING, embed: Embed = MISSING, - embeds: List[Embed] = MISSING, + embeds: list[Embed] = MISSING, allowed_mentions: AllowedMentions = MISSING, view: View = MISSING, thread: Snowflake = MISSING, - thread_name: Optional[str] = None, + thread_name: str | None = None, wait: Literal[False] = ..., ) -> None: ... @@ -1512,16 +1581,16 @@ async def send( tts: bool = False, ephemeral: bool = False, file: File = MISSING, - files: List[File] = MISSING, + files: list[File] = MISSING, embed: Embed = MISSING, - embeds: List[Embed] = MISSING, + embeds: list[Embed] = MISSING, allowed_mentions: AllowedMentions = MISSING, view: View = MISSING, thread: Snowflake = MISSING, - thread_name: Optional[str] = None, + thread_name: str | None = None, wait: bool = False, delete_after: float = None, - ) -> Optional[WebhookMessage]: + ) -> WebhookMessage | None: """|coro| Sends a message using the webhook. @@ -1536,7 +1605,7 @@ async def send( ``embeds`` parameter, which must be a :class:`list` of :class:`Embed` objects to send. Parameters - ------------ + ---------- content: :class:`str` The content of the message to send. wait: :class:`bool` @@ -1594,8 +1663,13 @@ async def send( If provided, the number of seconds to wait in the background before deleting the message we just sent. + Returns + ------- + Optional[:class:`WebhookMessage`] + If ``wait`` is ``True`` then the message that was sent, otherwise ``None``. + Raises - -------- + ------ HTTPException Sending the message failed. NotFound @@ -1610,17 +1684,16 @@ async def send( Either there was no token associated with this webhook, ``ephemeral`` was passed with the improper webhook type, there was no state attached with this webhook when giving it a view, or you specified both ``thread_name`` and ``thread``. - - Returns - --------- - Optional[:class:`WebhookMessage`] - If ``wait`` is ``True`` then the message that was sent, otherwise ``None``. """ if self.token is None: - raise InvalidArgument("This webhook does not have a token associated with it") + raise InvalidArgument( + "This webhook does not have a token associated with it" + ) - previous_mentions: Optional[AllowedMentions] = getattr(self._state, "allowed_mentions", None) + previous_mentions: AllowedMentions | None = getattr( + self._state, "allowed_mentions", None + ) if content is None: content = MISSING @@ -1629,14 +1702,18 @@ async def send( application_webhook = self.type is WebhookType.application if ephemeral and not application_webhook: - raise InvalidArgument("ephemeral messages can only be sent from application webhooks") + raise InvalidArgument( + "ephemeral messages can only be sent from application webhooks" + ) if application_webhook: wait = True if view is not MISSING: if isinstance(self._state, _WebhookState): - raise InvalidArgument("Webhook views require an associated state with the webhook") + raise InvalidArgument( + "Webhook views require an associated state with the webhook" + ) if ephemeral is True and view.timeout is None: view.timeout = 15 * 60.0 @@ -1655,7 +1732,7 @@ async def send( previous_allowed_mentions=previous_mentions, ) adapter = async_context.get() - thread_id: Optional[int] = None + thread_id: int | None = None if thread is not MISSING: thread_id = thread.id @@ -1691,7 +1768,9 @@ async def delete(): return msg - async def fetch_message(self, id: int, *, thread_id: Optional[int] = None) -> WebhookMessage: + async def fetch_message( + self, id: int, *, thread_id: int | None = None + ) -> WebhookMessage: """|coro| Retrieves a single :class:`~discord.WebhookMessage` owned by this webhook. @@ -1699,14 +1778,19 @@ async def fetch_message(self, id: int, *, thread_id: Optional[int] = None) -> We .. versionadded:: 2.0 Parameters - ------------ + ---------- id: :class:`int` The message ID to look for. thread_id: Optional[:class:`int`] The ID of the thread that contains the message. + Returns + ------- + :class:`~discord.WebhookMessage` + The message asked for. + Raises - -------- + ------ ~discord.NotFound The specified message was not found. ~discord.Forbidden @@ -1715,15 +1799,12 @@ async def fetch_message(self, id: int, *, thread_id: Optional[int] = None) -> We Retrieving the message failed. InvalidArgument There was no token associated with this webhook. - - Returns - -------- - :class:`~discord.WebhookMessage` - The message asked for. """ if self.token is None: - raise InvalidArgument("This webhook does not have a token associated with it") + raise InvalidArgument( + "This webhook does not have a token associated with it" + ) adapter = async_context.get() data = await adapter.get_webhook_message( @@ -1745,15 +1826,15 @@ async def edit_message( self, message_id: int, *, - content: Optional[str] = MISSING, - embeds: List[Embed] = MISSING, - embed: Optional[Embed] = MISSING, + content: str | None = MISSING, + embeds: list[Embed] = MISSING, + embed: Embed | None = MISSING, file: File = MISSING, - files: List[File] = MISSING, - attachments: List[Attachment] = MISSING, - view: Optional[View] = MISSING, - allowed_mentions: Optional[AllowedMentions] = None, - thread: Optional[Snowflake] = MISSING, + files: list[File] = MISSING, + attachments: list[Attachment] = MISSING, + view: View | None = MISSING, + allowed_mentions: AllowedMentions | None = None, + thread: Snowflake | None = MISSING, ) -> WebhookMessage: """|coro| @@ -1768,7 +1849,7 @@ async def edit_message( The edit is no longer in-place, instead the newly edited message is returned. Parameters - ------------ + ---------- message_id: :class:`int` The message ID to edit. content: Optional[:class:`str`] @@ -1802,8 +1883,13 @@ async def edit_message( thread: Optional[:class:`~discord.abc.Snowflake`] The thread that contains the message. - Raises + Returns ------- + :class:`WebhookMessage` + The newly edited webhook message. + + Raises + ------ HTTPException Editing the message failed. Forbidden @@ -1815,23 +1901,24 @@ async def edit_message( InvalidArgument There was no token associated with this webhook or the webhook had no state. - - Returns - -------- - :class:`WebhookMessage` - The newly edited webhook message. """ if self.token is None: - raise InvalidArgument("This webhook does not have a token associated with it") + raise InvalidArgument( + "This webhook does not have a token associated with it" + ) if view is not MISSING: if isinstance(self._state, _WebhookState): - raise InvalidArgument("This webhook does not have state associated with it") + raise InvalidArgument( + "This webhook does not have state associated with it" + ) self._state.prevent_view_updates_for(message_id) - previous_mentions: Optional[AllowedMentions] = getattr(self._state, "allowed_mentions", None) + previous_mentions: AllowedMentions | None = getattr( + self._state, "allowed_mentions", None + ) params = handle_message_parameters( content=content, file=file, @@ -1844,7 +1931,7 @@ async def edit_message( previous_allowed_mentions=previous_mentions, ) - thread_id: Optional[int] = None + thread_id: int | None = None if thread is not MISSING: thread_id = thread.id @@ -1867,7 +1954,9 @@ async def edit_message( self._state.store_view(view, message_id) return message - async def delete_message(self, message_id: int, *, thread_id: Optional[int] = None) -> None: + async def delete_message( + self, message_id: int, *, thread_id: int | None = None + ) -> None: """|coro| Deletes a message owned by this webhook. @@ -1878,21 +1967,23 @@ async def delete_message(self, message_id: int, *, thread_id: Optional[int] = No .. versionadded:: 1.6 Parameters - ------------ + ---------- message_id: :class:`int` The message ID to delete. thread_id: Optional[:class:`int`] The ID of the thread that contains the message. Raises - ------- + ------ HTTPException Deleting the message failed. Forbidden Deleted a message that is not yours. """ if self.token is None: - raise InvalidArgument("This webhook does not have a token associated with it") + raise InvalidArgument( + "This webhook does not have a token associated with it" + ) adapter = async_context.get() await adapter.delete_webhook_message( diff --git a/discord/webhook/sync.py b/discord/webhook/sync.py index e5ff135d50..2b0ecc882b 100644 --- a/discord/webhook/sync.py +++ b/discord/webhook/sync.py @@ -37,17 +37,7 @@ import threading import time import weakref -from typing import ( - TYPE_CHECKING, - Any, - Dict, - List, - Literal, - Optional, - Tuple, - Union, - overload, -) +from typing import TYPE_CHECKING, Any, Literal, overload from urllib.parse import quote as urlquote from .. import utils @@ -90,7 +80,7 @@ class DeferredLock: def __init__(self, lock: threading.Lock): self.lock = lock - self.delta: Optional[float] = None + self.delta: float | None = None def __enter__(self): self.lock.acquire() @@ -114,16 +104,16 @@ def request( route: Route, session: Session, *, - payload: Optional[Dict[str, Any]] = None, - multipart: Optional[List[Dict[str, Any]]] = None, - files: Optional[List[File]] = None, - reason: Optional[str] = None, - auth_token: Optional[str] = None, - params: Optional[Dict[str, Any]] = None, + payload: dict[str, Any] | None = None, + multipart: list[dict[str, Any]] | None = None, + files: list[File] | None = None, + reason: str | None = None, + auth_token: str | None = None, + params: dict[str, Any] | None = None, ) -> Any: - headers: Dict[str, str] = {} + headers: dict[str, str] = {} files = files or [] - to_send: Optional[Union[str, Dict[str, Any]]] = None + to_send: str | dict[str, Any] | None = None bucket = (route.webhook_id, route.webhook_token) try: @@ -141,9 +131,9 @@ def request( if reason is not None: headers["X-Audit-Log-Reason"] = urlquote(reason, safe="/ ") - response: Optional[Response] = None - data: Optional[Union[Dict[str, Any], str]] = None - file_data: Optional[Dict[str, Any]] = None + response: Response | None = None + data: dict[str, Any] | str | None = None + file_data: dict[str, Any] | None = None method = route.method url = route.url webhook_id = route.webhook_id @@ -187,7 +177,10 @@ def request( response.status = response.status_code # type: ignore data = response.text or None - if data and response.headers["Content-Type"] == "application/json": + if ( + data + and response.headers["Content-Type"] == "application/json" + ): data = json.loads(data) remaining = response.headers.get("X-Ratelimit-Remaining") @@ -244,9 +237,9 @@ def delete_webhook( self, webhook_id: int, *, - token: Optional[str] = None, + token: str | None = None, session: Session, - reason: Optional[str] = None, + reason: str | None = None, ): route = Route("DELETE", "/webhooks/{webhook_id}", webhook_id=webhook_id) return self.request(route, session, reason=reason, auth_token=token) @@ -257,7 +250,7 @@ def delete_webhook_with_token( token: str, *, session: Session, - reason: Optional[str] = None, + reason: str | None = None, ): route = Route( "DELETE", @@ -271,22 +264,24 @@ def edit_webhook( self, webhook_id: int, token: str, - payload: Dict[str, Any], + payload: dict[str, Any], *, session: Session, - reason: Optional[str] = None, + reason: str | None = None, ): route = Route("PATCH", "/webhooks/{webhook_id}", webhook_id=webhook_id) - return self.request(route, session, reason=reason, payload=payload, auth_token=token) + return self.request( + route, session, reason=reason, payload=payload, auth_token=token + ) def edit_webhook_with_token( self, webhook_id: int, token: str, - payload: Dict[str, Any], + payload: dict[str, Any], *, session: Session, - reason: Optional[str] = None, + reason: str | None = None, ): route = Route( "PATCH", @@ -302,11 +297,11 @@ def execute_webhook( token: str, *, session: Session, - payload: Optional[Dict[str, Any]] = None, - multipart: Optional[List[Dict[str, Any]]] = None, - files: Optional[List[File]] = None, - thread_id: Optional[int] = None, - thread_name: Optional[str] = None, + payload: dict[str, Any] | None = None, + multipart: list[dict[str, Any]] | None = None, + files: list[File] | None = None, + thread_id: int | None = None, + thread_name: str | None = None, wait: bool = False, ): params = {"wait": int(wait)} @@ -338,7 +333,7 @@ def get_webhook_message( message_id: int, *, session: Session, - thread_id: Optional[int] = None, + thread_id: int | None = None, ): params = {} @@ -361,10 +356,10 @@ def edit_webhook_message( message_id: int, *, session: Session, - thread_id: Optional[int] = None, - payload: Optional[Dict[str, Any]] = None, - multipart: Optional[List[Dict[str, Any]]] = None, - files: Optional[List[File]] = None, + thread_id: int | None = None, + payload: dict[str, Any] | None = None, + multipart: list[dict[str, Any]] | None = None, + files: list[File] | None = None, ): params = {} @@ -394,7 +389,7 @@ def delete_webhook_message( message_id: int, *, session: Session, - thread_id: Optional[int] = None, + thread_id: int | None = None, ): params = {} @@ -437,7 +432,7 @@ def fetch_webhook_with_token( class _WebhookContext(threading.local): - adapter: Optional[WebhookAdapter] = None + adapter: WebhookAdapter | None = None _context = _WebhookContext() @@ -465,17 +460,17 @@ class SyncWebhookMessage(Message): def edit( self, - content: Optional[str] = MISSING, - embeds: List[Embed] = MISSING, - embed: Optional[Embed] = MISSING, + content: str | None = MISSING, + embeds: list[Embed] = MISSING, + embed: Embed | None = MISSING, file: File = MISSING, - files: List[File] = MISSING, - allowed_mentions: Optional[AllowedMentions] = None, + files: list[File] = MISSING, + allowed_mentions: AllowedMentions | None = None, ) -> SyncWebhookMessage: """Edits the message. Parameters - ------------ + ---------- content: Optional[:class:`str`] The content to edit the message with or ``None`` to clear it. embeds: List[:class:`Embed`] @@ -492,8 +487,13 @@ def edit( Controls the mentions being processed in this message. See :meth:`.abc.Messageable.send` for more information. - Raises + Returns ------- + :class:`SyncWebhookMessage` + The newly edited message. + + Raises + ------ HTTPException Editing the message failed. Forbidden @@ -504,11 +504,6 @@ def edit( The length of ``embeds`` was invalid InvalidArgument There was no token associated with this webhook. - - Returns - -------- - :class:`SyncWebhookMessage` - The newly edited message. """ thread = MISSING if hasattr(self, "_thread_id"): @@ -527,11 +522,11 @@ def edit( thread=thread, ) - def delete(self, *, delay: Optional[float] = None) -> None: + def delete(self, *, delay: float | None = None) -> None: """Deletes the message. Parameters - ----------- + ---------- delay: Optional[:class:`float`] If provided, the number of seconds to wait before deleting the message. This blocks the thread. @@ -546,7 +541,7 @@ def delete(self, *, delay: Optional[float] = None) -> None: Deleting the message failed. """ - thread_id: Optional[int] = None + thread_id: int | None = None if hasattr(self, "_thread_id"): thread_id = self._thread_id elif isinstance(self.channel, Thread): @@ -581,7 +576,7 @@ class SyncWebhook(BaseWebhook): Webhooks are now comparable and hashable. Attributes - ------------ + ---------- id: :class:`int` The webhook's ID type: :class:`WebhookType` @@ -614,13 +609,13 @@ class SyncWebhook(BaseWebhook): .. versionadded:: 2.0 """ - __slots__: Tuple[str, ...] = ("session",) + __slots__: tuple[str, ...] = ("session",) def __init__( self, data: WebhookPayload, session: Session, - token: Optional[str] = None, + token: str | None = None, state=None, ): super().__init__(data, token, state) @@ -641,12 +636,12 @@ def partial( token: str, *, session: Session = MISSING, - bot_token: Optional[str] = None, + bot_token: str | None = None, ) -> SyncWebhook: """Creates a partial :class:`Webhook`. Parameters - ----------- + ---------- id: :class:`int` The ID of the webhook. token: :class:`str` @@ -661,7 +656,7 @@ def partial( involving the webhook. Returns - -------- + ------- :class:`Webhook` A partial :class:`Webhook`. A partial webhook is just a webhook object with an ID and a token. @@ -680,11 +675,13 @@ def partial( return cls(data, session, token=bot_token) @classmethod - def from_url(cls, url: str, *, session: Session = MISSING, bot_token: Optional[str] = None) -> SyncWebhook: + def from_url( + cls, url: str, *, session: Session = MISSING, bot_token: str | None = None + ) -> SyncWebhook: """Creates a partial :class:`Webhook` from a webhook URL. Parameters - ------------ + ---------- url: :class:`str` The URL of the webhook. session: :class:`requests.Session` @@ -696,16 +693,16 @@ def from_url(cls, url: str, *, session: Session = MISSING, bot_token: Optional[s The bot authentication token for authenticated requests involving the webhook. - Raises - ------- - InvalidArgument - The URL is invalid. - Returns - -------- + ------- :class:`Webhook` A partial :class:`Webhook`. A partial webhook is just a webhook object with an ID and a token. + + Raises + ------ + InvalidArgument + The URL is invalid. """ m = re.search( r"discord(?:app)?.com/api/webhooks/(?P\d{17,20})/(?P[\w\.\-_]{60,68})", @@ -714,7 +711,7 @@ def from_url(cls, url: str, *, session: Session = MISSING, bot_token: Optional[s if m is None: raise InvalidArgument("Invalid webhook URL given.") - data: Dict[str, Any] = m.groupdict() + data: dict[str, Any] = m.groupdict() data["type"] = 1 import requests @@ -736,41 +733,45 @@ def fetch(self, *, prefer_auth: bool = True) -> SyncWebhook: returned webhook does not contain any user information. Parameters - ----------- + ---------- prefer_auth: :class:`bool` Whether to use the bot token over the webhook token if available. Defaults to ``True``. - Raises + Returns ------- + :class:`SyncWebhook` + The fetched webhook. + + Raises + ------ HTTPException Could not fetch the webhook NotFound Could not find the webhook by this ID InvalidArgument This webhook does not have a token associated with it. - - Returns - -------- - :class:`SyncWebhook` - The fetched webhook. """ adapter: WebhookAdapter = _get_webhook_adapter() if prefer_auth and self.auth_token: data = adapter.fetch_webhook(self.id, self.auth_token, session=self.session) elif self.token: - data = adapter.fetch_webhook_with_token(self.id, self.token, session=self.session) + data = adapter.fetch_webhook_with_token( + self.id, self.token, session=self.session + ) else: - raise InvalidArgument("This webhook does not have a token associated with it") + raise InvalidArgument( + "This webhook does not have a token associated with it" + ) return SyncWebhook(data, self.session, token=self.auth_token, state=self._state) - def delete(self, *, reason: Optional[str] = None, prefer_auth: bool = True) -> None: + def delete(self, *, reason: str | None = None, prefer_auth: bool = True) -> None: """Deletes this Webhook. Parameters - ------------ + ---------- reason: Optional[:class:`str`] The reason for deleting this webhook. Shows up on the audit log. @@ -780,7 +781,7 @@ def delete(self, *, reason: Optional[str] = None, prefer_auth: bool = True) -> N if available. Defaults to ``True``. Raises - ------- + ------ HTTPException Deleting the webhook failed. NotFound @@ -791,28 +792,34 @@ def delete(self, *, reason: Optional[str] = None, prefer_auth: bool = True) -> N This webhook does not have a token associated with it. """ if self.token is None and self.auth_token is None: - raise InvalidArgument("This webhook does not have a token associated with it") + raise InvalidArgument( + "This webhook does not have a token associated with it" + ) adapter: WebhookAdapter = _get_webhook_adapter() if prefer_auth and self.auth_token: - adapter.delete_webhook(self.id, token=self.auth_token, session=self.session, reason=reason) + adapter.delete_webhook( + self.id, token=self.auth_token, session=self.session, reason=reason + ) elif self.token: - adapter.delete_webhook_with_token(self.id, self.token, session=self.session, reason=reason) + adapter.delete_webhook_with_token( + self.id, self.token, session=self.session, reason=reason + ) def edit( self, *, - reason: Optional[str] = None, - name: Optional[str] = MISSING, - avatar: Optional[bytes] = MISSING, - channel: Optional[Snowflake] = None, + reason: str | None = None, + name: str | None = MISSING, + avatar: bytes | None = MISSING, + channel: Snowflake | None = None, prefer_auth: bool = True, ) -> SyncWebhook: """Edits this Webhook. Parameters - ------------ + ---------- name: Optional[:class:`str`] The webhook's new default name. avatar: Optional[:class:`bytes`] @@ -827,8 +834,13 @@ def edit( Whether to use the bot token over the webhook token if available. Defaults to ``True``. - Raises + Returns ------- + :class:`SyncWebhook` + The newly edited webhook. + + Raises + ------ HTTPException Editing the webhook failed. NotFound @@ -836,25 +848,24 @@ def edit( InvalidArgument This webhook does not have a token associated with it, or it tried editing a channel without authentication. - - Returns - -------- - :class:`SyncWebhook` - The newly edited webhook. """ if self.token is None and self.auth_token is None: - raise InvalidArgument("This webhook does not have a token associated with it") + raise InvalidArgument( + "This webhook does not have a token associated with it" + ) payload = {} if name is not MISSING: payload["name"] = str(name) if name is not None else None if avatar is not MISSING: - payload["avatar"] = utils._bytes_to_base64_data(avatar) if avatar is not None else None + payload["avatar"] = ( + utils._bytes_to_base64_data(avatar) if avatar is not None else None + ) adapter: WebhookAdapter = _get_webhook_adapter() - data: Optional[WebhookPayload] = None + data: WebhookPayload | None = None # If a channel is given, always use the authenticated endpoint if channel is not None: if self.auth_token is None: @@ -889,7 +900,9 @@ def edit( if data is None: raise RuntimeError("Unreachable code hit: data was not assigned") - return SyncWebhook(data=data, session=self.session, token=self.auth_token, state=self._state) + return SyncWebhook( + data=data, session=self.session, token=self.auth_token, state=self._state + ) def _create_message(self, data): state = _WebhookState(self, parent=self._state) @@ -907,12 +920,12 @@ def send( avatar_url: Any = MISSING, tts: bool = MISSING, file: File = MISSING, - files: List[File] = MISSING, + files: list[File] = MISSING, embed: Embed = MISSING, - embeds: List[Embed] = MISSING, + embeds: list[Embed] = MISSING, allowed_mentions: AllowedMentions = MISSING, thread: Snowflake = MISSING, - thread_name: Optional[str] = None, + thread_name: str | None = None, wait: Literal[True], ) -> SyncWebhookMessage: ... @@ -926,12 +939,12 @@ def send( avatar_url: Any = MISSING, tts: bool = MISSING, file: File = MISSING, - files: List[File] = MISSING, + files: list[File] = MISSING, embed: Embed = MISSING, - embeds: List[Embed] = MISSING, + embeds: list[Embed] = MISSING, allowed_mentions: AllowedMentions = MISSING, thread: Snowflake = MISSING, - thread_name: Optional[str] = None, + thread_name: str | None = None, wait: Literal[False] = ..., ) -> None: ... @@ -944,14 +957,14 @@ def send( avatar_url: Any = MISSING, tts: bool = False, file: File = MISSING, - files: List[File] = MISSING, + files: list[File] = MISSING, embed: Embed = MISSING, - embeds: List[Embed] = MISSING, + embeds: list[Embed] = MISSING, allowed_mentions: AllowedMentions = MISSING, thread: Snowflake = MISSING, - thread_name: Optional[str] = None, + thread_name: str | None = None, wait: bool = False, - ) -> Optional[SyncWebhookMessage]: + ) -> SyncWebhookMessage | None: """Sends a message using the webhook. The content must be a type that can convert to a string through ``str(content)``. @@ -1004,6 +1017,11 @@ def send( .. versionadded:: 2.0 + Returns + ------- + Optional[:class:`SyncWebhookMessage`] + If ``wait`` is ``True`` then the message that was sent, otherwise ``None``. + Raises ------ HTTPException @@ -1019,17 +1037,16 @@ def send( InvalidArgument There was no token associated with this webhook, or you specified both a thread to send to and a thread to create (the ``thread`` and ``thread_name`` parameters). - - Returns - ------- - Optional[:class:`SyncWebhookMessage`] - If ``wait`` is ``True`` then the message that was sent, otherwise ``None``. """ if self.token is None: - raise InvalidArgument("This webhook does not have a token associated with it") + raise InvalidArgument( + "This webhook does not have a token associated with it" + ) - previous_mentions: Optional[AllowedMentions] = getattr(self._state, "allowed_mentions", None) + previous_mentions: AllowedMentions | None = getattr( + self._state, "allowed_mentions", None + ) if content is None: content = MISSING @@ -1049,7 +1066,7 @@ def send( previous_allowed_mentions=previous_mentions, ) adapter: WebhookAdapter = _get_webhook_adapter() - thread_id: Optional[int] = None + thread_id: int | None = None if thread is not MISSING: thread_id = thread.id @@ -1067,20 +1084,27 @@ def send( if wait: return self._create_message(data) - def fetch_message(self, id: int, *, thread_id: Optional[int] = None) -> SyncWebhookMessage: + def fetch_message( + self, id: int, *, thread_id: int | None = None + ) -> SyncWebhookMessage: """Retrieves a single :class:`~discord.SyncWebhookMessage` owned by this webhook. .. versionadded:: 2.0 Parameters - ------------ + ---------- id: :class:`int` The message ID to look for. thread_id: Optional[:class:`int`] The ID of the thread that contains the message. + Returns + ------- + :class:`~discord.SyncWebhookMessage` + The message asked for. + Raises - -------- + ------ ~discord.NotFound The specified message was not found. ~discord.Forbidden @@ -1089,15 +1113,12 @@ def fetch_message(self, id: int, *, thread_id: Optional[int] = None) -> SyncWebh Retrieving the message failed. InvalidArgument There was no token associated with this webhook. - - Returns - -------- - :class:`~discord.SyncWebhookMessage` - The message asked for. """ if self.token is None: - raise InvalidArgument("This webhook does not have a token associated with it") + raise InvalidArgument( + "This webhook does not have a token associated with it" + ) adapter: WebhookAdapter = _get_webhook_adapter() data = adapter.get_webhook_message( @@ -1117,13 +1138,13 @@ def edit_message( self, message_id: int, *, - content: Optional[str] = MISSING, - embeds: List[Embed] = MISSING, - embed: Optional[Embed] = MISSING, + content: str | None = MISSING, + embeds: list[Embed] = MISSING, + embed: Embed | None = MISSING, file: File = MISSING, - files: List[File] = MISSING, - allowed_mentions: Optional[AllowedMentions] = None, - thread: Optional[Snowflake] = MISSING, + files: list[File] = MISSING, + allowed_mentions: AllowedMentions | None = None, + thread: Snowflake | None = MISSING, ) -> SyncWebhookMessage: """Edits a message owned by this webhook. @@ -1133,7 +1154,7 @@ def edit_message( .. versionadded:: 1.6 Parameters - ------------ + ---------- message_id: :class:`int` The message ID to edit. content: Optional[:class:`str`] @@ -1155,7 +1176,7 @@ def edit_message( The thread that contains the message. Raises - ------- + ------ HTTPException Editing the message failed. Forbidden @@ -1169,9 +1190,13 @@ def edit_message( """ if self.token is None: - raise InvalidArgument("This webhook does not have a token associated with it") + raise InvalidArgument( + "This webhook does not have a token associated with it" + ) - previous_mentions: Optional[AllowedMentions] = getattr(self._state, "allowed_mentions", None) + previous_mentions: AllowedMentions | None = getattr( + self._state, "allowed_mentions", None + ) params = handle_message_parameters( content=content, file=file, @@ -1183,7 +1208,7 @@ def edit_message( ) adapter: WebhookAdapter = _get_webhook_adapter() - thread_id: Optional[int] = None + thread_id: int | None = None if thread is not MISSING: thread_id = thread.id @@ -1199,7 +1224,7 @@ def edit_message( ) return self._create_message(data) - def delete_message(self, message_id: int, *, thread_id: Optional[int] = None) -> None: + def delete_message(self, message_id: int, *, thread_id: int | None = None) -> None: """Deletes a message owned by this webhook. This is a lower level interface to :meth:`WebhookMessage.delete` in case @@ -1208,21 +1233,23 @@ def delete_message(self, message_id: int, *, thread_id: Optional[int] = None) -> .. versionadded:: 1.6 Parameters - ------------ + ---------- message_id: :class:`int` The message ID to delete. thread_id: Optional[:class:`int`] The ID of the thread that contains the message. Raises - ------- + ------ HTTPException Deleting the message failed. Forbidden Deleted a message that is not yours. """ if self.token is None: - raise InvalidArgument("This webhook does not have a token associated with it") + raise InvalidArgument( + "This webhook does not have a token associated with it" + ) adapter: WebhookAdapter = _get_webhook_adapter() adapter.delete_webhook_message( diff --git a/discord/welcome_screen.py b/discord/welcome_screen.py index 709be57694..841c5d8331 100644 --- a/discord/welcome_screen.py +++ b/discord/welcome_screen.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List, Optional, Union, overload +from typing import TYPE_CHECKING, overload from .partial_emoji import _EmojiTag from .utils import _get_as_snowflake, get @@ -66,7 +66,7 @@ def __init__( self, channel: Snowflake, description: str, - emoji: Union[Emoji, PartialEmoji, str], + emoji: Emoji | PartialEmoji | str, ): self.channel = channel self.description = description @@ -95,7 +95,9 @@ def to_dict(self) -> WelcomeScreenChannelPayload: return dict_ @classmethod - def _from_dict(cls, data: WelcomeScreenChannelPayload, guild: Guild) -> WelcomeScreenChannel: + def _from_dict( + cls, data: WelcomeScreenChannelPayload, guild: Guild + ) -> WelcomeScreenChannel: channel_id = _get_as_snowflake(data, "channel_id") channel = guild.get_channel(channel_id) description = data.get("description") @@ -129,8 +131,9 @@ def __repr__(self): def _update(self, data: WelcomeScreenPayload): self.description: str = data.get("description") - self.welcome_channels: List[WelcomeScreenChannel] = [ - WelcomeScreenChannel._from_dict(channel, self._guild) for channel in data.get("welcome_channels", []) + self.welcome_channels: list[WelcomeScreenChannel] = [ + WelcomeScreenChannel._from_dict(channel, self._guild) + for channel in data.get("welcome_channels", []) ] @property @@ -147,10 +150,10 @@ def guild(self) -> Guild: async def edit( self, *, - description: Optional[str] = ..., - welcome_channels: Optional[List[WelcomeScreenChannel]] = ..., - enabled: Optional[bool] = ..., - reason: Optional[str] = ..., + description: str | None = ..., + welcome_channels: list[WelcomeScreenChannel] | None = ..., + enabled: bool | None = ..., + reason: str | None = ..., ) -> None: ... @@ -166,27 +169,8 @@ async def edit(self, **options): You must have the :attr:`~Permissions.manage_guild` permission in the guild to do this. - Example - -------- - .. code-block:: python3 - - rules_channel = guild.get_channel(12345678) - announcements_channel = guild.get_channel(87654321) - custom_emoji = utils.get(guild.emojis, name='loudspeaker') - await welcome_screen.edit( - description='This is a very cool community server!', - welcome_channels=[ - WelcomeChannel(channel=rules_channel, description='Read the rules!', emoji='👨‍đŸĢ'), - WelcomeChannel(channel=announcements_channel, description='Watch out for announcements!', - emoji=custom_emoji), - ] - ) - - .. note:: - Welcome channels can only accept custom emojis if :attr:`~Guild.premium_tier` is level 2 or above. - Parameters - ------------ + ---------- description: Optional[:class:`str`] The new description of welcome screen. @@ -198,7 +182,7 @@ async def edit(self, **options): The reason that shows up on Audit log. Raises - ------- + ------ HTTPException Editing the welcome screen failed somehow. @@ -207,6 +191,24 @@ async def edit(self, **options): NotFound This welcome screen does not exist. + Example + ------- + .. code-block:: python3 + + rules_channel = guild.get_channel(12345678) + announcements_channel = guild.get_channel(87654321) + custom_emoji = utils.get(guild.emojis, name='loudspeaker') + await welcome_screen.edit( + description='This is a very cool community server!', + welcome_channels=[ + WelcomeChannel(channel=rules_channel, description='Read the rules!', emoji='👨‍đŸĢ'), + WelcomeChannel(channel=announcements_channel, description='Watch out for announcements!', + emoji=custom_emoji), + ] + ) + + .. note:: + Welcome channels can only accept custom emojis if :attr:`~Guild.premium_tier` is level 2 or above. """ welcome_channels = options.get("welcome_channels", []) @@ -214,7 +216,9 @@ async def edit(self, **options): for channel in welcome_channels: if not isinstance(channel, WelcomeScreenChannel): - raise TypeError("welcome_channels parameter must be a list of WelcomeScreenChannel.") + raise TypeError( + "welcome_channels parameter must be a list of WelcomeScreenChannel." + ) welcome_channels_data.append(channel.to_dict()) diff --git a/discord/widget.py b/discord/widget.py index 0eb145818d..a5780cdc53 100644 --- a/discord/widget.py +++ b/discord/widget.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, List, Optional, Union +from typing import TYPE_CHECKING, Any from .activity import BaseActivity, Spotify, create_activity from .enums import Status, try_enum @@ -69,7 +69,7 @@ class WidgetChannel: Returns the partial channel's name. Attributes - ----------- + ---------- id: :class:`int` The channel's ID. name: :class:`str` @@ -124,7 +124,7 @@ class WidgetMember(BaseUser): Returns the widget member's `name#discriminator`. Attributes - ----------- + ---------- id: :class:`int` The member's ID. name: :class:`str` @@ -167,21 +167,25 @@ class WidgetMember(BaseUser): ) if TYPE_CHECKING: - activity: Optional[Union[BaseActivity, Spotify]] + activity: BaseActivity | Spotify | None def __init__( self, *, state: ConnectionState, data: WidgetMemberPayload, - connected_channel: Optional[WidgetChannel] = None, + connected_channel: WidgetChannel | None = None, ) -> None: super().__init__(state=state, data=data) - self.nick: Optional[str] = data.get("nick") + self.nick: str | None = data.get("nick") self.status: Status = try_enum(Status, data.get("status")) - self.deafened: Optional[bool] = data.get("deaf", False) or data.get("self_deaf", False) - self.muted: Optional[bool] = data.get("mute", False) or data.get("self_mute", False) - self.suppress: Optional[bool] = data.get("suppress", False) + self.deafened: bool | None = data.get("deaf", False) or data.get( + "self_deaf", False + ) + self.muted: bool | None = data.get("mute", False) or data.get( + "self_mute", False + ) + self.suppress: bool | None = data.get("suppress", False) try: game = data["game"] @@ -190,9 +194,9 @@ def __init__( else: activity = create_activity(game) - self.activity: Optional[Union[BaseActivity, Spotify]] = activity + self.activity: BaseActivity | Spotify | None = activity - self.connected_channel: Optional[WidgetChannel] = connected_channel + self.connected_channel: WidgetChannel | None = connected_channel def __repr__(self) -> str: return ( @@ -224,7 +228,7 @@ class Widget: Returns the widget's JSON URL. Attributes - ----------- + ---------- id: :class:`int` The guild's ID. name: :class:`str` @@ -241,7 +245,6 @@ class Widget: the users will be "anonymized" with linear IDs and discriminator information being incorrect. Likewise, the number of members retrieved is capped. - """ __slots__ = ("_state", "channels", "_invite", "id", "members", "name") @@ -252,22 +255,30 @@ def __init__(self, *, state: ConnectionState, data: WidgetPayload) -> None: self.name: str = data["name"] self.id: int = int(data["id"]) - self.channels: List[WidgetChannel] = [] + self.channels: list[WidgetChannel] = [] for channel in data.get("channels", []): _id = int(channel["id"]) - self.channels.append(WidgetChannel(id=_id, name=channel["name"], position=channel["position"])) + self.channels.append( + WidgetChannel( + id=_id, name=channel["name"], position=channel["position"] + ) + ) - self.members: List[WidgetMember] = [] + self.members: list[WidgetMember] = [] channels = {channel.id: channel for channel in self.channels} for member in data.get("members", []): connected_channel = _get_as_snowflake(member, "channel_id") if connected_channel in channels: connected_channel = channels[connected_channel] # type: ignore elif connected_channel: - connected_channel = WidgetChannel(id=connected_channel, name="", position=0) + connected_channel = WidgetChannel( + id=connected_channel, name="", position=0 + ) self.members.append( - WidgetMember(state=self._state, data=member, connected_channel=connected_channel) + WidgetMember( + state=self._state, data=member, connected_channel=connected_channel + ) ) # type: ignore def __str__(self) -> str: @@ -279,7 +290,9 @@ def __eq__(self, other: Any) -> bool: return False def __repr__(self) -> str: - return f"" + return ( + f"" + ) @property def created_at(self) -> datetime.datetime: @@ -304,14 +317,14 @@ async def fetch_invite(self, *, with_counts: bool = True) -> Invite: code is abstracted away. Parameters - ----------- + ---------- with_counts: :class:`bool` Whether to include count information in the invite. This fills the :attr:`Invite.approximate_member_count` and :attr:`Invite.approximate_presence_count` fields. Returns - -------- + ------- :class:`Invite` The invite from the widget's invite URL. """ diff --git a/docs/_static/codeblocks.css b/docs/_static/codeblocks.css index 04755281cc..1b67806337 100644 --- a/docs/_static/codeblocks.css +++ b/docs/_static/codeblocks.css @@ -1,143 +1,466 @@ /* light theme: default */ -.highlight .hll { background-color: #ffffcc } -.highlight { background: #f0f0f0; } -.highlight .c { color: #60a0b0; font-style: italic } /* Comment */ -.highlight .err { border: 1px solid #FF0000 } /* Error */ -.highlight .k { color: #007020; font-weight: bold } /* Keyword */ -.highlight .o { color: #666666 } /* Operator */ -.highlight .ch { color: #60a0b0; font-style: italic } /* Comment.Hashbang */ -.highlight .cm { color: #60a0b0; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #007020 } /* Comment.Preproc */ -.highlight .cpf { color: #60a0b0; font-style: italic } /* Comment.PreprocFile */ -.highlight .c1 { color: #60a0b0; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #60a0b0; background-color: #fff0f0 } /* Comment.Special */ -.highlight .gd { color: #A00000 } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #FF0000 } /* Generic.Error */ -.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #00A000 } /* Generic.Inserted */ -.highlight .go { color: #888888 } /* Generic.Output */ -.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #0044DD } /* Generic.Traceback */ -.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #007020 } /* Keyword.Pseudo */ -.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #902000 } /* Keyword.Type */ -.highlight .m { color: #40a070 } /* Literal.Number */ -.highlight .s { color: #4070a0 } /* Literal.String */ -.highlight .na { color: #4070a0 } /* Name.Attribute */ -.highlight .nb { color: #007020 } /* Name.Builtin */ -.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ -.highlight .no { color: #60add5 } /* Name.Constant */ -.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ -.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ -.highlight .ne { color: #007020 } /* Name.Exception */ -.highlight .nf { color: #06287e } /* Name.Function */ -.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ -.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ -.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #bb60d5 } /* Name.Variable */ -.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mb { color: #40a070 } /* Literal.Number.Bin */ -.highlight .mf { color: #40a070 } /* Literal.Number.Float */ -.highlight .mh { color: #40a070 } /* Literal.Number.Hex */ -.highlight .mi { color: #40a070 } /* Literal.Number.Integer */ -.highlight .mo { color: #40a070 } /* Literal.Number.Oct */ -.highlight .sa { color: #4070a0 } /* Literal.String.Affix */ -.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ -.highlight .sc { color: #4070a0 } /* Literal.String.Char */ -.highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ -.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ -.highlight .s2 { color: #4070a0 } /* Literal.String.Double */ -.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ -.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ -.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ -.highlight .sx { color: #c65d09 } /* Literal.String.Other */ -.highlight .sr { color: #235388 } /* Literal.String.Regex */ -.highlight .s1 { color: #4070a0 } /* Literal.String.Single */ -.highlight .ss { color: #517918 } /* Literal.String.Symbol */ -.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ -.highlight .fm { color: #06287e } /* Name.Function.Magic */ -.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ -.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ -.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ -.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ -.highlight .il { color: #40a070 } /* Literal.Number.Integer.Long */ +.highlight .hll { + background-color: #ffffcc; +} +.highlight { + background: #f0f0f0; +} +.highlight .c { + color: #60a0b0; + font-style: italic; +} /* Comment */ +.highlight .err { + border: 1px solid #ff0000; +} /* Error */ +.highlight .k { + color: #007020; + font-weight: bold; +} /* Keyword */ +.highlight .o { + color: #666666; +} /* Operator */ +.highlight .ch { + color: #60a0b0; + font-style: italic; +} /* Comment.Hashbang */ +.highlight .cm { + color: #60a0b0; + font-style: italic; +} /* Comment.Multiline */ +.highlight .cp { + color: #007020; +} /* Comment.Preproc */ +.highlight .cpf { + color: #60a0b0; + font-style: italic; +} /* Comment.PreprocFile */ +.highlight .c1 { + color: #60a0b0; + font-style: italic; +} /* Comment.Single */ +.highlight .cs { + color: #60a0b0; + background-color: #fff0f0; +} /* Comment.Special */ +.highlight .gd { + color: #a00000; +} /* Generic.Deleted */ +.highlight .ge { + font-style: italic; +} /* Generic.Emph */ +.highlight .gr { + color: #ff0000; +} /* Generic.Error */ +.highlight .gh { + color: #000080; + font-weight: bold; +} /* Generic.Heading */ +.highlight .gi { + color: #00a000; +} /* Generic.Inserted */ +.highlight .go { + color: #888888; +} /* Generic.Output */ +.highlight .gp { + color: #c65d09; + font-weight: bold; +} /* Generic.Prompt */ +.highlight .gs { + font-weight: bold; +} /* Generic.Strong */ +.highlight .gu { + color: #800080; + font-weight: bold; +} /* Generic.Subheading */ +.highlight .gt { + color: #0044dd; +} /* Generic.Traceback */ +.highlight .kc { + color: #007020; + font-weight: bold; +} /* Keyword.Constant */ +.highlight .kd { + color: #007020; + font-weight: bold; +} /* Keyword.Declaration */ +.highlight .kn { + color: #007020; + font-weight: bold; +} /* Keyword.Namespace */ +.highlight .kp { + color: #007020; +} /* Keyword.Pseudo */ +.highlight .kr { + color: #007020; + font-weight: bold; +} /* Keyword.Reserved */ +.highlight .kt { + color: #902000; +} /* Keyword.Type */ +.highlight .m { + color: #40a070; +} /* Literal.Number */ +.highlight .s { + color: #4070a0; +} /* Literal.String */ +.highlight .na { + color: #4070a0; +} /* Name.Attribute */ +.highlight .nb { + color: #007020; +} /* Name.Builtin */ +.highlight .nc { + color: #0e84b5; + font-weight: bold; +} /* Name.Class */ +.highlight .no { + color: #60add5; +} /* Name.Constant */ +.highlight .nd { + color: #555555; + font-weight: bold; +} /* Name.Decorator */ +.highlight .ni { + color: #d55537; + font-weight: bold; +} /* Name.Entity */ +.highlight .ne { + color: #007020; +} /* Name.Exception */ +.highlight .nf { + color: #06287e; +} /* Name.Function */ +.highlight .nl { + color: #002070; + font-weight: bold; +} /* Name.Label */ +.highlight .nn { + color: #0e84b5; + font-weight: bold; +} /* Name.Namespace */ +.highlight .nt { + color: #062873; + font-weight: bold; +} /* Name.Tag */ +.highlight .nv { + color: #bb60d5; +} /* Name.Variable */ +.highlight .ow { + color: #007020; + font-weight: bold; +} /* Operator.Word */ +.highlight .w { + color: #bbbbbb; +} /* Text.Whitespace */ +.highlight .mb { + color: #40a070; +} /* Literal.Number.Bin */ +.highlight .mf { + color: #40a070; +} /* Literal.Number.Float */ +.highlight .mh { + color: #40a070; +} /* Literal.Number.Hex */ +.highlight .mi { + color: #40a070; +} /* Literal.Number.Integer */ +.highlight .mo { + color: #40a070; +} /* Literal.Number.Oct */ +.highlight .sa { + color: #4070a0; +} /* Literal.String.Affix */ +.highlight .sb { + color: #4070a0; +} /* Literal.String.Backtick */ +.highlight .sc { + color: #4070a0; +} /* Literal.String.Char */ +.highlight .dl { + color: #4070a0; +} /* Literal.String.Delimiter */ +.highlight .sd { + color: #4070a0; + font-style: italic; +} /* Literal.String.Doc */ +.highlight .s2 { + color: #4070a0; +} /* Literal.String.Double */ +.highlight .se { + color: #4070a0; + font-weight: bold; +} /* Literal.String.Escape */ +.highlight .sh { + color: #4070a0; +} /* Literal.String.Heredoc */ +.highlight .si { + color: #70a0d0; + font-style: italic; +} /* Literal.String.Interpol */ +.highlight .sx { + color: #c65d09; +} /* Literal.String.Other */ +.highlight .sr { + color: #235388; +} /* Literal.String.Regex */ +.highlight .s1 { + color: #4070a0; +} /* Literal.String.Single */ +.highlight .ss { + color: #517918; +} /* Literal.String.Symbol */ +.highlight .bp { + color: #007020; +} /* Name.Builtin.Pseudo */ +.highlight .fm { + color: #06287e; +} /* Name.Function.Magic */ +.highlight .vc { + color: #bb60d5; +} /* Name.Variable.Class */ +.highlight .vg { + color: #bb60d5; +} /* Name.Variable.Global */ +.highlight .vi { + color: #bb60d5; +} /* Name.Variable.Instance */ +.highlight .vm { + color: #bb60d5; +} /* Name.Variable.Magic */ +.highlight .il { + color: #40a070; +} /* Literal.Number.Integer.Long */ /* dark theme: modified "native" */ -:root[data-theme="dark"] .highlight pre { background-color: #2a2a2e } -:root[data-theme="dark"] .highlight .hll { background-color: #2a2a2e } -:root[data-theme="dark"] .highlight .c { color: #999999; font-style: italic } /* Comment */ -:root[data-theme="dark"] .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ -:root[data-theme="dark"] .highlight .g { color: #d0d0d0 } /* Generic */ -:root[data-theme="dark"] .highlight .k { color: #6ab825; font-weight: bold } /* Keyword */ -:root[data-theme="dark"] .highlight .l { color: #d0d0d0 } /* Literal */ -:root[data-theme="dark"] .highlight .n { color: #d0d0d0 } /* Name */ -:root[data-theme="dark"] .highlight .o { color: #d0d0d0 } /* Operator */ -:root[data-theme="dark"] .highlight .x { color: #d0d0d0 } /* Other */ -:root[data-theme="dark"] .highlight .p { color: #d0d0d0 } /* Punctuation */ -:root[data-theme="dark"] .highlight .cm { color: #999999; font-style: italic } /* Comment.Multiline */ -:root[data-theme="dark"] .highlight .cp { color: #cd2828; font-weight: bold } /* Comment.Preproc */ -:root[data-theme="dark"] .highlight .c1 { color: #999999; font-style: italic } /* Comment.Single */ -:root[data-theme="dark"] .highlight .cs { color: #e50808; font-weight: bold; background-color: #520000 } /* Comment.Special */ -:root[data-theme="dark"] .highlight .gd { color: #d22323 } /* Generic.Deleted */ -:root[data-theme="dark"] .highlight .ge { color: #d0d0d0; font-style: italic } /* Generic.Emph */ -:root[data-theme="dark"] .highlight .gr { color: #d22323 } /* Generic.Error */ -:root[data-theme="dark"] .highlight .gh { color: #ffffff; font-weight: bold } /* Generic.Heading */ -:root[data-theme="dark"] .highlight .gi { color: #589819 } /* Generic.Inserted */ -:root[data-theme="dark"] .highlight .go { color: #cccccc } /* Generic.Output */ -:root[data-theme="dark"] .highlight .gp { color: #aaaaaa } /* Generic.Prompt */ -:root[data-theme="dark"] .highlight .gs { color: #d0d0d0; font-weight: bold } /* Generic.Strong */ -:root[data-theme="dark"] .highlight .gu { color: #ffffff; text-decoration: underline } /* Generic.Subheading */ -:root[data-theme="dark"] .highlight .gt { color: #d22323 } /* Generic.Traceback */ -:root[data-theme="dark"] .highlight .kc { color: #6ab825; font-weight: bold } /* Keyword.Constant */ -:root[data-theme="dark"] .highlight .kd { color: #6ab825; font-weight: bold } /* Keyword.Declaration */ -:root[data-theme="dark"] .highlight .kn { color: #6ab825; font-weight: bold } /* Keyword.Namespace */ -:root[data-theme="dark"] .highlight .kp { color: #6ab825 } /* Keyword.Pseudo */ -:root[data-theme="dark"] .highlight .kr { color: #6ab825; font-weight: bold } /* Keyword.Reserved */ -:root[data-theme="dark"] .highlight .kt { color: #6ab825; font-weight: bold } /* Keyword.Type */ -:root[data-theme="dark"] .highlight .ld { color: #d0d0d0 } /* Literal.Date */ -:root[data-theme="dark"] .highlight .m { color: #7fb1d7 } /* Literal.Number */ -:root[data-theme="dark"] .highlight .s { color: #ed9d13 } /* Literal.String */ -:root[data-theme="dark"] .highlight .na { color: #bbbbbb; } /* Name.Attribute */ -:root[data-theme="dark"] .highlight .nb { color: #29a5b3; } /* Name.Builtin */ -:root[data-theme="dark"] .highlight .nc { color: #6494d8;} /* Name.Class */ -:root[data-theme="dark"] .highlight .no { color: #40ffff; } /* Name.Constant */ -:root[data-theme="dark"] .highlight .nd { color: #ffa500; } /* Name.Decorator */ -:root[data-theme="dark"] .highlight .ni { color: #d0d0d0; } /* Name.Entity */ -:root[data-theme="dark"] .highlight .ne { color: #bbbbbb; } /* Name.Exception */ -:root[data-theme="dark"] .highlight .nf { color: #6494d8; } /* Name.Function */ -:root[data-theme="dark"] .highlight .fm { color: #6494d8; } /* Name.Function.Magic */ -:root[data-theme="dark"] .highlight .nl { color: #d0d0d0; } /* Name.Label */ -:root[data-theme="dark"] .highlight .nn { color: #6494d8;} /* Name.Namespace */ -:root[data-theme="dark"] .highlight .nx { color: #d0d0d0; } /* Name.Other */ -:root[data-theme="dark"] .highlight .py { color: #d0d0d0; } /* Name.Property */ -:root[data-theme="dark"] .highlight .nt { color: #6ab825; font-weight: bold } /* Name.Tag */ -:root[data-theme="dark"] .highlight .nv { color: #40ffff; } /* Name.Variable */ -:root[data-theme="dark"] .highlight .ow { color: #6ab825; font-weight: bold } /* Operator.Word */ -:root[data-theme="dark"] .highlight .w { color: #666666; } /* Text.Whitespace */ -:root[data-theme="dark"] .highlight .mf { color: #7fb1d7; } /* Literal.Number.Float */ -:root[data-theme="dark"] .highlight .mh { color: #7fb1d7; } /* Literal.Number.Hex */ -:root[data-theme="dark"] .highlight .mi { color: #7fb1d7; } /* Literal.Number.Integer */ -:root[data-theme="dark"] .highlight .mo { color: #7fb1d7; } /* Literal.Number.Oct */ -:root[data-theme="dark"] .highlight .sb { color: #ed9d13; } /* Literal.String.Backtick */ -:root[data-theme="dark"] .highlight .sc { color: #ed9d13; } /* Literal.String.Char */ -:root[data-theme="dark"] .highlight .sd { color: #ed9d13; } /* Literal.String.Doc */ -:root[data-theme="dark"] .highlight .s2 { color: #ed9d13; } /* Literal.String.Double */ -:root[data-theme="dark"] .highlight .se { color: #ed9d13; } /* Literal.String.Escape */ -:root[data-theme="dark"] .highlight .sh { color: #ed9d13; } /* Literal.String.Heredoc */ -:root[data-theme="dark"] .highlight .si { color: #ed9d13; } /* Literal.String.Interpol */ -:root[data-theme="dark"] .highlight .sx { color: #ffa500; } /* Literal.String.Other */ -:root[data-theme="dark"] .highlight .sr { color: #ed9d13; } /* Literal.String.Regex */ -:root[data-theme="dark"] .highlight .s1 { color: #ed9d13; } /* Literal.String.Single */ -:root[data-theme="dark"] .highlight .ss { color: #ed9d13; } /* Literal.String.Symbol */ -:root[data-theme="dark"] .highlight .bp { color: #29a5b3; } /* Name.Builtin.Pseudo */ -:root[data-theme="dark"] .highlight .vc { color: #40ffff; } /* Name.Variable.Class */ -:root[data-theme="dark"] .highlight .vg { color: #40ffff; } /* Name.Variable.Global */ -:root[data-theme="dark"] .highlight .vi { color: #40ffff; } /* Name.Variable.Instance */ -:root[data-theme="dark"] .highlight .il { color: #7fb1d7; } /* Literal.Number.Integer.Long */ +:root[data-theme="dark"] .highlight pre { + background-color: #2a2a2e; +} +:root[data-theme="dark"] .highlight .hll { + background-color: #2a2a2e; +} +:root[data-theme="dark"] .highlight .c { + color: #999999; + font-style: italic; +} /* Comment */ +:root[data-theme="dark"] .highlight .err { + color: #a61717; + background-color: #e3d2d2; +} /* Error */ +:root[data-theme="dark"] .highlight .g { + color: #d0d0d0; +} /* Generic */ +:root[data-theme="dark"] .highlight .k { + color: #6ab825; + font-weight: bold; +} /* Keyword */ +:root[data-theme="dark"] .highlight .l { + color: #d0d0d0; +} /* Literal */ +:root[data-theme="dark"] .highlight .n { + color: #d0d0d0; +} /* Name */ +:root[data-theme="dark"] .highlight .o { + color: #d0d0d0; +} /* Operator */ +:root[data-theme="dark"] .highlight .x { + color: #d0d0d0; +} /* Other */ +:root[data-theme="dark"] .highlight .p { + color: #d0d0d0; +} /* Punctuation */ +:root[data-theme="dark"] .highlight .cm { + color: #999999; + font-style: italic; +} /* Comment.Multiline */ +:root[data-theme="dark"] .highlight .cp { + color: #cd2828; + font-weight: bold; +} /* Comment.Preproc */ +:root[data-theme="dark"] .highlight .c1 { + color: #999999; + font-style: italic; +} /* Comment.Single */ +:root[data-theme="dark"] .highlight .cs { + color: #e50808; + font-weight: bold; + background-color: #520000; +} /* Comment.Special */ +:root[data-theme="dark"] .highlight .gd { + color: #d22323; +} /* Generic.Deleted */ +:root[data-theme="dark"] .highlight .ge { + color: #d0d0d0; + font-style: italic; +} /* Generic.Emph */ +:root[data-theme="dark"] .highlight .gr { + color: #d22323; +} /* Generic.Error */ +:root[data-theme="dark"] .highlight .gh { + color: #ffffff; + font-weight: bold; +} /* Generic.Heading */ +:root[data-theme="dark"] .highlight .gi { + color: #589819; +} /* Generic.Inserted */ +:root[data-theme="dark"] .highlight .go { + color: #cccccc; +} /* Generic.Output */ +:root[data-theme="dark"] .highlight .gp { + color: #aaaaaa; +} /* Generic.Prompt */ +:root[data-theme="dark"] .highlight .gs { + color: #d0d0d0; + font-weight: bold; +} /* Generic.Strong */ +:root[data-theme="dark"] .highlight .gu { + color: #ffffff; + text-decoration: underline; +} /* Generic.Subheading */ +:root[data-theme="dark"] .highlight .gt { + color: #d22323; +} /* Generic.Traceback */ +:root[data-theme="dark"] .highlight .kc { + color: #6ab825; + font-weight: bold; +} /* Keyword.Constant */ +:root[data-theme="dark"] .highlight .kd { + color: #6ab825; + font-weight: bold; +} /* Keyword.Declaration */ +:root[data-theme="dark"] .highlight .kn { + color: #6ab825; + font-weight: bold; +} /* Keyword.Namespace */ +:root[data-theme="dark"] .highlight .kp { + color: #6ab825; +} /* Keyword.Pseudo */ +:root[data-theme="dark"] .highlight .kr { + color: #6ab825; + font-weight: bold; +} /* Keyword.Reserved */ +:root[data-theme="dark"] .highlight .kt { + color: #6ab825; + font-weight: bold; +} /* Keyword.Type */ +:root[data-theme="dark"] .highlight .ld { + color: #d0d0d0; +} /* Literal.Date */ +:root[data-theme="dark"] .highlight .m { + color: #7fb1d7; +} /* Literal.Number */ +:root[data-theme="dark"] .highlight .s { + color: #ed9d13; +} /* Literal.String */ +:root[data-theme="dark"] .highlight .na { + color: #bbbbbb; +} /* Name.Attribute */ +:root[data-theme="dark"] .highlight .nb { + color: #29a5b3; +} /* Name.Builtin */ +:root[data-theme="dark"] .highlight .nc { + color: #6494d8; +} /* Name.Class */ +:root[data-theme="dark"] .highlight .no { + color: #40ffff; +} /* Name.Constant */ +:root[data-theme="dark"] .highlight .nd { + color: #ffa500; +} /* Name.Decorator */ +:root[data-theme="dark"] .highlight .ni { + color: #d0d0d0; +} /* Name.Entity */ +:root[data-theme="dark"] .highlight .ne { + color: #bbbbbb; +} /* Name.Exception */ +:root[data-theme="dark"] .highlight .nf { + color: #6494d8; +} /* Name.Function */ +:root[data-theme="dark"] .highlight .fm { + color: #6494d8; +} /* Name.Function.Magic */ +:root[data-theme="dark"] .highlight .nl { + color: #d0d0d0; +} /* Name.Label */ +:root[data-theme="dark"] .highlight .nn { + color: #6494d8; +} /* Name.Namespace */ +:root[data-theme="dark"] .highlight .nx { + color: #d0d0d0; +} /* Name.Other */ +:root[data-theme="dark"] .highlight .py { + color: #d0d0d0; +} /* Name.Property */ +:root[data-theme="dark"] .highlight .nt { + color: #6ab825; + font-weight: bold; +} /* Name.Tag */ +:root[data-theme="dark"] .highlight .nv { + color: #40ffff; +} /* Name.Variable */ +:root[data-theme="dark"] .highlight .ow { + color: #6ab825; + font-weight: bold; +} /* Operator.Word */ +:root[data-theme="dark"] .highlight .w { + color: #666666; +} /* Text.Whitespace */ +:root[data-theme="dark"] .highlight .mf { + color: #7fb1d7; +} /* Literal.Number.Float */ +:root[data-theme="dark"] .highlight .mh { + color: #7fb1d7; +} /* Literal.Number.Hex */ +:root[data-theme="dark"] .highlight .mi { + color: #7fb1d7; +} /* Literal.Number.Integer */ +:root[data-theme="dark"] .highlight .mo { + color: #7fb1d7; +} /* Literal.Number.Oct */ +:root[data-theme="dark"] .highlight .sb { + color: #ed9d13; +} /* Literal.String.Backtick */ +:root[data-theme="dark"] .highlight .sc { + color: #ed9d13; +} /* Literal.String.Char */ +:root[data-theme="dark"] .highlight .sd { + color: #ed9d13; +} /* Literal.String.Doc */ +:root[data-theme="dark"] .highlight .s2 { + color: #ed9d13; +} /* Literal.String.Double */ +:root[data-theme="dark"] .highlight .se { + color: #ed9d13; +} /* Literal.String.Escape */ +:root[data-theme="dark"] .highlight .sh { + color: #ed9d13; +} /* Literal.String.Heredoc */ +:root[data-theme="dark"] .highlight .si { + color: #ed9d13; +} /* Literal.String.Interpol */ +:root[data-theme="dark"] .highlight .sx { + color: #ffa500; +} /* Literal.String.Other */ +:root[data-theme="dark"] .highlight .sr { + color: #ed9d13; +} /* Literal.String.Regex */ +:root[data-theme="dark"] .highlight .s1 { + color: #ed9d13; +} /* Literal.String.Single */ +:root[data-theme="dark"] .highlight .ss { + color: #ed9d13; +} /* Literal.String.Symbol */ +:root[data-theme="dark"] .highlight .bp { + color: #29a5b3; +} /* Name.Builtin.Pseudo */ +:root[data-theme="dark"] .highlight .vc { + color: #40ffff; +} /* Name.Variable.Class */ +:root[data-theme="dark"] .highlight .vg { + color: #40ffff; +} /* Name.Variable.Global */ +:root[data-theme="dark"] .highlight .vi { + color: #40ffff; +} /* Name.Variable.Instance */ +:root[data-theme="dark"] .highlight .il { + color: #7fb1d7; +} /* Literal.Number.Integer.Long */ diff --git a/docs/_static/copy.js b/docs/_static/copy.js index e1317810a2..6852fc7d52 100644 --- a/docs/_static/copy.js +++ b/docs/_static/copy.js @@ -9,7 +9,7 @@ const copy = async (obj) => { icon.textContent = COPIED; setTimeout(() => (icon.textContent = COPY), 2500); }, - (r) => alert('Could not copy the codeblock:\n' + r.toString()) + (r) => alert("Could not copy the codeblock:\n" + r.toString()), ); }; @@ -19,7 +19,7 @@ document.addEventListener("DOMContentLoaded", () => { for (let codeblock of allCodeblocks) { codeblock.parentNode.className += " relative-copy"; let copyEl = document.createElement("span"); - copyEl.addEventListener('click', () => copy(codeblock)); + copyEl.addEventListener("click", () => copy(codeblock)); copyEl.className = "copy"; copyEl.setAttribute("aria-label", "Copy This Code"); copyEl.setAttribute("title", "Copy This Code"); diff --git a/docs/_static/custom.js b/docs/_static/custom.js index e995275951..18ef5bd7cf 100644 --- a/docs/_static/custom.js +++ b/docs/_static/custom.js @@ -1,4 +1,4 @@ -'use-strict'; +"use-strict"; let activeModal = null; let bottomHeightThreshold, sections; @@ -14,7 +14,7 @@ class Modal { close() { activeModal = null; - this.element.style.display = 'none' + this.element.style.display = "none"; } open() { @@ -22,17 +22,16 @@ class Modal { activeModal.close(); } activeModal = this; - this.element.style.display = 'flex' + this.element.style.display = "flex"; } } class SearchBar { - constructor() { - this.box = document.querySelector('nav.mobile-only'); + this.box = document.querySelector("nav.mobile-only"); this.bar = document.querySelector('nav.mobile-only input[type="search"]'); - this.openButton = document.getElementById('open-search'); - this.closeButton = document.getElementById('close-search'); + this.openButton = document.getElementById("open-search"); + this.closeButton = document.getElementById("close-search"); } open() { @@ -47,50 +46,48 @@ class SearchBar { this.closeButton.hidden = true; this.box.style.top = "0"; } - } function scrollToTop() { - window.scrollTo({ top: 0, behavior: 'smooth' }); + window.scrollTo({ top: 0, behavior: "smooth" }); } -document.addEventListener('DOMContentLoaded', () => { +document.addEventListener("DOMContentLoaded", () => { mobileSearch = new SearchBar(); bottomHeightThreshold = document.documentElement.scrollHeight - 30; - sections = document.querySelectorAll('section'); - hamburgerToggle = document.getElementById('hamburger-toggle'); + sections = document.querySelectorAll("section"); + hamburgerToggle = document.getElementById("hamburger-toggle"); - toTop = document.getElementById('to-top'); + toTop = document.getElementById("to-top"); toTop.hidden = !(window.scrollY > 0); if (hamburgerToggle) { - hamburgerToggle.addEventListener('click', (e) => { - sidebar.element.classList.toggle('sidebar-toggle'); + hamburgerToggle.addEventListener("click", (e) => { + sidebar.element.classList.toggle("sidebar-toggle"); let button = hamburgerToggle.firstElementChild; - if (button.textContent == 'menu') { - button.textContent = 'close'; - } - else { - button.textContent = 'menu'; + if (button.textContent == "menu") { + button.textContent = "close"; + } else { + button.textContent = "menu"; } }); } - const tables = document.querySelectorAll('.py-attribute-table[data-move-to-id]'); - tables.forEach(table => { - let element = document.getElementById(table.getAttribute('data-move-to-id')); + const tables = document.querySelectorAll(".py-attribute-table[data-move-to-id]"); + tables.forEach((table) => { + let element = document.getElementById(table.getAttribute("data-move-to-id")); let parent = element.parentNode; // insert ourselves after the element parent.insertBefore(table, element.nextSibling); }); - window.addEventListener('scroll', () => { + window.addEventListener("scroll", () => { toTop.hidden = !(window.scrollY > 0); }); }); -document.addEventListener('keydown', (event) => { +document.addEventListener("keydown", (event) => { if (event.code == "Escape" && activeModal) { activeModal.close(); } diff --git a/docs/_static/icons.css b/docs/_static/icons.css index b237f109d6..9ae0919ff5 100644 --- a/docs/_static/icons.css +++ b/docs/_static/icons.css @@ -1,10 +1,10 @@ @font-face { - font-family: 'Custom Icons'; - font-style: normal; - font-weight: 400; - src: url('icons.woff') format('woff2'); + font-family: "Custom Icons"; + font-style: normal; + font-weight: 400; + src: url("icons.woff") format("woff2"); } .custom-icons { - font-family: 'Custom Icons' !important; + font-family: "Custom Icons" !important; } diff --git a/docs/_static/scorer.js b/docs/_static/scorer.js index f9f95cfe7c..a0f50cb083 100644 --- a/docs/_static/scorer.js +++ b/docs/_static/scorer.js @@ -1,11 +1,11 @@ -'use-strict'; +"use-strict"; let queryBeingDone = null; let pattern = null; const escapedRegex = /[-\/\\^$*+?.()|[\]{}]/g; function escapeRegex(e) { - return e.replace(escapedRegex, '\\$&'); + return e.replace(escapedRegex, "\\$&"); } // for some reason Sphinx shows some entries twice @@ -13,71 +13,70 @@ function escapeRegex(e) { const beenScored = new Set(); function __score(haystack, regex) { - let match = regex.exec(haystack); - if (match == null) { - return Number.MAX_VALUE; - } - let subLength = match[0].length; - let start = match.index; - return (subLength * 1000 + start) / 1000.0; + let match = regex.exec(haystack); + if (match == null) { + return Number.MAX_VALUE; + } + let subLength = match[0].length; + let start = match.index; + return (subLength * 1000 + start) / 1000.0; } // unused for now function __cleanNamespaces(query) { - return query.replace(/(discord\.(ext\.)?)?(.+)/, '$3'); + return query.replace(/(discord\.(ext\.)?)?(.+)/, "$3"); } Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [filename, title, anchor, descr, score] + // and returns the new score. + score: (result) => { + // only inflate the score of things that are actual API reference things + const [, title, , , score] = result; - // Implement the following function to further tweak the score for each result - // The function takes a result array [filename, title, anchor, descr, score] - // and returns the new score. - score: (result) => { - // only inflate the score of things that are actual API reference things - const [, title, , , score] = result; - - if (pattern !== null && title.startsWith('discord.')) { - let _score = __score(title, pattern); - if (_score === Number.MAX_VALUE) { - return score; - } - if (beenScored.has(title)) { - return 0; - } - beenScored.add(title); - let newScore = 100 + queryBeingDone.length - _score; - // console.log(`${title}: ${score} -> ${newScore} (${_score})`); - return newScore; - } + if (pattern !== null && title.startsWith("discord.")) { + let _score = __score(title, pattern); + if (_score === Number.MAX_VALUE) { return score; - }, + } + if (beenScored.has(title)) { + return 0; + } + beenScored.add(title); + let newScore = 100 + queryBeingDone.length - _score; + // console.log(`${title}: ${score} -> ${newScore} (${_score})`); + return newScore; + } + return score; + }, - // query matches the full name of an object - objNameMatch: 15, - // or matches in the last dotted part of the object name - objPartialMatch: 11, - // Additive scores depending on the priority of the object - objPrio: { - 0: 15, // used to be importantResults - 1: 7, // used to be objectResults - 2: -5 // used to be unimportantResults - }, - // Used when the priority is not in the mapping. - objPrioDefault: 0, + // query matches the full name of an object + objNameMatch: 15, + // or matches in the last dotted part of the object name + objPartialMatch: 11, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 7, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, - // query found in title - title: 15, - partialTitle: 7, - // query found in terms - term: 5, - partialTerm: 2 + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, }; -document.addEventListener('DOMContentLoaded', () => { - const params = new URLSearchParams(window.location.search); - queryBeingDone = params.get('q'); - if (queryBeingDone) { - let pattern = Array.from(queryBeingDone).map(escapeRegex).join('.*?'); - pattern = new RegExp(pattern, 'i'); - } +document.addEventListener("DOMContentLoaded", () => { + const params = new URLSearchParams(window.location.search); + queryBeingDone = params.get("q"); + if (queryBeingDone) { + let pattern = Array.from(queryBeingDone).map(escapeRegex).join(".*?"); + pattern = new RegExp(pattern, "i"); + } }); diff --git a/docs/_static/settings.js b/docs/_static/settings.js index 0a5a99eea0..8286455cfd 100644 --- a/docs/_static/settings.js +++ b/docs/_static/settings.js @@ -1,4 +1,4 @@ -'use-strict'; +"use-strict"; let settingsModal; @@ -10,7 +10,7 @@ class Setting { } setElement() { - throw new TypeError('Abstract methods should be implemented.'); + throw new TypeError("Abstract methods should be implemented."); } load() { @@ -25,13 +25,11 @@ class Setting { } update() { - throw new TypeError('Abstract methods should be implemented.'); + throw new TypeError("Abstract methods should be implemented."); } - } class CheckboxSetting extends Setting { - setElement() { let element = document.querySelector(`input[name=${this.name}]`); element.checked = this.value; @@ -41,13 +39,13 @@ class CheckboxSetting extends Setting { localStorage.setItem(this.name, element.checked); this.setValue(element.checked); } - } class RadioSetting extends Setting { - setElement() { - let element = document.querySelector(`input[name=${this.name}][value=${this.value}]`); + let element = document.querySelector( + `input[name=${this.name}][value=${this.value}]`, + ); element.checked = true; } @@ -55,7 +53,6 @@ class RadioSetting extends Setting { localStorage.setItem(this.name, `"${element.value}"`); this.setValue(element.value); } - } function getRootAttributeToggle(attributeName, valueName) { @@ -70,22 +67,24 @@ function getRootAttributeToggle(attributeName, valueName) { } function setTheme(value) { - if (value === 'automatic') { - if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { - document.documentElement.setAttribute('data-theme', 'dark'); + if (value === "automatic") { + if ( + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches + ) { + document.documentElement.setAttribute("data-theme", "dark"); } else { - document.documentElement.setAttribute('data-theme', 'light'); + document.documentElement.setAttribute("data-theme", "light"); } - } - else { - document.documentElement.setAttribute('data-theme', value); + } else { + document.documentElement.setAttribute("data-theme", value); } } const settings = [ - new CheckboxSetting('useSerifFont', false, getRootAttributeToggle('font', 'serif')), - new RadioSetting('setTheme', 'automatic', setTheme) -] + new CheckboxSetting("useSerifFont", false, getRootAttributeToggle("font", "serif")), + new RadioSetting("setTheme", "automatic", setTheme), +]; function updateSetting(element) { let setting = settings.find((s) => s.name == element.name); @@ -98,8 +97,8 @@ for (const setting of settings) { setting.load(); } -document.addEventListener('DOMContentLoaded', () => { - settingsModal = new Modal(document.querySelector('div#settings.modal')); +document.addEventListener("DOMContentLoaded", () => { + settingsModal = new Modal(document.querySelector("div#settings.modal")); for (const setting of settings) { setting.setElement(); } diff --git a/docs/_static/sidebar.js b/docs/_static/sidebar.js index d9291a2674..7f487381b6 100644 --- a/docs/_static/sidebar.js +++ b/docs/_static/sidebar.js @@ -3,12 +3,15 @@ class Sidebar { this.element = element; this.activeLink = null; - this.element.addEventListener('click', (e) => { + this.element.addEventListener("click", (e) => { // If we click a navigation, close the hamburger menu - if (e.target.tagName == 'A' && this.element.classList.contains('sidebar-toggle')) { - this.element.classList.remove('sidebar-toggle'); + if ( + e.target.tagName == "A" && + this.element.classList.contains("sidebar-toggle") + ) { + this.element.classList.remove("sidebar-toggle"); let button = hamburgerToggle.firstElementChild; - button.textContent = 'menu'; + button.textContent = "menu"; // Scroll a little up to actually see the header // Note: this is generally around ~55px @@ -22,35 +25,33 @@ class Sidebar { } createCollapsableSections() { - let toc = this.element.querySelector('ul'); + let toc = this.element.querySelector("ul"); if (!toc) { - return + return; } let allReferences = toc.querySelectorAll('a.reference.internal:not([href="#"])'); for (let ref of allReferences) { - let next = ref.nextElementSibling; if (next && next.tagName === "UL") { - - let icon = document.createElement('span'); - icon.className = 'material-icons collapsible-arrow expanded'; - icon.innerText = 'expand_more'; + let icon = document.createElement("span"); + icon.className = "material-icons collapsible-arrow expanded"; + icon.innerText = "expand_more"; if (next.parentElement.tagName == "LI") { - next.parentElement.classList.add('no-list-style') + next.parentElement.classList.add("no-list-style"); } - icon.addEventListener('click', () => { - if (icon.classList.contains('expanded')) { + icon.addEventListener("click", () => { + if (icon.classList.contains("expanded")) { this.collapseSection(icon); } else { this.expandSection(icon); } - }) + }); - ref.classList.add('ref-internal-padding') + ref.classList.add("ref-internal-padding"); ref.parentNode.insertBefore(icon, ref); } } @@ -58,28 +59,30 @@ class Sidebar { resize() { let rect = this.element.getBoundingClientRect(); - this.element.style.height = `calc(100vh - 1em - ${rect.top + document.body.offsetTop}px)`; + this.element.style.height = `calc(100vh - 1em - ${ + rect.top + document.body.offsetTop + }px)`; } collapseSection(icon) { - icon.classList.remove('expanded'); - icon.classList.add('collapsed'); + icon.classList.remove("expanded"); + icon.classList.add("collapsed"); let children = icon.nextElementSibling.nextElementSibling; // // --> - setTimeout(() => children.style.display = "none", 75) + setTimeout(() => (children.style.display = "none"), 75); } expandSection(icon) { - icon.classList.remove('collapse'); - icon.classList.add('expanded'); + icon.classList.remove("collapse"); + icon.classList.add("expanded"); let children = icon.nextElementSibling.nextElementSibling; - setTimeout(() => children.style.display = "block", 75) + setTimeout(() => (children.style.display = "block"), 75); } setActiveLink(section) { if (this.activeLink) { - this.activeLink.parentElement.classList.remove('active'); + this.activeLink.parentElement.classList.remove("active"); } if (section) { this.activeLink = document.querySelector(`#sidebar a[href="#${section.id}"]`); @@ -87,24 +90,22 @@ class Sidebar { let headingChildren = this.activeLink.parentElement.parentElement; let heading = headingChildren.previousElementSibling.previousElementSibling; - if (heading && headingChildren.style.display === 'none') { + if (heading && headingChildren.style.display === "none") { this.activeLink = heading; } - this.activeLink.parentElement.classList.add('active'); + this.activeLink.parentElement.classList.add("active"); } } } - } function getCurrentSection() { let currentSection; if (window.scrollY + window.innerHeight > bottomHeightThreshold) { currentSection = sections[sections.length - 1]; - } - else { + } else { if (sections) { - sections.forEach(section => { + sections.forEach((section) => { let rect = section.getBoundingClientRect(); if (rect.top + document.body.offsetTop < 1) { currentSection = section; @@ -115,12 +116,12 @@ function getCurrentSection() { return currentSection; } -document.addEventListener('DOMContentLoaded', () => { - sidebar = new Sidebar(document.getElementById('sidebar')); +document.addEventListener("DOMContentLoaded", () => { + sidebar = new Sidebar(document.getElementById("sidebar")); sidebar.resize(); sidebar.createCollapsableSections(); - window.addEventListener('scroll', () => { + window.addEventListener("scroll", () => { sidebar.setActiveLink(getCurrentSection()); sidebar.resize(); }); diff --git a/docs/_static/style.css b/docs/_static/style.css index d91ece1c05..207d7a9a0b 100644 --- a/docs/_static/style.css +++ b/docs/_static/style.css @@ -18,8 +18,10 @@ Historically however, thanks to: /* CSS variables would go here */ :root { - --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; - --monospace-font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; + --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, + Cantarell, "Open Sans", "Helvetica Neue", sans-serif; + --monospace-font-family: "Consolas", "Menlo", "Deja Vu Sans Mono", + "Bitstream Vera Sans Mono", monospace; /* palette goes here */ --white: #ffffff; @@ -40,7 +42,7 @@ Historically however, thanks to: --blue-4: #003eaa; --blue-5: #002275; --blue-6: #000f40; - --blurple: #7289DA; + --blurple: #7289da; --settings: var(--grey-1); --settings-hover: var(--grey-1-8); @@ -92,8 +94,8 @@ Historically however, thanks to: --table-border: var(--grey-4); --mobile-active-toc: var(--grey-7); --active-toc: var(--grey-3); - --scrollbar: rgba(0,0,0,0.2); - --scrollbar-hover: rgba(0,0,0,0.4); + --scrollbar: rgba(0, 0, 0, 0.2); + --scrollbar-hover: rgba(0, 0, 0, 0.4); --rtd-ad-border: var(--grey-3); --rtd-ad-background: var(--grey-2); --rtd-ad-main-text: var(--grey-6); @@ -111,7 +113,7 @@ Historically however, thanks to: } :root[data-font="serif"] { - --font-family: 'Georgia', 'Yu Gothic', 'Noto Sans CJK JP Regular', serif; + --font-family: "Georgia", "Yu Gothic", "Noto Sans CJK JP Regular", serif; } :root[data-theme="dark"] { @@ -140,7 +142,7 @@ Historically however, thanks to: --note-text: var(--white); --warning-background: #d7b600; --warning-text: var(--black); - --error-background: #d70022; + --error-background: #d70022; --error-text: var(--white); --helpful-background: #008ea4; --helpful-text: var(--white); @@ -154,8 +156,8 @@ Historically however, thanks to: --table-text: var(--grey-1); --table-border: var(--grey-4); --active-toc: var(--grey-6); - --scrollbar: rgba(0,0,0,0.5); - --scrollbar-hover: rgba(0,0,0,0.7); + --scrollbar: rgba(0, 0, 0, 0.5); + --scrollbar-hover: rgba(0, 0, 0, 0.7); --rtd-ad-border: var(--grey-6); --rtd-ad-background: var(--grey-5); --rtd-ad-main-text: var(--grey-2); @@ -170,7 +172,7 @@ Historically however, thanks to: --highlighted-text: rgba(250, 166, 26, 0.2); } -img[src$="snake_dark.svg"] { +img[src$="snake_dark.svg"] { display: none; } :root[data-theme="dark"] img[src$="snake.svg"] { @@ -190,7 +192,6 @@ body { color: var(--main-text); } - /* Scrollbar related */ #sidebar::-webkit-scrollbar { @@ -206,7 +207,6 @@ body { background-color: var(--scrollbar-hover); } - /* grid related */ .main-grid { @@ -316,7 +316,7 @@ header > nav > a:hover { border-bottom: 1px solid var(--search-border); appearance: none; - background-image: url('/_images/drop_down_icon.svg'); + background-image: url("/_images/drop_down_icon.svg"); background-repeat: no-repeat; background-position-x: 100%; background-position-y: 50%; @@ -325,7 +325,7 @@ header > nav > a:hover { } .sub-header option { - color: black; + color: black; } .sub-header > select:focus { @@ -385,7 +385,7 @@ aside h3 { } .collapsible-arrow { - font-size: 1.5em!important; + font-size: 1.5em !important; left: -1.166em; top: 0.25em; user-select: none; @@ -465,15 +465,15 @@ aside .material-icons, align-items: stretch; } -.search-wrapper > input[type=search] { +.search-wrapper > input[type="search"] { font-family: "Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif; outline: none; appearance: none; font-size: 1em; } -.search-wrapper > input[type=search], -.search-wrapper > button[type=submit] { +.search-wrapper > input[type="search"], +.search-wrapper > button[type="submit"] { background-color: var(--sub-header-background); border: none; color: var(--search-text); @@ -503,7 +503,7 @@ button[type=submit]:focus ~ input[type=search] { border-right: none; } */ -.search-wrapper > button[type=submit] { +.search-wrapper > button[type="submit"] { color: var(--search-button); /* border: 1px solid var(--search-border); */ /* border-left: none; */ @@ -519,7 +519,7 @@ input[type=search]:focus ~ button[type=submit] { border-left: none; } */ -.search-wrapper > button[type=submit]:hover { +.search-wrapper > button[type="submit"]:hover { background-color: var(--search-border); color: var(--search-button-hover); } @@ -544,14 +544,14 @@ div.modal { width: 100%; height: 100%; overflow: hidden; - background-color: rgba(0,0,0,0.4); + background-color: rgba(0, 0, 0, 0.4); cursor: pointer; display: none; } div.modal-content { background-color: var(--main-background); - box-shadow: 0 2px 8px rgba(0,0,0,0.54); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.54); padding: 24px; border-radius: 4px; max-width: 40%; @@ -639,40 +639,78 @@ main h2, main h3, main h4, main h5, -main h6 { font-weight: normal; } +main h6 { + font-weight: normal; +} main h1, main h2, main h3, -main h4 { color: var(--main-big-headers-text); } -main h5 { color: var(--main-h5-header-text); } -main h6 { color: var(--main-h6-header-text); } +main h4 { + color: var(--main-big-headers-text); +} +main h5 { + color: var(--main-h5-header-text); +} +main h6 { + color: var(--main-h6-header-text); +} -main h1 { margin: 0 0 10px 0; } +main h1 { + margin: 0 0 10px 0; +} main h2, -main h3 { margin: 10px 0px 10px 0px; } +main h3 { + margin: 10px 0px 10px 0px; +} main h4, main h5, -main h6 { margin: 20px 0px 10px 0px; } +main h6 { + margin: 20px 0px 10px 0px; +} -main h1 { padding: 0 0 10px 0; } +main h1 { + padding: 0 0 10px 0; +} main h2, -main h3 { padding: 10px 0 10px 0; } -main h4 { padding: 10px 0 10px 0; } +main h3 { + padding: 10px 0 10px 0; +} +main h4 { + padding: 10px 0 10px 0; +} main h5, -main h6 { padding: 10px 0 0 0; } +main h6 { + padding: 10px 0 0 0; +} main h1, main h2, -main h3 { border-bottom: 1px solid var(--main-big-headers-border); } -main h4 { border-bottom: 1px solid var(--main-h4-header-border); } +main h3 { + border-bottom: 1px solid var(--main-big-headers-border); +} +main h4 { + border-bottom: 1px solid var(--main-h4-header-border); +} -main h1 { font-size: 2.3em; } -main h2 { font-size: 1.8em; } -main h3 { font-size: 1.3em; } -main h4 { font-size: 1.1em; } -main h5 { font-size: 1.05em; } -main h6 { font-size: 1em; } +main h1 { + font-size: 2.3em; +} +main h2 { + font-size: 1.8em; +} +main h3 { + font-size: 1.3em; +} +main h4 { + font-size: 1.1em; +} +main h5 { + font-size: 1.05em; +} +main h6 { + font-size: 1em; +} a.headerlink { color: var(--header-link); @@ -697,7 +735,7 @@ dt:hover > a.headerlink, caption:hover > a.headerlink, p.caption:hover > a.headerlink, div.code-block-caption:hover > a.headerlink { - visibility: visible; + visibility: visible; } .versionmodified { @@ -710,8 +748,9 @@ main ul { padding-left: 1.3em; } -main ul ul, main ol ul { - margin: .2em 0; +main ul ul, +main ol ul { + margin: 0.2em 0; padding-left: 1.2em; } @@ -730,7 +769,7 @@ main ol { } main ol ol { - margin: .2em 0; + margin: 0.2em 0; } main ol > li { @@ -793,7 +832,7 @@ p.admonition-title { } p.admonition-title::before { - font: normal normal normal 24px/1 'Material Icons'; + font: normal normal normal 24px/1 "Material Icons"; display: inline-block; width: 24px; height: 24px; @@ -801,7 +840,10 @@ p.admonition-title::before { left: 9.6px; } -div.important, div.note, div.hint, div.tip { +div.important, +div.note, +div.hint, +div.tip { border-left-color: var(--note-background); } @@ -817,10 +859,12 @@ div.important > p.admonition-title::before, div.note > p.admonition-title::before, div.hint > p.admonition-title::before, div.tip > p.admonition-title::before { - content: '\0e88e'; + content: "\0e88e"; } -div.attention, div.warning, div.caution { +div.attention, +div.warning, +div.caution { border-left-color: var(--warning-background); } @@ -834,10 +878,11 @@ div.caution > p.admonition-title { div.attention > p.admonition-title::before, div.warning > p.admonition-title::before, div.caution > p.admonition-title::before { - content: '\0e002'; + content: "\0e002"; } -div.danger, div.error { +div.danger, +div.error { border-left-color: var(--error-background); } @@ -849,7 +894,7 @@ div.error > p.admonition-title { div.danger > p.admonition-title::before, div.error > p.admonition-title::before { - content: '\0e000'; + content: "\0e000"; } /* helpful admonitions */ @@ -863,7 +908,7 @@ div.helpful > p.admonition-title { } div.helpful > p.admonition-title::before { - content: '\0e873'; + content: "\0e873"; } dl.field-list > dd { @@ -916,7 +961,7 @@ a.reference.rfc > strong { } .exception-hierarchy-content ul { - list-style: 'Âģ' !important; + list-style: "Âģ" !important; } /* attribute tables */ @@ -995,7 +1040,8 @@ pre { overflow-x: auto; } -pre, code { +pre, +code { font-family: var(--monospace-font-family); font-size: 0.9em; overflow-wrap: break-word; @@ -1003,7 +1049,7 @@ pre, code { code { background-color: var(--inline-code-background); - padding: .15em; + padding: 0.15em; border-radius: 3px; } @@ -1022,7 +1068,8 @@ code.descclassname + code.descname { padding-left: 0; } -code.xref, a code { +code.xref, +a code { font-weight: normal; background-color: var(--xref-code-background); } @@ -1059,12 +1106,13 @@ dd { margin-left: 1.5em; } -dt:target, span.highlighted { - background-color: var(--highlighted-text); +dt:target, +span.highlighted { + background-color: var(--highlighted-text); } rect.highlighted { - fill: var(--highlighted-text); + fill: var(--highlighted-text); } .container.operations { @@ -1074,7 +1122,7 @@ rect.highlighted { } .container.operations::before { - content: 'Supported Operations'; + content: "Supported Operations"; color: var(--main-big-headers-text); display: block; padding-bottom: 0.5em; @@ -1134,7 +1182,7 @@ table.docutils tbody tr td ul, table.docutils thead tr td ol, table.docutils tfoot tr td ol, table.docutils tbody tr td ol { - margin: 0 0 .5em; + margin: 0 0 0.5em; } table.docutils thead tr td p.last, table.docutils tfoot tr td p.last, @@ -1182,7 +1230,6 @@ div.code-block-caption { font-weight: bold; } - /* desktop stuff */ @media screen and (min-width: 600px) { @@ -1278,9 +1325,9 @@ div.code-block-caption { color: var(--nav-hover-text); } - #hamburger-toggle, #settings-toggle { + #hamburger-toggle, + #settings-toggle { display: none; - } } @@ -1293,7 +1340,7 @@ div.code-block-caption { "h h h h h h h h h h h h h h h h" "n n n n n n n n n n n n n n n n" "s s s . . c c c c c c c c c . ." - "s s s f f f f f f f f f f f f f" + "s s s f f f f f f f f f f f f f"; } header > nav { diff --git a/docs/_templates/genindex.html b/docs/_templates/genindex.html index a5f2262ffd..02677c201a 100644 --- a/docs/_templates/genindex.html +++ b/docs/_templates/genindex.html @@ -1,27 +1,24 @@ -{%- extends "basic/genindex.html" %} +{%- extends "basic/genindex.html" %} {% block body %} {{ super() }} + + + el.textContent = key; + } + document.querySelectorAll("td").forEach((el) => (el.style.width = "auto")); + {% endblock %} diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html index 31d3900b05..c0a5f068d0 100644 --- a/docs/_templates/layout.html +++ b/docs/_templates/layout.html @@ -1,198 +1,287 @@ - + - - - - {{ title|striptags|e }}{{ titlesuffix }} - {%- block extrahead %} {% endblock %} - - - {%- block css %} - {%- for css in css_files %} - {%- if css|attr("filename") %} - {{ css_tag(css) }} - {%- else %} - - {%- endif %} - {%- endfor %} - {%- endblock %} - - - - {%- block scripts %} - - {%- for js in script_files %} - {{ js_tag(js) }} - {%- endfor %} - {%- endblock %} - {%- if pageurl %} - - {%- endif %} - {%- if favicon %} - - {%- endif %} - {%- block linktags %} - {%- if hasdoc('about') %} - - {%- endif %} - {%- if hasdoc('genindex') %} - - {%- endif %} - {%- if hasdoc('search') %} - - {%- endif %} - {%- if hasdoc('copyright') %} - - {%- endif %} - {%- if next %} - - {%- endif %} - {%- if prev %} - - {%- endif %} - {%- endblock %} - - -{%- block header %}{% endblock %} -
- {#- The main navigation header #} -
- - -
- {#- The sub-header with search and extension related selection #} -
- - - - settings + + settings + + + + {#- The actual body of the contents #} +
{% block body %} {% endblock %}
+ {%- block footer %} +
+ {%- if show_copyright %} {%- if hasdoc('copyright') %} {% trans + path=pathto('copyright'), copyright=copyright|e %}© + Copyright {{ copyright }}.{% endtrans %} {%- else %} {% + trans copyright=copyright|e %}© Copyright {{ copyright }}.{% endtrans %} + {%- endif %} {%- endif %} {%- if last_updated %} {% trans + last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %} + {%- endif %} {%- if show_sphinx %} {% trans sphinx_version=sphinx_version|e + %}Created using Sphinx {{ + sphinx_version }}.{% endtrans %} {%- endif %} +
+ {%- endblock %}
- {#- The sidebar component #} - - {#- The actual body of the contents #} -
- {% block body %} {% endblock %} -
-{%- block footer %} -
- {%- if show_copyright %} - {%- if hasdoc('copyright') %} - {% trans path=pathto('copyright'), copyright=copyright|e %}© Copyright {{ copyright }}.{% endtrans %} - {%- else %} - {% trans copyright=copyright|e %}© Copyright {{ copyright }}.{% endtrans %} - {%- endif %} - {%- endif %} - {%- if last_updated %} - {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %} - {%- endif %} - {%- if show_sphinx %} - {% trans sphinx_version=sphinx_version|e %}Created using Sphinx {{ sphinx_version }}.{% endtrans %} - {%- endif %} -
-{%- endblock %} -
- {%- if READTHEDOCS %} - - {%- endif %} + + {%- endif %} -