├── .github └── workflows │ └── greetings.yml ├── .gitignore ├── .readthedocs.yaml ├── .vscode └── settings.json ├── CHANGELOG.MD ├── CONTRIBUTING.MD ├── LICENSE ├── README.md ├── TODO.MD ├── docs ├── Makefile ├── make.bat ├── requirements.txt └── source │ ├── api │ ├── caching.rst │ ├── client.rst │ ├── dataclasses.rst │ ├── enums.rst │ ├── events.rst │ ├── exceptions.rst │ ├── index.rst │ ├── models.rst │ └── utils.rst │ ├── conf.py │ ├── contributing.rst │ ├── getting_started.rst │ ├── images │ └── logo.png │ ├── index.rst │ └── releases.rst ├── examples └── basic.py ├── pyproject.toml ├── qord ├── __init__.py ├── bases.py ├── core │ ├── __init__.py │ ├── cache.py │ ├── cache_impl.py │ ├── client.py │ ├── dispatch.py │ ├── ratelimits.py │ ├── rest.py │ └── shard.py ├── dataclasses │ ├── __init__.py │ ├── allowed_mentions.py │ ├── embeds.py │ ├── files.py │ ├── message_reference.py │ └── permission_overwrite.py ├── decorators.py ├── enums.py ├── events │ ├── __init__.py │ ├── base.py │ ├── channels.py │ ├── emojis.py │ ├── gateway.py │ ├── guild_members.py │ ├── guilds.py │ ├── invites.py │ ├── messages.py │ ├── reactions.py │ ├── roles.py │ ├── scheduled_events.py │ ├── stage_instances.py │ └── users.py ├── exceptions.py ├── flags │ ├── __init__.py │ ├── applications.py │ ├── base.py │ ├── intents.py │ ├── messages.py │ ├── permissions.py │ ├── system_channel.py │ └── users.py ├── internal │ ├── __init__.py │ ├── context_managers.py │ ├── helpers.py │ ├── mixins.py │ ├── types.py │ └── undefined.py ├── models │ ├── __init__.py │ ├── applications.py │ ├── base.py │ ├── channels.py │ ├── emojis.py │ ├── guild_members.py │ ├── guilds.py │ ├── invites.py │ ├── messages.py │ ├── roles.py │ ├── scheduled_events.py │ ├── stage_instances.py │ └── users.py ├── project_info.py └── utils.py ├── requirements.txt ├── setup.py └── tests ├── test_embed.py ├── test_flags.py ├── test_internal_helpers.py ├── test_permission_overwrite.py └── test_utils.py /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 2 | 3 | on: [pull_request, issues] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | steps: 12 | - uses: actions/first-interaction@v1 13 | with: 14 | repo-token: ${{ secrets.GITHUB_TOKEN }} 15 | issue-message: 'Hi, It looks like this is your first issue in this repository. We appreciate your contribution! :+1: If you have any questions about your issue, Consider discussing it in our [Discord Server](https://discord.gg/nE9cGtzayA)' 16 | pr-message: 'Thank you for contributing to Qord! :+1: As this is your first contribution, We recommend joining our [Discord server](https://discord.gg/nE9cGtzayA) for asking questions about your pull request and to discuss any major changes.' 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # VSCode - Ignore all files of .vscode/ except settings.json since it 132 | # includes the configuration for tests. 133 | .vscode/* 134 | !.vscode/settings.json 135 | 136 | # Optional 137 | .qord-misc/ 138 | test.* -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the version of Python and other tools you might need 9 | build: 10 | os: ubuntu-20.04 11 | tools: 12 | python: "3.9" 13 | nodejs: "16" 14 | 15 | # Build documentation in the docs/ directory with Sphinx 16 | sphinx: 17 | configuration: docs/source/conf.py 18 | 19 | python: 20 | install: 21 | - requirements: docs/requirements.txt 22 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.testing.unittestArgs": [ 3 | "-v", 4 | "-s", 5 | "./tests", 6 | "-p", 7 | "test_*.py" 8 | ], 9 | "python.testing.pytestEnabled": false, 10 | "python.testing.unittestEnabled": true 11 | } -------------------------------------------------------------------------------- /CHANGELOG.MD: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Changelogs for all public releases can be viewed on the [Releases](https://qord.rtfd.io/en/latest/releases.html) page on the documentation. (Recommended) 4 | 5 | You can also track the [GitHub releases](https://github.com/izxxr/qord/releases). 6 | 7 | ----- 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.MD: -------------------------------------------------------------------------------- 1 | # Contributing to Qord 2 | We appreciate any kind of contributions from the users :+1: 3 | 4 | For detailed instructions of contributing to the library, See [this page](https://qord.readthedocs.io/en/stable/contributing.html). 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Izhar Ahmad 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Qord 3 | 4 |
5 | 6 | 7 | 8 |

9 | 10 | Python library for Discord API based around asyncio. 11 | 12 | **Features:** 13 | 14 | - Object oriented, user friendly interface with no dirty payloads. 15 | - Easy to customise and manage. 16 | - Robust handling of HTTP ratelimits. 17 | - Supports automatic gateway sharding. 18 | 19 | ## Development stage 20 | Qord is currently under it's initial development (alpha) stage. During this phase, there may be breaking changes and public user-facing API should not be considered stable. There would be no efforts in keeping backward compatibility. While the library currently supports the basic functionalities required to build a bot, many essential features are yet to be implemented and for this reason, Qord isn't yet a library to choose for making full fledged bots. 21 | 22 | The complete lifetime of 0.x version is considered the development phase. More info about this semantic versioning specification can be found [here](https://semver.org/#spec-item-4). 23 | 24 | ## Installation 25 | Qord is installed using Python's traditional package manager, pip. 26 | ```bash 27 | python -m pip install -U qord 28 | ``` 29 | 30 | Qord requires **Python 3.8 or higher**. The dependencies are handled by pip automatically, see complete list of dependencies [here](https://github.com/izxxr/qord/blob/main/requirements.txt). 31 | 32 | ## Usage 33 | To whet your appetite, let's get a quickstart with an example of a simple "Ping-Pong" bot. 34 | ```py 35 | import qord 36 | 37 | intents = qord.Intents.unprivileged() 38 | intents.message_content = True 39 | client = qord.Client(intents=intents) 40 | 41 | @client.event(qord.GatewayEvent.READY) 42 | async def on_ready(event): 43 | print("Bot is ready.") 44 | print(f"Shards: {client.shards_count}") 45 | print(f"User: {client.user.proper_name}") 46 | print(f"Guilds: {len(client.cache.guilds())}") 47 | 48 | @client.event(qord.GatewayEvent.MESSAGE_CREATE) 49 | async def on_message_create(event): 50 | message = event.message 51 | 52 | if message.author.bot: 53 | # Don't respond to bot messages. 54 | return 55 | 56 | if message.content == "!ping": 57 | await message.channel.send("Pong!") 58 | 59 | client.start("BOT_TOKEN") 60 | ``` 61 | 62 | For a brief explanation of this example, go to [this page](https://github.com/izxxr/qord/blob/master/examples/basic.py). More examples can be found in the [`examples`](https://github.com/izxxr/qord/blob/master/examples) directory. 63 | 64 | ## Contributing 65 | Qord is under heavy development. You can help us in reaching 100% coverage of Discord API by reporting bugs, suggesting features or even directly contributing to the code base. Please read the [Contribution Guidelines](https://github.com/izxxr/qord/blob/main/CONTRIBUTING.MD). 66 | 67 | ---- 68 | 69 |
70 |
71 | DocumentationDiscord ServerPyPi 72 | • GitHubIssues Tracker 73 |

74 | Copyright (C) izxxr and contributors 2022, Under the MIT license. 75 |

