5 |
Diskord
6 |

7 |

8 |

9 |
10 |
This library is a maintained fork of now archived library, discord.py.
11 |
12 | A modern and easy to use API wrapper around Discord API written in Python.
13 |
14 |
15 |
16 | ## Features
17 | * Modern, Pythonic API based on `async` / `await`
18 | * Consistent, object oriented & easy to use interface
19 | * Provides full coverage of Discord API
20 | * Proper and sane ratelimit handling
21 | * Optimized in both speed and memory
22 | * Extensions support & prebuilt powerful commands handler
23 |
24 |
25 | ## Installation
26 |
27 | **Python 3.8 or higher** is required to install this library.
28 |
29 | ### Basic Installation
30 | To install the library **without** full voice support, you can just run the following command:
31 | ```sh
32 | python -m pip install diskord
33 | ```
34 |
35 | ### Voice Support
36 | Optionally, To interact with discord's voice API, You would require voice support of this library which you can install like so:
37 | ```sh
38 | python -m pip install diskord[voice]
39 | ```
40 |
41 | ### Development Version
42 | You must have git installed to install development version. Otherwise, you can download the code.
43 | ```sh
44 | $ git clone https://github.com/diskord-dev/diskord
45 | $ cd diskord
46 | $ python -m pip install -U .[voice]
47 | ```
48 | or in short;
49 | ```sh
50 | python -m pip install git+https://github.com/diskord-dev/diskord.git
51 | ```
52 |
53 | ## Quick Example
54 | Here are some quick examples to give you a quickstart and show off the basic features of the library.
55 |
56 | ### Application (Slash) Commands
57 | ```py
58 | import diskord
59 |
60 | client = diskord.Client()
61 |
62 | @client.slash_command(description='Ping-Pong!')
63 | async def ping(ctx):
64 | await ctx.respond('Pong!')
65 |
66 | client.run('token')
67 | ```
68 |
69 | ### Legacy (Prefixed) Commands
70 | ```py
71 | import diskord
72 | from diskord.ext import commands
73 |
74 | bot = commands.Bot(command_prefix='>')
75 |
76 | @bot.command()
77 | async def ping(ctx):
78 | await ctx.send('pong')
79 |
80 | bot.run('token')
81 | ```
82 | You can find more examples in the [`examples`](examples/) directory.
83 |
84 | ## Links
85 | * [Latest Documentation](https://diskord.readthedocs.io/en/latest/index.html)
86 | * [Official Discord Server](https://dsc.gg/diskord-dev)
87 | * [Discord API](https://discord.gg/discord-api)
88 | * [Developer Portal](https://developer.discord.com/applications)
89 |
--------------------------------------------------------------------------------
/docs/quickstart.rst:
--------------------------------------------------------------------------------
1 | :orphan:
2 |
3 | .. _quickstart:
4 |
5 | .. currentmodule:: diskord
6 |
7 | Quickstart
8 | ============
9 |
10 | This page gives a brief introduction to the library. It assumes you have the library installed,
11 | if you don't check the :ref:`installing` portion.
12 |
13 | A Minimal Bot
14 | ---------------
15 |
16 | Let's make a bot that responds to a specific message and walk you through it.
17 |
18 | It looks something like this:
19 |
20 | .. code-block:: python3
21 |
22 | import diskord
23 |
24 | client = diskord.Client()
25 |
26 | @client.event
27 | async def on_ready():
28 | print(f'We have logged in as {client.user}')
29 |
30 | @client.event
31 | async def on_message(message):
32 | if message.author == client.user:
33 | return
34 |
35 | if message.content.startswith('$hello'):
36 | await message.channel.send('Hello!')
37 |
38 | client.run('your token here')
39 |
40 | Let's name this file ``example_bot.py``. Make sure not to name it ``diskord`` as that'll conflict
41 | with the library.
42 |
43 | There's a lot going on here, so let's walk you through it step by step.
44 |
45 | 1. The first line just imports the library, if this raises a `ModuleNotFoundError` or `ImportError`
46 | then head on over to :ref:`installing` section to properly install.
47 | 2. Next, we create an instance of a :class:`Client`. This client is our connection to Discord.
48 | 3. We then use the :meth:`Client.event` decorator to register an event. This library has many events.
49 | Since this library is asynchronous, we do things in a "callback" style manner.
50 |
51 | A callback is essentially a function that is called when something happens. In our case,
52 | the :func:`on_ready` event is called when the bot has finished logging in and setting things
53 | up and the :func:`on_message` event is called when the bot has received a message.
54 | 4. Since the :func:`on_message` event triggers for *every* message received, we have to make
55 | sure that we ignore messages from ourselves. We do this by checking if the :attr:`Message.author`
56 | is the same as the :attr:`Client.user`.
57 | 5. Afterwards, we check if the :class:`Message.content` starts with ``'$hello'``. If it does,
58 | then we send a message in the channel it was used in with ``'Hello!'``. This is a basic way of
59 | handling commands, which can be later automated with the :doc:`./ext/commands/index` framework.
60 | 6. Finally, we run the bot with our login token. If you need help getting your token or creating a bot,
61 | look in the :ref:`diskord-intro` section.
62 |
63 |
64 | Now that we've made a bot, we have to *run* the bot. Luckily, this is simple since this is just a
65 | Python script, we can run it directly.
66 |
67 | On Windows:
68 |
69 | .. code-block:: shell
70 |
71 | $ py -3 example_bot.py
72 |
73 | On other systems:
74 |
75 | .. code-block:: shell
76 |
77 | $ python3 example_bot.py
78 |
79 | Now you can try playing around with your basic bot.
80 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 | import re
3 |
4 | requirements = []
5 | with open('requirements.txt') as f:
6 | requirements = f.read().splitlines()
7 |
8 | version = ''
9 | with open('diskord/__init__.py') as f:
10 | version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE).group(1)
11 |
12 | if not version:
13 | raise RuntimeError('version is not set')
14 |
15 | if version.endswith(('a', 'b', 'rc')):
16 | # append version identifier based on commit count
17 | try:
18 | import subprocess
19 | p = subprocess.Popen(['git', 'rev-list', '--count', 'HEAD'],
20 | stdout=subprocess.PIPE, stderr=subprocess.PIPE)
21 | out, err = p.communicate()
22 | if out:
23 | version += out.decode('utf-8').strip()
24 | p = subprocess.Popen(['git', 'rev-parse', '--short', 'HEAD'],
25 | stdout=subprocess.PIPE, stderr=subprocess.PIPE)
26 | out, err = p.communicate()
27 | if out:
28 | version += '+g' + out.decode('utf-8').strip()
29 | except Exception:
30 | pass
31 |
32 | readme = ''
33 | with open('README.MD') as f:
34 | readme = f.read()
35 |
36 | extras_require = {
37 | 'voice': ['PyNaCl>=1.3.0,<1.5'],
38 | 'docs': [
39 | 'sphinx==4.0.2',
40 | 'sphinxcontrib_trio==1.1.2',
41 | 'sphinxcontrib-websupport',
42 | ],
43 | 'speed': [
44 | 'orjson>=3.5.4',
45 | ]
46 | }
47 |
48 | packages = [
49 | 'diskord',
50 | 'diskord.types',
51 | 'diskord.ui',
52 | 'diskord.application',
53 | 'diskord.webhook',
54 | 'diskord.ext.commands',
55 | 'diskord.ext.tasks',
56 | ]
57 |
58 | setup(name='diskord',
59 | author='NerdGuyAhmad',
60 | url='https://github.com/nerdguyahmad/diskord',
61 | project_urls={
62 | "Documentation": "https://diskord.readthedocs.io/en/latest/",
63 | "Issue tracker": "https://github.com/nerdguyahmad/diskord/issues",
64 | },
65 | version=version,
66 | packages=packages,
67 | license='MIT',
68 | description='An API wrapper around Discord API written in Python',
69 | long_description=readme,
70 | long_description_content_type="text/markdown",
71 | include_package_data=True,
72 | install_requires=requirements,
73 | extras_require=extras_require,
74 | python_requires='>=3.8.0',
75 | classifiers=[
76 | 'Development Status :: 5 - Production/Stable',
77 | 'License :: OSI Approved :: MIT License',
78 | 'Intended Audience :: Developers',
79 | 'Natural Language :: English',
80 | 'Operating System :: OS Independent',
81 | 'Programming Language :: Python :: 3.8',
82 | 'Programming Language :: Python :: 3.9',
83 | 'Topic :: Internet',
84 | 'Topic :: Software Development :: Libraries',
85 | 'Topic :: Software Development :: Libraries :: Python Modules',
86 | 'Topic :: Utilities',
87 | 'Typing :: Typed',
88 | ]
89 | )
90 |
--------------------------------------------------------------------------------
/examples/views/persistent.py:
--------------------------------------------------------------------------------
1 | from diskord.ext import commands
2 | import diskord
3 |
4 |
5 | # Define a simple View that persists between bot restarts
6 | # In order a view to persist between restarts it needs to meet the following conditions:
7 | # 1) The timeout of the View has to be set to None
8 | # 2) Every item in the View has to have a custom_id set
9 | # It is recommended that the custom_id be sufficiently unique to
10 | # prevent conflicts with other buttons the bot sends.
11 | # For this example the custom_id is prefixed with the name of the bot.
12 | # Note that custom_ids can only be up to 100 characters long.
13 | class PersistentView(diskord.ui.View):
14 | def __init__(self):
15 | super().__init__(timeout=None)
16 |
17 | @diskord.ui.button(label='Green', style=diskord.ButtonStyle.green, custom_id='persistent_view:green')
18 | async def green(self, button: diskord.ui.Button, interaction: diskord.Interaction):
19 | await interaction.response.send_message('This is green.', ephemeral=True)
20 |
21 | @diskord.ui.button(label='Red', style=diskord.ButtonStyle.red, custom_id='persistent_view:red')
22 | async def red(self, button: diskord.ui.Button, interaction: diskord.Interaction):
23 | await interaction.response.send_message('This is red.', ephemeral=True)
24 |
25 | @diskord.ui.button(label='Grey', style=diskord.ButtonStyle.grey, custom_id='persistent_view:grey')
26 | async def grey(self, button: diskord.ui.Button, interaction: diskord.Interaction):
27 | await interaction.response.send_message('This is grey.', ephemeral=True)
28 |
29 |
30 | class PersistentViewBot(commands.Bot):
31 | def __init__(self):
32 | super().__init__(command_prefix=commands.when_mentioned_or('$'))
33 | self.persistent_views_added = False
34 |
35 | async def on_ready(self):
36 | if not self.persistent_views_added:
37 | # Register the persistent view for listening here.
38 | # Note that this does not send the view to any message.
39 | # In order to do this you need to first send a message with the View, which is shown below.
40 | # If you have the message_id you can also pass it as a keyword argument, but for this example
41 | # we don't have one.
42 | self.add_view(PersistentView())
43 | self.persistent_views_added = True
44 |
45 | print(f'Logged in as {self.user} (ID: {self.user.id})')
46 | print('------')
47 |
48 |
49 | bot = PersistentViewBot()
50 |
51 |
52 | @bot.command()
53 | @commands.is_owner()
54 | async def prepare(ctx: commands.Context):
55 | """Starts a persistent view."""
56 | # In order for a persistent view to be listened to, it needs to be sent to an actual message.
57 | # Call this method once just to store it somewhere.
58 | # In a more complicated program you might fetch the message_id from a database for use later.
59 | # However this is outside of the scope of this simple example.
60 | await ctx.send("What's your favourite colour?", view=PersistentView())
61 |
62 |
63 | bot.run('token')
64 |
--------------------------------------------------------------------------------
/docs/_static/settings.js:
--------------------------------------------------------------------------------
1 | 'use-strict';
2 |
3 | let settingsModal;
4 |
5 | class Setting {
6 | constructor(name, defaultValue, setter) {
7 | this.name = name;
8 | this.defaultValue = defaultValue;
9 | this.setValue = setter;
10 | }
11 |
12 | setElement() {
13 | throw new TypeError('Abstract methods should be implemented.');
14 | }
15 |
16 | load() {
17 | let value = JSON.parse(localStorage.getItem(this.name));
18 | this.value = value === null ? this.defaultValue : value;
19 | try {
20 | this.setValue(this.value);
21 | } catch (error) {
22 | console.error(`Failed to apply setting "${this.name}" With value:`, this.value);
23 | console.error(error);
24 | }
25 | }
26 |
27 | update() {
28 | throw new TypeError('Abstract methods should be implemented.');
29 | }
30 |
31 | }
32 |
33 | class CheckboxSetting extends Setting {
34 |
35 | setElement() {
36 | let element = document.querySelector(`input[name=${this.name}]`);
37 | element.checked = this.value;
38 | }
39 |
40 | update(element) {
41 | localStorage.setItem(this.name, element.checked);
42 | this.setValue(element.checked);
43 | }
44 |
45 | }
46 |
47 | class RadioSetting extends Setting {
48 |
49 | setElement() {
50 | let element = document.querySelector(`input[name=${this.name}][value=${this.value}]`);
51 | element.checked = true;
52 | }
53 |
54 | update(element) {
55 | localStorage.setItem(this.name, `"${element.value}"`);
56 | this.setValue(element.value);
57 | }
58 |
59 | }
60 |
61 | function getRootAttributeToggle(attributeName, valueName) {
62 | function toggleRootAttribute(set) {
63 | if (set) {
64 | document.documentElement.setAttribute(`data-${attributeName}`, valueName);
65 | } else {
66 | document.documentElement.removeAttribute(`data-${attributeName}`);
67 | }
68 | }
69 | return toggleRootAttribute;
70 | }
71 |
72 | function setTheme(value) {
73 | if (value === 'automatic') {
74 | if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
75 | document.documentElement.setAttribute('data-theme', 'dark');
76 | } else {
77 | document.documentElement.setAttribute('data-theme', 'light');
78 | }
79 | }
80 | else {
81 | document.documentElement.setAttribute('data-theme', value);
82 | }
83 | }
84 |
85 | const settings = [
86 | new CheckboxSetting('useSerifFont', false, getRootAttributeToggle('font', 'serif')),
87 | new RadioSetting('setTheme', 'automatic', setTheme)
88 | ]
89 |
90 | function updateSetting(element) {
91 | let setting = settings.find((s) => s.name == element.name);
92 | if (setting) {
93 | setting.update(element);
94 | }
95 | }
96 |
97 | for (const setting of settings) {
98 | setting.load();
99 | }
100 |
101 | document.addEventListener('DOMContentLoaded', () => {
102 | settingsModal = new Modal(document.querySelector('div#settings.modal'));
103 | for (const setting of settings) {
104 | setting.setElement();
105 | }
106 | });
107 |
--------------------------------------------------------------------------------
/diskord/types/invite.py:
--------------------------------------------------------------------------------
1 | """
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2015-present Rapptz
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a
7 | copy of this software and associated documentation files (the "Software"),
8 | to deal in the Software without restriction, including without limitation
9 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 | and/or sell copies of the Software, and to permit persons to whom the
11 | Software is furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 | DEALINGS IN THE SOFTWARE.
23 | """
24 |
25 | from __future__ import annotations
26 |
27 | from typing import Literal, Optional, TypedDict, Union
28 |
29 | from .snowflake import Snowflake
30 | from .guild import InviteGuild, _GuildPreviewUnique
31 | from .channel import PartialChannel
32 | from .user import PartialUser
33 | from .appinfo import PartialAppInfo
34 |
35 | InviteTargetType = Literal[1, 2]
36 |
37 |
38 | class _InviteOptional(TypedDict, total=False):
39 | guild: InviteGuild
40 | inviter: PartialUser
41 | target_user: PartialUser
42 | target_type: InviteTargetType
43 | target_application: PartialAppInfo
44 |
45 |
46 | class _InviteMetadata(TypedDict, total=False):
47 | uses: int
48 | max_uses: int
49 | max_age: int
50 | temporary: bool
51 | created_at: str
52 | expires_at: Optional[str]
53 |
54 |
55 | class VanityInvite(_InviteMetadata):
56 | code: Optional[str]
57 |
58 |
59 | class IncompleteInvite(_InviteMetadata):
60 | code: str
61 | channel: PartialChannel
62 |
63 |
64 | class Invite(IncompleteInvite, _InviteOptional):
65 | ...
66 |
67 |
68 | class InviteWithCounts(Invite, _GuildPreviewUnique):
69 | ...
70 |
71 |
72 | class _GatewayInviteCreateOptional(TypedDict, total=False):
73 | guild_id: Snowflake
74 | inviter: PartialUser
75 | target_type: InviteTargetType
76 | target_user: PartialUser
77 | target_application: PartialAppInfo
78 |
79 |
80 | class GatewayInviteCreate(_GatewayInviteCreateOptional):
81 | channel_id: Snowflake
82 | code: str
83 | created_at: str
84 | max_age: int
85 | max_uses: int
86 | temporary: bool
87 | uses: bool
88 |
89 |
90 | class _GatewayInviteDeleteOptional(TypedDict, total=False):
91 | guild_id: Snowflake
92 |
93 |
94 | class GatewayInviteDelete(_GatewayInviteDeleteOptional):
95 | channel_id: Snowflake
96 | code: str
97 |
98 |
99 | GatewayInvite = Union[GatewayInviteCreate, GatewayInviteDelete]
100 |
--------------------------------------------------------------------------------
/diskord/types/raw_models.py:
--------------------------------------------------------------------------------
1 | """
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2015-present Rapptz
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a
7 | copy of this software and associated documentation files (the "Software"),
8 | to deal in the Software without restriction, including without limitation
9 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 | and/or sell copies of the Software, and to permit persons to whom the
11 | Software is furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 | DEALINGS IN THE SOFTWARE.
23 | """
24 |
25 | from typing import TypedDict, List
26 | from .snowflake import Snowflake
27 | from .member import Member
28 | from .emoji import PartialEmoji
29 |
30 |
31 | class _MessageEventOptional(TypedDict, total=False):
32 | guild_id: Snowflake
33 |
34 |
35 | class MessageDeleteEvent(_MessageEventOptional):
36 | id: Snowflake
37 | channel_id: Snowflake
38 |
39 |
40 | class BulkMessageDeleteEvent(_MessageEventOptional):
41 | ids: List[Snowflake]
42 | channel_id: Snowflake
43 |
44 |
45 | class _ReactionActionEventOptional(TypedDict, total=False):
46 | guild_id: Snowflake
47 | member: Member
48 |
49 |
50 | class MessageUpdateEvent(_MessageEventOptional):
51 | id: Snowflake
52 | channel_id: Snowflake
53 |
54 |
55 | class ReactionActionEvent(_ReactionActionEventOptional):
56 | user_id: Snowflake
57 | channel_id: Snowflake
58 | message_id: Snowflake
59 | emoji: PartialEmoji
60 |
61 |
62 | class _ReactionClearEventOptional(TypedDict, total=False):
63 | guild_id: Snowflake
64 |
65 |
66 | class ReactionClearEvent(_ReactionClearEventOptional):
67 | channel_id: Snowflake
68 | message_id: Snowflake
69 |
70 |
71 | class _ReactionClearEmojiEventOptional(TypedDict, total=False):
72 | guild_id: Snowflake
73 |
74 |
75 | class ReactionClearEmojiEvent(_ReactionClearEmojiEventOptional):
76 | channel_id: int
77 | message_id: int
78 | emoji: PartialEmoji
79 |
80 |
81 | class _IntegrationDeleteEventOptional(TypedDict, total=False):
82 | application_id: Snowflake
83 |
84 |
85 | class IntegrationDeleteEvent(_IntegrationDeleteEventOptional):
86 | id: Snowflake
87 | guild_id: Snowflake
88 |
89 |
90 | class ThreadDeleteEvent(TypedDict):
91 | id: Snowflake
92 | thread_type: int
93 | guild_id: Snowflake
94 | parent_id: Snowflake
95 |
96 |
97 | class TypingEvent(TypedDict):
98 | user_id: Snowflake
99 | channel_id: Snowflake
100 | timestamp: str
101 |
--------------------------------------------------------------------------------
/diskord/context_managers.py:
--------------------------------------------------------------------------------
1 | """
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2015-present Rapptz
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a
7 | copy of this software and associated documentation files (the "Software"),
8 | to deal in the Software without restriction, including without limitation
9 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 | and/or sell copies of the Software, and to permit persons to whom the
11 | Software is furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 | DEALINGS IN THE SOFTWARE.
23 | """
24 |
25 | from __future__ import annotations
26 |
27 | import asyncio
28 | from typing import TYPE_CHECKING, TypeVar, Optional, Type
29 |
30 | if TYPE_CHECKING:
31 | from .abc import Messageable
32 |
33 | from types import TracebackType
34 |
35 | TypingT = TypeVar("TypingT", bound="Typing")
36 |
37 | __all__ = ("Typing",)
38 |
39 |
40 | def _typing_done_callback(fut: asyncio.Future) -> None:
41 | # just retrieve any exception and call it a day
42 | try:
43 | fut.exception()
44 | except (asyncio.CancelledError, Exception):
45 | pass
46 |
47 |
48 | class Typing:
49 | def __init__(self, messageable: Messageable) -> None:
50 | self.loop: asyncio.AbstractEventLoop = messageable._state.loop
51 | self.messageable: Messageable = messageable
52 |
53 | async def do_typing(self) -> None:
54 | try:
55 | channel = self._channel
56 | except AttributeError:
57 | channel = await self.messageable._get_channel()
58 |
59 | typing = channel._state.http.send_typing
60 |
61 | while True:
62 | await typing(channel.id)
63 | await asyncio.sleep(5)
64 |
65 | def __enter__(self: TypingT) -> TypingT:
66 | self.task: asyncio.Task = self.loop.create_task(self.do_typing())
67 | self.task.add_done_callback(_typing_done_callback)
68 | return self
69 |
70 | def __exit__(
71 | self,
72 | exc_type: Optional[Type[BaseException]],
73 | exc_value: Optional[BaseException],
74 | traceback: Optional[TracebackType],
75 | ) -> None:
76 | self.task.cancel()
77 |
78 | async def __aenter__(self: TypingT) -> TypingT:
79 | self._channel = channel = await self.messageable._get_channel()
80 | await channel._state.http.send_typing(channel.id)
81 | return self.__enter__()
82 |
83 | async def __aexit__(
84 | self,
85 | exc_type: Optional[Type[BaseException]],
86 | exc_value: Optional[BaseException],
87 | traceback: Optional[TracebackType],
88 | ) -> None:
89 | self.task.cancel()
90 |
--------------------------------------------------------------------------------
/diskord/object.py:
--------------------------------------------------------------------------------
1 | """
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2015-present Rapptz
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a
7 | copy of this software and associated documentation files (the "Software"),
8 | to deal in the Software without restriction, including without limitation
9 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 | and/or sell copies of the Software, and to permit persons to whom the
11 | Software is furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 | DEALINGS IN THE SOFTWARE.
23 | """
24 |
25 | from __future__ import annotations
26 |
27 | from . import utils
28 | from .mixins import Hashable
29 |
30 | from typing import (
31 | SupportsInt,
32 | TYPE_CHECKING,
33 | Union,
34 | )
35 |
36 | if TYPE_CHECKING:
37 | import datetime
38 |
39 | SupportsIntCast = Union[SupportsInt, str, bytes, bytearray]
40 |
41 | __all__ = ("Object",)
42 |
43 |
44 | class Object(Hashable):
45 | """Represents a generic Discord object.
46 |
47 | The purpose of this class is to allow you to create 'miniature'
48 | versions of data classes if you want to pass in just an ID. Most functions
49 | that take in a specific data class with an ID can also take in this class
50 | as a substitute instead. Note that even though this is the case, not all
51 | objects (if any) actually inherit from this class.
52 |
53 | There are also some cases where some websocket events are received
54 | in :issue:`strange order <21>` and when such events happened you would
55 | receive this class rather than the actual data class. These cases are
56 | extremely rare.
57 |
58 | .. container:: operations
59 |
60 | .. describe:: x == y
61 |
62 | Checks if two objects are equal.
63 |
64 | .. describe:: x != y
65 |
66 | Checks if two objects are not equal.
67 |
68 | .. describe:: hash(x)
69 |
70 | Returns the object's hash.
71 |
72 | Attributes
73 | -----------
74 | id: :class:`int`
75 | The ID of the object.
76 | """
77 |
78 | def __init__(self, id: SupportsIntCast):
79 | try:
80 | id = int(id)
81 | except ValueError:
82 | raise TypeError(
83 | f"id parameter must be convertable to int not {id.__class__!r}"
84 | ) from None
85 | else:
86 | self.id = id
87 |
88 | def __repr__(self) -> str:
89 | return f"