76 | -------------------------------------------------------------------------------- /TODO.MD: -------------------------------------------------------------------------------- 1 | # Discord API Coverage 2 | 3 | ## [Audit Log](https://discord.com/developers/docs/resources/audit-log) 4 | 5 | - [ ] Get Guild Audit Log 6 | 7 | ## [Channel](https://discord.com/developers/docs/resources/channel) 8 | 9 | - [x] Get Channel 10 | - [x] Modify Channel 11 | - [x] Delete/Close Channel 12 | - [x] Get Channel Messages 13 | - [x] Get Channel Message 14 | - [x] Create Message 15 | - [x] Crosspost Message 16 | - [x] Create Reaction 17 | - [x] Delete Own Reaction 18 | - [x] Get Reactions 19 | - [x] Delete All Reactions 20 | - [x] Delete All Reactions for Emoji 21 | - [x] Edit Message 22 | - [x] Delete Message 23 | - [ ] Bulk Delete Messages 24 | - [x] Edit Channel Permissions 25 | - [ ] Get Channel Invites 26 | - [ ] Create Channel Invite 27 | - [x] Delete Channel Permission 28 | - [ ] Follow News Channel 29 | - [x] Trigger Typing Indicator 30 | - [x] Get Pinned Messages 31 | - [x] Pin Message 32 | - [x] Unpin Message 33 | - [ ] Start Thread with a Message 34 | - [ ] Start Thread without a Message 35 | - [ ] Join Thread 36 | - [ ] Add Thread Member 37 | - [ ] Leave Thread 38 | - [ ] Remove Thread Member 39 | - [ ] Get Thread Member 40 | - [ ] List Thread Members 41 | - [ ] List Active Threads 42 | - [ ] List Public Archived Threads 43 | - [ ] List Private Archived Threads 44 | - [ ] List Joined Private Archived Threads 45 | 46 | 47 | ## [Emoji](https://discord.com/developers/docs/resources/emoji) 48 | 49 | - [x] List Guild Emojis 50 | - [x] Get Guild Emoji 51 | - [x] Create Guild Emoji 52 | - [x] Modify Guild Emoji 53 | - [x] Delete Guild Emoji 54 | 55 | ## [Guild](https://discord.com/developers/docs/resources/guild) 56 | 57 | - [ ] Create Guild 58 | - [x] Get Guild 59 | - [ ] Get Guild Preview 60 | - [ ] Modify Guild 61 | - [ ] Delete Guild 62 | - [x] Get Guild Channels 63 | - [x] Create Guild Channel 64 | - [ ] Modify Guild Channel Positions 65 | - [x] Modify Guild Channel 66 | - [ ] List Active Threads 67 | - [x] Get Guild Member 68 | - [x] List Guild Members 69 | - [x] Search Guild Members 70 | - [x] Modify Guild Member 71 | - [x] Modify Current Member 72 | - [x] Add Guild Member Role 73 | - [x] Remove Guild Member Role 74 | - [x] Remove Guild Member 75 | - [ ] Get Guild Bans 76 | - [ ] Get Guild Ban 77 | - [ ] Create Guild Ban 78 | - [ ] Remove Guild Ban 79 | - [x] Get Guild Roles 80 | - [x] Create Guild Role 81 | - [ ] Modify Guild Role Positions 82 | - [x] Modify Guild Role 83 | - [x] Delete Guild Role 84 | - [ ] Get Guild Prune Count 85 | - [ ] Begin Guild Prune 86 | - [ ] Get Guild Voice Regions 87 | - [ ] Get Guild Invites 88 | - [ ] Get Guild Integrations 89 | - [ ] Delete Guild Integration 90 | - [ ] Get Guild Widget Settings 91 | - [ ] Modify Guild Widget 92 | - [ ] Get Guild Widget 93 | - [ ] Get Guild Prune Count 94 | - [ ] Get Guild Vanity URL 95 | - [ ] Get Guild Widget Image 96 | - [ ] Get Guild Welcome Screen 97 | - [ ] Modify Guild Welcome Screen 98 | - [ ] Modify Current User Voice State 99 | - [ ] Modify User Voice State 100 | 101 | ## [Guild Scheduled Event](https://discord.com/developers/docs/resources/guild-scheduled-event) 102 | 103 | - [x] List Scheduled Events for Guild 104 | - [x] Create Guild Scheduled Event 105 | - [x] Get Guild Scheduled Event 106 | - [x] Modify Guild Scheduled Event 107 | - [x] Delete Guild Scheduled Event 108 | - [x] Get Guild Scheduled Users 109 | 110 | ## [Guild Template](https://discord.com/developers/docs/resources/guild-template) 111 | 112 | - [ ] Get Guild Template 113 | - [ ] Create Guild from Guild Template 114 | - [ ] Get Guild Templates 115 | - [ ] Create Guild Templates 116 | - [ ] Sync Guild Template 117 | - [ ] Modify Guild Template 118 | - [ ] Delete Guild Template 119 | 120 | ## [Invite](https://discord.com/developers/docs/resources/invite) 121 | 122 | - [ ] Get Invite 123 | - [ ] Delete Invite 124 | 125 | ## [Stage Instance](https://discord.com/developers/docs/resources/stage-instance) 126 | 127 | - [x] Create Stage Instance 128 | - [x] Get Stage Instance 129 | - [x] Modify Stage Instance 130 | - [x] Delete Stage Instance 131 | 132 | ## [Sticker](https://discord.com/developers/docs/resources/sticker) 133 | 134 | - [ ] Get Sticker 135 | - [ ] List Nitro Sticker Packs 136 | - [ ] List Guild Stickers 137 | - [ ] Get Guild Sticker 138 | - [ ] Create Guild Sticker 139 | - [ ] Modify Guild Sticker 140 | - [ ] Delete Guild Sticker 141 | 142 | ## [User](https://discord.com/developers/docs/resources/user) 143 | 144 | - [x] Get Current User 145 | - [x] Get User 146 | - [x] Modify Current User 147 | - [ ] Get Current User Guilds 148 | - [x] Leave Guild 149 | - [x] Create DM 150 | 151 | ## [Voice](https://discord.com/developers/docs/resources/voice) 152 | 153 | - [ ] List Voice Regions 154 | 155 | ## [Webhook](https://discord.com/developers/docs/resources/webhook) 156 | 157 | - [ ] Create Webhook 158 | - [ ] Get Channel Webhooks 159 | - [ ] Get Guild Webhooks 160 | - [ ] Get Webhook 161 | - [ ] Get Webhook with Token 162 | - [ ] Modify Webhook 163 | - [ ] Modify Webhook with Token 164 | - [ ] Delete Webhook 165 | - [ ] Delete Webhook with Token 166 | - [ ] Execute Webhook 167 | - [ ] Get Webhook Message 168 | - [ ] Edit Webhook Message 169 | - [ ] Delete Webhook Message 170 | 171 | ## [Gateway events](https://discord.com/developers/docs/topics/gateway#commands-and-events-gateway-events) 172 | - [x] Hello 173 | - [x] Ready 174 | - [x] Resumed 175 | - [x] Reconnect 176 | - [x] Invalid Session 177 | - [x] Channel Create 178 | - [x] Channel Update 179 | - [x] Channel Delete 180 | - [x] Channel Pins Update 181 | - [ ] Thread Create 182 | - [ ] Thread Update 183 | - [ ] Thread Delete 184 | - [ ] Thread List Sync 185 | - [ ] Thread Member Update 186 | - [ ] Thread Members Update 187 | - [x] Guild Create 188 | - [x] Guild Update 189 | - [x] Guild Delete 190 | - [ ] Guild Ban Add 191 | - [ ] Guild Ban Remove 192 | - [x] Guild Emojis Update 193 | - [ ] Guild Stickers Update 194 | - [ ] Guild Integrations Update 195 | - [x] Guild Member Add 196 | - [x] Guild Member Remove 197 | - [x] Guild Member Update 198 | - [ ] Guild Members Chunk 199 | - [x] Guild Role Create 200 | - [x] Guild Role Update 201 | - [x] Guild Role Delete 202 | - [x] Guild Scheduled Event Create 203 | - [x] Guild Scheduled Event Update 204 | - [x] Guild Scheduled Event Delete 205 | - [x] Guild Scheduled Event User Add 206 | - [x] Guild Scheduled Event User Remove 207 | - [ ] Integration Create 208 | - [ ] Integration Update 209 | - [ ] Integration Delete 210 | - [ ] Interaction Create 211 | - [ ] Invite Create 212 | - [ ] Invite Delete 213 | - [x] Message Create 214 | - [x] Message Update 215 | - [x] Message Delete 216 | - [x] Message Delete Bulk 217 | - [x] Message Reaction Add 218 | - [x] Message Reaction Remove 219 | - [x] Message Reaction Remove All 220 | - [x] Message Reaction Remove Emoji 221 | - [ ] Presence Update 222 | - [x] Stage Instance Create 223 | - [x] Stage Instance Delete 224 | - [x] Stage Instance Update 225 | - [x] Typing Start 226 | - [ ] User Update 227 | - [ ] Voice State Update 228 | - [ ] Voice Server Update 229 | - [ ] Webhooks Update 230 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.https://www.sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp 2 | furo -------------------------------------------------------------------------------- /docs/source/api/caching.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: qord 2 | 3 | .. _api-caching: 4 | 5 | Caching 6 | ======= 7 | 8 | Caching is an important part of maintaining a stateful bot. In order to avoid excessive 9 | API calls, The data sent over gateway by Discord is cached by the client and can be retrieved 10 | from cache when needed rather than fetching from the API. Discord sends gateway events that 11 | allow us to easily track updates in the cached data. 12 | 13 | 14 | .. _api-caching-cache-handlers: 15 | 16 | Cache Handlers 17 | -------------- 18 | 19 | Qord provides a user friendly API for caching. By default, The library provides :ref:`default cache 20 | handlers ` that suit most of the use cases. On a large scale however, you might need to maintain 21 | custom caching using some system such as Redis. To acheive this, There are some :ref:`abstract classes ` 22 | provided by the library that allow you to implement custom cache handlers according to your needs. 23 | 24 | Two different cache handlers are used by the library for caching entities sent over gateway. 25 | 26 | - :class:`ClientCache` (Accessed through :attr:`Client.cache`) 27 | - :class:`GuildCache` (Accessed through :attr:`Guild.cache`) 28 | 29 | The :class:`ClientCache` handler is used for storing global entities such as guilds, users and 30 | messages etc. It can be accessed through the :attr:`Client.cache` property. 31 | 32 | On the other hand, the :class:`GuildCache` handler is used to store entities specific to a 33 | :class:`Guild`. Each :class:`Guild` has a separate cache handler for storing objects 34 | related to that guild that can be accessed by :attr:`Guild.cache` property. 35 | 36 | 37 | .. _api-caching-abstract-classes: 38 | 39 | Abstract classes 40 | ---------------- 41 | 42 | Methods and attributes of each of the handlers described above are documented in detail below. 43 | 44 | ClientCache 45 | ~~~~~~~~~~~ 46 | 47 | .. autoclass:: ClientCache 48 | :members: 49 | 50 | GuildCache 51 | ~~~~~~~~~~ 52 | 53 | .. autoclass:: GuildCache 54 | :members: 55 | 56 | .. _api-caching-default-handlers: 57 | 58 | Default Handlers 59 | ---------------- 60 | 61 | These classes are default implementation for caching provided by the library. 62 | 63 | DefaultClientCache 64 | ~~~~~~~~~~~~~~~~~~ 65 | 66 | .. autoclass:: DefaultClientCache 67 | :members: 68 | 69 | DefaultGuildCache 70 | ~~~~~~~~~~~~~~~~~ 71 | 72 | .. autoclass:: DefaultGuildCache 73 | :members: 74 | -------------------------------------------------------------------------------- /docs/source/api/client.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: qord 2 | 3 | .. _api-client: 4 | 5 | Client 6 | ====== 7 | 8 | The client class is the basic starter point of any bot created using Qord. This class 9 | exposes the main machinery used for interacting with the Discord API. All methods and 10 | properties of this class are documented below. 11 | 12 | .. autoclass:: Client 13 | :members: 14 | -------------------------------------------------------------------------------- /docs/source/api/dataclasses.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: qord 2 | 3 | .. _api-dataclasses: 4 | 5 | Data classes 6 | ============= 7 | 8 | These are some classes that aid in certain tasks and purposes. Unlike :ref:`api-models`, 9 | most of these classes except few **can be** initialized by users. 10 | 11 | 12 | Shard 13 | ~~~~~ 14 | 15 | .. autoclass:: Shard() 16 | :members: 17 | 18 | PermissionOverwrite 19 | ~~~~~~~~~~~~~~~~~~~ 20 | 21 | .. autoclass:: PermissionOverwrite 22 | :members: 23 | 24 | AllowedMentions 25 | ~~~~~~~~~~~~~~~ 26 | 27 | .. autoclass:: AllowedMentions 28 | :members: 29 | 30 | File 31 | ~~~~ 32 | 33 | .. autoclass:: File 34 | :members: 35 | 36 | MessageReference 37 | ~~~~~~~~~~~~~~~~ 38 | 39 | .. autoclass:: MessageReference 40 | :members: 41 | 42 | Embed 43 | ~~~~~ 44 | 45 | .. autoclass:: Embed 46 | :members: 47 | 48 | EmbedImage 49 | ~~~~~~~~~~ 50 | 51 | .. autoclass:: EmbedImage 52 | :members: 53 | 54 | EmbedThumbnail 55 | ~~~~~~~~~~~~~~ 56 | 57 | .. autoclass:: EmbedThumbnail 58 | :members: 59 | 60 | EmbedVideo 61 | ~~~~~~~~~~ 62 | 63 | .. autoclass:: EmbedVideo 64 | :members: 65 | 66 | EmbedField 67 | ~~~~~~~~~~ 68 | 69 | .. autoclass:: EmbedField 70 | :members: 71 | 72 | EmbedAuthor 73 | ~~~~~~~~~~~ 74 | 75 | .. autoclass:: EmbedAuthor 76 | :members: 77 | 78 | EmbedFooter 79 | ~~~~~~~~~~~ 80 | 81 | .. autoclass:: EmbedFooter 82 | :members: 83 | 84 | EmbedProvider 85 | ~~~~~~~~~~~~~ 86 | 87 | .. autoclass:: EmbedProvider 88 | :members: 89 | 90 | Flags 91 | ~~~~~ 92 | 93 | .. autoclass:: Flags() 94 | :members: 95 | 96 | Permissions 97 | ~~~~~~~~~~~ 98 | 99 | .. autoclass:: Permissions() 100 | :members: 101 | 102 | Intents 103 | ~~~~~~~ 104 | 105 | .. autoclass:: Intents() 106 | :members: 107 | 108 | UserFlags 109 | ~~~~~~~~~ 110 | 111 | .. autoclass:: UserFlags() 112 | :members: 113 | 114 | SystemChannelFlags 115 | ~~~~~~~~~~~~~~~~~~ 116 | 117 | .. autoclass:: SystemChannelFlags() 118 | :members: 119 | 120 | MessageFlags 121 | ~~~~~~~~~~~~ 122 | 123 | .. autoclass:: MessageFlags() 124 | :members: 125 | 126 | ApplicationFlags 127 | ~~~~~~~~~~~~~~~~ 128 | 129 | .. autoclass:: ApplicationFlags() 130 | :members: 131 | -------------------------------------------------------------------------------- /docs/source/api/enums.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: qord 2 | 3 | .. _api-enums: 4 | 5 | Enumerations 6 | ============ 7 | 8 | Discord API uses integers based enumerations in the API responses. The classes documented 9 | below provide a way of referencing these enumeration values easily by their name. 10 | 11 | 12 | GatewayEvent 13 | ~~~~~~~~~~~~ 14 | 15 | .. autoclass:: GatewayEvent() 16 | :members: 17 | 18 | PremiumType 19 | ~~~~~~~~~~~ 20 | 21 | .. autoclass:: PremiumType() 22 | :members: 23 | 24 | ImageExtension 25 | ~~~~~~~~~~~~~~ 26 | 27 | .. autoclass:: ImageExtension() 28 | :members: 29 | 30 | DefaultAvatar 31 | ~~~~~~~~~~~~~ 32 | 33 | .. autoclass:: DefaultAvatar() 34 | :members: 35 | 36 | VerificationLevel 37 | ~~~~~~~~~~~~~~~~~ 38 | 39 | .. autoclass:: VerificationLevel() 40 | :members: 41 | 42 | NSFWLevel 43 | ~~~~~~~~~ 44 | 45 | .. autoclass:: NSFWLevel() 46 | :members: 47 | 48 | NotificationLevel 49 | ~~~~~~~~~~~~~~~~~ 50 | 51 | .. autoclass:: NotificationLevel() 52 | :members: 53 | 54 | ExplicitContentFilter 55 | ~~~~~~~~~~~~~~~~~~~~~ 56 | 57 | .. autoclass:: ExplicitContentFilter() 58 | :members: 59 | 60 | ChannelType 61 | ~~~~~~~~~~~ 62 | 63 | .. autoclass:: ChannelType() 64 | :members: 65 | 66 | VideoQualityMode 67 | ~~~~~~~~~~~~~~~~ 68 | 69 | .. autoclass:: VideoQualityMode() 70 | :members: 71 | 72 | PremiumTier 73 | ~~~~~~~~~~~ 74 | 75 | .. autoclass:: PremiumTier() 76 | :members: 77 | 78 | MFALevel 79 | ~~~~~~~~ 80 | 81 | .. autoclass:: MFALevel() 82 | :members: 83 | 84 | MessageType 85 | ~~~~~~~~~~~ 86 | 87 | .. autoclass:: MessageType() 88 | :members: 89 | 90 | ChannelPermissionType 91 | ~~~~~~~~~~~~~~~~~~~~~~~ 92 | 93 | .. autoclass:: ChannelPermissionType() 94 | :members: 95 | 96 | TimestampStyle 97 | ~~~~~~~~~~~~~~ 98 | 99 | .. autoclass:: TimestampStyle() 100 | :members: 101 | 102 | EventEntityType 103 | ~~~~~~~~~~~~~~~ 104 | 105 | .. autoclass:: EventEntityType() 106 | :members: 107 | 108 | EventPrivacyLevel 109 | ~~~~~~~~~~~~~~~~~ 110 | 111 | .. autoclass:: EventPrivacyLevel() 112 | :members: 113 | 114 | EventStatus 115 | ~~~~~~~~~~~ 116 | 117 | .. autoclass:: EventStatus() 118 | :members: 119 | 120 | StagePrivacyLevel 121 | ~~~~~~~~~~~~~~~~~ 122 | 123 | .. autoclass:: StagePrivacyLevel() 124 | :members: 125 | 126 | TeamMembershipState 127 | ~~~~~~~~~~~~~~~~~~~~ 128 | 129 | .. autoclass:: TeamMembershipState() 130 | :members: 131 | 132 | InviteTargetType 133 | ~~~~~~~~~~~~~~~~ 134 | 135 | .. autoclass:: InviteTargetType() 136 | :members: 137 | -------------------------------------------------------------------------------- /docs/source/api/events.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: qord 2 | 3 | .. _api-events: 4 | 5 | Events (``qord.events``) 6 | ========================= 7 | 8 | Qord exposes a rich interface via :class:`Client` class that allows you to listen 9 | to specific gateway events and perform certain operations on those events. In addition 10 | to this, This interface also allows you to create and invoke custom events. 11 | 12 | 13 | .. _api-events-registering-event-listeners: 14 | 15 | Registering event listeners 16 | --------------------------- 17 | 18 | The recommended way to register an event listener, is to use the :meth:`qord.Client.event` 19 | decorator to decorate the callback coroutine. However when subclassing :class:`Client`, consider 20 | using the :func:`qord.event` decorator. Example:: 21 | 22 | @client.event(qord.GatewayEvent.MESSAGE_CREATE) 23 | async def on_message_create(event): 24 | pass 25 | 26 | # ---- or ---- 27 | 28 | class MyClient(qord.Client): 29 | @qord.event(qord.GatewayEvent.MESSAGE_CREATE) 30 | async def on_message_create(event): 31 | pass 32 | 33 | All event listeners must take a single ``event`` parameter that is an instance 34 | of one of :ref:`api-events-event-objects` representing the context of event and 35 | contains data relevant to the invoked event. 36 | 37 | The :class:`qord.GatewayEvent` enumeration details the event names that are sent over gateway. 38 | 39 | .. note:: 40 | Toggling certain :class:`Intents` flags will also disable or enable related 41 | gateway events to that intent for your bot. It is recommended to keep at 42 | least the :attr:`Intents.guilds` intent enabled for proper functioning of library. 43 | 44 | .. autofunction:: qord.event 45 | 46 | 47 | .. _api-events-custom-events: 48 | 49 | Custom events 50 | ------------- 51 | 52 | Custom events are useful for several use cases and library allows you to create 53 | them and easily invoke them. 54 | 55 | Inherit a class from :class:`events.BaseEvent` with ``event_name`` parameter 56 | being the name of event. The instance of this class will be passed to the 57 | event listeners as the context of this event. 58 | 59 | .. warning:: 60 | 61 | Make sure **not** to use an already reserved event name from :class:`GatewayEvent` 62 | or any other event names provided by the library. 63 | 64 | Example:: 65 | 66 | from qord import events 67 | from dataclasses import dataclass 68 | 69 | @dataclass 70 | class ApplicationSubmit(events.BaseEvent, event_name="application_submit"): 71 | id: int 72 | name: str 73 | 74 | @client.event("application_submit") 75 | async def on_application_submit(event: ApplicationSubmit): 76 | print("Application submitted.") 77 | print(f"Name: {event.name}") 78 | print(f"ID: {event.id}") 79 | 80 | You can then invoke the event somewhere else using :meth:`Client.invoke_event` method 81 | and passing the event object:: 82 | 83 | event = ApplicationSubmit(id=1, name="Jake") 84 | client.invoke_event(event) 85 | 86 | .. _api-events-base-classes: 87 | 88 | Base Classes 89 | ------------ 90 | 91 | These are some base classes for the :ref:`api-events-event-objects` 92 | 93 | BaseEvent 94 | ~~~~~~~~~ 95 | 96 | .. autoclass:: qord.events.BaseEvent() 97 | :members: 98 | 99 | BaseGatewayEvent 100 | ~~~~~~~~~~~~~~~~ 101 | 102 | .. autoclass:: qord.events.BaseGatewayEvent() 103 | :members: 104 | 105 | 106 | .. _api-events-event-objects: 107 | 108 | Event Objects 109 | ------------- 110 | 111 | The classes documented below expose the data related to a specific gateway event. The 112 | instance of these classes are passed to the event listener coroutines. 113 | 114 | GatewayDispatch 115 | ~~~~~~~~~~~~~~~ 116 | 117 | .. autoclass:: qord.events.GatewayDispatch() 118 | :members: 119 | 120 | ShardReady 121 | ~~~~~~~~~~ 122 | 123 | .. autoclass:: qord.events.ShardReady() 124 | :members: 125 | 126 | Ready 127 | ~~~~~ 128 | 129 | .. autoclass:: qord.events.Ready() 130 | :members: 131 | 132 | Resumed 133 | ~~~~~~~ 134 | 135 | .. autoclass:: qord.events.Resumed() 136 | :members: 137 | 138 | UserUpdate 139 | ~~~~~~~~~~ 140 | 141 | .. autoclass:: qord.events.UserUpdate() 142 | :members: 143 | 144 | GuildAvailable 145 | ~~~~~~~~~~~~~~ 146 | 147 | .. autoclass:: qord.events.GuildAvailable() 148 | :members: 149 | 150 | GuildUnavailable 151 | ~~~~~~~~~~~~~~~~ 152 | 153 | .. autoclass:: qord.events.GuildUnavailable() 154 | :members: 155 | 156 | GuildJoin 157 | ~~~~~~~~~ 158 | 159 | .. autoclass:: qord.events.GuildJoin() 160 | :members: 161 | 162 | 163 | GuildLeave 164 | ~~~~~~~~~~ 165 | 166 | .. autoclass:: qord.events.GuildLeave() 167 | :members: 168 | 169 | GuildUpdate 170 | ~~~~~~~~~~~ 171 | 172 | .. autoclass:: qord.events.GuildUpdate() 173 | :members: 174 | 175 | RoleCreate 176 | ~~~~~~~~~~ 177 | 178 | .. autoclass:: qord.events.RoleCreate() 179 | :members: 180 | 181 | RoleUpdate 182 | ~~~~~~~~~~ 183 | 184 | .. autoclass:: qord.events.RoleUpdate() 185 | :members: 186 | 187 | RoleDelete 188 | ~~~~~~~~~~ 189 | 190 | .. autoclass:: qord.events.RoleDelete() 191 | :members: 192 | 193 | GuildMemberAdd 194 | ~~~~~~~~~~~~~~ 195 | 196 | .. autoclass:: qord.events.GuildMemberAdd() 197 | :members: 198 | 199 | GuildMemberUpdate 200 | ~~~~~~~~~~~~~~~~~ 201 | 202 | .. autoclass:: qord.events.GuildMemberUpdate() 203 | :members: 204 | 205 | GuildMemberRemove 206 | ~~~~~~~~~~~~~~~~~ 207 | 208 | .. autoclass:: qord.events.GuildMemberRemove() 209 | :members: 210 | 211 | 212 | ChannelCreate 213 | ~~~~~~~~~~~~~ 214 | 215 | .. autoclass:: qord.events.ChannelCreate() 216 | :members: 217 | 218 | 219 | ChannelUpdate 220 | ~~~~~~~~~~~~~ 221 | 222 | .. autoclass:: qord.events.ChannelUpdate() 223 | :members: 224 | 225 | ChannelPinsUpdate 226 | ~~~~~~~~~~~~~~~~~ 227 | 228 | .. autoclass:: qord.events.ChannelPinsUpdate() 229 | :members: 230 | 231 | ChannelDelete 232 | ~~~~~~~~~~~~~ 233 | 234 | .. autoclass:: qord.events.ChannelDelete() 235 | :members: 236 | 237 | TypingStart 238 | ~~~~~~~~~~~ 239 | 240 | .. autoclass:: qord.events.TypingStart() 241 | :members: 242 | 243 | EmojisUpdate 244 | ~~~~~~~~~~~~ 245 | 246 | .. autoclass:: qord.events.EmojisUpdate() 247 | :members: 248 | 249 | MessageCreate 250 | ~~~~~~~~~~~~~ 251 | 252 | .. autoclass:: qord.events.MessageCreate() 253 | :members: 254 | 255 | 256 | MessageUpdate 257 | ~~~~~~~~~~~~~ 258 | 259 | .. autoclass:: qord.events.MessageUpdate() 260 | :members: 261 | 262 | 263 | MessageDelete 264 | ~~~~~~~~~~~~~ 265 | 266 | .. autoclass:: qord.events.MessageDelete() 267 | :members: 268 | 269 | 270 | MessageBulkDelete 271 | ~~~~~~~~~~~~~~~~~~~ 272 | 273 | .. autoclass:: qord.events.MessageBulkDelete() 274 | :members: 275 | 276 | ReactionAdd 277 | ~~~~~~~~~~~ 278 | 279 | .. autoclass:: qord.events.ReactionAdd() 280 | :members: 281 | 282 | ReactionRemove 283 | ~~~~~~~~~~~~~~ 284 | 285 | .. autoclass:: qord.events.ReactionRemove() 286 | :members: 287 | 288 | ReactionClear 289 | ~~~~~~~~~~~~~ 290 | 291 | .. autoclass:: qord.events.ReactionClear() 292 | :members: 293 | 294 | ReactionClearEmoji 295 | ~~~~~~~~~~~~~~~~~~ 296 | 297 | .. autoclass:: qord.events.ReactionClearEmoji() 298 | :members: 299 | 300 | 301 | ScheduledEventCreate 302 | ~~~~~~~~~~~~~~~~~~~~ 303 | 304 | .. autoclass:: qord.events.ScheduledEventCreate() 305 | :members: 306 | 307 | 308 | ScheduledEventUpdate 309 | ~~~~~~~~~~~~~~~~~~~~ 310 | 311 | .. autoclass:: qord.events.ScheduledEventUpdate() 312 | :members: 313 | 314 | 315 | ScheduledEventDelete 316 | ~~~~~~~~~~~~~~~~~~~~ 317 | 318 | .. autoclass:: qord.events.ScheduledEventDelete() 319 | :members: 320 | 321 | 322 | ScheduledEventUserAdd 323 | ~~~~~~~~~~~~~~~~~~~~~ 324 | 325 | .. autoclass:: qord.events.ScheduledEventUserAdd() 326 | :members: 327 | 328 | ScheduledEventUserRemove 329 | ~~~~~~~~~~~~~~~~~~~~~~~~ 330 | 331 | .. autoclass:: qord.events.ScheduledEventUserRemove() 332 | :members: 333 | 334 | -------------------------------------------------------------------------------- /docs/source/api/exceptions.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: qord 2 | 3 | Errors and Exceptions 4 | ===================== 5 | 6 | These are the special exceptions raised by the library in certain scenarios. All of these 7 | exceptions inherit a common :exc:`QordException` class. 8 | 9 | QordException 10 | ~~~~~~~~~~~~~ 11 | 12 | .. autoexception:: QordException() 13 | 14 | ClientSetupRequired 15 | ~~~~~~~~~~~~~~~~~~~ 16 | 17 | .. autoexception:: ClientSetupRequired() 18 | 19 | HTTPException 20 | ~~~~~~~~~~~~~ 21 | 22 | .. autoexception:: HTTPException() 23 | 24 | HTTPBadRequest 25 | ~~~~~~~~~~~~~~ 26 | 27 | .. autoexception:: HTTPBadRequest() 28 | 29 | HTTPForbidden 30 | ~~~~~~~~~~~~~ 31 | 32 | .. autoexception:: HTTPForbidden() 33 | 34 | HTTPNotFound 35 | ~~~~~~~~~~~~ 36 | 37 | .. autoexception:: HTTPNotFound() 38 | 39 | HTTPServerError 40 | ~~~~~~~~~~~~~~~ 41 | 42 | .. autoexception:: HTTPServerError() 43 | 44 | ShardException 45 | ~~~~~~~~~~~~~~ 46 | 47 | .. autoexception:: ShardException() 48 | 49 | ShardCloseException 50 | ~~~~~~~~~~~~~~~~~~~ 51 | 52 | .. autoexception:: ShardCloseException() 53 | 54 | MissingPrivilegedIntents 55 | ~~~~~~~~~~~~~~~~~~~~~~~~ 56 | 57 | .. autoexception:: MissingPrivilegedIntents() 58 | -------------------------------------------------------------------------------- /docs/source/api/index.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============= 3 | 4 | This section documents the entire user facing API of Qord. All the technical information 5 | regarding the library is discussed here. 6 | 7 | 8 | .. toctree:: 9 | :maxdepth: 1 10 | :caption: Contents: 11 | 12 | client 13 | caching 14 | models 15 | dataclasses 16 | enums 17 | exceptions 18 | events 19 | utils 20 | -------------------------------------------------------------------------------- /docs/source/api/models.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: qord 2 | 3 | 4 | .. _api-models: 5 | 6 | Discord Models 7 | ============== 8 | 9 | These classes wrap the Discord's complex data models in easy to use interfaces. 10 | 11 | It is worth noting that these classes are not meant to be initialized by users and 12 | must only be retrieved from cache or fetched from the API using relevant API methods. 13 | 14 | 15 | Base Classes 16 | ------------- 17 | 18 | BaseModel 19 | ~~~~~~~~~ 20 | 21 | .. autoclass:: BaseModel() 22 | :members: 23 | 24 | BaseMessageChannel 25 | ~~~~~~~~~~~~~~~~~~ 26 | 27 | .. autoclass:: BaseMessageChannel() 28 | :members: 29 | 30 | Applications 31 | ------------ 32 | 33 | Application 34 | ~~~~~~~~~~~ 35 | 36 | .. autoclass:: Application() 37 | :members: 38 | :inherited-members: 39 | 40 | ApplicationInstallParams 41 | ~~~~~~~~~~~~~~~~~~~~~~~~ 42 | 43 | .. autoclass:: ApplicationInstallParams() 44 | :members: 45 | :inherited-members: 46 | 47 | Team 48 | ~~~~ 49 | 50 | .. autoclass:: Team() 51 | :members: 52 | :inherited-members: 53 | 54 | TeamMember 55 | ~~~~~~~~~~ 56 | 57 | .. autoclass:: TeamMember() 58 | :members: 59 | :inherited-members: 60 | 61 | Users 62 | ----- 63 | 64 | User 65 | ~~~~ 66 | 67 | .. autoclass:: User() 68 | :inherited-members: 69 | :members: 70 | 71 | ClientUser 72 | ~~~~~~~~~~ 73 | 74 | .. autoclass:: ClientUser() 75 | :inherited-members: 76 | :members: 77 | 78 | Guilds 79 | ------ 80 | 81 | Guild 82 | ~~~~~ 83 | 84 | .. autoclass:: Guild() 85 | :inherited-members: 86 | :members: 87 | 88 | GuildMember 89 | ~~~~~~~~~~~ 90 | 91 | .. autoclass:: GuildMember() 92 | :inherited-members: 93 | :members: 94 | 95 | Role 96 | ~~~~ 97 | 98 | .. autoclass:: Role() 99 | :inherited-members: 100 | :members: 101 | 102 | ScheduledEvent 103 | ~~~~~~~~~~~~~~ 104 | 105 | .. autoclass:: ScheduledEvent() 106 | :inherited-members: 107 | :members: 108 | 109 | Channels 110 | -------- 111 | 112 | GuildChannel 113 | ~~~~~~~~~~~~ 114 | 115 | .. autoclass:: GuildChannel() 116 | :inherited-members: 117 | :members: 118 | 119 | CategoryChannel 120 | ~~~~~~~~~~~~~~~ 121 | 122 | .. autoclass:: CategoryChannel() 123 | :inherited-members: 124 | :members: 125 | 126 | TextChannel 127 | ~~~~~~~~~~~ 128 | 129 | .. autoclass:: TextChannel() 130 | :inherited-members: 131 | :members: 132 | 133 | NewsChannel 134 | ~~~~~~~~~~~ 135 | 136 | .. autoclass:: NewsChannel() 137 | :inherited-members: 138 | :members: 139 | 140 | VoiceChannel 141 | ~~~~~~~~~~~~ 142 | 143 | .. autoclass:: VoiceChannel() 144 | :inherited-members: 145 | :members: 146 | 147 | StageChannel 148 | ~~~~~~~~~~~~ 149 | 150 | .. autoclass:: StageChannel() 151 | :inherited-members: 152 | :members: 153 | 154 | PrivateChannel 155 | ~~~~~~~~~~~~~~ 156 | 157 | .. autoclass:: PrivateChannel() 158 | :inherited-members: 159 | :members: 160 | 161 | DMChannel 162 | ~~~~~~~~~ 163 | 164 | .. autoclass:: DMChannel() 165 | :inherited-members: 166 | :members: 167 | 168 | ChannelPermission 169 | ~~~~~~~~~~~~~~~~~ 170 | 171 | .. autoclass:: ChannelPermission() 172 | :members: 173 | 174 | StageInstance 175 | ~~~~~~~~~~~~~ 176 | 177 | .. autoclass:: StageInstance() 178 | :inherited-members: 179 | :members: 180 | 181 | Messages 182 | -------- 183 | 184 | Message 185 | ~~~~~~~ 186 | 187 | .. autoclass:: Message() 188 | :inherited-members: 189 | :members: 190 | 191 | Attachment 192 | ~~~~~~~~~~ 193 | 194 | .. autoclass:: Attachment() 195 | :inherited-members: 196 | :members: 197 | 198 | Reaction 199 | ~~~~~~~~ 200 | 201 | .. autoclass:: Reaction() 202 | :members: 203 | 204 | ChannelMention 205 | ~~~~~~~~~~~~~~ 206 | 207 | .. autoclass:: ChannelMention() 208 | :inherited-members: 209 | :members: 210 | 211 | 212 | Emojis 213 | ------ 214 | 215 | Emoji 216 | ~~~~~ 217 | 218 | .. autoclass:: Emoji() 219 | :inherited-members: 220 | :members: 221 | 222 | PartialEmoji 223 | ~~~~~~~~~~~~ 224 | 225 | .. autoclass:: PartialEmoji() 226 | :members: 227 | 228 | Invites 229 | ------- 230 | 231 | Invite 232 | ~~~~~~ 233 | 234 | .. autoclass:: Invite() 235 | :members: 236 | :inherited-members: 237 | 238 | PartialInviteGuild 239 | ~~~~~~~~~~~~~~~~~~ 240 | 241 | .. autoclass:: PartialInviteGuild() 242 | :members: 243 | :inherited-members: 244 | 245 | PartialInviteChannel 246 | ~~~~~~~~~~~~~~~~~~~~~ 247 | 248 | .. autoclass:: PartialInviteChannel() 249 | :members: 250 | :inherited-members: 251 | 252 | PartialInviteApplication 253 | ~~~~~~~~~~~~~~~~~~~~~~~~ 254 | 255 | .. autoclass:: PartialInviteApplication() 256 | :members: 257 | :inherited-members: 258 | -------------------------------------------------------------------------------- /docs/source/api/utils.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: qord 2 | 3 | 4 | .. _api-utils: 5 | 6 | General Utilities (``qord.utils``) 7 | ==================================== 8 | 9 | The ``qord.utils`` module provides some helpers that aid in certain common tasks. These 10 | helpers are not fixed for a specific purpose but for various different purposes. 11 | 12 | .. autofunction:: qord.utils.create_timestamp 13 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | 13 | import os 14 | import sys 15 | sys.path.insert(0, os.path.abspath('../../')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'Qord' 21 | copyright = '2022, izxxr' 22 | author = 'izxxr' 23 | 24 | 25 | # -- General configuration --------------------------------------------------- 26 | 27 | # Add any Sphinx extension module names here, as strings. They can be 28 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 29 | # ones. 30 | extensions = [ 31 | "sphinx.ext.autodoc", 32 | "sphinx.ext.napoleon", 33 | ] 34 | 35 | # Add any paths that contain templates here, relative to this directory. 36 | templates_path = ['_templates'] 37 | 38 | # List of patterns, relative to source directory, that match files and 39 | # directories to ignore when looking for source files. 40 | # This pattern also affects html_static_path and html_extra_path. 41 | exclude_patterns = [] 42 | 43 | 44 | # -- Options for HTML output ------------------------------------------------- 45 | 46 | # The theme to use for HTML and HTML Help pages. See the documentation for 47 | # a list of builtin themes. 48 | # 49 | html_theme = 'furo' 50 | 51 | # Add any paths that contain custom static files (such as style sheets) here, 52 | # relative to this directory. They are copied after the builtin static files, 53 | # so a file named "default.css" will overwrite the builtin "default.css". 54 | html_static_path = ['_static'] 55 | 56 | # RST Prolog 57 | embed_restricted_field = "This field can only be returned by embeds from API responses " \ 58 | "that are created by external sources. This field is not available " \ 59 | "to be set by bots or webhooks. As such you should never set this " \ 60 | "field manually, setting it will either have no effect on the embed " \ 61 | "or you will run into unexpected issues." # type: ignore 62 | 63 | supports_comparison = "This class supports equality comparison between instances of this class " \ 64 | "by the :attr:`.id` attribute." # type: ignore 65 | 66 | rst_prolog = f""" 67 | .. |embed-restricted-field| replace:: {embed_restricted_field} 68 | .. |supports-comparison| replace:: {supports_comparison} 69 | .. |discord-guild-invite| replace:: https://discord.gg/nE9cGtzayA 70 | """ 71 | 72 | autodoc_type_aliases = { 73 | "MessageChannelT": "Union[TextChannel, DMChannel, VoiceChannel]", 74 | "_OverwriteValue": "Optional[bool]", 75 | } 76 | 77 | autodoc_member_order = 'bysource' 78 | -------------------------------------------------------------------------------- /docs/source/contributing.rst: -------------------------------------------------------------------------------- 1 | .. _support: 2 | 3 | Contributing to Qord 4 | ==================== 5 | 6 | The development of this library is powered by a small set of volunteers who are spending 7 | their valuable time on making the library better and keeping it up-to-date with constant 8 | changes being done in the Discord API. 9 | 10 | We'd appreciate any help by the users in making the library better. If you are facing a bug, 11 | want to suggest a new feature or contribute directly to the codebase, This page is meant for 12 | you. We also want to appreciate your interest in contributing to the library. 13 | 14 | .. _contributing-discord-server: 15 | 16 | Discord Server 17 | -------------- 18 | 19 | If you are having a problem with the library and want help regarding it. We highly suggest 20 | joining the Discord server of the library as it helps us (maintainers) to stay in touch with 21 | you and discuss what the issue is. 22 | 23 | Discord server is also the only place where you can ask questions or help regarding the library 24 | and if you are facing difficulty in implementing a certain feature, You can ask it in the Discord 25 | server. 26 | 27 | `Join the Discord Server <|discord-guild-invite|>`_ 28 | 29 | 30 | .. _contributing-suggesting-features-or-reporting-bugs: 31 | 32 | Suggesting features or Reporting Bugs 33 | ------------------------------------- 34 | 35 | We love to hear your creative ideas about implementing a specific feature in the library. We 36 | also appreciate bug reports as it helps us keeping the library as stable as possible. 37 | 38 | In order to keep track of feature requests and bug reports, We use `GitHub issues tracker `_. 39 | 40 | When creating issues, make sure to describe your issue in detail. When suggesting a new feature, 41 | you should give a brief detail of the feature and also provide some solid reasons of how the 42 | feature may prove useful. In case of bug reports, you should provide a summary of bug alongside 43 | the steps to reproduce the bug. 44 | 45 | Contributing to codebase 46 | ------------------------ 47 | 48 | If you know how to fix a certain bug or implement a specific feature, consider `opening a pull 49 | request `_ for your change. 50 | 51 | When making code changes make sure to: 52 | 53 | 1. Stay consistent with the current code style of library. We suggest formatting your code with Black formatter. 54 | 2. Write meaningful and brief commit messages. Don't worry about this one though, The commits would be taken care of when merging the pull request. 55 | 3. Try to keep the scope of changes as minimal as possible. Avoid making multiple changes in one pull request. 56 | 57 | For large and major changes, Discuss your idea first in the :ref:`contributing-discord-server` first. 58 | If a pull request is opened implementing a major change without discussion, It'd likely be rejected. 59 | -------------------------------------------------------------------------------- /docs/source/getting_started.rst: -------------------------------------------------------------------------------- 1 | .. _getting-started: 2 | 3 | Getting Started 4 | =============== 5 | 6 | This short section describes the installation process and basic usage of the library. 7 | 8 | Installation 9 | ------------ 10 | 11 | Qord is installed using Python's traditional package manager, pip:: 12 | 13 | python -m pip install -U qord 14 | 15 | Qord requires **Python 3.8 or higher.** The dependencies are handled by pip automatically, 16 | See complete list of dependencies in `here `_. 17 | 18 | Usage 19 | ----- 20 | 21 | To whet your appetite, Let's get a quickstart with an example of a simple "Ping-Pong" bot:: 22 | 23 | import qord 24 | 25 | intents = qord.Intents.unprivileged() 26 | intents.message_content = True 27 | client = qord.Client(intents=intents) 28 | 29 | @client.event(qord.GatewayEvent.READY) 30 | async def on_ready(event): 31 | print("Bot is ready.") 32 | print(f"Shards: {client.shards_count}") 33 | print(f"User: {client.user.proper_name}") 34 | print(f"Guilds: {len(client.cache.guilds())}") 35 | 36 | @client.event(qord.GatewayEvent.MESSAGE_CREATE) 37 | async def on_message_create(event): 38 | message = event.message 39 | 40 | if message.author.bot: 41 | # Don't respond to bot messages. 42 | return 43 | 44 | if message.content == "!ping": 45 | await message.channel.send("Pong!") 46 | 47 | client.start("BOT_TOKEN") 48 | -------------------------------------------------------------------------------- /docs/source/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/izxxr/qord-legacy/6d92751efc76b1f0bbcc350eff30369cb5a1c51a/docs/source/images/logo.png -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. Qord documentation master file, created by 2 | sphinx-quickstart on Sun Feb 6 15:07:27 2022. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Qord's documentation! 7 | ================================ 8 | 9 | Python library for Discord API based around asyncio. 10 | 11 | **Features:** 12 | 13 | - Object oriented, user friendly interface with no dirty payloads. 14 | - Easy to customise and manage. 15 | - Robust handling of HTTP ratelimits. 16 | - Supports automatic gateway sharding. 17 | 18 | .. note:: 19 | Qord is currently under it's initial development (alpha) stage. During this phase, There may 20 | be breaking changes and public user-facing API should not be considered stable. There would 21 | be no efforts in keeping backward compatibility. While the library currently supports the 22 | basic functionalities required to build a bot, many essential features are yet to be 23 | implemented and for this reason, Qord isn't yet a library to choose for making full fledged 24 | bots. 25 | 26 | The complete lifetime of 0.x version is considered the development phase. More info about 27 | this `semantic versioning specification is found here. `_ 28 | 29 | .. toctree:: 30 | :maxdepth: 1 31 | :caption: Table of Content: 32 | 33 | api/index 34 | getting_started 35 | contributing 36 | releases 37 | -------------------------------------------------------------------------------- /docs/source/releases.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: qord 2 | 3 | Releases 4 | ======== 5 | 6 | This page details the changelog containing every notable change of every releases. 7 | 8 | .. tip:: 9 | Want to stay notified when a new release is published? Join our `Discord server <|discord-guild-invite|>`_ 10 | to stay in touch with all the changes in the library. 11 | 12 | .. note:: 13 | The releases with "Pre-release" in title or if the version ends with an identifier 14 | indicates that the release was a pre-release. 15 | 16 | 17 | Latest 18 | ------ 19 | 20 | Breaking Changes 21 | ~~~~~~~~~~~~~~~~ 22 | 23 | - :class:`StageChannel` no longer inherits :class:`VoiceChannel` and is now a completely independent channel type. 24 | - :attr:`Message.channel` and other message related channel attributes can now return :class:`VoiceChannel`. 25 | - Rename :class:`Cache` to :class:`ClientCache` and :class:`DefaultCache` to :class:`DefaultClientCache` for the sake of consistency with it's guild counterpart. 26 | 27 | Additions 28 | ~~~~~~~~~ 29 | 30 | - Added support for applications. 31 | - Added support for messages in :class:`VoiceChannel`. 32 | - Added :func:`qord.event` decorator for registering listeners in a subclassed :class:`Client`. 33 | - Added :meth:`Client.wait_for_event` method to allow waiting for event invocations. 34 | 35 | Bug fixes 36 | ~~~~~~~~~ 37 | 38 | - Fix HTTP ratelimits state being incorrectly stored. Route's major parameters are respected now 39 | while storing ratelimit data internally. 40 | - Fix :attr:`Message.referenced_message` being unbound when message type does not meet the criteria for it. 41 | 42 | v0.4.0 43 | ------ 44 | 45 | Additions 46 | ~~~~~~~~~ 47 | 48 | - Added support for guild scheduled events. 49 | - Added support for stage instances. 50 | - Added following shortcut properties to :class:`Guild`: 51 | - :attr:`~Guild.afk_channel` 52 | - :attr:`~Guild.system_channel` 53 | - :attr:`~Guild.widget_channel` 54 | - :attr:`~Guild.rules_channel` 55 | - :attr:`~Guild.public_updates_channel` 56 | 57 | 58 | Fixes 59 | ~~~~~ 60 | 61 | - Fix crash with :exc:`KeyError` during :attr:`~GatewayEvent.MESSAGE_UPDATE` event. 62 | 63 | 64 | v0.3.0 65 | ------ 66 | 67 | Additions 68 | ~~~~~~~~~ 69 | 70 | - Added support for custom guild emojis. 71 | - Added support for message reactions. 72 | - Added :attr:`Guild.me` property for retreiving bot member. 73 | - Added :attr:`created_at` property on appropriate Discord models. 74 | - Added :meth:`~BaseMessageChannel.messages` method to iterate through channels history. 75 | - Added :meth:`Guild.members` method to iterate through guild members. 76 | - Added :attr:`PrivateChannel.url`, :attr:`GuildChannel.url` and :attr:`Message.url` properties 77 | - Added :meth:`BaseMessageChannel.trigger_typing` and :meth:`BaseMessageChannel.typing` for working with typing indicators. 78 | - Added :meth:`Message.crosspost` for crossposting messages in news channels. 79 | 80 | Changes 81 | ~~~~~~~~ 82 | 83 | - :class:`ChannelPermission` now supports equality comparisons. 84 | - All models now shows useful information in :func:`repr()` 85 | 86 | Bug fixes 87 | ~~~~~~~~~ 88 | 89 | - Fixed :attr:`Embed.video` property running into infinite loop. 90 | - Fixed disparity between ``embed`` and ``embeds`` parameters in :meth:`~BaseMessageChannel.send` 91 | - Fixed typing of :attr:`Message.channel` not including DM channels. 92 | 93 | v0.3.0a2 (Pre-release) 94 | ---------------------- 95 | 96 | Additions 97 | ~~~~~~~~~ 98 | 99 | - Added handling of HTTP ratelimits. 100 | - Added support for channel permission overwrites. 101 | - Added equality comparison support for various Discord models. 102 | - Added module ``qord.utils``, see :ref:`reference-utilities` for more info. 103 | - Added :attr:`Message.referenced_message` attribute. 104 | - Added :func:`qord.utils.create_timestamp` helper function. 105 | - Added :meth:`Embed.total_length` and :meth:`builtins.len()` support on :class:`Embed` 106 | - Added ``channel`` keyword argument in :attr:`GuildMember.edit` 107 | 108 | Improvements/Misc. 109 | ~~~~~~~~~~~~~~~~~~ 110 | 111 | - :attr:`User.mention` string no longer includes ``!``, This is done in order to comply with the recent change done to Discord client. For more information, see `this issue `_ 112 | - :attr:`DefaultClientCache.private_channels` cache is now bound to limit of 256 channels. 113 | - :class:`File` constructor no longer raises :exc:`RuntimeError` on failing to resolve file name and now fallbacks to ``untitled`` 114 | 115 | Fixes 116 | ~~~~~ 117 | 118 | - Fixed cache not cleaning up on client closure. 119 | - Fixed typing issues across the library. 120 | - Passing ``None`` is not supported in various places especially ``x_url()`` methods. 121 | - ``None`` is now allowed in ``reason`` parameters in REST methods. 122 | - Various methods of cache handlers now return :class:`typing.List` rather than the :attr:`typing.Sequence` 123 | - Other minor improvements and fixes. 124 | - Fixed :meth:`GuildCache.roles` returning empty list for HTTP retrieved guilds. 125 | 126 | v0.3.0a1 (Pre-release) 127 | ---------------------- 128 | 129 | Breaking Changes 130 | ~~~~~~~~~~~~~~~~~ 131 | 132 | - Event system restructure 133 | 134 | - Custom events are now created using BaseEvent 135 | - :meth:`Client.invoke_event()` now takes single BaseEvent instance. 136 | - BaseEvent is no longer a protocol, all custom events must inherit it. 137 | - New protocol class BaseGatewayEvent has been added for gateway related events. 138 | - MessagesSupport was renamed to BaseMessageChannel for consistency. 139 | 140 | Additions 141 | ~~~~~~~~~ 142 | 143 | - Added :class:`MessageType` enumeration. 144 | - Added support for message embeds. 145 | - Added support for message allowed mentions. 146 | - Added support for message flags. 147 | - Added support for message references. 148 | - Added :meth:`Message.edit()` and :meth:`Message.delete()` methods. 149 | - Added :meth:`Shard.disconnect()` and :meth:`~Shard.reconnect()` methods. 150 | - Added :meth:`PrivateChannel.close()` method. 151 | - Added :attr:`Intents.message_content` privileged intent flag. 152 | - Added support for embeds, files and other options in :meth:`~BaseMessageChannel.send()` 153 | 154 | Fixes 155 | ~~~~~ 156 | 157 | - Fix various crashes on startup. 158 | - Fix minor bugs. 159 | 160 | Improvements 161 | ~~~~~~~~~~~~ 162 | 163 | - Startup time has minor improvements. 164 | - Library is now completely typed, there may be breaking type changes. 165 | 166 | v0.2.0 167 | ------ 168 | 169 | Additions 170 | ~~~~~~~~~ 171 | 172 | - Added support for guild roles. 173 | - Added support for guild members. 174 | - Added support for permissions. 175 | - Added support for guild channels. 176 | - Added support for messages. 177 | - Added :attr:`User.proper_name` property. 178 | - Added :attr:`User.mention` property. 179 | 180 | Improvements 181 | ~~~~~~~~~~~~ 182 | 183 | - :attr:`Guild.cache` is no longer optional. 184 | - Startup time has been significantly improved. 185 | 186 | Fixes 187 | ~~~~~ 188 | 189 | - Fixed :meth:`GuildCache.clear()` not getting called upon guild evictions. 190 | - Fixed extension parameter incorrectly behaving for various URL methods. 191 | - Fixed shards closing on receiving unhandleable OP code. 192 | - Fixed client not properly handling graceful closure in some cases. 193 | - Fixed :meth:`Client.launch()` raising RuntimeError upon relaunching the client after closing. 194 | 195 | 196 | v0.2.0a1 (Pre-release) 197 | ---------------------- 198 | 199 | Additions 200 | ~~~~~~~~~ 201 | 202 | Add support for users. 203 | Add support for guilds. 204 | Add support for caching. 205 | 206 | Improvements 207 | ~~~~~~~~~~~~ 208 | 209 | - Event listeners tasks now have proper exception handling. 210 | - Various performance improvements. 211 | 212 | Fixes 213 | ~~~~~ 214 | 215 | - Fixed wrong instance check on manually passing a client session. 216 | 217 | v0.1.0 218 | ------ 219 | 220 | - Initial release. 221 | -------------------------------------------------------------------------------- /examples/basic.py: -------------------------------------------------------------------------------- 1 | """ 2 | Basic Usage 3 | ~~~~~~~~~~~ 4 | 5 | This example showcases the basic usage of the library by implementing a simple 6 | ping-pong bot that responds to messages. 7 | 8 | This example considers that you already have a bot user configured with message 9 | content privileged intent enabled. If not, consider doing that first. 10 | 11 | NOTE: Typehints are not required, they are only specified for convenience and may be omitted. 12 | """ 13 | 14 | import qord 15 | 16 | # Intents.unprivileged() creates Intents instance with all unprivileged intents enabled 17 | # Intents in short words, allow you to subscribe or unsubscribe to certain gateway events 18 | # Filtering unnecessary intents is out of scope of this simple example. 19 | intents = qord.Intents.unprivileged() 20 | 21 | # We need message content intent enabled in order to respond to 22 | # message content. It is a privileged intent and requires explicit 23 | # enabling from Developers portal application page. 24 | # 25 | # WARNING: If not explicitly enabled, An error will be raised upon starting 26 | # the client. 27 | intents.message_content = True 28 | 29 | # Instansiating our Client instance 30 | client = qord.Client(intents=intents) 31 | 32 | # event() decorator registers an event listener for the given event. In this case, READY. 33 | # READY event is fired whenever client connects and becomes ready. 34 | # We are registering this event listener as a notification of client 35 | # becoming ready by printing to console. 36 | @client.event(qord.GatewayEvent.READY) 37 | async def on_ready(event: qord.events.Ready): 38 | print(f"Connected as {client.user.proper_name}") 39 | 40 | # MESSAGE_CREATE event is fired whenever we have a new message 41 | # sent by someone in a channel. This is for our commands handling. 42 | @client.event(qord.GatewayEvent.MESSAGE_CREATE) 43 | async def on_message_create(event: qord.events.MessageCreate): 44 | message = event.message 45 | 46 | if message.author.bot or not message.content: 47 | # We don't want to handle messages that are sent 48 | # by a bot or don't have any content, so return 49 | # This clause already handles the case of responding 50 | # to our own message and causing an infinite loop, if 51 | # you remove this if statement consider adding one to 52 | # ignore messages from ourselves and potentially running 53 | # into an infinite loop. 54 | return 55 | 56 | # Commands handling 57 | if message.content == "?ping": 58 | await message.channel.send(f"{message.author.proper_name} used `?ping`: Pong :ping_pong:") 59 | 60 | # Start our client 61 | # This method blocks until the client is not stopped. 62 | # For that reason, All events listeners and other pre-processing must be done 63 | # before this method's call. 64 | client.start("BOT_TOKEN_HERE") -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=42"] 3 | build-backend = "setuptools.build_meta" 4 | -------------------------------------------------------------------------------- /qord/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Qord 3 | ==== 4 | 5 | A high level library for building Discord bots. 6 | """ 7 | 8 | from qord import events, utils 9 | from qord.bases import * 10 | from qord.decorators import * 11 | from qord.enums import * 12 | from qord.exceptions import * 13 | from qord.project_info import * 14 | from qord.core.cache import * 15 | from qord.core.cache_impl import * 16 | from qord.core.client import * 17 | from qord.core.shard import * 18 | from qord.flags.base import * 19 | from qord.flags.intents import * 20 | from qord.flags.permissions import * 21 | from qord.flags.users import * 22 | from qord.flags.system_channel import * 23 | from qord.flags.messages import * 24 | from qord.flags.applications import * 25 | from qord.models.base import * 26 | from qord.models.channels import * 27 | from qord.models.users import * 28 | from qord.models.guilds import * 29 | from qord.models.guild_members import * 30 | from qord.models.roles import * 31 | from qord.models.messages import * 32 | from qord.models.emojis import * 33 | from qord.models.applications import * 34 | from qord.models.scheduled_events import * 35 | from qord.models.stage_instances import * 36 | from qord.models.invites import * 37 | from qord.dataclasses.allowed_mentions import * 38 | from qord.dataclasses.embeds import * 39 | from qord.dataclasses.files import * 40 | from qord.dataclasses.message_reference import * 41 | from qord.dataclasses.permission_overwrite import * 42 | -------------------------------------------------------------------------------- /qord/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/izxxr/qord-legacy/6d92751efc76b1f0bbcc350eff30369cb5a1c51a/qord/core/__init__.py -------------------------------------------------------------------------------- /qord/core/ratelimits.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | import asyncio 26 | import typing 27 | 28 | __all__ = ( 29 | "REST_BASE_URL", 30 | "Route", 31 | "RatelimitHandler", 32 | ) 33 | 34 | REST_BASE_URL = "https://discord.com/api/v10" 35 | 36 | 37 | class Route: 38 | __slots__ = ("method", "path", "requires_auth", "params") 39 | 40 | def __init__(self, 41 | method: typing.Literal["GET", "POST", "PUT", "DELETE", "PATCH"], 42 | path: str, 43 | *, 44 | requires_auth: bool = True, 45 | **params: typing.Any, 46 | ) -> None: 47 | 48 | self.method = method 49 | self.path = path 50 | self.requires_auth = requires_auth 51 | self.params = params 52 | 53 | @property 54 | def url(self) -> str: 55 | return REST_BASE_URL + self.path.format_map(self.params) 56 | 57 | @property 58 | def ratelimit_path(self) -> str: 59 | params = self.params 60 | guild_id = params.get("guild_id") 61 | channel_id = params.get("channel_id") 62 | return f"{self.method}-{self.path}-{guild_id}:{channel_id}" 63 | 64 | def __repr__(self) -> str: 65 | return f"{self.method} {self.url}" 66 | 67 | 68 | class RatelimitHandler: 69 | def __init__(self) -> None: 70 | # Lock for tracking global ratelimit 71 | self.global_ratelimit_cleared = asyncio.Event() 72 | self.global_ratelimit_cleared.set() 73 | 74 | # { bucket_hash | ratelimit_path : asyncio.Lock } 75 | self.locks: typing.Dict[str, asyncio.Lock] = {} 76 | 77 | # { ratelimit_path : bucket_hash } 78 | self.buckets: typing.Dict[str, str] = {} 79 | 80 | def clear(self) -> None: 81 | """Clears internal ratelimit data including locks and bucket hashes.""" 82 | self.locks.clear() 83 | self.buckets.clear() 84 | 85 | def set_global(self) -> None: 86 | """Sets the global ratelimit, preventing any HTTP requests.""" 87 | self.global_ratelimit_cleared.clear() 88 | 89 | def reset_global(self) -> None: 90 | """Resets the global ratelimit, awakening the waiter coroutines.""" 91 | self.global_ratelimit_cleared.set() 92 | 93 | async def wait_until_global_reset(self) -> None: 94 | """Blocks until global ratelimit is cleared.""" 95 | await self.global_ratelimit_cleared.wait() 96 | 97 | def get_lock(self, path: str) -> asyncio.Lock: 98 | """Gets the asyncio.Lock instance for given route's path.""" 99 | 100 | # Firstly, try to retrieve the bucket hash for this path. 101 | key = self.buckets.get(path) 102 | 103 | if key is None: 104 | # Bucket hash not found, As a workaround, we will be falling 105 | # back to storing lock with the route's path. 106 | key = path 107 | 108 | try: 109 | return self.locks[key] 110 | except KeyError: 111 | self.locks[key] = lock = asyncio.Lock() 112 | return lock 113 | 114 | def set_bucket(self, path: str, bucket: str) -> None: 115 | """Stores the bucket hash for the given route's path.""" 116 | 117 | # If we have a "fallback" lock stored in the locks mapping with 118 | # the route's path, We want to store that lock with the bucket hash 119 | # as key and remove the route's path from the mapping. 120 | try: 121 | lock = self.locks.pop(path) 122 | except KeyError: 123 | pass 124 | else: 125 | self.locks[bucket] = lock 126 | 127 | self.buckets[path] = bucket 128 | -------------------------------------------------------------------------------- /qord/dataclasses/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/izxxr/qord-legacy/6d92751efc76b1f0bbcc350eff30369cb5a1c51a/qord/dataclasses/__init__.py -------------------------------------------------------------------------------- /qord/dataclasses/allowed_mentions.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | import typing 26 | 27 | __all__ = ( 28 | "AllowedMentions", 29 | ) 30 | 31 | class AllowedMentions: 32 | """Represents the allowed mentions of a message. 33 | 34 | Allowed mentions are used to control the behaviour of mentions done 35 | in messages that are sent by the bot. You can toggle the mentions 36 | that should be parsed in the message content. 37 | 38 | Parameters 39 | ---------- 40 | users: :class:`builtins.bool` 41 | Whether to enable users mentions in the messages. 42 | roles: :class:`builtins.bool` 43 | Whether to enable roles mentions in the messages. 44 | everyone: :class:`builtins.bool` 45 | Whether to enable mentions for @everyone/@here in messages. 46 | replied_user: :class:`builtins.bool` 47 | Whether to mention the author of replied message in messages 48 | that contain a reply. 49 | mentioned_roles: Sequence[:class:`builtins.int`] 50 | The list of role IDs to mention in the messages. Can contain maximum 51 | of 100 role IDs. Duplicate IDs will be removed. 52 | mentioned_users: Sequence[:class:`builtins.int`] 53 | The list of user IDs to mention in the messages. Can contain maximum 54 | of 100 user IDs. Duplicate IDs will be removed. 55 | """ 56 | 57 | def __init__( 58 | self, 59 | *, 60 | users: bool = False, 61 | roles: bool = False, 62 | everyone: bool = False, 63 | replied_user: bool = False, 64 | mentioned_roles: typing.Optional[typing.Sequence[int]] = None, 65 | mentioned_users: typing.Optional[typing.Sequence[int]] = None, 66 | ) -> None: 67 | 68 | self._mentioned_roles = set(mentioned_roles) if mentioned_roles is not None else set() 69 | self._mentioned_users = set(mentioned_users) if mentioned_users is not None else set() 70 | 71 | if len(self._mentioned_roles) > 100 or len(self._mentioned_users) > 100: 72 | raise ValueError("mentioned_roles and mentioned_users length cannot be greater then 100.") 73 | 74 | self.users = users 75 | self.roles = roles 76 | self.everyone = everyone 77 | self.replied_user = replied_user 78 | 79 | @property 80 | def mentioned_roles(self) -> typing.Set[int]: 81 | """The roles that are allowed to be mentioned. 82 | 83 | Returns 84 | ------- 85 | typing.Set[:class:`builtins.int`] 86 | """ 87 | return self._mentioned_roles.copy() 88 | 89 | def add_role(self, role_id: int) -> None: 90 | """Adds a role ID to the set of mentioned roles. 91 | 92 | Parameters 93 | ---------- 94 | role_id: :class:`builtins.int` 95 | The ID of role to add. 96 | 97 | Raises 98 | ------ 99 | ValueError 100 | Roles limit has reached. 101 | """ 102 | if len(self._mentioned_roles) == 100: 103 | raise ValueError("Role mentions cannot contain more than 100 roles.") 104 | 105 | self._mentioned_roles.add(role_id) 106 | 107 | 108 | def remove_role(self, role_id: int) -> None: 109 | """Removes a role ID from the set of mentioned roles. 110 | 111 | If the role ID does not exist, No error is raised. 112 | 113 | Parameters 114 | ---------- 115 | role_id: :class:`builtins.int` 116 | The ID of role to remove. 117 | """ 118 | try: 119 | self._mentioned_roles.remove(role_id) 120 | except KeyError: 121 | return 122 | 123 | @property 124 | def mentioned_users(self) -> typing.Set[int]: 125 | """The users that are allowed to be mentioned. 126 | 127 | Returns 128 | ------- 129 | typing.Set[:class:`builtins.int`] 130 | """ 131 | return self._mentioned_roles.copy() 132 | 133 | def add_user(self, user_id: int) -> None: 134 | """Adds a user ID to the set of mentioned users. 135 | 136 | Parameters 137 | ---------- 138 | user_id: :class:`builtins.int` 139 | The ID of user to add. 140 | 141 | Raises 142 | ------ 143 | ValueError 144 | Users limit has reached. 145 | """ 146 | if len(self._mentioned_users) == 100: 147 | raise ValueError("User mentions cannot contain more than 100 users.") 148 | 149 | self._mentioned_users.add(user_id) 150 | 151 | def remove_user(self, user_id: int) -> None: 152 | """Removes a user ID from the set of mentioned users. 153 | 154 | If the user ID does not exist, No error is raised. 155 | 156 | Parameters 157 | ---------- 158 | user_id: :class:`builtins.int` 159 | The ID of user to remove. 160 | """ 161 | try: 162 | self._mentioned_users.remove(user_id) 163 | except KeyError: 164 | return 165 | 166 | @classmethod 167 | def all(cls) -> AllowedMentions: 168 | """Creates a :class:`AllowedMentions` with all options enabled.""" 169 | return cls( 170 | users=True, 171 | roles=True, 172 | everyone=True, 173 | replied_user=True, 174 | ) 175 | 176 | def to_dict(self) -> typing.Dict[str, typing.Any]: 177 | ret = { 178 | "replied_user": self.replied_user, 179 | "roles": list(self._mentioned_roles), 180 | "users": list(self._mentioned_users), 181 | } 182 | 183 | parse = [] 184 | 185 | if self.users: 186 | parse.append("users") 187 | # When user mentions are parsed, passing explicit user IDs 188 | # will cause validation error so we will remove user IDs 189 | ret.pop("users") 190 | if self.roles: 191 | parse.append("roles") 192 | # Same case as above applies here. 193 | ret.pop("roles") 194 | if self.everyone: 195 | parse.append("everyone") 196 | 197 | ret["parse"] = parse 198 | return ret 199 | -------------------------------------------------------------------------------- /qord/dataclasses/files.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from qord.internal.undefined import UNDEFINED 26 | 27 | import typing 28 | import os 29 | 30 | if typing.TYPE_CHECKING: 31 | from io import BufferedReader 32 | 33 | 34 | __all__ = ( 35 | "File", 36 | ) 37 | 38 | 39 | class File: 40 | """Represents a file that is sent in messages. 41 | 42 | Example usage with :meth:`~BaseMessageChannel.send` method:: 43 | 44 | file = qord.File("path/to/file.png") 45 | await channel.send(file=file) 46 | 47 | Parameters 48 | ---------- 49 | content: Union[:class:`builtins.str`, :class:`builtins.bytes`, :class:`io.BufferedReader`] 50 | The content of files. If a string is being passed, It would be considered 51 | the file path and would be opened and red in "read binary" mode. 52 | name: :class:`builtins.str` 53 | The name of file. The name would be retrieved from given file path or buffer 54 | object if possible and would fallback to "untitled" if couldn't be resolved. 55 | spoiler: :class:`builtins.bool` 56 | Whether the file should be marked as spoiler when sent. 57 | description: :class:`builtins.str` 58 | The description of attachment. 59 | 60 | Attributes 61 | ---------- 62 | content: :class:`builtins.bytes` 63 | The file contents. 64 | """ 65 | 66 | if typing.TYPE_CHECKING: 67 | content: bytes 68 | name: str 69 | spoiler: bool 70 | description: typing.Optional[str] 71 | 72 | def __init__( 73 | self, 74 | content: typing.Union[str, bytes, BufferedReader], 75 | /, 76 | *, 77 | name: typing.Optional[str] = None, 78 | description: typing.Optional[str] = None, 79 | spoiler: bool = False, 80 | ) -> None: 81 | 82 | if isinstance(content, str): 83 | with open(content, "rb") as f: 84 | self.content = f.read() 85 | if name is None: 86 | name = os.path.basename(content) 87 | elif isinstance(content, bytes): 88 | self.content = content 89 | elif isinstance(content, BufferedReader): 90 | if not content.readable(): 91 | raise RuntimeError("content is not readable.") 92 | self.content = content.read() 93 | if name is None: 94 | name = content.name 95 | 96 | if name is None: 97 | name = "untitled" 98 | 99 | self.name = name 100 | self.description = description 101 | self.spoiler = spoiler or name.startswith("SPOILER_") 102 | 103 | @property 104 | def proper_name(self) -> str: 105 | """Returns the proper name of file with required prefixes attached if any.""" 106 | 107 | if self.spoiler and not self.name.startswith("SPOILER_"): 108 | return f"SPOILER_{self.name}" 109 | 110 | return self.name 111 | -------------------------------------------------------------------------------- /qord/dataclasses/message_reference.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from qord.internal.helpers import get_optional_snowflake 26 | import typing 27 | 28 | if typing.TYPE_CHECKING: 29 | from qord.models.messages import Message 30 | 31 | 32 | __all__ = ( 33 | "MessageReference", 34 | ) 35 | 36 | class MessageReference: 37 | """Represents a reference to another message in a :class:`Message`. 38 | 39 | This class also allows you to create custom message references for replying 40 | to messages via :meth:`~BaseMessageChannel.send` method. 41 | 42 | .. note:: 43 | If you have the :class:`Message` that you are replying to, consider using the 44 | :meth:`~Message.reply` method for more user friendly API interface. 45 | 46 | .. tip:: 47 | For creating message replies, Only ``message_id`` parameter is required however 48 | ``channel_id`` and ``guild_id`` would be validated when supplied. 49 | 50 | From API responses, This class is present on :attr:`Message.message_reference` 51 | attribute indicating a reference to another message. It is sent when the :class:`Message` 52 | has the following type/flag: 53 | 54 | - :class:`MessageFlags.is_crosspost` 55 | - :class:`MessageType.REPLY` 56 | - :class:`MessageType.CHANNEL_FOLLOW_ADD` 57 | - :class:`MessageType.CHANNEL_PIN_ADD` 58 | - :class:`MessageType.THREAD_STARTER_MESSAGE` 59 | 60 | Parameters 61 | ---------- 62 | message_id: :class:`builtins.int` 63 | The ID of message being replied. 64 | channel_id: :class:`builtins.int` 65 | The ID of channel that the referenced message belongs to. 66 | guild_id: :class:`builtins.int` 67 | The ID of guild that the referenced message belongs to, if any. 68 | fail_if_not_exists: :class:`builtins.bool` 69 | Whether the API should throw :class:`HTTPException` if the message 70 | being referenced does not exist. Defaults to ``True``. 71 | 72 | Attributes 73 | ---------- 74 | message_id: Optional[:class:`builtins.int`] 75 | The ID of message that is being referenced. 76 | 77 | For message of type :attr:`~MessageType.CHANNEL_FOLLOW_ADD`, This is 78 | ``None``. 79 | channel_id: Optional[:class:`builtins.int`] 80 | The ID of channel that the reference belongs to. 81 | This is always present when getting this class from an API 82 | response and is optional when instansiating the class manually. 83 | guild_id: Optional[:class:`builtins.int`] 84 | The ID of guild that the reference belongs to, if any. 85 | """ 86 | 87 | if typing.TYPE_CHECKING: 88 | message_id: typing.Optional[int] 89 | channel_id: typing.Optional[int] 90 | guild_id: typing.Optional[int] 91 | 92 | def __init__( 93 | self, 94 | message_id: int, 95 | channel_id: typing.Optional[int] = None, 96 | guild_id: typing.Optional[int] = None, 97 | *, 98 | fail_if_not_exists: bool = True, 99 | ) -> None: 100 | 101 | self.message_id = message_id 102 | self.channel_id = channel_id 103 | self.guild_id = guild_id 104 | self.fail_if_not_exists = fail_if_not_exists 105 | 106 | def to_dict(self) -> typing.Dict[str, typing.Any]: 107 | ret = {"message_id": self.message_id, "fail_if_not_exists": self.fail_if_not_exists} 108 | 109 | if self.channel_id is not None: 110 | ret["channel_id"] = self.channel_id 111 | 112 | elif self.guild_id is not None: 113 | ret["guild_id"] = self.guild_id 114 | 115 | return ret 116 | 117 | @classmethod 118 | def from_dict(cls, data: typing.Dict[str, typing.Any]) -> MessageReference: 119 | # For API responses, The message_id key is optional while 120 | # it is annotated as `int` in constructor to aid user facing API. 121 | return cls( 122 | message_id=get_optional_snowflake(data, "message_id"), # type: ignore # See above 123 | channel_id=int(data["channel_id"]), 124 | guild_id=get_optional_snowflake(data, "guild_id"), 125 | ) 126 | 127 | @classmethod 128 | def from_message(cls, message: Message, *, fail_if_not_exists: bool = True) -> MessageReference: 129 | """Creates a message reference from a :class:`Message`. 130 | 131 | .. tip:: 132 | For creating message replies, Only ``message_id`` parameter is required however 133 | ``channel_id`` and ``guild_id`` would be validated when supplied. 134 | 135 | Parameters 136 | ---------- 137 | message: :class:`Message` 138 | The message to create reference for. 139 | fail_if_not_exists: :class:`builtins.bool` 140 | Whether the API should throw :class:`HTTPException` when sending 141 | message with this reference if the message being referenced does 142 | not exist. Defaults to ``True``. 143 | 144 | Returns 145 | ------- 146 | :class:`MessageReference` 147 | The created reference for given message. 148 | """ 149 | return cls( 150 | message_id=message.id, 151 | channel_id=message.channel_id, 152 | guild_id=message.guild_id, 153 | fail_if_not_exists=fail_if_not_exists, 154 | ) 155 | 156 | -------------------------------------------------------------------------------- /qord/dataclasses/permission_overwrite.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from qord.flags.permissions import Permissions 26 | import typing 27 | 28 | __all__ = ( 29 | "PermissionOverwrite", 30 | ) 31 | 32 | 33 | _OverwriteValue = typing.Optional[bool] 34 | 35 | 36 | class PermissionOverwrite: 37 | """A class representing the permissions overwrite on a guild channel. 38 | 39 | This class allows you to create permission overwrites that you can 40 | apply on guild channels. 41 | 42 | While initializing, this class takes the same keyword parameters as 43 | :class:`Permissions` i.e permissions. The permissions have their default 44 | value set to ``None`` indicating that no override is configured for the 45 | permission. The values of ``True`` and ``False`` explicitly indicates the 46 | allow and deny of the permission, respectively. 47 | 48 | This class supports equality operation with other :class:`PermissionOverwrite` 49 | instances to check if both have same overrides. 50 | """ 51 | if typing.TYPE_CHECKING: 52 | create_instant_invite: _OverwriteValue 53 | kick_members: _OverwriteValue 54 | ban_members: _OverwriteValue 55 | administrator: _OverwriteValue 56 | manage_channels: _OverwriteValue 57 | manage_guild: _OverwriteValue 58 | add_reactions: _OverwriteValue 59 | view_audit_log: _OverwriteValue 60 | priority_speaker: _OverwriteValue 61 | stream: _OverwriteValue 62 | view_channel: _OverwriteValue 63 | send_messages: _OverwriteValue 64 | send_tts_messages: _OverwriteValue 65 | manage_messages: _OverwriteValue 66 | embed_links: _OverwriteValue 67 | attach_files: _OverwriteValue 68 | read_message_history: _OverwriteValue 69 | mention_everyone: _OverwriteValue 70 | use_external_emojis: _OverwriteValue 71 | view_guild_insights: _OverwriteValue 72 | connect: _OverwriteValue 73 | speak: _OverwriteValue 74 | mute_members: _OverwriteValue 75 | deafen_members: _OverwriteValue 76 | move_members: _OverwriteValue 77 | use_vad: _OverwriteValue 78 | change_nickname: _OverwriteValue 79 | manage_nicknames: _OverwriteValue 80 | manage_roles: _OverwriteValue 81 | manage_permissions: _OverwriteValue 82 | manage_webhooks: _OverwriteValue 83 | manage_emojis_and_stickers: _OverwriteValue 84 | use_application_commands: _OverwriteValue 85 | request_to_speak: _OverwriteValue 86 | manage_events: _OverwriteValue 87 | manage_threads: _OverwriteValue 88 | create_public_threads: _OverwriteValue 89 | create_private_threads: _OverwriteValue 90 | use_external_stickers: _OverwriteValue 91 | send_messages_in_threads: _OverwriteValue 92 | start_embedded_activities: _OverwriteValue 93 | moderate_members: _OverwriteValue 94 | 95 | def __init__(self, **permissions: _OverwriteValue) -> None: 96 | invalid = set(permissions).difference(Permissions.__name_value_map__) 97 | 98 | if invalid: 99 | raise TypeError(f"Invalid parameters for PermissionOverwrite(): {', '.join(invalid)}") 100 | 101 | self._overrides: typing.Dict[str, _OverwriteValue] = {} 102 | 103 | for permission, value in permissions.items(): 104 | if value is None: 105 | continue 106 | elif value is True: 107 | self._overrides[permission] = value 108 | elif value is False: 109 | self._overrides[permission] = value 110 | else: 111 | raise TypeError(f"The value for parameter {permission!r} must be a bool or None.") 112 | 113 | def __eq__(self, other: typing.Any) -> bool: 114 | return isinstance(other, PermissionOverwrite) and other._overrides == self._overrides 115 | 116 | def __repr__(self) -> str: 117 | # _overrides mapping only includes the permissions that are explicitly 118 | # overriden by the overwrite. 119 | overrides = self._overrides 120 | return f"PermissionOverwrite({', '.join(f'{k}={v}' for k, v in overrides.items())})" 121 | 122 | def permissions(self) -> typing.Tuple[Permissions, Permissions]: 123 | """Returns the (allow, deny) tuple for the overwrite. 124 | 125 | The first element of the tuple is :class:`Permissions` instance 126 | with all permissions set to ``True`` that are explicitly allowed 127 | by this overwrite. The second element is :class:`Permissions` with 128 | all permissions set to ``True`` that are explicitly denied in the 129 | overwrite. 130 | 131 | Returns 132 | ------- 133 | Tuple[:class:`Permissions`, :class:`Permissions`] 134 | """ 135 | 136 | allow = Permissions() 137 | deny = Permissions() 138 | 139 | for permission, override in self._overrides.items(): 140 | if override is True: 141 | allow._apply(permission, True) 142 | elif override is False: 143 | deny._apply(permission, True) 144 | 145 | return allow, deny 146 | 147 | @classmethod 148 | def from_permissions(cls: typing.Type[PermissionOverwrite], allow: Permissions, deny: Permissions) -> PermissionOverwrite: 149 | """Creates a :class:`PermissionOverwrite` with the given pair of permissions. 150 | 151 | Parameters 152 | ---------- 153 | allow: :class:`Permissions` 154 | The permissions with all permissions set to ``True`` that are 155 | explicitly allowed in the overwrite. 156 | deny: :class:`Permissions` 157 | The permissions with all permissions set to ``True`` that are 158 | explicitly denied in the overwrite. 159 | 160 | Returns 161 | ------- 162 | :class:`PermissionOverwrite` 163 | """ 164 | 165 | overwrite = cls() 166 | overrides = overwrite._overrides 167 | 168 | # TODO: Is there a faster alternative to this? 169 | 170 | for permission in Permissions.__name_value_map__: 171 | if allow._has(permission): 172 | overrides[permission] = True 173 | elif deny._has(permission): 174 | overrides[permission] = False 175 | 176 | return overwrite 177 | 178 | # Setting default values on PermissionOverwrite object 179 | for permission in Permissions.__name_value_map__: 180 | # Since 'permission' value can change during this for loop, we have to 181 | # pass it in fget and fset as a default parameter to have the correct 182 | # value in getter and setter. 183 | def fget(self, permission=permission): 184 | return self._overrides.get(permission) 185 | 186 | def fset(self, value, permission=permission): 187 | if value is not None and not isinstance(value, bool): 188 | raise TypeError(f"The value for {permission!r} must be a bool or None.") 189 | 190 | if value is None: 191 | self._overrides.pop(permission, None) 192 | else: 193 | self._overrides[permission] = value 194 | 195 | prop = property( 196 | fget=fget, 197 | fset=fset, 198 | doc=f"Overwrite value for :attr:`~Permissions.{permission}` permission." 199 | ) 200 | setattr(PermissionOverwrite, permission, prop) 201 | -------------------------------------------------------------------------------- /qord/decorators.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | 26 | def event(event_name: str): 27 | """A decorator for registering event listeners in a :class:`Client`. 28 | 29 | This decorator is exactly same in behaviour as :meth:`Client.event` but 30 | is meant to be used when registering event listeners in a subclassed 31 | :class:`Client`. 32 | 33 | Parameters 34 | ---------- 35 | event_name: :class:`builtins.str` 36 | The name of event to register. 37 | """ 38 | def wrap(func): 39 | func.__listener_event__ = event_name 40 | return func 41 | 42 | return wrap 43 | -------------------------------------------------------------------------------- /qord/events/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | qord.events 3 | ~~~~~~~~~~~ 4 | 5 | Data classes that detail events. 6 | """ 7 | 8 | from qord.events.base import * 9 | from qord.events.channels import * 10 | from qord.events.gateway import * 11 | from qord.events.guild_members import * 12 | from qord.events.guilds import * 13 | from qord.events.roles import * 14 | from qord.events.messages import * 15 | from qord.events.users import * 16 | from qord.events.emojis import * 17 | from qord.events.reactions import * 18 | from qord.events.scheduled_events import * 19 | from qord.events.stage_instances import * 20 | from qord.events.invites import * 21 | -------------------------------------------------------------------------------- /qord/events/base.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | import typing 26 | 27 | if typing.TYPE_CHECKING: 28 | from qord.core.shard import Shard 29 | 30 | __all__ = ( 31 | "BaseEvent", 32 | "BaseGatewayEvent", 33 | ) 34 | 35 | class BaseEvent: 36 | """Base class for events. 37 | 38 | All custom events must inherit from this class. When subclassing the 39 | ``event_name`` parameter is required. 40 | 41 | .. note:: 42 | Parameters documented below are passed during subclassing. 43 | 44 | Parameters 45 | ---------- 46 | event_name: :class:`builtins.str` 47 | The string representation of name of event. This is used for 48 | identifying the event and must be unique. 49 | 50 | .. warning:: 51 | Do not use event names that are already reserved by the library 52 | for example the event names from :class:`GatewayEvent`. 53 | """ 54 | 55 | __event_name__: str 56 | 57 | def __init_subclass__(cls, event_name: str) -> None: 58 | if not isinstance(event_name, str): 59 | raise TypeError("'event_name' parameter must be str.") 60 | 61 | cls.__event_name__ = event_name 62 | 63 | 64 | @typing.runtime_checkable 65 | class BaseGatewayEvent(typing.Protocol): 66 | """A :class:`typing.Protocol` that details events sent over the gateway. 67 | 68 | This protocol supports runtime checks like :meth:`isinstance` 69 | or :meth:`issubclass` etc. 70 | """ 71 | 72 | shard: typing.Optional[Shard] 73 | """The shard that received this event over gateway. 74 | 75 | This attribute can be ``None`` in events that are not shard specific and are 76 | not invoked by a shard. The most common example is :class:`events.Ready`. 77 | """ -------------------------------------------------------------------------------- /qord/events/channels.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from qord.events.base import BaseEvent 26 | from qord.enums import GatewayEvent 27 | 28 | import typing 29 | from dataclasses import dataclass 30 | 31 | if typing.TYPE_CHECKING: 32 | from datetime import datetime 33 | from qord.core.shard import Shard 34 | from qord.models.channels import GuildChannel 35 | from qord.models.guilds import Guild 36 | from qord.models.guild_members import GuildMember 37 | from qord.models.users import User 38 | from qord.internal.types import MessageChannelT 39 | 40 | 41 | __all__ = ( 42 | "ChannelCreate", 43 | "ChannelUpdate", 44 | "ChannelDelete", 45 | "ChannelPinsUpdate", 46 | "TypingStart", 47 | ) 48 | 49 | @dataclass(frozen=True) 50 | class ChannelCreate(BaseEvent, event_name=GatewayEvent.CHANNEL_CREATE): 51 | """Structure for :attr:`~qord.GatewayEvent.CHANNEL_CREATE` event. 52 | 53 | This event is called whenever a new channel is created in a guild. 54 | 55 | Requires the :attr:`~qord.Intents.guilds` intents to be enabled. This intent 56 | is enabled by default. 57 | """ 58 | shard: Shard 59 | 60 | channel: GuildChannel 61 | """The channel that was created.""" 62 | 63 | guild: Guild 64 | """The guild where the event happened.""" 65 | 66 | @dataclass(frozen=True) 67 | class ChannelUpdate(BaseEvent, event_name=GatewayEvent.CHANNEL_UPDATE): 68 | """Structure for :attr:`~qord.GatewayEvent.CHANNEL_UPDATE` event. 69 | 70 | This event is called whenever one or more properties of a guild channel 71 | are updated. 72 | 73 | Requires the :attr:`~qord.Intents.guilds` intents to be enabled. This intent 74 | is enabled by default. 75 | """ 76 | shard: Shard 77 | 78 | before: GuildChannel 79 | """The channel before the update.""" 80 | 81 | after: GuildChannel 82 | """The channel after the update.""" 83 | 84 | guild: Guild 85 | """The guild that the updated channel belonged to.""" 86 | 87 | @dataclass(frozen=True) 88 | class ChannelPinsUpdate(BaseEvent, event_name=GatewayEvent.CHANNEL_PINS_UPDATE): 89 | """Structure for :attr:`~qord.GatewayEvent.CHANNEL_PINS_UPDATE` event. 90 | 91 | This event is called whenever a message is pinned or unpinned in a channel. 92 | This event is not called if a pinned message is deleted. 93 | 94 | Requires the :attr:`~qord.Intents.guilds` intents to be enabled. This intent 95 | is enabled by default. 96 | """ 97 | shard: Shard 98 | 99 | channel: MessageChannelT 100 | """The channel whose pins were updated.""" 101 | 102 | guild: typing.Optional[Guild] 103 | """The guild where the event happened; If applicable otherwise ``None``.""" 104 | 105 | @dataclass(frozen=True) 106 | class ChannelDelete(BaseEvent, event_name=GatewayEvent.CHANNEL_DELETE): 107 | """Structure for :attr:`~qord.GatewayEvent.CHANNEL_DELETE` event. 108 | 109 | This event is called whenever a channel is deleted in a guild. 110 | 111 | Requires the :attr:`~qord.Intents.guilds` intents to be enabled. This intent 112 | is enabled by default. 113 | """ 114 | shard: Shard 115 | 116 | channel: GuildChannel 117 | """The channel that was deleted.""" 118 | 119 | guild: Guild 120 | """The guild that the delete channel belonged to.""" 121 | 122 | @dataclass(frozen=True) 123 | class TypingStart(BaseEvent, event_name=GatewayEvent.TYPING_START): 124 | """Structure for :attr:`~qord.GatewayEvent.TYPING_START` event. 125 | 126 | This event is called whenever a user starts typing in a channel. 127 | 128 | Requires the :attr:`~qord.Intents.guild_message_typing` intents to be enabled for 129 | guild typing events and :attr:`~qord.Intents.direct_message_typing` for DM typing 130 | events. These intents are enabled by default. 131 | """ 132 | shard: Shard 133 | 134 | channel: MessageChannelT 135 | """The channel in which typing started in.""" 136 | 137 | started_at: datetime 138 | """The time when the typing started.""" 139 | 140 | user: typing.Union[User, GuildMember] 141 | """The user or member that started typing. 142 | 143 | If the event happened in a guild, This is :class:`GuildMember` otherwise 144 | it is a :class:`User`. 145 | """ 146 | 147 | guild: typing.Optional[Guild] 148 | """The guild in which typing started in if any.""" 149 | -------------------------------------------------------------------------------- /qord/events/emojis.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from qord.events.base import BaseEvent 26 | from qord.enums import GatewayEvent 27 | 28 | import typing 29 | from dataclasses import dataclass 30 | 31 | if typing.TYPE_CHECKING: 32 | from qord.core.shard import Shard 33 | from qord.models.emojis import Emoji 34 | from qord.models.guilds import Guild 35 | 36 | 37 | __all__ = ( 38 | "EmojisUpdate", 39 | ) 40 | 41 | 42 | @dataclass 43 | class EmojisUpdate(BaseEvent, event_name=GatewayEvent.EMOJIS_UPDATE): 44 | """Structure for :attr:`~qord.GatewayEvent.EMOJIS_UPDATE` event. 45 | 46 | This event is called whenever emojis are updated in a guild i.e 47 | a new emoji is created, an emoji is deleted or updated. 48 | 49 | This requires :attr:`Intents.emojis_and_stickers` to be enabled. 50 | """ 51 | shard: Shard 52 | 53 | guild: Guild 54 | """The guild whose emojis were updated.""" 55 | 56 | before: typing.List[Emoji] 57 | """The list of emojis before the update.""" 58 | 59 | after: typing.List[Emoji] 60 | """The list of emojis after the update.""" 61 | -------------------------------------------------------------------------------- /qord/events/gateway.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from qord.events.base import BaseEvent 26 | from qord.enums import GatewayEvent 27 | 28 | import typing 29 | from dataclasses import dataclass 30 | 31 | if typing.TYPE_CHECKING: 32 | from qord.core.shard import Shard 33 | 34 | 35 | __all__ = ( 36 | "GatewayDispatch", 37 | "ShardReady", 38 | "Ready", 39 | "Resumed", 40 | ) 41 | 42 | 43 | @dataclass(frozen=True) 44 | class GatewayDispatch(BaseEvent, event_name=GatewayEvent.GATEWAY_DISPATCH): 45 | """Structure of a :attr:`~qord.GatewayEvent.GATEWAY_DISPATCH` event. 46 | 47 | This event is called whenever gateway sends an event dispatch. 48 | 49 | This event purely exists for debugging and experimental purposes and should 50 | not generally be used. This event will also call for dispatch events that 51 | are not supported by the library. 52 | 53 | This event is only called when ``debug_events`` parameter is enabled in 54 | :class:`Client`. 55 | """ 56 | shard: Shard 57 | 58 | title: str 59 | """The title/name of event. 60 | 61 | This name isn't same as how library defines the events name. See Discord 62 | documentation for all events names. 63 | 64 | https://discord.dev/topics/gateway#commands-and-events 65 | """ 66 | 67 | data: typing.Optional[typing.Dict[str, typing.Any]] = None 68 | """The raw event data. 69 | 70 | This is mostly raw JSON payload however for some events, This can be ``None``. 71 | """ 72 | 73 | @dataclass(frozen=True) 74 | class ShardReady(BaseEvent, event_name=GatewayEvent.SHARD_READY): 75 | """Structure of a :attr:`~qord.GatewayEvent.SHARD_READY` event. 76 | 77 | This event is called whenever a shard successfully establishes 78 | a connection with Discord gateway and lazy loads cache for all guilds 79 | associated to that shard. 80 | """ 81 | shard: Shard 82 | 83 | @dataclass(frozen=True) 84 | class Ready(BaseEvent, event_name=GatewayEvent.READY): 85 | """Structure of a :attr:`~qord.GatewayEvent.READY` event. 86 | 87 | This event is called when all shards associated to the client 88 | have completely prepared their guilds cache and client is in ready state. 89 | 90 | This event is not shard specific as such :attr:`.shard` is always ``None``. 91 | """ 92 | shard = None 93 | 94 | @dataclass(frozen=True) 95 | class Resumed(BaseEvent, event_name=GatewayEvent.RESUMED): 96 | """Structure of a :attr:`~qord.GatewayEvent.RESUMED` event. 97 | 98 | This event is called whenver a shard is resumed i.e successfully re-establishes 99 | a previously disconnected session. 100 | """ 101 | 102 | shard: Shard 103 | """The shard that was resumed.""" 104 | -------------------------------------------------------------------------------- /qord/events/guild_members.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from qord.events.base import BaseEvent 26 | from qord.enums import GatewayEvent 27 | 28 | import typing 29 | from dataclasses import dataclass 30 | 31 | if typing.TYPE_CHECKING: 32 | from qord.core.shard import Shard 33 | from qord.models.guilds import Guild 34 | from qord.models.guild_members import GuildMember 35 | 36 | __all__ = ( 37 | "GuildMemberAdd", 38 | "GuildMemberUpdate", 39 | "GuildMemberRemove", 40 | ) 41 | 42 | 43 | @dataclass(frozen=True) 44 | class GuildMemberAdd(BaseEvent, event_name=GatewayEvent.GUILD_MEMBER_ADD): 45 | """Structure for :attr:`~qord.GatewayEvent.GUILD_MEMBER_ADD` event. 46 | 47 | This event is called whenever a new member joins the guild. 48 | 49 | This event requires the privileged intent, :attr:`~qord.Intents.members` to be enabled. 50 | """ 51 | shard: Shard 52 | 53 | member: GuildMember 54 | """The member that joined the :attr:`.guild`.""" 55 | 56 | guild: Guild 57 | """The guild that was joined by the member.""" 58 | 59 | @dataclass(frozen=True) 60 | class GuildMemberUpdate(BaseEvent, event_name=GatewayEvent.GUILD_MEMBER_UPDATE): 61 | """Structure for :attr:`~qord.GatewayEvent.GUILD_MEMBER_UPDATE` event. 62 | 63 | This event is called whenever a guild member is updated. 64 | 65 | This event requires the privileged intent, :attr:`~qord.Intents.members` to be enabled. 66 | """ 67 | shard: Shard 68 | 69 | before: GuildMember 70 | """The member before the update.""" 71 | 72 | after: GuildMember 73 | """The member after the update.""" 74 | 75 | guild: Guild 76 | """The associated guild.""" 77 | 78 | @dataclass(frozen=True) 79 | class GuildMemberRemove(BaseEvent, event_name=GatewayEvent.GUILD_MEMBER_REMOVE): 80 | """Structure for :attr:`~qord.GatewayEvent.GUILD_MEMBER_REMOVE` event. 81 | 82 | This event is called whenever a member is removed from the guild. The 83 | removal may be in form of leaving, getting kicked or banned etc. 84 | 85 | This event requires the privileged intent, :attr:`~qord.Intents.members` to be enabled. 86 | """ 87 | shard: Shard 88 | 89 | member: GuildMember 90 | """The member that had left the :attr:`.guild`.""" 91 | 92 | guild: Guild 93 | """The guild that was left by the member.""" 94 | -------------------------------------------------------------------------------- /qord/events/guilds.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from qord.events.base import BaseEvent 26 | from qord.enums import GatewayEvent 27 | 28 | import typing 29 | from dataclasses import dataclass 30 | 31 | if typing.TYPE_CHECKING: 32 | from qord.core.shard import Shard 33 | from qord.models.guilds import Guild 34 | 35 | 36 | __all__ = ( 37 | "GuildAvailable", 38 | "GuildUnavailable", 39 | "GuildJoin", 40 | "GuildUpdate", 41 | "GuildLeave", 42 | ) 43 | 44 | 45 | @dataclass(frozen=True) 46 | class GuildAvailable(BaseEvent, event_name=GatewayEvent.GUILD_AVAILABLE): 47 | """Structure for :attr:`~qord.GatewayEvent.GUILD_AVAILABLE` event. 48 | 49 | This event is called whenever a guild becomes available to the client. When 50 | initially connecting, This event may call several times for lazy loading of 51 | client guilds. 52 | 53 | This event requires the :attr:`~qord.Intents.guilds` to be enabled. This 54 | intent is enabled by default. 55 | """ 56 | shard: Shard 57 | 58 | guild: Guild 59 | """The guild that became available.""" 60 | 61 | @dataclass(frozen=True) 62 | class GuildUnavailable(BaseEvent, event_name=GatewayEvent.GUILD_UNAVAILABLE): 63 | """Structure for :attr:`~qord.GatewayEvent.GUILD_UNAVAILABLE` event. 64 | 65 | This event is called whenever a guild becomes unavailable to the client 66 | most likely due to an outage. 67 | 68 | This event requires the :attr:`~qord.Intents.guilds` to be enabled. This 69 | intent is enabled by default. 70 | """ 71 | shard: Shard 72 | 73 | guild: Guild 74 | """The guild that became unavailable.""" 75 | 76 | @dataclass(frozen=True) 77 | class GuildJoin(BaseEvent, event_name=GatewayEvent.GUILD_JOIN): 78 | """Structure for :attr:`~qord.GatewayEvent.GUILD_JOIN` event. 79 | 80 | This event is called whenever the client user or bot joins a new 81 | guild. 82 | 83 | This event requires the :attr:`~qord.Intents.guilds` to be enabled. This 84 | intent is enabled by default. 85 | """ 86 | shard: Shard 87 | 88 | guild: Guild 89 | """The joined guild.""" 90 | 91 | @dataclass(frozen=True) 92 | class GuildLeave(BaseEvent, event_name=GatewayEvent.GUILD_LEAVE): 93 | """Structure for :attr:`~qord.GatewayEvent.GUILD_LEAVE` event. 94 | 95 | This event is called whenever the client user or bot is removed 96 | (kicked, banned or simply left) from a guild. 97 | 98 | This event requires the :attr:`~qord.Intents.guilds` to be enabled. This 99 | intent is enabled by default. 100 | """ 101 | shard: Shard 102 | 103 | guild: Guild 104 | """The left guild.""" 105 | 106 | @dataclass(frozen=True) 107 | class GuildUpdate(BaseEvent, event_name=GatewayEvent.GUILD_UPDATE): 108 | """Structure for :attr:`~qord.GatewayEvent.GUILD_UPDATE` event. 109 | 110 | This event is called whenever one or more properties of a guild are updated. 111 | 112 | This event requires the :attr:`~qord.Intents.guilds` to be enabled. This 113 | intent is enabled by default. 114 | """ 115 | shard: Shard 116 | 117 | before: Guild 118 | """The copy of guild before the update.""" 119 | 120 | after: Guild 121 | """The guild after the update.""" 122 | -------------------------------------------------------------------------------- /qord/events/invites.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from qord.events.base import BaseEvent 26 | from qord.enums import GatewayEvent 27 | 28 | import typing 29 | from dataclasses import dataclass 30 | 31 | if typing.TYPE_CHECKING: 32 | from qord.core.shard import Shard 33 | from qord.models.invites import Invite 34 | from qord.models.guilds import Guild 35 | from qord.models.channels import GuildChannel 36 | 37 | 38 | __all__ = ( 39 | "InviteCreate", 40 | "InviteDelete", 41 | ) 42 | 43 | 44 | @dataclass 45 | class InviteCreate(BaseEvent, event_name=GatewayEvent.INVITE_CREATE): 46 | """Structure for :attr:`~qord.GatewayEvent.INVITE_CREATE` event. 47 | 48 | This event is called whenever a new invite is created in a guild. 49 | 50 | Requires the :attr:`~qord.Intents.invites` intents to be enabled. 51 | This intent is enabled by default. 52 | """ 53 | shard: Shard 54 | 55 | guild: Guild 56 | """The guild in which invite was created.""" 57 | 58 | channel: GuildChannel 59 | """The channel that the invite belongs to.""" 60 | 61 | invite: Invite 62 | """The created invite.""" 63 | 64 | 65 | @dataclass 66 | class InviteDelete(BaseEvent, event_name=GatewayEvent.INVITE_DELETE): 67 | """Structure for :attr:`~qord.GatewayEvent.INVITE_DELETE` event. 68 | 69 | This event is called whenever an invite is deleted in a guild. 70 | 71 | Requires the :attr:`~qord.Intents.invites` intents to be enabled. 72 | This intent is enabled by default. 73 | """ 74 | shard: Shard 75 | 76 | guild: Guild 77 | """The guild from which the invite was deleted.""" 78 | 79 | channel: GuildChannel 80 | """The channel that the invite belonged to.""" 81 | 82 | code: str 83 | """The deleted invite's code.""" 84 | -------------------------------------------------------------------------------- /qord/events/messages.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from qord.events.base import BaseEvent 26 | from qord.enums import GatewayEvent 27 | 28 | import typing 29 | from dataclasses import dataclass 30 | 31 | if typing.TYPE_CHECKING: 32 | from qord.core.shard import Shard 33 | from qord.models.guilds import Guild 34 | from qord.models.messages import Message 35 | from qord.internal.types import MessageChannelT 36 | 37 | 38 | __all__ = ( 39 | "MessageCreate", 40 | "MessageDelete", 41 | "MessageUpdate", 42 | "MessageBulkDelete", 43 | ) 44 | 45 | 46 | @dataclass(frozen=True) 47 | class MessageCreate(BaseEvent, event_name=GatewayEvent.MESSAGE_CREATE): 48 | """Structure for :attr:`~qord.GatewayEvent.MESSAGE_CREATE` event. 49 | 50 | This event is called whenever a new message is sent in a guild or 51 | private channel. 52 | 53 | This event requires the :attr:`~qord.Intents.guild_messages` and 54 | :attr:`~qord.Intents.direct_messages` intents enabled for guild and DM 55 | message events respectively. These intents are enabled by default. 56 | """ 57 | shard: Shard 58 | 59 | message: Message 60 | """The message that was sent.""" 61 | 62 | @dataclass(frozen=True) 63 | class MessageDelete(BaseEvent, event_name=GatewayEvent.MESSAGE_DELETE): 64 | """Structure for :attr:`~qord.GatewayEvent.MESSAGE_DELETE` event. 65 | 66 | This event is called whenever a message is deleted. 67 | 68 | This event requires the :attr:`~qord.Intents.guild_messages` and 69 | :attr:`~qord.Intents.direct_messages` intents enabled for guild and DM 70 | message events respectively. These intents are enabled by default. 71 | """ 72 | shard: Shard 73 | 74 | message: Message 75 | """The message that was deleted.""" 76 | 77 | @dataclass(frozen=True) 78 | class MessageUpdate(BaseEvent, event_name=GatewayEvent.MESSAGE_UPDATE): 79 | """Structure for :attr:`~qord.GatewayEvent.MESSAGE_UPDATE` event. 80 | 81 | This event is called whenever a message is updated aka edited. 82 | 83 | This event requires the :attr:`~qord.Intents.guild_messages` and 84 | :attr:`~qord.Intents.direct_messages` intents enabled for guild and DM 85 | message events respectively. These intents are enabled by default. 86 | """ 87 | shard: Shard 88 | 89 | before: Message 90 | """The message that was edited, before the edit happened.""" 91 | 92 | after: Message 93 | """The message that was edited, after the edit happened.""" 94 | 95 | 96 | @dataclass(frozen=True) 97 | class MessageBulkDelete(BaseEvent, event_name=GatewayEvent.MESSAGE_BULK_DELETE): 98 | """Structure for :attr:`~qord.GatewayEvent.MESSAGE_BULK_DELETE` event. 99 | 100 | This event is called whenever multiple messages are deleted at the same 101 | time in a channel. 102 | 103 | This event requires the :attr:`~qord.Intents.guild_messages` and 104 | :attr:`~qord.Intents.direct_messages` intents enabled for guild and DM 105 | message events respectively. These intents are enabled by default. 106 | """ 107 | shard: Shard 108 | 109 | messages: typing.List[Message] 110 | """The list of messages that were deleted. 111 | 112 | This list only includes messages that could be resolved from 113 | the bot's cache and may not include all messages that were deleted. 114 | You should use :attr:`.message_ids` to get the IDs of all the messages 115 | deleted however do note that you cannot fetch those messages as 116 | they have been deleted. 117 | """ 118 | 119 | channel: MessageChannelT 120 | """The channel in which messages were bulk deleted.""" 121 | 122 | guild: typing.Optional[Guild] 123 | """The relevant guild if any, ``None`` if the bulk delete happened in DMs.""" 124 | 125 | message_ids: typing.List[int] 126 | """The list of IDs of messages that were deleted.""" 127 | -------------------------------------------------------------------------------- /qord/events/reactions.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from qord.events.base import BaseEvent 26 | from qord.enums import GatewayEvent 27 | 28 | import typing 29 | from dataclasses import dataclass 30 | 31 | if typing.TYPE_CHECKING: 32 | from qord.core.shard import Shard 33 | from qord.models.guild_members import GuildMember 34 | from qord.models.users import User 35 | from qord.models.emojis import PartialEmoji 36 | from qord.models.messages import Message, Reaction 37 | 38 | 39 | __all__ = ( 40 | "ReactionAdd", 41 | "ReactionRemove", 42 | "ReactionClear", 43 | "ReactionClearEmoji", 44 | ) 45 | 46 | 47 | @dataclass 48 | class ReactionAdd(BaseEvent, event_name=GatewayEvent.REACTION_ADD): 49 | """Structure for :attr:`~qord.GatewayEvent.REACTION_ADD` event. 50 | 51 | This event is called whenever a reaction is added on a message. 52 | 53 | This requires :attr:`Intents.guild_message_reactions` or 54 | :attr:`Intents.direct_message_reactions` to be enabled for 55 | guilds and direct messages respectively. 56 | """ 57 | shard: Shard 58 | 59 | message: Message 60 | """The message on which reaction was added.""" 61 | 62 | reaction: Reaction 63 | """The added reaction.""" 64 | 65 | user: typing.Union[User, GuildMember] 66 | """The user who added the reaction.""" 67 | 68 | 69 | @dataclass 70 | class ReactionRemove(BaseEvent, event_name=GatewayEvent.REACTION_REMOVE): 71 | """Structure for :attr:`~qord.GatewayEvent.REACTION_REMOVE` event. 72 | 73 | This event is called whenever a reaction is removed from a message. 74 | 75 | This requires :attr:`Intents.guild_message_reactions` or 76 | :attr:`Intents.direct_message_reactions` to be enabled for 77 | guilds and direct messages respectively. 78 | """ 79 | shard: Shard 80 | 81 | message: Message 82 | """The message from which the reaction was removed.""" 83 | 84 | reaction: Reaction 85 | """The removed reaction.""" 86 | 87 | user: typing.Union[User, GuildMember] 88 | """The user who removed their reaction. 89 | 90 | Unlike the :class:`events.ReactionAdd` event, This attribute can be 91 | a :class:`GuildMember` object only when :attr:`~Intents.members` intents 92 | are enabled as Discord does not tend to send the member data in this event. 93 | """ 94 | 95 | 96 | @dataclass 97 | class ReactionClear(BaseEvent, event_name=GatewayEvent.REACTION_CLEAR): 98 | """Structure for :attr:`~qord.GatewayEvent.REACTION_CLEAR` event. 99 | 100 | This event is called whenever all reactions are cleared from 101 | a message at once. 102 | 103 | This requires :attr:`Intents.guild_message_reactions` or 104 | :attr:`Intents.direct_message_reactions` to be enabled for 105 | guilds and direct messages respectively. 106 | """ 107 | shard: Shard 108 | 109 | message: Message 110 | """The message from which the reactions were cleared.""" 111 | 112 | reactions: typing.List[Reaction] 113 | """The list of cleared reactions.""" 114 | 115 | 116 | @dataclass 117 | class ReactionClearEmoji(BaseEvent, event_name=GatewayEvent.REACTION_CLEAR_EMOJI): 118 | """Structure for :attr:`~qord.GatewayEvent.REACTION_CLEAR_EMOJI` event. 119 | 120 | This event is called whenever all reactions for a specific emoji are 121 | cleared from a message at once. 122 | 123 | This requires :attr:`Intents.guild_message_reactions` or 124 | :attr:`Intents.direct_message_reactions` to be enabled for 125 | guilds and direct messages respectively. 126 | """ 127 | shard: Shard 128 | 129 | message: Message 130 | """The message from which the reactions were cleared.""" 131 | 132 | emoji: PartialEmoji 133 | """The emoji whose reactions were cleared.""" 134 | 135 | reaction: Reaction 136 | """The reaction that was cleared.""" 137 | 138 | -------------------------------------------------------------------------------- /qord/events/roles.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from qord.events.base import BaseEvent 26 | from qord.enums import GatewayEvent 27 | 28 | import typing 29 | from dataclasses import dataclass 30 | 31 | if typing.TYPE_CHECKING: 32 | from qord.core.shard import Shard 33 | from qord.models.roles import Role 34 | from qord.models.guilds import Guild 35 | 36 | 37 | __all__ = ( 38 | "RoleCreate", 39 | "RoleUpdate", 40 | "RoleDelete", 41 | ) 42 | 43 | 44 | @dataclass(frozen=True) 45 | class RoleCreate(BaseEvent, event_name=GatewayEvent.ROLE_CREATE): 46 | """Structure for :attr:`~qord.GatewayEvent.ROLE_CREATE` event. 47 | 48 | This event is called whenever a role is created in a guild. 49 | 50 | This event requires the :attr:`~qord.Intents.guilds` to be enabled. This 51 | intent is enabled by default. 52 | """ 53 | shard: Shard 54 | 55 | role: Role 56 | """The role that was created.""" 57 | 58 | guild: Guild 59 | """The guild that the created role belonged to.""" 60 | 61 | @dataclass(frozen=True) 62 | class RoleUpdate(BaseEvent, event_name=GatewayEvent.ROLE_UPDATE): 63 | """Structure for :attr:`~qord.GatewayEvent.ROLE_UPDATE` event. 64 | 65 | This event is called whenever one or more properties of a guild role 66 | are updated. 67 | 68 | This event requires the :attr:`~qord.Intents.guilds` to be enabled. This 69 | intent is enabled by default. 70 | """ 71 | shard: Shard 72 | 73 | before: Role 74 | """The role before the update.""" 75 | 76 | after: Role 77 | """The role after the update.""" 78 | 79 | guild: Guild 80 | """The guild that the updated role belonged to.""" 81 | 82 | @dataclass(frozen=True) 83 | class RoleDelete(BaseEvent, event_name=GatewayEvent.ROLE_DELETE): 84 | """Structure for :attr:`~qord.GatewayEvent.ROLE_DELETE` event. 85 | 86 | This event is called whenever a role is deleted in a guild. 87 | 88 | This event requires the :attr:`~qord.Intents.guilds` to be enabled. This 89 | intent is enabled by default. 90 | """ 91 | shard: Shard 92 | 93 | role: Role 94 | """The deleted role.""" 95 | 96 | guild: Guild 97 | """The guild that the updated role belonged to.""" 98 | -------------------------------------------------------------------------------- /qord/events/scheduled_events.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from qord.events.base import BaseEvent 26 | from qord.enums import GatewayEvent 27 | 28 | import typing 29 | from dataclasses import dataclass 30 | 31 | if typing.TYPE_CHECKING: 32 | from qord.core.shard import Shard 33 | from qord.models.scheduled_events import ScheduledEvent 34 | from qord.models.guilds import Guild 35 | from qord.models.guild_members import GuildMember 36 | 37 | 38 | __all__ = ( 39 | "ScheduledEventCreate", 40 | "ScheduledEventUpdate", 41 | "ScheduledEventDelete", 42 | "ScheduledEventUserAdd", 43 | "ScheduledEventUserRemove", 44 | ) 45 | 46 | 47 | @dataclass 48 | class ScheduledEventCreate(BaseEvent, event_name=GatewayEvent.SCHEDULED_EVENT_CREATE): 49 | """Structure for :attr:`~qord.GatewayEvent.SCHEDULED_EVENT_CREATE` event. 50 | 51 | This event is called whenever a new scheduled event is created in a guild. 52 | 53 | Requires the :attr:`~qord.Intents.scheduled_events` intents to be enabled. 54 | This intent is enabled by default. 55 | """ 56 | shard: Shard 57 | 58 | guild: Guild 59 | """The guild in which event was created.""" 60 | 61 | scheduled_event: ScheduledEvent 62 | """The created scheduled event.""" 63 | 64 | 65 | @dataclass 66 | class ScheduledEventUpdate(BaseEvent, event_name=GatewayEvent.SCHEDULED_EVENT_UPDATE): 67 | """Structure for :attr:`~qord.GatewayEvent.SCHEDULED_EVENT_UPDATE` event. 68 | 69 | This event is called whenever a scheduled event is updated in a guild. 70 | 71 | Requires the :attr:`~qord.Intents.scheduled_events` intents to be enabled. 72 | This intent is enabled by default. 73 | """ 74 | shard: Shard 75 | 76 | guild: Guild 77 | """The guild in which event was created.""" 78 | 79 | before: ScheduledEvent 80 | """The scheduled event before the update.""" 81 | 82 | after: ScheduledEvent 83 | """The scheduled event after the update.""" 84 | 85 | 86 | @dataclass 87 | class ScheduledEventDelete(BaseEvent, event_name=GatewayEvent.SCHEDULED_EVENT_DELETE): 88 | """Structure for :attr:`~qord.GatewayEvent.SCHEDULED_EVENT_DELETE` event. 89 | 90 | This event is called whenever a scheduled event is deleted in a guild. 91 | 92 | Requires the :attr:`~qord.Intents.scheduled_events` intents to be enabled. 93 | This intent is enabled by default. 94 | """ 95 | shard: Shard 96 | 97 | guild: Guild 98 | """The guild from which the event was deleted.""" 99 | 100 | scheduled_event: ScheduledEvent 101 | """The deleted scheduled event.""" 102 | 103 | 104 | @dataclass 105 | class ScheduledEventUserAdd(BaseEvent, event_name=GatewayEvent.SCHEDULED_EVENT_USER_ADD): 106 | """Structure for :attr:`~qord.GatewayEvent.SCHEDULED_EVENT_USER_ADD` event. 107 | 108 | This event is called whenever a user subscribes to a scheduled event. 109 | 110 | Requires the :attr:`~qord.Intents.scheduled_events` intents to be enabled. 111 | This intent is enabled by default. 112 | """ 113 | shard: Shard 114 | 115 | guild: Guild 116 | """The guild that the event belongs to.""" 117 | 118 | scheduled_event: ScheduledEvent 119 | """The scheduled event on which user subscribed.""" 120 | 121 | user_id: int 122 | """The ID of user who subscribed.""" 123 | 124 | user: typing.Optional[GuildMember] 125 | """The user who subscribed. 126 | 127 | **This can be ``None``!** When :attr:`~qord.Intents.members` are not enabled or member 128 | isn't cached by any chance, This attribute can be ``None``. You should consider fetching 129 | the member via :meth:`Guild.fetch_member` with the given :attr:`.user_id`. 130 | """ 131 | 132 | 133 | @dataclass 134 | class ScheduledEventUserRemove(BaseEvent, event_name=GatewayEvent.SCHEDULED_EVENT_USER_REMOVE): 135 | """Structure for :attr:`~qord.GatewayEvent.SCHEDULED_EVENT_USER_REMOVE` event. 136 | 137 | This event is called whenever a user unsubscribes to a scheduled event. 138 | 139 | Requires the :attr:`~qord.Intents.scheduled_events` intents to be enabled. 140 | This intent is enabled by default. 141 | """ 142 | shard: Shard 143 | 144 | guild: Guild 145 | """The guild that the event belongs to.""" 146 | 147 | scheduled_event: ScheduledEvent 148 | """The scheduled event on which user unsubscribed.""" 149 | 150 | user_id: int 151 | """The ID of user who unsubscribed.""" 152 | 153 | user: typing.Optional[GuildMember] 154 | """The user who unsubscribed. 155 | 156 | **This can be ``None``!** When :attr:`~qord.Intents.members` are not enabled or member 157 | isn't cached by any chance, This attribute can be ``None``. You should consider fetching 158 | the member via :meth:`Guild.fetch_member` with the given :attr:`.user_id`. 159 | """ 160 | -------------------------------------------------------------------------------- /qord/events/stage_instances.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from qord.events.base import BaseEvent 26 | from qord.enums import GatewayEvent 27 | 28 | import typing 29 | from dataclasses import dataclass 30 | 31 | if typing.TYPE_CHECKING: 32 | from qord.core.shard import Shard 33 | from qord.models.stage_instances import StageInstance 34 | from qord.models.guilds import Guild 35 | 36 | 37 | __all__ = ( 38 | "StageInstanceCreate", 39 | "StageInstanceUpdate", 40 | "StageInstanceDelete", 41 | ) 42 | 43 | 44 | @dataclass 45 | class StageInstanceCreate(BaseEvent, event_name=GatewayEvent.STAGE_INSTANCE_CREATE): 46 | """Structure for :attr:`~qord.GatewayEvent.STAGE_INSTANCE_CREATE` event. 47 | 48 | This event is called whenever a new stage instance is created in a guild. 49 | 50 | Requires the :attr:`~qord.Intents.guilds` intents to be enabled. 51 | This intent is enabled by default. 52 | """ 53 | shard: Shard 54 | 55 | guild: Guild 56 | """The guild in which stage instance was created.""" 57 | 58 | stage_instance: StageInstance 59 | """The created stage instance.""" 60 | 61 | 62 | @dataclass 63 | class StageInstanceUpdate(BaseEvent, event_name=GatewayEvent.STAGE_INSTANCE_UPDATE): 64 | """Structure for :attr:`~qord.GatewayEvent.STAGE_INSTANCE_UPDATE` event. 65 | 66 | This event is called whenever a stage instance is updated in a guild. 67 | 68 | Requires the :attr:`~qord.Intents.guilds` intents to be enabled. 69 | This intent is enabled by default. 70 | """ 71 | shard: Shard 72 | 73 | guild: Guild 74 | """The guild in which stage instance was created.""" 75 | 76 | before: StageInstance 77 | """The stage instance before the update.""" 78 | 79 | after: StageInstance 80 | """The stage instance after the update.""" 81 | 82 | 83 | @dataclass 84 | class StageInstanceDelete(BaseEvent, event_name=GatewayEvent.STAGE_INSTANCE_DELETE): 85 | """Structure for :attr:`~qord.GatewayEvent.STAGE_INSTANCE_DELETE` event. 86 | 87 | This event is called whenever a stage instance is deleted in a guild. 88 | 89 | Requires the :attr:`~qord.Intents.guilds` intents to be enabled. 90 | This intent is enabled by default. 91 | """ 92 | shard: Shard 93 | 94 | guild: Guild 95 | """The guild from which the stage instance was deleted.""" 96 | 97 | stage_instance: StageInstance 98 | """The deleted stage instance.""" -------------------------------------------------------------------------------- /qord/events/users.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from qord.events.base import BaseEvent 26 | from qord.enums import GatewayEvent 27 | 28 | import typing 29 | from dataclasses import dataclass 30 | 31 | if typing.TYPE_CHECKING: 32 | from qord.core.shard import Shard 33 | from qord.models.users import User 34 | 35 | 36 | __all__ = ( 37 | "UserUpdate", 38 | ) 39 | 40 | @dataclass(frozen=True) 41 | class UserUpdate(BaseEvent, event_name=GatewayEvent.USER_UPDATE): 42 | """Structure for :attr:`~qord.GatewayEvent.USER_UPDATE` event. 43 | 44 | This event is called whenever one or more properties of a user are updated. 45 | """ 46 | shard: Shard 47 | 48 | before: User 49 | """The user before the update.""" 50 | 51 | after: User 52 | """The user after the update.""" 53 | -------------------------------------------------------------------------------- /qord/exceptions.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | import typing 26 | 27 | if typing.TYPE_CHECKING: 28 | from qord.core.shard import Shard 29 | from aiohttp import ClientResponse 30 | 31 | 32 | __all__ = ( 33 | "QordException", 34 | "ClientSetupRequired", 35 | "HTTPException", 36 | "HTTPBadRequest", 37 | "HTTPForbidden", 38 | "HTTPNotFound", 39 | "HTTPServerError", 40 | "ShardException", 41 | "ShardCloseException", 42 | "MissingPrivilegedIntents", 43 | ) 44 | 45 | class QordException(Exception): 46 | r"""Base exception class for all exceptions raised by the library.""" 47 | 48 | class ClientSetupRequired(QordException): 49 | r"""An exception indicating that client setup is required to perform the attempted 50 | operation that caused the exception. 51 | 52 | For HTTPs operations, This generally means that requested endpoint requires 53 | authorization with a bot token but no bot token is set yet. 54 | 55 | You must call :meth:`Client.setup` with a proper bot token first to setup 56 | the client first before retrying. 57 | """ 58 | pass 59 | 60 | class HTTPException(QordException): 61 | r"""Base exception class for all exceptions that indicate failure of a HTTP request 62 | i.e request returned with an unsuccessful status code.. 63 | 64 | Attributes 65 | ---------- 66 | response: :class:`aiohttp.ClientResponse` 67 | The failed request response. 68 | data: Union[:class:`builtins.dict`, :class:`builtins.str`] 69 | The data from the response. In most cases, This is a dictionary representing 70 | the JSON responose however in rare cases like CloudFlare errors, This can be 71 | a string of raw HTML. 72 | """ 73 | def __init__(self, response: ClientResponse, data: typing.Union[str, dict]) -> None: 74 | self.response = response 75 | self.data = data 76 | 77 | if isinstance(data, dict): 78 | try: 79 | data = data["message"] 80 | except KeyError: 81 | pass 82 | 83 | super().__init__(data) 84 | 85 | class HTTPBadRequest(HTTPException): 86 | r""":exc:`HTTPException` indicating a ``400 Bad Request`` response.""" 87 | 88 | class HTTPForbidden(HTTPException): 89 | r""":exc:`HTTPException` indicating a ``403 Forbidden`` response.""" 90 | 91 | class HTTPNotFound(HTTPException): 92 | r""":exc:`HTTPException` indicating a ``404 Not Found`` response.""" 93 | 94 | class HTTPServerError(HTTPException): 95 | r""":exc:`HTTPException` indicating a 500s response.""" 96 | 97 | class ShardException(QordException): 98 | r"""Base class for all shards related errors. 99 | 100 | Attributes 101 | ---------- 102 | shard: :class:`Shard` 103 | The shard that caused the error. 104 | """ 105 | def __init__(self, shard: Shard, *args: object) -> None: 106 | self.shard = shard 107 | super().__init__(*args) 108 | 109 | class ShardCloseException(ShardException): 110 | r"""An exception indicating that a shard closed with an unhandleable close code. 111 | 112 | This inherits :exc:`ShardException`. 113 | 114 | Attributes 115 | ---------- 116 | code: :class:`builtins.int` 117 | The close code that caused the error. 118 | """ 119 | 120 | def __init__(self, shard: Shard, code: int, *args) -> None: 121 | self.code = code 122 | super().__init__(shard, *args) 123 | 124 | class MissingPrivilegedIntents(ShardCloseException): 125 | r"""An exception indicating that a shard closed because client has requested 126 | access to certain *privileged* intents that are not provided by Discord. 127 | 128 | This inherits :exc:`ShardCloseException`, The :attr:`~ShardCloseException.code` 129 | attribute is always ``4014`` when this error is raised. 130 | """ 131 | def __init__(self, shard: Shard) -> None: 132 | super().__init__( 133 | shard, 4014, 134 | "Client is not whitelisted to use requested privileged intents.", 135 | ) 136 | -------------------------------------------------------------------------------- /qord/flags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/izxxr/qord-legacy/6d92751efc76b1f0bbcc350eff30369cb5a1c51a/qord/flags/__init__.py -------------------------------------------------------------------------------- /qord/flags/applications.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from qord.flags.base import Flags 26 | 27 | 28 | __all__ = ( 29 | "ApplicationFlags", 30 | ) 31 | 32 | 33 | class ApplicationFlags(Flags): 34 | """:class:`Flags` subclass that details the flags of a :class:`Application`. This is mostly 35 | obtained using :attr:`Application.flags` or :attr:`Client.application_flags` attribute. 36 | 37 | The application flags detail the features of an application. 38 | """ 39 | 40 | gateway_presence = 1 << 12 41 | """Whether the application has gateway presence intent.""" 42 | 43 | gateway_presence_limited = 1 << 13 44 | """Whether the application requires whitelisting to use gateway presence intent.""" 45 | 46 | gateway_members = 1 << 14 47 | """Whether the application has gateway guild members intent.""" 48 | 49 | gateway_members_limited = 1 << 15 50 | """Whether the application requires whitelisting to use gateway members intents.""" 51 | 52 | verification_pending_guild_limit = 1 << 16 53 | """Indicates unusual growth of an app that prevents verification.""" 54 | 55 | embedded = 1 << 17 56 | """Whether the application is embedded application in Discord client.""" 57 | 58 | gateway_message_content = 1 << 18 59 | """Whether the application has gateway guild message content intent.""" 60 | 61 | gateway_message_content_limited = 1 << 19 62 | """Whether the application requires whitelisting to use gateway message content intents.""" 63 | -------------------------------------------------------------------------------- /qord/flags/base.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | import typing 26 | 27 | # Thanks to discord.py for being a good base design for Flags. 28 | # This is inspired by discord.py 29 | 30 | __all__ = ( 31 | "Flags", 32 | ) 33 | 34 | 35 | class Flags: 36 | r"""A class that interfaces manipulating bitwise flags. 37 | 38 | This class provides a user friendly way of interacting with bitwise values 39 | returned by Discord. The most common example is :class:`Permissions`. 40 | 41 | This class is documented for allowing users to create custom flags classes. The 42 | way this class works can be described by the example below:: 43 | 44 | class MyFlags(qord.Flags): 45 | foo = 1 << 0 46 | bar = 1 << 2 47 | baz = 1 << 3 48 | bac = 1 << 4 49 | 50 | >>> flags = MyFlags(foo=True, bar=False) 51 | >>> flags.foo 52 | True 53 | >>> flags.bar 54 | False 55 | >>> flags.value 56 | 5 57 | >>> flags = MyFlags(5) 58 | >>> flags.foo 59 | True 60 | >>> flags.bar 61 | False 62 | >>> flags.bar = False 63 | >>> flags.value 64 | 1 65 | 66 | When initializing, Either a bitwise value can be passed as first positional argument 67 | or flags can be toggled using :class:`builtins.bool`. Accessing a flag from 68 | non-initialized flags class returns it's raw value. 69 | 70 | .. tip:: 71 | This class also supports comparison with other :class:`Flags` instances 72 | as well as :class:`~builtins.int` casting. On iterating, Yields the flag 73 | name and it's toggle as :class:`~builtins.bool`. 74 | 75 | .. note:: 76 | The parameters documented below are passed during subclassing this class. 77 | 78 | Parameters 79 | ---------- 80 | ignore_extraneous: :class:`builtins.bool` 81 | Whether to ignore extra flags passed during initalization and not 82 | raise :exc:`TypeError`. Defaults to ``False``. 83 | 84 | Attributes 85 | ---------- 86 | value: :class:`builtins.int` 87 | The raw flags value. 88 | """ 89 | __name_value_map__: typing.Dict[str, int] 90 | __flags_settings__: typing.Dict[str, typing.Any] 91 | 92 | 93 | def __init__(self, value: int = 0, **flags: bool) -> None: 94 | self.value = value 95 | 96 | ignore_extraneous = self.__flags_settings__.get("ignore_extraneous", False) 97 | 98 | for flag, toggle in flags.items(): 99 | if not flag in self.__name_value_map__ and not ignore_extraneous: 100 | raise TypeError(f"{flag} is not a valid flag for {self.__class__.__name__}()") 101 | 102 | self._apply(flag, toggle) 103 | 104 | def _apply(self, flag: str, toggle: bool): 105 | value = self.__name_value_map__[flag] 106 | 107 | if toggle is True: 108 | self.value |= value 109 | elif toggle is False: 110 | self.value &= ~value 111 | else: 112 | raise TypeError(f"{flag} value must be a bool, Not {toggle.__class__!r}") 113 | 114 | def _has(self, flag: str) -> bool: 115 | value = self.__name_value_map__[flag] 116 | return (self.value & value) > 0 117 | 118 | def __init_subclass__(cls, ignore_extraneous: bool = False) -> None: 119 | nv_map = {} 120 | 121 | for name, value in vars(cls).items(): 122 | if name.startswith("_") or not isinstance(value, int): 123 | continue 124 | 125 | nv_map[name] = value 126 | setattr(cls, name, _Flag(name, value)) 127 | 128 | cls.__name_value_map__ = nv_map 129 | cls.__flags_settings__ = {"ignore_extraneous": ignore_extraneous} 130 | 131 | def __int__(self) -> int: 132 | return self.value 133 | 134 | def __iter__(self) -> typing.Iterator[typing.Tuple[str, bool]]: 135 | for name in self.__name_value_map__: 136 | yield name, getattr(self, name) 137 | 138 | def __repr__(self) -> str: 139 | flags = list(self.__name_value_map__.keys()) 140 | ret = f"{self.__class__.__name__}(%s)" 141 | return ret % (", ".join(f"{k}={self._has(k)}" for k in flags)) 142 | 143 | __eq__ = lambda self, other: isinstance(other, self.__class__) and self.value == other.value # type: ignore 144 | __ne__ = lambda self, other: isinstance(other, self.__class__) and self.value != other.value # type: ignore 145 | __lt__ = lambda self, other: isinstance(other, self.__class__) and self.value < other.value 146 | __le__ = lambda self, other: isinstance(other, self.__class__) and self.value <= other.value 147 | __gt__ = lambda self, other: isinstance(other, self.__class__) and self.value > other.value 148 | __ge__ = lambda self, other: isinstance(other, self.__class__) and self.value >= other.value 149 | 150 | class _Flag: 151 | def __init__(self, name: str, value: int) -> None: 152 | self.name = name 153 | self.value = value 154 | 155 | def __get__(self, instance: typing.Optional[Flags], owner: typing.Type[Flags]) -> typing.Union[int, bool]: 156 | if instance is None: 157 | return self.value 158 | 159 | return (instance.value & self.value > 0) 160 | 161 | def __set__(self, instance: typing.Optional[Flags], toggle: bool) -> None: 162 | if instance is None: 163 | raise AttributeError("Cannot set this attribute on non-instansiated Flags class.") 164 | 165 | instance._apply(self.name, toggle) 166 | -------------------------------------------------------------------------------- /qord/flags/intents.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from qord.flags.base import Flags 26 | 27 | 28 | __all__ = ( 29 | "Intents", 30 | ) 31 | 32 | 33 | class Intents(Flags): 34 | """:class:`Flags` subclass that details the gateway intents. 35 | 36 | Gateway intents allow you to toggle specific gateway events if whether 37 | you want to receive them or not. This also affects the caching for relevant 38 | entity. Gateway intents are useful to disable events that you don't need for 39 | your bot and decrease the workload. For example, if your bot doesn't need 40 | direct messages events, you can set :attr:`.direct_messages` to ``False``. 41 | 42 | Some intents are marked as privileged. These intents are required to be 43 | enabled explicitly from the bot's application page on Discord Developers Portal. 44 | If the bot is in more then 100 servers, These intents require verification 45 | and whitelisting. 46 | 47 | Attempting to enable these intents without enabling them from Developers Portal 48 | will cause the bot to terminate with :exc:`MissingPrivilegedIntents` error. 49 | 50 | Current privileged intents are: 51 | 52 | - :attr:`.members` 53 | - :attr:`.presences` 54 | - :attr:`.message_content` 55 | """ 56 | 57 | guilds = 1 << 0 58 | """Whether to enable guild events and caching. 59 | 60 | This intent is generally required by most bots and disabling it will 61 | cause most of functionality of library to be disabled. Only disable this 62 | when your bot is completely DMs or interactions based. 63 | """ 64 | 65 | members = 1 << 1 66 | """Whether to enable events and caching for guild members. This 67 | also controls most of bot's user caching. 68 | 69 | This is a privileged intent, See :class:`Intents` documentation. 70 | """ 71 | 72 | bans = 1 << 2 73 | """Whether to enable events for guild bans.""" 74 | 75 | emojis_and_stickers = 1 << 3 76 | """Whether to enable events for guild stickers and emojis.""" 77 | 78 | integrations = 1 << 4 79 | """Whether to enable events for guild integrations.""" 80 | 81 | webhooks = 1 << 5 82 | """Whether to enable events for webhooks.""" 83 | 84 | invites = 1 << 6 85 | """Whether to enable events for invites.""" 86 | 87 | voice_states = 1 << 7 88 | """Whether to enable events for voice state updates.""" 89 | 90 | presences = 1 << 8 91 | """Whether to enable events and for presences. 92 | 93 | This is a privileged intent, See :class:`Intents` documentation. 94 | """ 95 | 96 | guild_messages = 1 << 9 97 | """Whether to enable events and caching for guild messages.""" 98 | 99 | guild_message_reactions = 1 << 10 100 | """Whether to enable events and caching for reactions on guild messages.""" 101 | 102 | guild_message_typing = 1 << 11 103 | """Whether to enable events for message typing in guilds.""" 104 | 105 | direct_messages = 1 << 12 106 | """Whether to enable events and caching for direct messages.""" 107 | 108 | direct_message_reactions = 1 << 13 109 | """Whether to enable events and caching for reactions on direct messages.""" 110 | 111 | direct_message_typing = 1 << 14 112 | """Whether to enable events for message typing in DMs.""" 113 | 114 | message_content = 1 << 15 115 | """Whether the bot can receive message content on message objects. 116 | 117 | This is a privileged intent, See :class:`Intents` documentation. 118 | """ 119 | 120 | scheduled_events = 1 << 16 121 | """Whether to enable events and caching for guild scheduled events.""" 122 | 123 | @classmethod 124 | def all(cls) -> Intents: 125 | """Returns the :class:`Intents` with all intents *including* privileged enabled.""" 126 | return cls(sum(cls.__name_value_map__.values())) 127 | 128 | @classmethod 129 | def unprivileged(cls) -> Intents: 130 | """Returns the :class:`Intents` with all intents *excluding* privileged enabled.""" 131 | ret = cls.all() 132 | ret.members = False 133 | ret.presences = False 134 | ret.message_content = False 135 | return ret 136 | -------------------------------------------------------------------------------- /qord/flags/messages.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from qord.flags.base import Flags 26 | 27 | 28 | __all__ = ( 29 | "MessageFlags", 30 | ) 31 | 32 | 33 | class MessageFlags(Flags): 34 | """:class:`Flags` subclass that details the flags of a :class:`Message`. 35 | 36 | This is mostly obtained using :attr:`Message.flags` attribute. 37 | """ 38 | 39 | crossposted = 1 << 0 40 | """The message is crossposted to following channels.""" 41 | 42 | is_crosspost = 1 << 1 43 | """The message is a crosspost from another channel.""" 44 | 45 | suppress_embeds = 1 << 2 46 | """The message does not include any embeds.""" 47 | 48 | source_message_deleted = 1 << 3 49 | """The source message for a crosspost message is deleted.""" 50 | 51 | urgent = 1 << 4 52 | """The message is an urgent message from system.""" 53 | 54 | has_thread = 1 << 5 55 | """The message has a thread associated to tit.""" 56 | 57 | ephemeral = 1 << 6 58 | """The message is an ephemeral message, in response to interaction.""" 59 | 60 | loading = 1 << 7 61 | """The message is an interaction response and application is in "Thinking" state.""" 62 | 63 | thread_role_mention_failed = 1 << 8 64 | """This message failed to mention some roles and add their members in a thread.""" 65 | -------------------------------------------------------------------------------- /qord/flags/permissions.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from qord.flags.base import Flags 26 | 27 | 28 | __all__ = ( 29 | "Permissions", 30 | ) 31 | 32 | 33 | class Permissions(Flags): 34 | r"""A class that provides rich interface for manipulating permissions bitwise value. 35 | 36 | This class subclasses :class:`Flags`. See it's documentation for more info about 37 | using this class for working with permissions. 38 | """ 39 | 40 | create_instant_invite = 1 << 0 41 | r"""Allows creating instant guild or channel invites.""" 42 | 43 | kick_members = 1 << 1 44 | r"""Allows kicking other members from a guild.""" 45 | 46 | ban_members = 1 << 2 47 | r"""Allows banning other members from a guild.""" 48 | 49 | administrator = 1 << 3 50 | r"""Bypasses all permissions. 51 | 52 | Members with this permission enabled have all permissions enabled 53 | and they bypass all permission overwrites. 54 | """ 55 | 56 | manage_channels = 1 << 4 57 | r"""Allows management of the guild channels.""" 58 | 59 | manage_guild = 1 << 5 60 | r"""Allows management of the guild settings including adding bots.""" 61 | 62 | add_reactions = 1 << 6 63 | r"""Allows the addition of reactions on messages.""" 64 | 65 | view_audit_log = 1 << 7 66 | r"""Allows viewing of the guild's audit log.""" 67 | 68 | priority_speaker = 1 << 8 69 | r"""Allows usage of priority speaker in a guild voice channel.""" 70 | 71 | stream = 1 << 9 72 | r"""Allows live streaming in a voice channel.""" 73 | 74 | view_channel = 1 << 10 75 | r"""Allows viewing guild channel.""" 76 | 77 | send_messages = 1 << 11 78 | r"""Allows to send messages in text channels.""" 79 | 80 | send_tts_messages = 1 << 12 81 | r"""Allows the users to send TTS messages through ``/tts`` command.""" 82 | 83 | manage_messages = 1 << 13 84 | r"""Allows management of messages.""" 85 | 86 | embed_links = 1 << 14 87 | r"""Allows usage of embedded links in the messages.""" 88 | 89 | attach_files = 1 << 15 90 | r"""Allows attaching files to the messages.""" 91 | 92 | read_message_history = 1 << 16 93 | r"""Allows reading a text channel's message history.""" 94 | 95 | mention_everyone = 1 << 17 96 | r"""Allows mentioning the @everyone and @here roles.""" 97 | 98 | use_external_emojis = 1 << 18 99 | r"""Allows usage of emojis from other guilds.""" 100 | 101 | view_guild_insights = 1 << 19 102 | r"""Allows viewing the guild's insights data.""" 103 | 104 | connect = 1 << 20 105 | r"""Allows joining a voice or stage channel.""" 106 | 107 | speak = 1 << 21 108 | r"""Allows speaking in a voice channel.""" 109 | 110 | mute_members = 1 << 22 111 | r"""Allows muting members in a voice channel.""" 112 | 113 | deafen_members = 1 << 23 114 | r"""Allows deafening members in a voice channel.""" 115 | 116 | move_members = 1 << 24 117 | r"""Allows moving and removing members from a voice channel.""" 118 | 119 | use_vad = 1 << 25 120 | r"""Allows usage of voice activity detection in a voice channel.""" 121 | 122 | change_nickname = 1 << 26 123 | r"""Allows changing own username in the guild.""" 124 | 125 | manage_nicknames = 1 << 27 126 | r"""Allows changing other members nickname in a guild.""" 127 | 128 | manage_roles = 1 << 28 129 | r"""Allows management of guild roles.""" 130 | 131 | manage_permissions = manage_roles 132 | r"""An alias for :attr:`.manage_roles`.""" 133 | 134 | manage_webhooks = 1 << 29 135 | r"""Allows management of guild webhooks.""" 136 | 137 | manage_emojis_and_stickers = 1 << 30 138 | r"""Allows management of emojis and stickers of a guild.""" 139 | 140 | use_application_commands = 1 << 31 141 | r"""Allows usage of application commands in a guild.""" 142 | 143 | request_to_speak = 1 << 32 144 | r"""Allows requesting to speak in a stage channel.""" 145 | 146 | manage_events = 1 << 33 147 | r"""Allows management of guild scheduled events.""" 148 | 149 | manage_threads = 1 << 34 150 | r"""Allows management of threads.""" 151 | 152 | create_public_threads = 1 << 35 153 | r"""Allows creation of public or news threads.""" 154 | 155 | create_private_threads = 1 << 36 156 | r"""Allows creation of private threads.""" 157 | 158 | use_external_stickers = 1 << 37 159 | r"""Allows usage of external stickers.""" 160 | 161 | send_messages_in_threads = 1 << 38 162 | r"""Allows sending of messages in therads.""" 163 | 164 | start_embedded_activities = 1 << 39 165 | r"""Allows starting embedded activities in a voice channel.""" 166 | 167 | moderate_members = 1 << 40 168 | r"""Allows moderating members including managing members timeout.""" 169 | 170 | @classmethod 171 | def all(cls) -> Permissions: 172 | """Creates a :class:`Permissions` instance with all permissions enabled.""" 173 | return cls(2199023255551) 174 | -------------------------------------------------------------------------------- /qord/flags/system_channel.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from qord.flags.base import Flags 26 | 27 | 28 | __all__ = ( 29 | "SystemChannelFlags", 30 | ) 31 | 32 | 33 | class SystemChannelFlags(Flags): 34 | r""":class:`Flags` subclass that details the flags for a guild's system channel.""" 35 | 36 | suppress_join_notifications = 1 << 0 37 | r"""Whether system channel will not receive a random message when a member joins.""" 38 | 39 | suppress_premium_subscriptions = 1 << 1 40 | r"""Whether system channel will not receive a notification when someone boosts the guild.""" 41 | 42 | suppress_guild_reminders = 1 << 2 43 | r"""Whether system channel will not receive tips for setting up guilds.""" 44 | 45 | suppress_join_notification_replies = 1 << 3 46 | r"""Whether messages sent on member join in system channel allow replying with stickers.""" 47 | -------------------------------------------------------------------------------- /qord/flags/users.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from qord.flags.base import Flags 26 | 27 | 28 | __all__ = ( 29 | "UserFlags", 30 | ) 31 | 32 | 33 | class UserFlags(Flags): 34 | r""":class:`Flags` subclass that details the flags of a :class:`User`. This is mostly 35 | obtained using :attr:`User.flags` attribute. 36 | 37 | A user's flags include several things including the "badges" on the 38 | user's account etc. 39 | """ 40 | 41 | staff = 1 << 0 42 | r"""User is a Discord employee/staff.""" 43 | 44 | partner = 1 << 1 45 | r"""User is a owner of a partnered guild.""" 46 | 47 | hypesquad = 1 << 2 48 | r"""User is a HypeSquad's events coordinator.""" 49 | 50 | bug_hunter_level_1 = 1 << 3 51 | r"""User is a level 1 bug hunter.""" 52 | 53 | hypesquad_bravery = 1 << 6 54 | r"""User is member of HypeSquad bravey house.""" 55 | 56 | hypesquad_brilliance = 1 << 7 57 | r"""User is member of HypeSquad brilliance house.""" 58 | 59 | hypesquad_brilliance = 1 << 8 60 | r"""User is member of HypeSquad balance house.""" 61 | 62 | early_premium_supporter = 1 << 9 63 | r"""User is an early nitro supporter.""" 64 | 65 | team = 1 << 10 66 | r"""User is a psuedo user, representing a team.""" 67 | 68 | bug_hunter_level_2 = 1 << 14 69 | r"""User is a bug hunter of level 2.""" 70 | 71 | verified_bot = 1 << 16 72 | r"""User is a verified bot.""" 73 | 74 | verified_developer = 1 << 17 75 | r"""User is a "early" verified bot developer.""" 76 | 77 | certified_moderator = 1 << 18 78 | r"""User is a certified Discord moderator.""" 79 | 80 | bot_http_interactions = 1 << 19 81 | r"""The user (bot) only uses HTTPs for interactions.""" 82 | -------------------------------------------------------------------------------- /qord/internal/__init__.py: -------------------------------------------------------------------------------- 1 | """Non-public internal utilities used across the library. 2 | 3 | The classes and functions under this module are not meant to be touched by users. 4 | """ 5 | 6 | __all__ = () 7 | -------------------------------------------------------------------------------- /qord/internal/context_managers.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | 24 | from __future__ import annotations 25 | 26 | import asyncio 27 | import typing 28 | 29 | if typing.TYPE_CHECKING: 30 | from qord.bases import BaseMessageChannel 31 | 32 | 33 | class TypingContextManager: 34 | def __init__(self, channel: BaseMessageChannel) -> None: 35 | self.channel = channel 36 | self._typing = False 37 | self._task = None 38 | 39 | async def _typing_task(self) -> None: 40 | while self._typing: 41 | await self.channel.trigger_typing() 42 | # The typing indicator disappears after 9-10 seconds 43 | await asyncio.sleep(8) 44 | 45 | async def __aenter__(self) -> None: 46 | coro = self._typing_task() 47 | self._typing = True 48 | self._task = asyncio.create_task(coro) 49 | 50 | async def __aexit__(self, *args) -> None: 51 | self._typing = False 52 | task = self._task 53 | 54 | if task and not task.cancelled(): 55 | task.cancel() 56 | -------------------------------------------------------------------------------- /qord/internal/helpers.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | 24 | from __future__ import annotations 25 | 26 | from qord.internal.undefined import UNDEFINED 27 | 28 | from datetime import datetime, timezone 29 | from base64 import b64encode 30 | import typing 31 | 32 | 33 | __all__ = ( 34 | "BASE_CDN_URL", 35 | "BASIC_STATIC_EXTS", 36 | "BASIC_EXTS", 37 | "create_cdn_url", 38 | "get_optional_snowflake", 39 | "compute_shard_id", 40 | "get_image_data", 41 | "parse_iso_timestamp", 42 | ) 43 | 44 | 45 | BASE_CDN_URL = "https://cdn.discordapp.com" 46 | BASIC_STATIC_EXTS = ["png", "jpg", "jpeg", "webp"] 47 | BASIC_EXTS = ["png", "jpg", "jpeg", "webp", "gif"] 48 | 49 | def create_cdn_url(path: str, extension: str, size: int = UNDEFINED, valid_exts: typing.List[str] = UNDEFINED): 50 | """Create a CDN URL with provided path, file extension and size.""" 51 | 52 | if valid_exts is None: 53 | # Defaulting to general formats used on most endpoints which 54 | # are currently png, jpg, webp. 55 | # When using with endpoints that have special formats 56 | # consider passing the valid formats explicitly. 57 | valid_exts = BASIC_STATIC_EXTS 58 | 59 | if not extension.lower() in valid_exts: 60 | raise ValueError(f"Invalid image extension {extension!r}, Expected one of {', '.join(valid_exts)}") 61 | 62 | ret = f"{BASE_CDN_URL}{path}.{extension}" 63 | 64 | if size is not UNDEFINED: 65 | if size < 64 and size > 4096: 66 | raise ValueError("size must be between 64 and 4096. Got %s instead." % size) 67 | if not (size & (size-1) == 0) and (size != 0 and size-1 != 0): 68 | raise ValueError("size must be a power of 2 between 64 and 4096, %s is invalid." % size) 69 | 70 | return f"{ret}?size={size}" 71 | 72 | return ret 73 | 74 | def get_optional_snowflake(data: typing.Dict[str, typing.Any], key: str) -> typing.Optional[int]: 75 | """Helper to obtain optional or nullable snowflakes from a raw payload.""" 76 | try: 77 | return int(data[key]) 78 | except (KeyError, ValueError, TypeError): 79 | return None 80 | 81 | def compute_shard_id(guild_id: int, shards_count: int) -> int: 82 | """Computes shard ID for the provided guild ID with respect to given shards count.""" 83 | return (guild_id >> 22) % shards_count 84 | 85 | def get_image_data(img_bytes: bytes) -> str: 86 | """Gets Data URI format for provided image bytes.""" 87 | 88 | if img_bytes.startswith(b"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"): 89 | content_type = "image/png" 90 | elif img_bytes[0:3] == b"\xff\xd8\xff" or img_bytes[6:10] in (b"JFIF", b"Exif"): 91 | content_type = "image/jpeg" 92 | elif img_bytes.startswith((b"\x47\x49\x46\x38\x37\x61", b"\x47\x49\x46\x38\x39\x61")): 93 | content_type = "image/gif" 94 | elif img_bytes.startswith(b"RIFF") and img_bytes[8:12] == b"WEBP": 95 | content_type = "image/webp" 96 | else: 97 | raise TypeError("Invalid image type was provided.") 98 | 99 | return f"data:{content_type};base64,{b64encode(img_bytes).decode('ascii')}" 100 | 101 | def parse_iso_timestamp(timestamp: str) -> datetime: 102 | """Parse ISO timestamp string to a datetime.datetime instance.""" 103 | return datetime.fromisoformat(timestamp) 104 | 105 | def compute_creation_time(snowflake: int) -> datetime: 106 | """Computes the creation time of the given snowflake as UTC timezone aware datetime.""" 107 | timestamp = ((snowflake >> 22) + 1420070400000) / 1000 108 | return datetime.fromtimestamp(timestamp, tz=timezone.utc) 109 | 110 | def compute_snowflake(time: datetime) -> int: 111 | """Computes the snowflake from given timestamp or datetime object.""" 112 | return int(time.timestamp() * 1000 - 1420070400000) << 22 113 | -------------------------------------------------------------------------------- /qord/internal/mixins.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from qord.internal.helpers import compute_creation_time 26 | 27 | import typing 28 | 29 | if typing.TYPE_CHECKING: 30 | from datetime import datetime 31 | 32 | 33 | __all__ = ( 34 | "Comparable", 35 | ) 36 | 37 | 38 | class Comparable: 39 | __slots__ = () 40 | 41 | id: int 42 | 43 | def __eq__(self, other: typing.Any) -> bool: 44 | return isinstance(other, self.__class__) and other.id == self.id 45 | 46 | 47 | class CreationTime: 48 | __slots__ = () 49 | 50 | id: int 51 | 52 | @property 53 | def created_at(self) -> datetime: 54 | """The time when this entity was created. 55 | 56 | Returns 57 | ------- 58 | :class:`datetime.datetime` 59 | UTC aware datetime object representing the creation time. 60 | """ 61 | return compute_creation_time(self.id) 62 | -------------------------------------------------------------------------------- /qord/internal/types.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | import typing 26 | 27 | if typing.TYPE_CHECKING: 28 | from qord.models.channels import ( 29 | TextChannel, 30 | DMChannel, 31 | VoiceChannel, 32 | ) 33 | 34 | 35 | MessageChannelT = typing.Union["TextChannel", "DMChannel", "VoiceChannel"] 36 | -------------------------------------------------------------------------------- /qord/internal/undefined.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | import typing 26 | 27 | __all__ = ( 28 | "UNDEFINED", 29 | ) 30 | 31 | 32 | class _Undefined: 33 | def __bool__(self) -> bool: 34 | return False 35 | 36 | def __eq__(self, o: object) -> bool: 37 | return False 38 | 39 | def __repr__(self) -> str: 40 | return "..." 41 | 42 | UNDEFINED: typing.Any = _Undefined() 43 | """A sentinel used at places where None is ambiguous""" -------------------------------------------------------------------------------- /qord/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/izxxr/qord-legacy/6d92751efc76b1f0bbcc350eff30369cb5a1c51a/qord/models/__init__.py -------------------------------------------------------------------------------- /qord/models/base.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from abc import ABC, abstractmethod 26 | import typing 27 | 28 | if typing.TYPE_CHECKING: 29 | from qord.core.client import Client 30 | 31 | 32 | __all__ = ( 33 | "BaseModel", 34 | ) 35 | 36 | 37 | class BaseModel(ABC): 38 | r"""Common base class for all other Discord models. 39 | 40 | It is important to note that most of Discord models are not meant 41 | to be initialized by the user. You should either obtain them by using 42 | relevant HTTPs methods or from cache when available. 43 | """ 44 | __slots__ = () 45 | 46 | _client: Client 47 | 48 | @property 49 | def client(self) -> Client: 50 | r"""The :class:`Client` that had instansiated this model.""" 51 | return self._client 52 | 53 | @abstractmethod 54 | def _update_with_data(self, data: typing.Dict[str, typing.Any]) -> None: 55 | raise NotImplementedError 56 | -------------------------------------------------------------------------------- /qord/models/stage_instances.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from __future__ import annotations 24 | 25 | from qord.models.base import BaseModel 26 | from qord.internal.mixins import Comparable, CreationTime 27 | from qord.internal.undefined import UNDEFINED 28 | from qord.internal.helpers import get_optional_snowflake 29 | 30 | import typing 31 | 32 | if typing.TYPE_CHECKING: 33 | from qord.models.guilds import Guild 34 | from qord.models.channels import StageChannel 35 | from qord.models.scheduled_events import ScheduledEvent 36 | 37 | 38 | __all__ = ( 39 | "StageInstance", 40 | ) 41 | 42 | 43 | class StageInstance(BaseModel, Comparable, CreationTime): 44 | """Represents a live stage instace from a :class:`StageChannel`. 45 | 46 | |supports-comparison| 47 | 48 | Attributes 49 | ---------- 50 | guild: :class:`Guild` 51 | The guild that the stage instance is live in. 52 | id: :class:`builtins.int` 53 | The ID of stage instance. 54 | channel_id: :class:`builtins.int` 55 | The ID of channel that the stage instance is live in. 56 | privacy_level: :class:`builtins.int` 57 | The privacy level of the stage instance, see :class:`StagePrivacyLevel` for 58 | all possible values for this attribute. 59 | topic: :class:`builtins.str` 60 | The topic of stage instance. 61 | scheduled_event_id: Optional[:class:`builtins.int`] 62 | The scheduled event's ID that is associated to the stage instance, if any. 63 | """ 64 | 65 | if typing.TYPE_CHECKING: 66 | id: int 67 | channel_id: int 68 | guild_id: int 69 | privacy_level: int 70 | topic: str 71 | scheduled_event_id: typing.Optional[int] 72 | 73 | __slots__ = ( 74 | "_client", 75 | "guild", 76 | "id", 77 | "channel_id", 78 | "guild_id", 79 | "topic", 80 | "privacy_level", 81 | "scheduled_event_id", 82 | ) 83 | 84 | def __init__(self, data: typing.Dict[str, typing.Any], guild: Guild) -> None: 85 | self.guild = guild 86 | self._client = guild._client 87 | self._update_with_data(data) 88 | 89 | def _update_with_data(self, data: typing.Dict[str, typing.Any]) -> None: 90 | self.id = int(data["id"]) 91 | self.channel_id = int(data["channel_id"]) 92 | self.guild_id = data.get("guild_id", self.guild.id) 93 | self.topic = data.get("topic", "") 94 | self.privacy_level = data.get("privacy_level", 2) 95 | self.scheduled_event_id = get_optional_snowflake(data, "guild_scheduled_event_id") 96 | 97 | @property 98 | def channel(self) -> typing.Optional[StageChannel]: 99 | """Returns the stage channel associated to this stage instance. 100 | 101 | Returns 102 | ------- 103 | Optional[:class:`StageChannel`] 104 | """ 105 | # This shall always return StageChannel 106 | return self.guild._cache.get_channel(self.channel_id) # type: ignore 107 | 108 | @property 109 | def scheduled_event(self) -> typing.Optional[ScheduledEvent]: 110 | """Returns the scheduled event associated to the stage instance, if any. 111 | 112 | Returns 113 | ------- 114 | Optional[:class:`ScheduledEvent`] 115 | """ 116 | event_id = self.scheduled_event_id 117 | 118 | if event_id is None: 119 | return None 120 | 121 | return self.guild._cache.get_scheduled_event(event_id) 122 | 123 | async def delete(self, *, reason: typing.Optional[str] = None) -> None: 124 | """Deletes the stage instance. 125 | 126 | This operation requires the bot to be stage moderator, i.e has following 127 | permissions in the stage channel: 128 | 129 | - :attr:`~Permissions.manage_channels` 130 | - :attr:`~Permissions.move_members` 131 | - :attr:`~Permissions.mute_members` 132 | 133 | Parameters 134 | ---------- 135 | reason: :class:`builtins.str` 136 | The reason for doing this action. 137 | 138 | Raises 139 | ------ 140 | HTTPForbidden 141 | You are not allowed to do this. 142 | HTTPException 143 | The operation failed. 144 | """ 145 | await self._client._rest.delete_stage_instance( 146 | channel_id=self.channel_id, 147 | reason=reason, 148 | ) 149 | 150 | async def edit( 151 | self, 152 | topic: str = UNDEFINED, 153 | privacy_level: int = UNDEFINED, 154 | reason: typing.Optional[str] = None, 155 | ) -> None: 156 | """Edits the stage instance. 157 | 158 | This operation requires the bot to be stage moderator, i.e has following 159 | permissions in the stage channel: 160 | 161 | - :attr:`~Permissions.manage_channels` 162 | - :attr:`~Permissions.move_members` 163 | - :attr:`~Permissions.mute_members` 164 | 165 | Parameters 166 | ---------- 167 | topic: :class:`builtins.str` 168 | The topic of this tage instance. 169 | privacy_level: :class:`builtins.int` 170 | The privacy level of stage instance, see :class:`StagePrivacyLevel` for 171 | all possible values. 172 | reason: :class:`builtins.str` 173 | The reason for doing this action. 174 | 175 | Raises 176 | ------ 177 | HTTPForbidden 178 | You are not allowed to do this. 179 | HTTPException 180 | The operation failed. 181 | """ 182 | json = {} 183 | 184 | if topic is not UNDEFINED: 185 | json["topic"] = topic 186 | 187 | if privacy_level is not UNDEFINED: 188 | json["privacy_level"] = privacy_level 189 | 190 | if json: 191 | data = await self._client._rest.edit_stage_instance( 192 | channel_id=self.channel_id, 193 | json=json, 194 | reason=reason, 195 | ) 196 | self._update_with_data(data) 197 | -------------------------------------------------------------------------------- /qord/project_info.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | 24 | __version__ = "0.5.0a1" 25 | __license__ = "MIT" 26 | __notice__ = "Copyright (c) nerdguyahmad 2022-present. Under the MIT license." 27 | __author__ = "nerdguyahmad " 28 | __github__ = "https://github.com/nerdguyahmad/qord" -------------------------------------------------------------------------------- /qord/utils.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Izhar Ahmad 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | """ 24 | qord.utils 25 | ~~~~~~~~~~ 26 | 27 | General utilities to aid in specific common tasks. 28 | """ 29 | 30 | from __future__ import annotations 31 | 32 | from datetime import datetime 33 | import typing 34 | 35 | 36 | __all__ = ( 37 | "create_timestamp", 38 | ) 39 | 40 | 41 | def create_timestamp( 42 | time: typing.Optional[typing.Union[datetime, int, float]] = None, 43 | style: typing.Optional[str] = None, 44 | ) -> str: 45 | 46 | """Creates a markdown timestamp from the given datetime object or unix timestamp. 47 | 48 | Parameters 49 | ---------- 50 | time: Optional[Union[:class:`datetime.datetime`, :class:`builtins.int`, :class:`builtins.float`]] 51 | The timestamp to use. If not given, The result of :meth:`datetime.datetime.now` is used. 52 | If a datetime object is given, The epoch timestamp would be extracted from it. If 53 | a float is given, It would be rounded of. 54 | style: :class:`builtins.str` 55 | The style for the timestamp. If not provided, The default style 56 | is used, See :class:`TimestampStyle` for all possible values. 57 | 58 | .. note:: 59 | This parameter is not validated by the library in case Discord 60 | adds a new style. You should consider validating it yourself. 61 | 62 | Returns 63 | ------- 64 | :class:`builtins.str` 65 | The created timestamp in proper format. 66 | """ 67 | if time is None: 68 | time = round(datetime.now().timestamp()) 69 | elif isinstance(time, datetime): 70 | time = round(time.timestamp()) 71 | elif isinstance(time, float): 72 | time = round(time) 73 | 74 | if style is None: 75 | return f"" 76 | else: 77 | return f"" 78 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp==3.8.1 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | with open("README.MD", "r", encoding="utf-8") as f: 4 | LONG_DESCRIPTION = f.read() 5 | 6 | VERSION = "0.5.0a1" 7 | GITHUB = "https://github.com/izxxr/qord" 8 | DOCUMENTATION = "https://qord.readthedocs.io" 9 | LICENSE = "MIT" 10 | 11 | with open("requirements.txt", "r") as f: 12 | REQUIREMENTS = f.readlines() 13 | 14 | while "\n" in REQUIREMENTS: 15 | REQUIREMENTS.remove("\n") 16 | 17 | PACKAGES = [ 18 | "qord", 19 | "qord.core", 20 | "qord.events", 21 | "qord.flags", 22 | "qord.models", 23 | "qord.internal", 24 | "qord.dataclasses", 25 | ] 26 | 27 | setup( 28 | name="qord", 29 | author="izxxr", 30 | version=VERSION, 31 | license=LICENSE, 32 | url=GITHUB, 33 | project_urls={ 34 | "Documentation": DOCUMENTATION, 35 | "Issue tracker": GITHUB + "/issues", 36 | }, 37 | description='Python library for Discord API based around asyncio.', 38 | long_description=LONG_DESCRIPTION, 39 | long_description_content_type="text/markdown", 40 | include_package_data=True, 41 | install_requires=REQUIREMENTS, 42 | packages=PACKAGES, 43 | python_requires='>=3.8.0', 44 | classifiers=[ 45 | 'Development Status :: 3 - Alpha', 46 | 'License :: OSI Approved :: MIT License', 47 | 'Intended Audience :: Developers', 48 | 'Natural Language :: English', 49 | 'Operating System :: OS Independent', 50 | 'Programming Language :: Python :: 3.8', 51 | 'Programming Language :: Python :: 3.9', 52 | 'Topic :: Internet', 53 | 'Topic :: Software Development :: Libraries', 54 | 'Topic :: Software Development :: Libraries :: Python Modules', 55 | 'Topic :: Utilities', 56 | 'Typing :: Typed', 57 | ] 58 | ) 59 | -------------------------------------------------------------------------------- /tests/test_embed.py: -------------------------------------------------------------------------------- 1 | """Tests for qord.Embed""" 2 | 3 | from qord.dataclasses.embeds import ( 4 | Embed, 5 | EmbedAuthor, 6 | EmbedFooter, 7 | ) 8 | 9 | import unittest 10 | 11 | 12 | class TestEmbeds(unittest.TestCase): 13 | def test_total_length(self): 14 | embed = Embed() 15 | embed.title = "*" * 952 16 | embed.description = "*" * 2048 17 | 18 | assert embed.total_length() == 3000 19 | 20 | embed.author = EmbedAuthor(name="*" * 1000) 21 | embed.footer = EmbedFooter(text="*" * 1000) 22 | embed.set_field(name="*" * 500, value="*" * 500) 23 | 24 | assert embed.total_length() == 6000 25 | 26 | 27 | if __name__ == "__main__": 28 | unittest.main() 29 | -------------------------------------------------------------------------------- /tests/test_flags.py: -------------------------------------------------------------------------------- 1 | """Tests for qord.Flags""" 2 | 3 | from qord.flags.base import Flags 4 | 5 | import unittest 6 | 7 | class Permissions(Flags): 8 | send_messages = 1 << 0 9 | read_message_history = 1 << 1 10 | view_channel = 1 << 2 11 | manage_messages = 1 << 3 12 | manage_channels = 1 << 4 13 | 14 | class TestFlags(unittest.TestCase): 15 | def test_value(self) -> None: 16 | flags = Permissions(send_messages=True, manage_messages=True, manage_channels=False) 17 | assert flags.value == (Permissions.send_messages | Permissions.manage_messages) 18 | 19 | flags.value = (Permissions.send_messages | Permissions.manage_channels) 20 | 21 | assert flags.send_messages 22 | assert flags.manage_channels 23 | 24 | flags.view_channel = True 25 | 26 | assert flags.value == ( 27 | Permissions.send_messages 28 | | Permissions.manage_channels 29 | | Permissions.view_channel 30 | ) 31 | 32 | flags.view_channel = False 33 | 34 | assert flags.value == ( 35 | Permissions.send_messages 36 | | Permissions.manage_channels 37 | ) 38 | 39 | 40 | def test_comparison(self) -> None: 41 | flags = Permissions(send_messages=True, manage_messages=True) 42 | assert flags == Permissions(send_messages=True, manage_messages=True) 43 | assert flags > Permissions(send_messages=True) 44 | assert flags < Permissions(send_messages=True, manage_messages=True, manage_channels=True) 45 | 46 | if __name__ == "__main__": 47 | unittest.main() -------------------------------------------------------------------------------- /tests/test_internal_helpers.py: -------------------------------------------------------------------------------- 1 | from qord.internal import helpers 2 | import datetime 3 | import unittest 4 | 5 | class TestInternalHelpers(unittest.TestCase): 6 | def test_compute_creation_time(self) -> None: 7 | snowflake = 175928847299117063 8 | creation_time = datetime.datetime(2016, 4, 30, 11, 18, 25, 796000, datetime.timezone.utc) 9 | 10 | assert helpers.compute_creation_time(snowflake) == creation_time 11 | -------------------------------------------------------------------------------- /tests/test_permission_overwrite.py: -------------------------------------------------------------------------------- 1 | """Tests for qord.PermissionOverwrite""" 2 | 3 | from qord import PermissionOverwrite, Permissions 4 | import unittest 5 | 6 | 7 | class TestPermissionOverwrite(unittest.TestCase): 8 | def test_attrs_comparison(self): 9 | overwrite = PermissionOverwrite() 10 | 11 | overwrite.send_messages = True 12 | overwrite.manage_channels = False 13 | 14 | overwrite2 = PermissionOverwrite(send_messages=True, manage_channels=False) 15 | assert overwrite2 == overwrite 16 | 17 | def test_pairs(self): 18 | overwrite = PermissionOverwrite( 19 | manage_channels=True, 20 | manage_messages=True, 21 | send_messages=False, 22 | connect=False, 23 | ) 24 | 25 | allow = Permissions(manage_channels=True, manage_messages=True) 26 | deny = Permissions(send_messages=True, connect=True) 27 | 28 | overwrite_allow, overwrite_deny = overwrite.permissions() 29 | 30 | assert overwrite_allow == allow 31 | assert overwrite_deny == deny 32 | 33 | overwrite_with_permissions = PermissionOverwrite.from_permissions(allow, deny) 34 | assert overwrite_with_permissions == overwrite 35 | 36 | if __name__ == "__main__": 37 | unittest.main() -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | """Tests for qord.utils""" 2 | 3 | from qord import utils, TimestampStyle 4 | 5 | from datetime import datetime 6 | import unittest 7 | 8 | 9 | class TestUtils(unittest.TestCase): 10 | def test_create_timestamp(self) -> None: 11 | # March 27 2022, 15:07:43 12 | time_datetime = datetime(2022, 3, 27, 15, 7, 43) 13 | time_epoch = time_datetime.timestamp() 14 | 15 | timestamp = f"" 16 | timestamp_styled = f"" 17 | 18 | assert utils.create_timestamp(time_datetime) == timestamp 19 | assert utils.create_timestamp(time_epoch) == timestamp 20 | assert utils.create_timestamp(time_datetime, TimestampStyle.RELATIVE_TIME) == timestamp_styled 21 | 22 | 23 | if __name__ == "__main__": 24 | unittest.main() --------------------------------------------------------------------------------