├── diskord ├── py.typed ├── bin │ ├── libopus-0.x64.dll │ ├── libopus-0.x86.dll │ └── COPYING ├── types │ ├── __init__.py │ ├── snowflake.py │ ├── gateway.py │ ├── welcome_screen.py │ ├── team.py │ ├── emoji.py │ ├── user.py │ ├── role.py │ ├── template.py │ ├── member.py │ ├── widget.py │ ├── events.py │ ├── webhook.py │ ├── appinfo.py │ ├── components.py │ ├── threads.py │ ├── integration.py │ ├── voice.py │ ├── embed.py │ ├── sticker.py │ ├── invite.py │ ├── raw_models.py │ ├── activity.py │ ├── message.py │ └── channel.py ├── webhook │ └── __init__.py ├── ui │ └── __init__.py ├── application │ ├── __init__.py │ └── types.py ├── ext │ └── commands │ │ ├── __init__.py │ │ └── _types.py ├── mixins.py ├── __init__.py ├── context_managers.py ├── object.py ├── backoff.py └── oggparse.py ├── docs ├── requirements.txt ├── _templates │ ├── relations.html │ └── genindex.html ├── _static │ ├── icons.woff │ ├── icons.css │ ├── copy.js │ ├── custom.js │ ├── scorer.js │ ├── settings.js │ └── sidebar.js ├── images │ ├── diskord_logo.ico │ ├── slash_command.png │ ├── user_command.png │ ├── commands │ │ ├── flags1.png │ │ ├── flags2.png │ │ ├── flags3.png │ │ ├── greedy1.png │ │ ├── keyword1.png │ │ ├── keyword2.png │ │ ├── optional1.png │ │ ├── variable1.png │ │ ├── variable2.png │ │ ├── variable3.png │ │ ├── positional1.png │ │ ├── positional2.png │ │ └── positional3.png │ ├── discord_bot_tab.png │ ├── discord_oauth2.png │ ├── diskord-banner.PNG │ ├── message_command.png │ ├── discord_oauth2_perms.png │ ├── discord_oauth2_scope.png │ ├── slash_command_option.png │ ├── slash_command_result.png │ ├── discord_create_app_form.png │ ├── discord_create_bot_user.png │ ├── discord_bot_user_options.png │ ├── discord_create_app_button.png │ ├── slash_command_sub_command.png │ ├── application_commands_scope.png │ ├── discord_privileged_intents.png │ ├── slash_command_option_choices.png │ ├── slash_command_option_member.png │ ├── slash_command_option_typing.png │ ├── slash_command_sub_command_group.png │ └── drop_down_icon.svg ├── ext │ ├── commands │ │ ├── index.rst │ │ └── extensions.rst │ └── tasks │ │ └── index.rst ├── extensions │ ├── nitpick_file_ignorer.py │ ├── exception_hierarchy.py │ ├── resourcelinks.py │ ├── details.py │ └── builder.py ├── locale │ └── ja │ │ └── LC_MESSAGES │ │ ├── sphinx.po │ │ ├── ext │ │ └── commands │ │ │ ├── index.po │ │ │ └── extensions.po │ │ ├── index.po │ │ ├── logging.po │ │ ├── version_guarantees.po │ │ └── quickstart.po ├── version_guarantees.rst ├── logging.rst ├── index.rst ├── quickstart.rst ├── intro.rst └── discord.rst ├── requirements.txt ├── MANIFEST.in ├── .gitignore ├── .readthedocs.yml ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.yml │ └── bug_report.yml └── PULL_REQUEST_TEMPLATE.md ├── examples ├── reply.py ├── new_member.py ├── application_commands │ ├── option_names.py │ ├── min_max_values.py │ ├── basic.py │ ├── option_choices.py │ ├── subcommand_groups.py │ ├── options.py │ ├── autocomplete.py │ └── autocomplete_multiple_options.py ├── edits.py ├── deleted.py ├── background_task_asyncio.py ├── background_task.py ├── guessing_game.py ├── views │ ├── link.py │ ├── counter.py │ ├── confirm.py │ ├── ephemeral.py │ ├── dropdown.py │ └── persistent.py ├── custom_context.py ├── basic_bot.py ├── reaction_roles.py └── secret.py ├── LICENSE ├── README.MD └── setup.py /diskord/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp>=3.6.0,<3.8.0 -------------------------------------------------------------------------------- /docs/_templates/relations.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/_static/icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/_static/icons.woff -------------------------------------------------------------------------------- /diskord/bin/libopus-0.x64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/diskord/bin/libopus-0.x64.dll -------------------------------------------------------------------------------- /diskord/bin/libopus-0.x86.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/diskord/bin/libopus-0.x86.dll -------------------------------------------------------------------------------- /docs/images/diskord_logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/diskord_logo.ico -------------------------------------------------------------------------------- /docs/images/slash_command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/slash_command.png -------------------------------------------------------------------------------- /docs/images/user_command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/user_command.png -------------------------------------------------------------------------------- /docs/images/commands/flags1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/commands/flags1.png -------------------------------------------------------------------------------- /docs/images/commands/flags2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/commands/flags2.png -------------------------------------------------------------------------------- /docs/images/commands/flags3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/commands/flags3.png -------------------------------------------------------------------------------- /docs/images/discord_bot_tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/discord_bot_tab.png -------------------------------------------------------------------------------- /docs/images/discord_oauth2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/discord_oauth2.png -------------------------------------------------------------------------------- /docs/images/diskord-banner.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/diskord-banner.PNG -------------------------------------------------------------------------------- /docs/images/message_command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/message_command.png -------------------------------------------------------------------------------- /docs/images/commands/greedy1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/commands/greedy1.png -------------------------------------------------------------------------------- /docs/images/commands/keyword1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/commands/keyword1.png -------------------------------------------------------------------------------- /docs/images/commands/keyword2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/commands/keyword2.png -------------------------------------------------------------------------------- /docs/images/commands/optional1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/commands/optional1.png -------------------------------------------------------------------------------- /docs/images/commands/variable1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/commands/variable1.png -------------------------------------------------------------------------------- /docs/images/commands/variable2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/commands/variable2.png -------------------------------------------------------------------------------- /docs/images/commands/variable3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/commands/variable3.png -------------------------------------------------------------------------------- /docs/images/commands/positional1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/commands/positional1.png -------------------------------------------------------------------------------- /docs/images/commands/positional2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/commands/positional2.png -------------------------------------------------------------------------------- /docs/images/commands/positional3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/commands/positional3.png -------------------------------------------------------------------------------- /docs/images/discord_oauth2_perms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/discord_oauth2_perms.png -------------------------------------------------------------------------------- /docs/images/discord_oauth2_scope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/discord_oauth2_scope.png -------------------------------------------------------------------------------- /docs/images/slash_command_option.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/slash_command_option.png -------------------------------------------------------------------------------- /docs/images/slash_command_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/slash_command_result.png -------------------------------------------------------------------------------- /docs/images/discord_create_app_form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/discord_create_app_form.png -------------------------------------------------------------------------------- /docs/images/discord_create_bot_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/discord_create_bot_user.png -------------------------------------------------------------------------------- /docs/images/discord_bot_user_options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/discord_bot_user_options.png -------------------------------------------------------------------------------- /docs/images/discord_create_app_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/discord_create_app_button.png -------------------------------------------------------------------------------- /docs/images/slash_command_sub_command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/slash_command_sub_command.png -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include LICENSE 3 | include requirements.txt 4 | include diskord/bin/*.dll 5 | include diskord/py.typed 6 | -------------------------------------------------------------------------------- /docs/images/application_commands_scope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/application_commands_scope.png -------------------------------------------------------------------------------- /docs/images/discord_privileged_intents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/discord_privileged_intents.png -------------------------------------------------------------------------------- /docs/images/slash_command_option_choices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/slash_command_option_choices.png -------------------------------------------------------------------------------- /docs/images/slash_command_option_member.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/slash_command_option_member.png -------------------------------------------------------------------------------- /docs/images/slash_command_option_typing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/slash_command_option_typing.png -------------------------------------------------------------------------------- /docs/images/slash_command_sub_command_group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskord-dev/diskord/HEAD/docs/images/slash_command_sub_command_group.png -------------------------------------------------------------------------------- /docs/images/drop_down_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /diskord/types/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | discord.types 3 | ~~~~~~~~~~~~~~ 4 | 5 | Typings for the Discord API 6 | 7 | :copyright: (c) 2015-present Rapptz 8 | :license: MIT, see LICENSE for more details. 9 | 10 | """ 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.json 2 | *.py[cod] 3 | *.log 4 | *.egg-info 5 | venv 6 | .venv 7 | docs/_build 8 | docs/crowdin.py 9 | *.buildinfo 10 | *.mp3 11 | *.m4a 12 | *.wav 13 | *.jpg 14 | *.flac 15 | *.png 16 | *.mo 17 | *.test.* 18 | build/ 19 | dist/ -------------------------------------------------------------------------------- /diskord/webhook/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | discord.webhook 3 | ~~~~~~~~~~~~~~ 4 | 5 | Webhook support 6 | 7 | :copyright: (c) 2015-present Rapptz 8 | :license: MIT, see LICENSE for more details. 9 | 10 | """ 11 | 12 | from .async_ import * 13 | from .sync import * 14 | -------------------------------------------------------------------------------- /docs/_static/icons.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Custom Icons'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: url('icons.woff') format('woff2'); 6 | } 7 | 8 | .custom-icons { 9 | font-family: 'Custom Icons' !important; 10 | } 11 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | formats: [] 3 | 4 | build: 5 | image: latest 6 | 7 | sphinx: 8 | configuration: docs/conf.py 9 | fail_on_warning: false 10 | builder: html 11 | 12 | python: 13 | version: 3.8 14 | install: 15 | - method: pip 16 | path: . 17 | extra_requirements: 18 | - docs -------------------------------------------------------------------------------- /diskord/ui/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | diskord.ui 3 | ~~~~~~~~~~~ 4 | 5 | Bot UI Kit helper for the Discord API 6 | 7 | :copyright: (c) 2015-present Rapptz 8 | :license: MIT, see LICENSE for more details. 9 | 10 | """ 11 | 12 | from .view import * 13 | from .item import * 14 | from .button import * 15 | from .select import * 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Ask a question 4 | about: Ask questions and discuss with other users of the library. 5 | url: https://github.com/diskord-dev/diskord/discussions 6 | - name: Discord Server 7 | about: Use our official Discord server to ask for help and questions as well. 8 | url: https://discord.gg/V6VxfDYHkB 9 | -------------------------------------------------------------------------------- /diskord/application/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | diskord.application 3 | ~~~~~~~~~~~~~~~~~~~ 4 | 5 | Helper for building application commands. 6 | 7 | 8 | :copyright: (c) 2021-present nerdguyahmad 9 | :license: MIT, see LICENSE for more details. 10 | """ 11 | from .command import * 12 | from .context_menus import * 13 | from .slash import * 14 | from .permissions import * 15 | 16 | from . import mixins, types -------------------------------------------------------------------------------- /diskord/ext/commands/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | diskord.ext.commands 3 | ~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | An extension module to facilitate creation of bot commands. 6 | 7 | :copyright: (c) 2015-present Rapptz 8 | :license: MIT, see LICENSE for more details. 9 | """ 10 | 11 | from .bot import * 12 | from .context import * 13 | from .core import * 14 | from .errors import * 15 | from .help import * 16 | from .converter import * 17 | from .cooldowns import * 18 | from .cog import * 19 | from .flags import * 20 | -------------------------------------------------------------------------------- /examples/reply.py: -------------------------------------------------------------------------------- 1 | import diskord 2 | 3 | class MyClient(diskord.Client): 4 | async def on_ready(self): 5 | print(f'Logged in as {self.user} (ID: {self.user.id})') 6 | print('------') 7 | 8 | async def on_message(self, message): 9 | # we do not want the bot to reply to itself 10 | if message.author.id == self.user.id: 11 | return 12 | 13 | if message.content.startswith('!hello'): 14 | await message.reply('Hello!', mention_author=True) 15 | 16 | client = MyClient() 17 | client.run('token') 18 | -------------------------------------------------------------------------------- /examples/new_member.py: -------------------------------------------------------------------------------- 1 | # This example requires the 'members' privileged intents 2 | 3 | import diskord 4 | 5 | class MyClient(diskord.Client): 6 | async def on_ready(self): 7 | print(f'Logged in as {self.user} (ID: {self.user.id})') 8 | print('------') 9 | 10 | async def on_member_join(self, member): 11 | guild = member.guild 12 | if guild.system_channel is not None: 13 | to_send = f'Welcome {member.mention} to {guild.name}!' 14 | await guild.system_channel.send(to_send) 15 | 16 | 17 | intents = diskord.Intents.default() 18 | intents.members = True 19 | 20 | client = MyClient(intents=intents) 21 | client.run('token') 22 | -------------------------------------------------------------------------------- /examples/application_commands/option_names.py: -------------------------------------------------------------------------------- 1 | import diskord 2 | from diskord import commands 3 | 4 | bot = commands.Bot(command_prefix='!') 5 | 6 | # you can set "arg" keyword argument to the name of argument that represents the option in the command function 7 | # and then change the option name as desired. 8 | @bot.slash_command() 9 | @diskord.application.option('sentence', arg='text', description='The text to say!') 10 | async def say(ctx, text): 11 | await ctx.respond(f'{ctx.author.name} said: {text}') 12 | 13 | # in above command, the option name in discord will appear "sentence" but in this function, it will 14 | # be passed to text argument 15 | 16 | 17 | bot.run('token') 18 | -------------------------------------------------------------------------------- /docs/ext/commands/index.rst: -------------------------------------------------------------------------------- 1 | .. _discord_ext_commands: 2 | 3 | ``diskord.ext.commands`` -- Bot commands framework 4 | ==================================================== 5 | 6 | ``diskord`` offers a lower level aspect on interacting with Discord. Often times, the library is used for the creation of 7 | bots. However this task can be daunting and confusing to get correctly the first time. Many times there comes a repetition in 8 | creating a bot command framework that is extensible, flexible, and powerful. For this reason, ``diskord.py`` comes with an 9 | extension library that handles this for you. 10 | 11 | 12 | .. toctree:: 13 | :maxdepth: 2 14 | 15 | commands 16 | cogs 17 | extensions 18 | api 19 | -------------------------------------------------------------------------------- /examples/edits.py: -------------------------------------------------------------------------------- 1 | import diskord 2 | import asyncio 3 | 4 | class MyClient(diskord.Client): 5 | async def on_ready(self): 6 | print(f'Logged in as {self.user} (ID: {self.user.id})') 7 | print('------') 8 | 9 | async def on_message(self, message): 10 | if message.content.startswith('!editme'): 11 | msg = await message.channel.send('10') 12 | await asyncio.sleep(3.0) 13 | await msg.edit(content='40') 14 | 15 | async def on_message_edit(self, before, after): 16 | msg = f'**{before.author}** edited their message:\n{before.content} -> {after.content}' 17 | await before.channel.send(msg) 18 | 19 | client = MyClient() 20 | client.run('token') 21 | -------------------------------------------------------------------------------- /examples/application_commands/min_max_values.py: -------------------------------------------------------------------------------- 1 | import diskord 2 | from diskord.ext import commands 3 | 4 | bot = commands.Bot(command_prefix='>') 5 | 6 | 7 | # minimum values and maximum values can be set on an integer or number (float) 8 | # option types using `min_value` and `max_value` keyword arguments in `option()` 9 | 10 | # in the command below, the "quantity" option cannot be lower then "1" and higher then "10" 11 | @bot.slash_command(name='buy-cookies') 12 | @bot.application.option('quantity', min_value=1, max_value=10, description='The quantity of cookies to buy. Can be higher then 0 and max 10.') 13 | async def buy_cookies(ctx, quantity: int): 14 | await ctx.send(f'You bought {quantity} cookies.') 15 | 16 | bot.run('token') 17 | -------------------------------------------------------------------------------- /examples/deleted.py: -------------------------------------------------------------------------------- 1 | import diskord 2 | 3 | class MyClient(diskord.Client): 4 | async def on_ready(self): 5 | print(f'Logged in as {self.user} (ID: {self.user.id})') 6 | print('------') 7 | 8 | async def on_message(self, message): 9 | if message.content.startswith('!deleteme'): 10 | msg = await message.channel.send('I will delete myself now...') 11 | await msg.delete() 12 | 13 | # this also works 14 | await message.channel.send('Goodbye in 3 seconds...', delete_after=3.0) 15 | 16 | async def on_message_delete(self, message): 17 | msg = f'{message.author} has deleted the message: {message.content}' 18 | await message.channel.send(msg) 19 | 20 | client = MyClient() 21 | client.run('token') 22 | -------------------------------------------------------------------------------- /docs/extensions/nitpick_file_ignorer.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from sphinx.application import Sphinx 4 | from sphinx.util import logging as sphinx_logging 5 | 6 | 7 | class NitpickFileIgnorer(logging.Filter): 8 | 9 | def __init__(self, app: Sphinx) -> None: 10 | self.app = app 11 | super().__init__() 12 | 13 | def filter(self, record: sphinx_logging.SphinxLogRecord) -> bool: 14 | if getattr(record, 'type', None) == 'ref': 15 | return record.location.get('refdoc') not in self.app.config.nitpick_ignore_files 16 | return True 17 | 18 | 19 | def setup(app: Sphinx): 20 | app.add_config_value('nitpick_ignore_files', [], '') 21 | f = NitpickFileIgnorer(app) 22 | sphinx_logging.getLogger('sphinx.transforms.post_transforms').logger.addFilter(f) 23 | -------------------------------------------------------------------------------- /examples/application_commands/basic.py: -------------------------------------------------------------------------------- 1 | import diskord 2 | from diskord.ext import commands 3 | 4 | bot = commands.Bot(command_prefix='!') 5 | 6 | @bot.event 7 | async def on_ready(): 8 | print('ready') 9 | 10 | # slash commands can be used by writing "/" in the chat 11 | @bot.slash_command() 12 | async def ping(ctx): 13 | await ctx.respond('pong') 14 | 15 | # message commands can be used by right clicking a user > apps > command 16 | @bot.user_command() 17 | async def slap(ctx, user): 18 | await ctx.respond(f'{ctx.author.name} slapped {user.name}') 19 | 20 | # message commands can be used by right clicking a message > apps > command 21 | @bot.message_command() 22 | async def say(ctx, message): 23 | await ctx.respond(f'{ctx.author.name} requested to say: {message.content}') 24 | 25 | bot.run('token') 26 | -------------------------------------------------------------------------------- /docs/locale/ja/LC_MESSAGES/sphinx.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: discordpy\n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "POT-Creation-Date: 2019-06-22 09:35-0400\n" 6 | "PO-Revision-Date: 2020-10-24 02:41\n" 7 | "Last-Translator: \n" 8 | "Language-Team: Japanese\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "Plural-Forms: nplurals=1; plural=0;\n" 13 | "X-Crowdin-Project: discordpy\n" 14 | "X-Crowdin-Project-ID: 362783\n" 15 | "X-Crowdin-Language: ja\n" 16 | "X-Crowdin-File: sphinx.pot\n" 17 | "X-Crowdin-File-ID: 70\n" 18 | "Language: ja_JP\n" 19 | 20 | #: ../../_templates/layout.html:24 21 | msgid "Created using Sphinx %(sphinx_version)s." 22 | msgstr "Sphinx %(sphinx_version)s で作成されました。" 23 | 24 | -------------------------------------------------------------------------------- /docs/_templates/genindex.html: -------------------------------------------------------------------------------- 1 | {%- extends "basic/genindex.html" %} 2 | 3 | {% block body %} 4 | {{ super() }} 5 | 6 | 27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /examples/application_commands/option_choices.py: -------------------------------------------------------------------------------- 1 | import diskord 2 | from diskord.ext import commands 3 | 4 | bot = commands.Bot(command_prefix='!') 5 | 6 | # you can add certain choices to an option by passing in choices kwarg in slash_option decorator! 7 | @bot.slash_command() 8 | @bot.application.option('item', description='The item to buy.', choices=[ 9 | # the name is represents the name of choice that is displayed in discord and value is what is passed in command function 10 | diskord.OptionChoice(name='Cake', value='cake'), 11 | diskord.OptionChoice(name='Cookie', value='cookie'), 12 | diskord.OptionChoice(name='Candy', value='candy'), 13 | ]) 14 | @diskord.application.option('quantity', description='The quantity of items to buy. Defaults to 1') 15 | async def buy(ctx, item, quantity: int = 1): 16 | await ctx.respond(f'you bought {quantity} {item}(s)!') 17 | 18 | bot.run('token') 19 | -------------------------------------------------------------------------------- /examples/background_task_asyncio.py: -------------------------------------------------------------------------------- 1 | import diskord 2 | import asyncio 3 | 4 | class MyClient(diskord.Client): 5 | def __init__(self, *args, **kwargs): 6 | super().__init__(*args, **kwargs) 7 | 8 | # create the background task and run it in the background 9 | self.bg_task = self.loop.create_task(self.my_background_task()) 10 | 11 | async def on_ready(self): 12 | print(f'Logged in as {self.user} (ID: {self.user.id})') 13 | print('------') 14 | 15 | async def my_background_task(self): 16 | await self.wait_until_ready() 17 | counter = 0 18 | channel = self.get_channel(1234567) # channel ID goes here 19 | while not self.is_closed(): 20 | counter += 1 21 | await channel.send(counter) 22 | await asyncio.sleep(60) # task runs every 60 seconds 23 | 24 | 25 | client = MyClient() 26 | client.run('token') 27 | -------------------------------------------------------------------------------- /examples/application_commands/subcommand_groups.py: -------------------------------------------------------------------------------- 1 | import diskord 2 | from diskord.ext import commands 3 | 4 | bot = commands.Bot(command_prefix='!') 5 | 6 | # want to nest subcommands and groups in slash commands? you can do that! 7 | 8 | # note: once you define subcommands and groups your top command or the parent command is not valid as it is i.e you 9 | # can't do "/top" but you have to specify the subcommand. 10 | @bot.slash_command() 11 | async def top(ctx): 12 | pass 13 | 14 | @top.sub_command() 15 | async def sub(ctx): 16 | await ctx.respond('a subcommand!') 17 | 18 | # the usage would be "/top sub" 19 | 20 | # you can also make command groups 21 | 22 | @bot.slash_command() 23 | async def grouptop(ctx): 24 | pass 25 | 26 | @grouptop.sub_command_group() 27 | async def group(ctx): 28 | pass 29 | 30 | @group.sub_command() 31 | async def groupcmd(ctx): 32 | await ctx.respond('i am in a command in a group!') 33 | 34 | # the usage would be "/grouptop group groupcmd" 35 | bot.run('token') 36 | -------------------------------------------------------------------------------- /examples/background_task.py: -------------------------------------------------------------------------------- 1 | from diskord.ext import tasks 2 | 3 | import diskord 4 | 5 | class MyClient(diskord.Client): 6 | def __init__(self, *args, **kwargs): 7 | super().__init__(*args, **kwargs) 8 | 9 | # an attribute we can access from our task 10 | self.counter = 0 11 | 12 | # start the task to run in the background 13 | self.my_background_task.start() 14 | 15 | async def on_ready(self): 16 | print(f'Logged in as {self.user} (ID: {self.user.id})') 17 | print('------') 18 | 19 | @tasks.loop(seconds=60) # task runs every 60 seconds 20 | async def my_background_task(self): 21 | channel = self.get_channel(1234567) # channel ID goes here 22 | self.counter += 1 23 | await channel.send(self.counter) 24 | 25 | @my_background_task.before_loop 26 | async def before_my_task(self): 27 | await self.wait_until_ready() # wait until the bot logs in 28 | 29 | client = MyClient() 30 | client.run('token') 31 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Summary 4 | 5 | 6 | ## Checklist 7 | 8 | 9 | 10 | - [ ] If code changes were made then they have been tested. 11 | - [ ] I have updated the documentation to reflect the changes. 12 | - [ ] This PR fixes an issue. 13 | - [ ] This PR adds something new (e.g. new method or parameters). 14 | - [ ] This PR is a breaking change (e.g. methods or parameters removed/renamed) 15 | - [ ] This PR is **not** a code change (e.g. documentation, README, ...) 16 | 17 | ## Confirmations 18 | 19 | 20 | - [ ] I have checked the open PRs and no PR exists like this one. 21 | - [ ] My PR focuses on only one issue/change. 22 | -------------------------------------------------------------------------------- /docs/extensions/exception_hierarchy.py: -------------------------------------------------------------------------------- 1 | from docutils.parsers.rst import Directive 2 | from docutils.parsers.rst import states, directives 3 | from docutils.parsers.rst.roles import set_classes 4 | from docutils import nodes 5 | from sphinx.locale import _ 6 | 7 | class exception_hierarchy(nodes.General, nodes.Element): 8 | pass 9 | 10 | def visit_exception_hierarchy_node(self, node): 11 | self.body.append(self.starttag(node, 'div', CLASS='exception-hierarchy-content')) 12 | 13 | def depart_exception_hierarchy_node(self, node): 14 | self.body.append('\n') 15 | 16 | class ExceptionHierarchyDirective(Directive): 17 | has_content = True 18 | 19 | def run(self): 20 | self.assert_has_content() 21 | node = exception_hierarchy('\n'.join(self.content)) 22 | self.state.nested_parse(self.content, self.content_offset, node) 23 | return [node] 24 | 25 | def setup(app): 26 | app.add_node(exception_hierarchy, html=(visit_exception_hierarchy_node, depart_exception_hierarchy_node)) 27 | app.add_directive('exception_hierarchy', ExceptionHierarchyDirective) 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-present Rapptz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 16 | OR 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 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /docs/_static/copy.js: -------------------------------------------------------------------------------- 1 | const COPY = "content_copy"; 2 | const COPIED = "done"; 3 | 4 | const copy = async (obj) => { 5 | // {{text}} 6 | await navigator.clipboard.writeText(obj.children[1].innerText).then( 7 | () => { 8 | let icon = obj.children[0].children[0]; 9 | icon.textContent = COPIED; 10 | setTimeout(() => (icon.textContent = COPY), 2500); 11 | }, 12 | (r) => alert('Could not copy codeblock:\n' + r.toString()) 13 | ); 14 | }; 15 | 16 | document.addEventListener("DOMContentLoaded", () => { 17 | let allCodeblocks = document.querySelectorAll("div[class='highlight']"); 18 | 19 | for (let codeblock of allCodeblocks) { 20 | codeblock.parentNode.className += " relative-copy"; 21 | let copyEl = document.createElement("span"); 22 | copyEl.addEventListener('click', () => copy(codeblock)); 23 | copyEl.className = "copy"; 24 | copyEl.setAttribute("aria-label", "Copy Code"); 25 | copyEl.setAttribute("title", "Copy Code"); 26 | 27 | let copyIcon = document.createElement("span"); 28 | copyIcon.className = "material-icons"; 29 | copyIcon.textContent = COPY; 30 | copyEl.append(copyIcon); 31 | 32 | codeblock.prepend(copyEl); 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /examples/guessing_game.py: -------------------------------------------------------------------------------- 1 | import diskord 2 | import random 3 | import asyncio 4 | 5 | class MyClient(diskord.Client): 6 | async def on_ready(self): 7 | print(f'Logged in as {self.user} (ID: {self.user.id})') 8 | print('------') 9 | 10 | async def on_message(self, message): 11 | # we do not want the bot to reply to itself 12 | if message.author.id == self.user.id: 13 | return 14 | 15 | if message.content.startswith('$guess'): 16 | await message.channel.send('Guess a number between 1 and 10.') 17 | 18 | def is_correct(m): 19 | return m.author == message.author and m.content.isdigit() 20 | 21 | answer = random.randint(1, 10) 22 | 23 | try: 24 | guess = await self.wait_for('message', check=is_correct, timeout=5.0) 25 | except asyncio.TimeoutError: 26 | return await message.channel.send(f'Sorry, you took too long it was {answer}.') 27 | 28 | if int(guess.content) == answer: 29 | await message.channel.send('You are right!') 30 | else: 31 | await message.channel.send(f'Oops. It is actually {answer}.') 32 | 33 | client = MyClient() 34 | client.run('token') 35 | -------------------------------------------------------------------------------- /diskord/types/snowflake.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 List, Union 26 | 27 | Snowflake = Union[str, int] 28 | SnowflakeList = List[Snowflake] 29 | -------------------------------------------------------------------------------- /examples/views/link.py: -------------------------------------------------------------------------------- 1 | from diskord.ext import commands 2 | 3 | import diskord 4 | from urllib.parse import quote_plus 5 | 6 | class GoogleBot(commands.Bot): 7 | def __init__(self): 8 | super().__init__(command_prefix=commands.when_mentioned_or('$')) 9 | 10 | async def on_ready(self): 11 | print(f'Logged in as {self.user} (ID: {self.user.id})') 12 | print('------') 13 | 14 | 15 | # Define a simple View that gives us a google link button. 16 | # We take in `query` as the query that the command author requests for 17 | class Google(diskord.ui.View): 18 | def __init__(self, query: str): 19 | super().__init__() 20 | # we need to quote the query string to make a valid url. Discord will raise an error if it isn't valid. 21 | query = quote_plus(query) 22 | url = f'https://www.google.com/search?q={query}' 23 | 24 | # Link buttons cannot be made with the decorator 25 | # Therefore we have to manually create one. 26 | # We add the quoted url to the button, and add the button to the view. 27 | self.add_item(diskord.ui.Button(label='Click Here', url=url)) 28 | 29 | 30 | bot = GoogleBot() 31 | 32 | 33 | @bot.command() 34 | async def google(ctx: commands.Context, *, query: str): 35 | """Returns a google link for a query""" 36 | await ctx.send(f'Google Result for: `{query}`', view=Google(query)) 37 | 38 | 39 | bot.run('token') 40 | -------------------------------------------------------------------------------- /diskord/application/types.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015-present Rapptz 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the "Software"), 10 | to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | """ 26 | from __future__ import annotations 27 | 28 | from typing import Callable, TYPE_CHECKING 29 | from ..interactions import InteractionContext 30 | 31 | Check = Callable[[InteractionContext, "Context"], bool] -------------------------------------------------------------------------------- /docs/locale/ja/LC_MESSAGES/ext/commands/index.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: discordpy\n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "POT-Creation-Date: 2019-06-22 09:35-0400\n" 6 | "PO-Revision-Date: 2020-10-24 02:41\n" 7 | "Last-Translator: \n" 8 | "Language-Team: Japanese\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "Plural-Forms: nplurals=1; plural=0;\n" 13 | "X-Crowdin-Project: discordpy\n" 14 | "X-Crowdin-Project-ID: 362783\n" 15 | "X-Crowdin-Language: ja\n" 16 | "X-Crowdin-File: /ext/commands/index.pot\n" 17 | "X-Crowdin-File-ID: 66\n" 18 | "Language: ja_JP\n" 19 | 20 | #: ../../ext/commands/index.rst:4 21 | msgid "``discord.ext.commands`` -- Bot commands framework" 22 | msgstr "``discord.ext.commands`` -- ボットコマンドのフレームワーク" 23 | 24 | #: ../../ext/commands/index.rst:6 25 | msgid "``discord.py`` offers a lower level aspect on interacting with Discord. Often times, the library is used for the creation of bots. However this task can be daunting and confusing to get correctly the first time. Many times there comes a repetition in creating a bot command framework that is extensible, flexible, and powerful. For this reason, ``discord.py`` comes with an extension library that handles this for you." 26 | msgstr "``discord.py`` は、Discordと連携するための低レベルな機能を提供します。ときどき、このライブラリーはBotの作成に用いられています。しかしこの作業を正しくやるのは最初のときは気が重くややこしいものです。何度も繰り返し、拡張可能で柔軟、そしてパワフルなBotコマンドフレームワークを作成しています。この理由より、 ``discord.py`` にはこれを扱う拡張ライブラリがついてきます。" 27 | 28 | -------------------------------------------------------------------------------- /examples/views/counter.py: -------------------------------------------------------------------------------- 1 | from diskord.ext import commands 2 | 3 | import diskord 4 | 5 | 6 | class CounterBot(commands.Bot): 7 | def __init__(self): 8 | super().__init__(command_prefix=commands.when_mentioned_or('$')) 9 | 10 | async def on_ready(self): 11 | print(f'Logged in as {self.user} (ID: {self.user.id})') 12 | print('------') 13 | 14 | 15 | # Define a simple View that gives us a counter button 16 | class Counter(diskord.ui.View): 17 | 18 | # Define the actual button 19 | # When pressed, this increments the number displayed until it hits 5. 20 | # When it hits 5, the counter button is disabled and it turns green. 21 | # note: The name of the function does not matter to the library 22 | @diskord.ui.button(label='0', style=diskord.ButtonStyle.red) 23 | async def count(self, button: diskord.ui.Button, interaction: diskord.Interaction): 24 | number = int(button.label) if button.label else 0 25 | if number + 1 >= 5: 26 | button.style = diskord.ButtonStyle.green 27 | button.disabled = True 28 | button.label = str(number + 1) 29 | 30 | # Make sure to update the message with our updated selves 31 | await interaction.response.edit_message(view=self) 32 | 33 | 34 | bot = CounterBot() 35 | 36 | 37 | @bot.command() 38 | async def counter(ctx: commands.Context): 39 | """Starts a counter for pressing.""" 40 | await ctx.send('Press!', view=Counter()) 41 | 42 | 43 | bot.run('token') 44 | -------------------------------------------------------------------------------- /diskord/types/gateway.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 26 | 27 | 28 | class SessionStartLimit(TypedDict): 29 | total: int 30 | remaining: int 31 | reset_after: int 32 | max_concurrency: int 33 | 34 | 35 | class Gateway(TypedDict): 36 | url: str 37 | 38 | 39 | class GatewayBot(Gateway): 40 | shards: int 41 | session_start_limit: SessionStartLimit 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest a feature for this library 3 | labels: feature request 4 | body: 5 | - type: input 6 | attributes: 7 | label: Summary 8 | description: > 9 | A short summary of what your feature request is. 10 | validations: 11 | required: true 12 | - type: dropdown 13 | attributes: 14 | multiple: false 15 | label: What is the feature request for? 16 | options: 17 | - The core library 18 | - diskord.ext.commands 19 | - diskord.ext.tasks 20 | - The documentation 21 | validations: 22 | required: true 23 | - type: textarea 24 | attributes: 25 | label: The Problem 26 | description: > 27 | What problem is your feature trying to solve? 28 | What becomes easier or possible when this feature is implemented? 29 | validations: 30 | required: true 31 | - type: textarea 32 | attributes: 33 | label: The Ideal Solution 34 | description: > 35 | What is your ideal solution to the problem? 36 | What would you like this feature to do? 37 | validations: 38 | required: true 39 | - type: textarea 40 | attributes: 41 | label: The Current Solution 42 | description: > 43 | What is the current solution to the problem, if any? 44 | validations: 45 | required: false 46 | - type: textarea 47 | attributes: 48 | label: Additional Context 49 | description: If there is anything else to say, please do so here. 50 | -------------------------------------------------------------------------------- /docs/version_guarantees.rst: -------------------------------------------------------------------------------- 1 | .. _version_guarantees: 2 | 3 | Version Guarantees 4 | ===================== 5 | 6 | The library follows a `semantic versioning principle `_ which means that the major version is updated every time there is an incompatible API change. However due to the lack of guarantees on the Discord side when it comes to breaking changes along with the fairly dynamic nature of Python it can be hard to discern what can be considered a breaking change and what isn't. 7 | 8 | The first thing to keep in mind is that breaking changes only apply to **publicly documented functions and classes**. If it's not listed in the documentation here then it is not part of the public API and is thus bound to change. This includes attributes that start with an underscore or functions without an underscore that are not documented. 9 | 10 | .. note:: 11 | 12 | The examples below are non-exhaustive. 13 | 14 | Examples of Breaking Changes 15 | ------------------------------ 16 | 17 | - Changing the default parameter value to something else. 18 | - Renaming a function without an alias to an old function. 19 | - Adding or removing parameters to an event. 20 | 21 | Examples of Non-Breaking Changes 22 | ---------------------------------- 23 | 24 | - Adding or removing private underscored attributes. 25 | - Adding an element into the ``__slots__`` of a data class. 26 | - Changing the behaviour of a function to fix a bug. 27 | - Changes in the documentation. 28 | - Modifying the internal HTTP handling. 29 | - Upgrading the dependencies to a new version, major or otherwise. 30 | 31 | -------------------------------------------------------------------------------- /diskord/bin/COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 1994-2013 Xiph.Org Foundation and contributors 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 7 | - Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | - Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | - Neither the name of the Xiph.Org Foundation nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION 22 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /examples/application_commands/options.py: -------------------------------------------------------------------------------- 1 | import diskord 2 | from diskord.ext import commands 3 | 4 | bot = commands.Bot(command_prefix='!') 5 | 6 | @bot.event 7 | async def on_ready(): 8 | print('ready') 9 | 10 | # this example will go through the process of creating options in a slash command. 11 | # Bot.slash_option decorator can be used to make options. 12 | @bot.slash_command() 13 | # the first argument "text" is the name of option and the name of argument 14 | # in command function that represents this option. 15 | @diskord.application.option('text', description='The text to say!') 16 | async def say(ctx, text): 17 | await ctx.respond(f'{ctx.author.name} said: {text}') 18 | 19 | # We can do similar for users, roles, channels etc. 20 | @diskord.slash_command() 21 | @diskord.application.option('user', description='The user to slap!') 22 | @diskord.application.option('amount', description='Amounts of slaps! Defaults to 1') 23 | # annotating user as "diskord.User" will make option type a user and same for "int" 24 | # setting a default value to an argument will make that option optional. 25 | async def slap(ctx, user: diskord.User, amount: int = 1): 26 | await ctx.respond(f'{ctx.author.name} slapped {user.name}, {amount} times!') 27 | 28 | # available types are: 29 | # diskord.Role: For role 30 | # diskord.abc.GuildChannel: for channels 31 | # diskord.Member or diskord.User: for users 32 | # typing.Union[diskord.Member, diskord.Role]: Any mentionable i.e user or role 33 | # int: for integers 34 | # float: for numbers 35 | # str: for strings 36 | # bool: for booleans 37 | bot.run('token') 38 | 39 | -------------------------------------------------------------------------------- /docs/extensions/resourcelinks.py: -------------------------------------------------------------------------------- 1 | # Credit to sphinx.ext.extlinks for being a good starter 2 | # Copyright 2007-2020 by the Sphinx team 3 | # Licensed under BSD. 4 | 5 | from typing import Any, Dict, List, Tuple 6 | 7 | from docutils import nodes, utils 8 | from docutils.nodes import Node, system_message 9 | from docutils.parsers.rst.states import Inliner 10 | 11 | import sphinx 12 | from sphinx.application import Sphinx 13 | from sphinx.util.nodes import split_explicit_title 14 | from sphinx.util.typing import RoleFunction 15 | 16 | 17 | def make_link_role(resource_links: Dict[str, str]) -> RoleFunction: 18 | def role( 19 | typ: str, 20 | rawtext: str, 21 | text: str, 22 | lineno: int, 23 | inliner: Inliner, 24 | options: Dict = {}, 25 | content: List[str] = [] 26 | ) -> Tuple[List[Node], List[system_message]]: 27 | 28 | text = utils.unescape(text) 29 | has_explicit_title, title, key = split_explicit_title(text) 30 | full_url = resource_links[key] 31 | if not has_explicit_title: 32 | title = full_url 33 | pnode = nodes.reference(title, title, internal=False, refuri=full_url) 34 | return [pnode], [] 35 | return role 36 | 37 | 38 | def add_link_role(app: Sphinx) -> None: 39 | app.add_role('resource', make_link_role(app.config.resource_links)) 40 | 41 | def setup(app: Sphinx) -> Dict[str, Any]: 42 | app.add_config_value('resource_links', {}, 'env') 43 | app.connect('builder-inited', add_link_role) 44 | return {'version': sphinx.__display_version__, 'parallel_read_safe': True} 45 | -------------------------------------------------------------------------------- /diskord/types/welcome_screen.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 List, Optional, TypedDict 28 | from .snowflake import Snowflake 29 | 30 | 31 | class WelcomeScreen(TypedDict): 32 | description: str 33 | welcome_channels: List[WelcomeScreenChannel] 34 | 35 | 36 | class WelcomeScreenChannel(TypedDict): 37 | channel_id: Snowflake 38 | description: str 39 | emoji_id: Optional[Snowflake] 40 | emoji_name: Optional[str] 41 | -------------------------------------------------------------------------------- /diskord/types/team.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 TypedDict, List, Optional 28 | 29 | from .user import PartialUser 30 | from .snowflake import Snowflake 31 | 32 | 33 | class TeamMember(TypedDict): 34 | user: PartialUser 35 | membership_state: int 36 | permissions: List[str] 37 | team_id: Snowflake 38 | 39 | 40 | class Team(TypedDict): 41 | id: Snowflake 42 | name: str 43 | owner_id: Snowflake 44 | members: List[TeamMember] 45 | icon: Optional[str] 46 | -------------------------------------------------------------------------------- /diskord/types/emoji.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 Optional, TypedDict 26 | from .snowflake import Snowflake, SnowflakeList 27 | from .user import User 28 | 29 | 30 | class PartialEmoji(TypedDict): 31 | id: Optional[Snowflake] 32 | name: Optional[str] 33 | 34 | 35 | class Emoji(PartialEmoji, total=False): 36 | roles: SnowflakeList 37 | user: User 38 | require_colons: bool 39 | managed: bool 40 | animated: bool 41 | available: bool 42 | 43 | 44 | class EditEmoji(TypedDict): 45 | name: str 46 | roles: Optional[SnowflakeList] 47 | -------------------------------------------------------------------------------- /diskord/types/user.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 .snowflake import Snowflake 26 | from typing import Literal, Optional, TypedDict 27 | 28 | 29 | class PartialUser(TypedDict): 30 | id: Snowflake 31 | username: str 32 | discriminator: str 33 | avatar: Optional[str] 34 | 35 | 36 | PremiumType = Literal[0, 1, 2] 37 | 38 | 39 | class User(PartialUser, total=False): 40 | bot: bool 41 | system: bool 42 | mfa_enabled: bool 43 | local: str 44 | verified: bool 45 | email: Optional[str] 46 | flags: int 47 | premium_type: PremiumType 48 | public_flags: int 49 | -------------------------------------------------------------------------------- /diskord/types/role.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 TypedDict 28 | from .snowflake import Snowflake 29 | 30 | 31 | class _RoleOptional(TypedDict, total=False): 32 | tags: RoleTags 33 | 34 | 35 | class Role(_RoleOptional): 36 | id: Snowflake 37 | name: str 38 | color: int 39 | hoist: bool 40 | position: int 41 | permissions: str 42 | managed: bool 43 | mentionable: bool 44 | icon: str 45 | 46 | 47 | class RoleTags(TypedDict, total=False): 48 | bot_id: Snowflake 49 | integration_id: Snowflake 50 | premium_subscriber: None 51 | -------------------------------------------------------------------------------- /docs/logging.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | .. versionadded:: 0.6.0 4 | .. _logging_setup: 5 | 6 | Setting Up Logging 7 | =================== 8 | 9 | *diskord* logs errors and debug information via the :mod:`logging` python 10 | module. It is strongly recommended that the logging module is 11 | configured, as no errors or warnings will be output if it is not set up. 12 | Configuration of the ``logging`` module can be as simple as:: 13 | 14 | import logging 15 | 16 | logging.basicConfig(level=logging.INFO) 17 | 18 | Placed at the start of the application. This will output the logs from 19 | discord as well as other libraries that use the ``logging`` module 20 | directly to the console. 21 | 22 | The optional ``level`` argument specifies what level of events to log 23 | out and can be any of ``CRITICAL``, ``ERROR``, ``WARNING``, ``INFO``, and 24 | ``DEBUG`` and if not specified defaults to ``WARNING``. 25 | 26 | More advanced setups are possible with the :mod:`logging` module. For 27 | example to write the logs to a file called ``diskord.log`` instead of 28 | outputting them to the console the following snippet can be used:: 29 | 30 | import diskord 31 | import logging 32 | 33 | logger = logging.getLogger('diskord') 34 | logger.setLevel(logging.DEBUG) 35 | handler = logging.FileHandler(filename='diskord.log', encoding='utf-8', mode='w') 36 | handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) 37 | logger.addHandler(handler) 38 | 39 | This is recommended, especially at verbose levels such as ``INFO`` 40 | and ``DEBUG``, as there are a lot of events logged and it would clog the 41 | stdout of your program. 42 | 43 | 44 | 45 | For more information, check the documentation and tutorial of the 46 | :mod:`logging` module. 47 | -------------------------------------------------------------------------------- /diskord/mixins.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 | __all__ = ( 26 | "EqualityComparable", 27 | "Hashable", 28 | ) 29 | 30 | 31 | class EqualityComparable: 32 | __slots__ = () 33 | 34 | id: int 35 | 36 | def __eq__(self, other: object) -> bool: 37 | return isinstance(other, self.__class__) and other.id == self.id 38 | 39 | def __ne__(self, other: object) -> bool: 40 | if isinstance(other, self.__class__): 41 | return other.id != self.id 42 | return True 43 | 44 | 45 | class Hashable(EqualityComparable): 46 | __slots__ = () 47 | 48 | def __hash__(self) -> int: 49 | return self.id >> 22 50 | -------------------------------------------------------------------------------- /diskord/types/template.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 Optional, TypedDict 28 | from .snowflake import Snowflake 29 | from .user import User 30 | from .guild import Guild 31 | 32 | 33 | class CreateTemplate(TypedDict): 34 | name: str 35 | icon: Optional[bytes] 36 | 37 | 38 | class Template(TypedDict): 39 | code: str 40 | name: str 41 | description: Optional[str] 42 | usage_count: int 43 | creator_id: Snowflake 44 | creator: User 45 | created_at: str 46 | updated_at: str 47 | source_guild_id: Snowflake 48 | serialized_source_guild: Guild 49 | is_dirty: Optional[bool] 50 | -------------------------------------------------------------------------------- /docs/extensions/details.py: -------------------------------------------------------------------------------- 1 | from docutils.parsers.rst import Directive 2 | from docutils.parsers.rst import states, directives 3 | from docutils.parsers.rst.roles import set_classes 4 | from docutils import nodes 5 | 6 | class details(nodes.General, nodes.Element): 7 | pass 8 | 9 | class summary(nodes.General, nodes.Element): 10 | pass 11 | 12 | def visit_details_node(self, node): 13 | self.body.append(self.starttag(node, 'details', CLASS=node.attributes.get('class', ''))) 14 | 15 | def visit_summary_node(self, node): 16 | self.body.append(self.starttag(node, 'summary', CLASS=node.attributes.get('summary-class', ''))) 17 | self.body.append(node.rawsource) 18 | 19 | def depart_details_node(self, node): 20 | self.body.append('\n') 21 | 22 | def depart_summary_node(self, node): 23 | self.body.append('') 24 | 25 | class DetailsDirective(Directive): 26 | final_argument_whitespace = True 27 | optional_arguments = 1 28 | 29 | option_spec = { 30 | 'class': directives.class_option, 31 | 'summary-class': directives.class_option, 32 | } 33 | 34 | has_content = True 35 | 36 | def run(self): 37 | set_classes(self.options) 38 | self.assert_has_content() 39 | 40 | text = '\n'.join(self.content) 41 | node = details(text, **self.options) 42 | 43 | if self.arguments: 44 | summary_node = summary(self.arguments[0], **self.options) 45 | summary_node.source, summary_node.line = self.state_machine.get_source_and_line(self.lineno) 46 | node += summary_node 47 | 48 | self.state.nested_parse(self.content, self.content_offset, node) 49 | return [node] 50 | 51 | def setup(app): 52 | app.add_node(details, html=(visit_details_node, depart_details_node)) 53 | app.add_node(summary, html=(visit_summary_node, depart_summary_node)) 54 | app.add_directive('details', DetailsDirective) 55 | 56 | -------------------------------------------------------------------------------- /examples/custom_context.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import diskord 4 | from diskord.ext import commands 5 | 6 | 7 | class MyContext(commands.Context): 8 | async def tick(self, value): 9 | # reacts to the message with an emoji 10 | # depending on whether value is True or False 11 | # if its True, it'll add a green check mark 12 | # otherwise, it'll add a red cross mark 13 | emoji = '\N{WHITE HEAVY CHECK MARK}' if value else '\N{CROSS MARK}' 14 | try: 15 | # this will react to the command author's message 16 | await self.message.add_reaction(emoji) 17 | except diskord.HTTPException: 18 | # sometimes errors occur during this, for example 19 | # maybe you don't have permission to do that 20 | # we don't mind, so we can just ignore them 21 | pass 22 | 23 | 24 | class MyBot(commands.Bot): 25 | async def get_context(self, message, *, cls=MyContext): 26 | # when you override this method, you pass your new Context 27 | # subclass to the super() method, which tells the bot to 28 | # use the new MyContext class 29 | return await super().get_context(message, cls=cls) 30 | 31 | 32 | bot = MyBot(command_prefix='!') 33 | 34 | @bot.command() 35 | async def guess(ctx, number: int): 36 | """ Guess a random number from 1 to 6. """ 37 | # explained in a previous example, this gives you 38 | # a random number from 1-6 39 | value = random.randint(1, 6) 40 | # with your new helper function, you can add a 41 | # green check mark if the guess was correct, 42 | # or a red cross mark if it wasn't 43 | await ctx.tick(number == value) 44 | 45 | # IMPORTANT: You shouldn't hard code your token 46 | # these are very important, and leaking them can 47 | # let people do very malicious things with your 48 | # bot. Try to use a file or something to keep 49 | # them private, and don't commit it to GitHub 50 | token = "your token here" 51 | bot.run(token) 52 | -------------------------------------------------------------------------------- /diskord/types/member.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 26 | from .snowflake import SnowflakeList 27 | from .user import User 28 | 29 | 30 | class Nickname(TypedDict): 31 | nick: str 32 | 33 | 34 | class PartialMember(TypedDict): 35 | roles: SnowflakeList 36 | joined_at: str 37 | deaf: str 38 | mute: str 39 | 40 | 41 | class Member(PartialMember, total=False): 42 | avatar: str 43 | user: User 44 | nick: str 45 | premium_since: str 46 | pending: bool 47 | permissions: str 48 | 49 | 50 | class _OptionalMemberWithUser(PartialMember, total=False): 51 | avatar: str 52 | nick: str 53 | premium_since: str 54 | pending: bool 55 | permissions: str 56 | 57 | 58 | class MemberWithUser(_OptionalMemberWithUser): 59 | user: User 60 | 61 | 62 | class UserWithMember(User, total=False): 63 | member: _OptionalMemberWithUser 64 | -------------------------------------------------------------------------------- /examples/application_commands/autocomplete.py: -------------------------------------------------------------------------------- 1 | import diskord 2 | from diskord.ext import commands 3 | 4 | bot = commands.Bot(command_prefix='>') 5 | 6 | # these are the list of items that will be 7 | # shown as choices in autocomplete. 8 | ITEMS = ['Bun', 'Cake', 'Cookie', 'Bread', 'Orange Juice'] 9 | 10 | # this function would autocomplete the option choices. You can use this 11 | # function to do your processing for example getting some data from 12 | # a database or from an API but this out of scope of this simple example. 13 | 14 | async def autocomplete_items(value: str, option: diskord.application.Option, interaction: diskord.Interaction): 15 | if not value: 16 | # there is a chance user has not entered any value, i.e empty string 17 | # in which case, we will simply return all the autocomplete items. 18 | ac_items = [diskord.OptionChoice(name=item, value=item) for item in ITEMS] 19 | else: 20 | # in this case, user has input something and we will return 21 | # the items that start with provided query... 22 | 23 | # lowercase the value because case doesn't matter for us and we don't 24 | # want autocomplete to break if the value case is not same as items case. 25 | value = value.lower() 26 | 27 | # now return the items whose names start with 28 | # the user's entered value. We also lowered case the item name. 29 | ac_items = [diskord.OptionChoice(name=item, value=item) for item in ITEMS if item.lower().startswith(value)] 30 | 31 | # discord API does not allow showing more then 25 choices in autocomplete so we will return 32 | # the first 25 items otherwise it would raise HTTPException. 33 | return ac_items[:25] 34 | 35 | 36 | # the command that would autocomplete 37 | # autocomplete function can be specified in `autocomplete` keyword argument. note: autocomplete is only supported 38 | # for string type options. 39 | 40 | @bot.slash_command() 41 | @diskord.application.option('item', autocomplete=autocomplete_items) 42 | async def buy(ctx, item: str): 43 | await ctx.send(f'You bought {item}!') 44 | 45 | bot.run('token') 46 | -------------------------------------------------------------------------------- /diskord/types/widget.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 List, Optional, TypedDict 26 | from .activity import Activity 27 | from .snowflake import Snowflake 28 | from .user import User 29 | 30 | 31 | class WidgetChannel(TypedDict): 32 | id: Snowflake 33 | name: str 34 | position: int 35 | 36 | 37 | class WidgetMember(User, total=False): 38 | nick: str 39 | game: Activity 40 | status: str 41 | avatar_url: str 42 | deaf: bool 43 | self_deaf: bool 44 | mute: bool 45 | self_mute: bool 46 | suppress: bool 47 | 48 | 49 | class _WidgetOptional(TypedDict, total=False): 50 | channels: List[WidgetChannel] 51 | members: List[WidgetMember] 52 | presence_count: int 53 | 54 | 55 | class Widget(_WidgetOptional): 56 | id: Snowflake 57 | name: str 58 | instant_invite: str 59 | 60 | 61 | class WidgetSettings(TypedDict): 62 | enabled: bool 63 | channel_id: Optional[Snowflake] 64 | -------------------------------------------------------------------------------- /examples/application_commands/autocomplete_multiple_options.py: -------------------------------------------------------------------------------- 1 | import diskord 2 | from diskord.ext import commands 3 | 4 | bot = commands.Bot(command_prefix='>') 5 | 6 | # This example showcases the way of showing dynamic choices depending on the value 7 | # that the user passes in previous option. 8 | 9 | async def autocomplete(value: str, option: diskord.application.Option, interaction: diskord.Interaction): 10 | # "option" is the currently focused option. 11 | # as such, we will check if the focused option name is color, 12 | # if it is, we would simply return all the color names. 13 | if option.name == 'color': 14 | items = ['brown', 'green', 'blue'] 15 | 16 | # if the focused option isn't color and is "organism" instead, this means 17 | # the user has specified a color and now we have to return the list 18 | # of organisms of that color. 19 | 20 | elif option.name == 'organism': 21 | # interaction.data is the raw data of interaction and 22 | # we are accessing "options" which is list of options that is being 23 | # specified by user. The first element here is the "color" option and we 24 | # are accessing it's value. 25 | color = interaction.data['options'][0]['value'] 26 | 27 | # now "color" is the value that user specified for color option. 28 | # let's now return the names depending on value. 29 | if color == 'brown': 30 | items = ['Bear', 'Dog', 'Donkey'] 31 | elif color == 'green': 32 | items = ['Snake', 'Grasshopper', 'Frog'] 33 | elif color == 'blue': 34 | items = ['Whale', 'Jelly fish', 'Alien'] 35 | 36 | # finally return the choices. 37 | return [diskord.OptionChoice(name=item, value=item) for item in items] 38 | 39 | 40 | # the actual command 41 | @bot.slash_command() 42 | @diskord.application.option('color', autocomplete=autocomplete) 43 | @diskord.application.option('organism', autocomplete=autocomplete) 44 | async def organism(ctx, color: str, organism: str): 45 | await ctx.send(f'A {color} colored {organism}.') 46 | 47 | bot.run('token') -------------------------------------------------------------------------------- /examples/views/confirm.py: -------------------------------------------------------------------------------- 1 | from diskord.ext import commands 2 | 3 | import diskord 4 | 5 | 6 | class Bot(commands.Bot): 7 | def __init__(self): 8 | super().__init__(command_prefix=commands.when_mentioned_or('$')) 9 | 10 | async def on_ready(self): 11 | print(f'Logged in as {self.user} (ID: {self.user.id})') 12 | print('------') 13 | 14 | 15 | # Define a simple View that gives us a confirmation menu 16 | class Confirm(diskord.ui.View): 17 | def __init__(self): 18 | super().__init__() 19 | self.value = None 20 | 21 | # When the confirm button is pressed, set the inner value to `True` and 22 | # stop the View from listening to more input. 23 | # We also send the user an ephemeral message that we're confirming their choice. 24 | @diskord.ui.button(label='Confirm', style=diskord.ButtonStyle.green) 25 | async def confirm(self, button: diskord.ui.Button, interaction: diskord.Interaction): 26 | await interaction.response.send_message('Confirming', ephemeral=True) 27 | self.value = True 28 | self.stop() 29 | 30 | # This one is similar to the confirmation button except sets the inner value to `False` 31 | @diskord.ui.button(label='Cancel', style=diskord.ButtonStyle.grey) 32 | async def cancel(self, button: diskord.ui.Button, interaction: diskord.Interaction): 33 | await interaction.response.send_message('Cancelling', ephemeral=True) 34 | self.value = False 35 | self.stop() 36 | 37 | 38 | bot = Bot() 39 | 40 | 41 | @bot.command() 42 | async def ask(ctx: commands.Context): 43 | """Asks the user a question to confirm something.""" 44 | # We create the view and assign it to a variable so we can wait for it later. 45 | view = Confirm() 46 | await ctx.send('Do you want to continue?', view=view) 47 | # Wait for the View to stop listening for input... 48 | await view.wait() 49 | if view.value is None: 50 | print('Timed out...') 51 | elif view.value: 52 | print('Confirmed...') 53 | else: 54 | print('Cancelled...') 55 | 56 | 57 | bot.run('token') 58 | -------------------------------------------------------------------------------- /examples/views/ephemeral.py: -------------------------------------------------------------------------------- 1 | from diskord.ext import commands 2 | 3 | import diskord 4 | 5 | class EphemeralCounterBot(commands.Bot): 6 | def __init__(self): 7 | super().__init__(command_prefix=commands.when_mentioned_or('$')) 8 | 9 | async def on_ready(self): 10 | print(f'Logged in as {self.user} (ID: {self.user.id})') 11 | print('------') 12 | 13 | # Define a simple View that gives us a counter button 14 | class Counter(diskord.ui.View): 15 | 16 | # Define the actual button 17 | # When pressed, this increments the number displayed until it hits 5. 18 | # When it hits 5, the counter button is disabled and it turns green. 19 | # note: The name of the function does not matter to the library 20 | @diskord.ui.button(label='0', style=diskord.ButtonStyle.red) 21 | async def count(self, button: diskord.ui.Button, interaction: diskord.Interaction): 22 | number = int(button.label) if button.label else 0 23 | if number + 1 >= 5: 24 | button.style = diskord.ButtonStyle.green 25 | button.disabled = True 26 | button.label = str(number + 1) 27 | 28 | # Make sure to update the message with our updated selves 29 | await interaction.response.edit_message(view=self) 30 | 31 | # Define a View that will give us our own personal counter button 32 | class EphemeralCounter(diskord.ui.View): 33 | # When this button is pressed, it will respond with a Counter view that will 34 | # give the button presser their own personal button they can press 5 times. 35 | @diskord.ui.button(label='Click', style=diskord.ButtonStyle.blurple) 36 | async def receive(self, button: diskord.ui.Button, interaction: diskord.Interaction): 37 | # ephemeral=True makes the message hidden from everyone except the button presser 38 | await interaction.response.send_message('Enjoy!', view=Counter(), ephemeral=True) 39 | 40 | bot = EphemeralCounterBot() 41 | 42 | @bot.command() 43 | async def counter(ctx: commands.Context): 44 | """Starts a counter for pressing.""" 45 | await ctx.send('Press!', view=EphemeralCounter()) 46 | 47 | bot.run('token') 48 | -------------------------------------------------------------------------------- /diskord/types/events.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 | from typing import Optional, TypedDict, List 27 | 28 | from .snowflake import Snowflake 29 | from .user import User 30 | 31 | PrivacyLevel = Literal[1, 2] 32 | Status = Literal[1, 2, 3, 4] 33 | EntityType = Literal[0, 1, 2, 3] 34 | 35 | class EntityMetadata(TypedDict, total=False): 36 | speaker_ids: List[Snowflake] 37 | location: str 38 | 39 | class _ScheduledEventOptional(TypedDict, total=False): 40 | creator_id: Snowflake 41 | description: str 42 | creator: User 43 | user_count: int 44 | 45 | class ScheduledEvent(_ScheduledEventOptional): 46 | id: Snowflake 47 | guild_id: Snowflake 48 | channel_id: Optional[Snowflake] 49 | name: str 50 | scheduled_start_time: str 51 | scheduled_end_time: str 52 | privacy_level: PrivacyLevel 53 | status: Status 54 | entity_type: EntityType 55 | entity_id: Optional[Snowflake] 56 | entity_metadata: Optional[EntityMetadata] 57 | -------------------------------------------------------------------------------- /diskord/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Discord API Wrapper 3 | ~~~~~~~~~~~~~~~~~~~ 4 | 5 | A basic wrapper for the Discord API. 6 | 7 | :copyright: (c) 2015-present Rapptz 8 | :license: MIT, see LICENSE for more details. 9 | 10 | """ 11 | 12 | __title__ = "diskord" 13 | __author__ = ("Rapptz", "NerdGuyAhmad") 14 | __license__ = "MIT" 15 | __copyright__ = "Copyright 2015-2021 Rapptz, 2021-present NerdGuyAhmad" 16 | __version__ = "2.7.0" 17 | 18 | __path__ = __import__("pkgutil").extend_path(__path__, __name__) 19 | 20 | import logging 21 | from typing import NamedTuple, Literal 22 | 23 | from . import utils, opus, abc, ui, application 24 | from .activity import * 25 | from .appinfo import * 26 | from .application_commands import * 27 | from .asset import * 28 | from .colour import * 29 | from .components import * 30 | from .client import * 31 | from .emoji import * 32 | from .errors import * 33 | from .file import * 34 | from .flags import * 35 | from .guild import * 36 | from .user import * 37 | from .partial_emoji import * 38 | from .channel import * 39 | from .member import * 40 | from .message import * 41 | from .permissions import * 42 | from .role import * 43 | from .integrations import * 44 | from .invite import * 45 | from .template import * 46 | from .widget import * 47 | from .object import * 48 | from .reaction import * 49 | from .enums import * 50 | from .embeds import * 51 | from .mentions import * 52 | from .shard import * 53 | from .player import * 54 | from .webhook import * 55 | from .voice_client import * 56 | from .audit_logs import * 57 | from .raw_models import * 58 | from .team import * 59 | from .sticker import * 60 | from .stage_instance import * 61 | from .interactions import * 62 | from .threads import * 63 | from .welcome_screen import * 64 | from .events import * 65 | 66 | 67 | class VersionInfo(NamedTuple): 68 | major: int 69 | minor: int 70 | micro: int 71 | releaselevel: Literal["alpha", "beta", "candidate", "final"] 72 | serial: int 73 | 74 | 75 | version_info: VersionInfo = VersionInfo( 76 | major=2, minor=7, micro=0, releaselevel="alpha", serial=0 77 | ) 78 | 79 | logging.getLogger(__name__).addHandler(logging.NullHandler()) 80 | -------------------------------------------------------------------------------- /diskord/ext/commands/_types.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 | 26 | from typing import Any, Callable, Coroutine, TYPE_CHECKING, TypeVar, Union 27 | 28 | 29 | if TYPE_CHECKING: 30 | from .context import Context 31 | from .cog import Cog 32 | from .errors import CommandError 33 | 34 | T = TypeVar("T") 35 | 36 | Coro = Coroutine[Any, Any, T] 37 | MaybeCoro = Union[T, Coro[T]] 38 | CoroFunc = Callable[..., Coro[Any]] 39 | 40 | Check = Union[ 41 | Callable[["Cog", "Context[Any]"], MaybeCoro[bool]], 42 | Callable[["Context[Any]"], MaybeCoro[bool]], 43 | ] 44 | Hook = Union[ 45 | Callable[["Cog", "Context[Any]"], Coro[Any]], Callable[["Context[Any]"], Coro[Any]] 46 | ] 47 | Error = Union[ 48 | Callable[["Cog", "Context[Any]", "CommandError"], Coro[Any]], 49 | Callable[["Context[Any]", "CommandError"], Coro[Any]], 50 | ] 51 | 52 | 53 | # This is merely a tag type to avoid circular import issues. 54 | # Yes, this is a terrible solution but ultimately it is the only solution. 55 | class _BaseCommand: 56 | __slots__ = () 57 | -------------------------------------------------------------------------------- /examples/basic_bot.py: -------------------------------------------------------------------------------- 1 | # This example requires the 'members' privileged intents 2 | 3 | import diskord 4 | from diskord.ext import commands 5 | import random 6 | 7 | description = '''An example bot to showcase the diskord.ext.commands extension 8 | module. 9 | 10 | There are a number of utility commands being showcased here.''' 11 | 12 | intents = diskord.Intents.default() 13 | intents.members = True 14 | 15 | bot = commands.Bot(command_prefix='?', description=description, intents=intents) 16 | 17 | @bot.event 18 | async def on_ready(): 19 | print(f'Logged in as {bot.user} (ID: {bot.user.id})') 20 | print('------') 21 | 22 | @bot.command() 23 | async def add(ctx, left: int, right: int): 24 | """Adds two numbers together.""" 25 | await ctx.send(left + right) 26 | 27 | @bot.command() 28 | async def roll(ctx, dice: str): 29 | """Rolls a dice in NdN format.""" 30 | try: 31 | rolls, limit = map(int, dice.split('d')) 32 | except Exception: 33 | await ctx.send('Format has to be in NdN!') 34 | return 35 | 36 | result = ', '.join(str(random.randint(1, limit)) for r in range(rolls)) 37 | await ctx.send(result) 38 | 39 | @bot.command(description='For when you wanna settle the score some other way') 40 | async def choose(ctx, *choices: str): 41 | """Chooses between multiple choices.""" 42 | await ctx.send(random.choice(choices)) 43 | 44 | @bot.command() 45 | async def repeat(ctx, times: int, content='repeating...'): 46 | """Repeats a message multiple times.""" 47 | for i in range(times): 48 | await ctx.send(content) 49 | 50 | @bot.command() 51 | async def joined(ctx, member: diskord.Member): 52 | """Says when a member joined.""" 53 | await ctx.send(f'{member.name} joined in {member.joined_at}') 54 | 55 | @bot.group() 56 | async def cool(ctx): 57 | """Says if a user is cool. 58 | 59 | In reality this just checks if a subcommand is being invoked. 60 | """ 61 | if ctx.invoked_subcommand is None: 62 | await ctx.send(f'No, {ctx.subcommand_passed} is not cool') 63 | 64 | @cool.command(name='bot') 65 | async def _bot(ctx): 66 | """Is the bot cool?""" 67 | await ctx.send('Yes, the bot is cool.') 68 | 69 | bot.run('token') 70 | -------------------------------------------------------------------------------- /diskord/types/webhook.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 | from typing import Literal, Optional, TypedDict 27 | from .snowflake import Snowflake 28 | from .user import User 29 | from .channel import PartialChannel 30 | 31 | 32 | class SourceGuild(TypedDict): 33 | id: int 34 | name: str 35 | icon: str 36 | 37 | 38 | class _WebhookOptional(TypedDict, total=False): 39 | guild_id: Snowflake 40 | user: User 41 | token: str 42 | 43 | 44 | WebhookType = Literal[1, 2, 3] 45 | 46 | 47 | class _FollowerWebhookOptional(TypedDict, total=False): 48 | source_channel: PartialChannel 49 | source_guild: SourceGuild 50 | 51 | 52 | class FollowerWebhook(_FollowerWebhookOptional): 53 | channel_id: Snowflake 54 | webhook_id: Snowflake 55 | 56 | 57 | class PartialWebhook(_WebhookOptional): 58 | id: Snowflake 59 | type: WebhookType 60 | 61 | 62 | class _FullWebhook(TypedDict, total=False): 63 | name: Optional[str] 64 | avatar: Optional[str] 65 | channel_id: Snowflake 66 | application_id: Optional[Snowflake] 67 | 68 | 69 | class Webhook(PartialWebhook, _FullWebhook): 70 | ... 71 | -------------------------------------------------------------------------------- /docs/locale/ja/LC_MESSAGES/index.po: -------------------------------------------------------------------------------- 1 | 2 | msgid "" 3 | msgstr "" 4 | "Project-Id-Version: discordpy\n" 5 | "Report-Msgid-Bugs-To: \n" 6 | "POT-Creation-Date: 2020-10-23 22:41-0400\n" 7 | "PO-Revision-Date: 2020-10-24 02:41+0000\n" 8 | "Last-Translator: \n" 9 | "Language: ja_JP\n" 10 | "Language-Team: Japanese\n" 11 | "Plural-Forms: nplurals=1; plural=0\n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=utf-8\n" 14 | "Content-Transfer-Encoding: 8bit\n" 15 | "Generated-By: Babel 2.5.3\n" 16 | 17 | #: ../../index.rst:7 18 | msgid "Welcome to discord.py" 19 | msgstr "discord.py へようこそ。" 20 | 21 | #: ../../index.rst:11 22 | msgid "" 23 | "discord.py is a modern, easy to use, feature-rich, and async ready API " 24 | "wrapper for Discord." 25 | msgstr "discord.py は機能豊富かつモダンで使いやすい、非同期処理にも対応したDiscord用のAPIラッパーです。" 26 | 27 | #: ../../index.rst:14 28 | msgid "**Features:**" 29 | msgstr "**特徴:**" 30 | 31 | #: ../../index.rst:16 32 | msgid "Modern Pythonic API using ``async``\\/``await`` syntax" 33 | msgstr "``async``\\/``await`` 構文を使ったモダンなPythonらしいAPI" 34 | 35 | #: ../../index.rst:17 36 | msgid "Sane rate limit handling that prevents 429s" 37 | msgstr "429エラー防止の為のレート制限" 38 | 39 | #: ../../index.rst:18 40 | msgid "Implements the entire Discord API" 41 | msgstr "Discord APIを完全にカバー" 42 | 43 | #: ../../index.rst:19 44 | msgid "Command extension to aid with bot creation" 45 | msgstr "Bot作成に便利なコマンド拡張" 46 | 47 | #: ../../index.rst:20 48 | msgid "Easy to use with an object oriented design" 49 | msgstr "オブジェクト指向設計で使いやすい" 50 | 51 | #: ../../index.rst:21 52 | msgid "Optimised for both speed and memory" 53 | msgstr "メモリと速度の両方を最適化" 54 | 55 | #: ../../index.rst:24 56 | msgid "Documentation Contents" 57 | msgstr "ドキュメントの目次" 58 | 59 | #: ../../index.rst:36 60 | msgid "Extensions" 61 | msgstr "拡張機能" 62 | 63 | #: ../../index.rst:46 64 | msgid "Additional Information" 65 | msgstr "追加情報" 66 | 67 | #: ../../index.rst:57 68 | msgid "" 69 | "If you still can't find what you're looking for, try in one of the " 70 | "following pages:" 71 | msgstr "探しているものが見つからない場合は、以下のページを試してください。" 72 | 73 | #: ../../index.rst:59 74 | msgid ":ref:`genindex`" 75 | msgstr ":ref:`genindex`" 76 | 77 | #: ../../index.rst:60 78 | msgid ":ref:`search`" 79 | msgstr ":ref:`search`" 80 | 81 | #~ msgid ":ref:`modindex`" 82 | #~ msgstr ":ref:`modindex`" 83 | 84 | -------------------------------------------------------------------------------- /diskord/types/appinfo.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 TypedDict, List, Optional 28 | 29 | from .user import User 30 | from .team import Team 31 | from .snowflake import Snowflake 32 | 33 | 34 | class BaseAppInfo(TypedDict): 35 | id: Snowflake 36 | name: str 37 | verify_key: str 38 | icon: Optional[str] 39 | summary: str 40 | description: str 41 | 42 | 43 | class _AppInfoOptional(TypedDict, total=False): 44 | team: Team 45 | guild_id: Snowflake 46 | primary_sku_id: Snowflake 47 | slug: str 48 | terms_of_service_url: str 49 | privacy_policy_url: str 50 | hook: bool 51 | max_participants: int 52 | 53 | 54 | class AppInfo(BaseAppInfo, _AppInfoOptional): 55 | rpc_origins: List[str] 56 | owner: User 57 | bot_public: bool 58 | bot_require_code_grant: bool 59 | 60 | 61 | class _PartialAppInfoOptional(TypedDict, total=False): 62 | rpc_origins: List[str] 63 | cover_image: str 64 | hook: bool 65 | terms_of_service_url: str 66 | privacy_policy_url: str 67 | max_participants: int 68 | flags: int 69 | 70 | 71 | class PartialAppInfo(_PartialAppInfoOptional, BaseAppInfo): 72 | pass 73 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. diskord documentation master file, created by 2 | sphinx-quickstart on Fri Aug 21 05:43:30 2015. 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 diskord 7 | =========================== 8 | 9 | .. image:: /images/snake.svg 10 | .. image:: /images/snake_dark.svg 11 | 12 | diskord is a modern, easy to use, feature-rich, and async ready API wrapper 13 | for Discord. 14 | 15 | **Features:** 16 | 17 | - Modern Pythonic API using ``async``\/``await`` syntax 18 | - Sane rate limit handling that prevents 429s 19 | - Command extension to aid with bot creation 20 | - Easy to use with an object oriented design 21 | - Optimised for both speed and memory 22 | 23 | Getting started 24 | ----------------- 25 | 26 | Is this your first time using the library? This is the place to get started! 27 | 28 | - **First steps:** :doc:`intro` | :doc:`quickstart` | :doc:`logging` 29 | - **Working with Discord:** :doc:`discord` | :doc:`intents` 30 | - **Examples:** Many examples are available in the :resource:`repository `. 31 | 32 | Getting help 33 | -------------- 34 | 35 | If you're having trouble with something, these resources might help. 36 | 37 | - Try the :doc:`faq` first, it's got answers to all common questions. 38 | - Ask us and hang out with us in our :resource:`Discord ` server. 39 | - If you're looking for something specific, try the :ref:`index ` or :ref:`searching `. 40 | - Report bugs in the :resource:`issue tracker `. 41 | - Ask in our :resource:`GitHub discussions page `. 42 | 43 | Extensions 44 | ------------ 45 | 46 | These extensions help you during development when it comes to common tasks. 47 | 48 | .. toctree:: 49 | :maxdepth: 1 50 | 51 | ext/commands/index.rst 52 | ext/tasks/index.rst 53 | 54 | Manuals 55 | --------- 56 | 57 | These pages go into great detail about everything the API can do. 58 | 59 | .. toctree:: 60 | :maxdepth: 1 61 | 62 | api 63 | diskord.ext.commands API Reference 64 | diskord.ext.tasks API Reference 65 | 66 | Meta 67 | ------ 68 | 69 | If you're looking for something related to the project itself, it's here. 70 | 71 | .. toctree:: 72 | :maxdepth: 1 73 | 74 | migrating 75 | whats_new 76 | version_guarantees 77 | application_commands -------------------------------------------------------------------------------- /diskord/types/components.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 List, Literal, TypedDict, Union 28 | from .emoji import PartialEmoji 29 | 30 | ComponentType = Literal[1, 2, 3] 31 | ButtonStyle = Literal[1, 2, 3, 4, 5] 32 | 33 | 34 | class ActionRow(TypedDict): 35 | type: Literal[1] 36 | components: List[Component] 37 | 38 | 39 | class _ButtonComponentOptional(TypedDict, total=False): 40 | custom_id: str 41 | url: str 42 | disabled: bool 43 | emoji: PartialEmoji 44 | label: str 45 | 46 | 47 | class ButtonComponent(_ButtonComponentOptional): 48 | type: Literal[2] 49 | style: ButtonStyle 50 | 51 | 52 | class _SelectMenuOptional(TypedDict, total=False): 53 | placeholder: str 54 | min_values: int 55 | max_values: int 56 | disabled: bool 57 | 58 | 59 | class _SelectOptionsOptional(TypedDict, total=False): 60 | description: str 61 | emoji: PartialEmoji 62 | 63 | 64 | class SelectOption(_SelectOptionsOptional): 65 | label: str 66 | value: str 67 | default: bool 68 | 69 | 70 | class SelectMenu(_SelectMenuOptional): 71 | type: Literal[3] 72 | custom_id: str 73 | options: List[SelectOption] 74 | 75 | 76 | Component = Union[ActionRow, ButtonComponent, SelectMenu] 77 | -------------------------------------------------------------------------------- /examples/views/dropdown.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | import diskord 4 | from diskord.ext import commands 5 | 6 | # Defines a custom Select containing colour options 7 | # that the user can choose. The callback function 8 | # of this class is called when the user changes their choice 9 | class Dropdown(diskord.ui.Select): 10 | def __init__(self): 11 | 12 | # Set the options that will be presented inside the dropdown 13 | options = [ 14 | diskord.SelectOption(label='Red', description='Your favourite colour is red', emoji='🟥'), 15 | diskord.SelectOption(label='Green', description='Your favourite colour is green', emoji='🟩'), 16 | diskord.SelectOption(label='Blue', description='Your favourite colour is blue', emoji='🟦') 17 | ] 18 | 19 | # The placeholder is what will be shown when no option is chosen 20 | # The min and max values indicate we can only pick one of the three options 21 | # The options parameter defines the dropdown options. We defined this above 22 | super().__init__(placeholder='Choose your favourite colour...', min_values=1, max_values=1, options=options) 23 | 24 | async def callback(self, interaction: diskord.Interaction): 25 | # Use the interaction object to send a response message containing 26 | # the user's favourite colour or choice. The self object refers to the 27 | # Select object, and the values attribute gets a list of the user's 28 | # selected options. We only want the first one. 29 | await interaction.response.send_message(f'Your favourite colour is {self.values[0]}') 30 | 31 | 32 | class DropdownView(diskord.ui.View): 33 | def __init__(self): 34 | super().__init__() 35 | 36 | # Adds the dropdown to our view object. 37 | self.add_item(Dropdown()) 38 | 39 | 40 | class Bot(commands.Bot): 41 | def __init__(self): 42 | super().__init__(command_prefix=commands.when_mentioned_or('$')) 43 | 44 | async def on_ready(self): 45 | print(f'Logged in as {self.user} (ID: {self.user.id})') 46 | print('------') 47 | 48 | 49 | bot = Bot() 50 | 51 | 52 | @bot.command() 53 | async def colour(ctx): 54 | """Sends a message with our dropdown containing colours""" 55 | 56 | # Create the view containing our dropdown 57 | view = DropdownView() 58 | 59 | # Sending a message containing our view 60 | await ctx.send('Pick your favourite colour:', view=view) 61 | 62 | 63 | bot.run('token') 64 | -------------------------------------------------------------------------------- /diskord/types/threads.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 | from typing import List, Literal, Optional, TypedDict 27 | 28 | from .snowflake import Snowflake 29 | 30 | ThreadType = Literal[10, 11, 12] 31 | ThreadArchiveDuration = Literal[60, 1440, 4320, 10080] 32 | 33 | 34 | class ThreadMember(TypedDict): 35 | id: Snowflake 36 | user_id: Snowflake 37 | join_timestamp: str 38 | flags: int 39 | 40 | 41 | class _ThreadMetadataOptional(TypedDict, total=False): 42 | archiver_id: Snowflake 43 | locked: bool 44 | invitable: bool 45 | 46 | 47 | class ThreadMetadata(_ThreadMetadataOptional): 48 | archived: bool 49 | auto_archive_duration: ThreadArchiveDuration 50 | archive_timestamp: str 51 | 52 | 53 | class _ThreadOptional(TypedDict, total=False): 54 | member: ThreadMember 55 | last_message_id: Optional[Snowflake] 56 | last_pin_timestamp: Optional[Snowflake] 57 | 58 | 59 | class Thread(_ThreadOptional): 60 | id: Snowflake 61 | guild_id: Snowflake 62 | parent_id: Snowflake 63 | owner_id: Snowflake 64 | name: str 65 | type: ThreadType 66 | member_count: int 67 | message_count: int 68 | rate_limit_per_user: int 69 | thread_metadata: ThreadMetadata 70 | 71 | 72 | class ThreadPaginationPayload(TypedDict): 73 | threads: List[Thread] 74 | members: List[ThreadMember] 75 | has_more: bool 76 | -------------------------------------------------------------------------------- /diskord/types/integration.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 | from .snowflake import Snowflake 29 | from .user import User 30 | 31 | 32 | class _IntegrationApplicationOptional(TypedDict, total=False): 33 | bot: User 34 | 35 | 36 | class IntegrationApplication(_IntegrationApplicationOptional): 37 | id: Snowflake 38 | name: str 39 | icon: Optional[str] 40 | description: str 41 | summary: str 42 | 43 | 44 | class IntegrationAccount(TypedDict): 45 | id: str 46 | name: str 47 | 48 | 49 | IntegrationExpireBehavior = Literal[0, 1] 50 | 51 | 52 | class PartialIntegration(TypedDict): 53 | id: Snowflake 54 | name: str 55 | type: IntegrationType 56 | account: IntegrationAccount 57 | 58 | 59 | IntegrationType = Literal["twitch", "youtube", "discord"] 60 | 61 | 62 | class BaseIntegration(PartialIntegration): 63 | enabled: bool 64 | syncing: bool 65 | synced_at: str 66 | user: User 67 | expire_behavior: IntegrationExpireBehavior 68 | expire_grace_period: int 69 | 70 | 71 | class StreamIntegration(BaseIntegration): 72 | role_id: Optional[Snowflake] 73 | enable_emoticons: bool 74 | subscriber_count: int 75 | revoked: bool 76 | 77 | 78 | class BotIntegration(BaseIntegration): 79 | application: IntegrationApplication 80 | 81 | 82 | Integration = Union[BaseIntegration, StreamIntegration, BotIntegration] 83 | -------------------------------------------------------------------------------- /diskord/types/voice.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 Optional, TypedDict, List, Literal 26 | from .snowflake import Snowflake 27 | from .member import MemberWithUser 28 | 29 | 30 | SupportedModes = Literal[ 31 | "xsalsa20_poly1305_lite", "xsalsa20_poly1305_suffix", "xsalsa20_poly1305" 32 | ] 33 | 34 | 35 | class _PartialVoiceStateOptional(TypedDict, total=False): 36 | member: MemberWithUser 37 | self_stream: bool 38 | 39 | 40 | class _VoiceState(_PartialVoiceStateOptional): 41 | user_id: Snowflake 42 | session_id: str 43 | deaf: bool 44 | mute: bool 45 | self_deaf: bool 46 | self_mute: bool 47 | self_video: bool 48 | suppress: bool 49 | 50 | 51 | class GuildVoiceState(_VoiceState): 52 | channel_id: Snowflake 53 | 54 | 55 | class VoiceState(_VoiceState, total=False): 56 | channel_id: Optional[Snowflake] 57 | guild_id: Snowflake 58 | 59 | 60 | class VoiceRegion(TypedDict): 61 | id: str 62 | name: str 63 | vip: bool 64 | optimal: bool 65 | deprecated: bool 66 | custom: bool 67 | 68 | 69 | class VoiceServerUpdate(TypedDict): 70 | token: str 71 | guild_id: Snowflake 72 | endpoint: Optional[str] 73 | 74 | 75 | class VoiceIdentify(TypedDict): 76 | server_id: Snowflake 77 | user_id: Snowflake 78 | session_id: str 79 | token: str 80 | 81 | 82 | class VoiceReady(TypedDict): 83 | ssrc: int 84 | ip: str 85 | port: int 86 | modes: List[SupportedModes] 87 | heartbeat_interval: int 88 | -------------------------------------------------------------------------------- /diskord/types/embed.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 List, Literal, TypedDict 26 | 27 | 28 | class _EmbedFooterOptional(TypedDict, total=False): 29 | icon_url: str 30 | proxy_icon_url: str 31 | 32 | 33 | class EmbedFooter(_EmbedFooterOptional): 34 | text: str 35 | 36 | 37 | class _EmbedFieldOptional(TypedDict, total=False): 38 | inline: bool 39 | 40 | 41 | class EmbedField(_EmbedFieldOptional): 42 | name: str 43 | value: str 44 | 45 | 46 | class EmbedThumbnail(TypedDict, total=False): 47 | url: str 48 | proxy_url: str 49 | height: int 50 | width: int 51 | 52 | 53 | class EmbedVideo(TypedDict, total=False): 54 | url: str 55 | proxy_url: str 56 | height: int 57 | width: int 58 | 59 | 60 | class EmbedImage(TypedDict, total=False): 61 | url: str 62 | proxy_url: str 63 | height: int 64 | width: int 65 | 66 | 67 | class EmbedProvider(TypedDict, total=False): 68 | name: str 69 | url: str 70 | 71 | 72 | class EmbedAuthor(TypedDict, total=False): 73 | name: str 74 | url: str 75 | icon_url: str 76 | proxy_icon_url: str 77 | 78 | 79 | EmbedType = Literal["rich", "image", "video", "gifv", "article", "link"] 80 | 81 | 82 | class Embed(TypedDict, total=False): 83 | title: str 84 | type: EmbedType 85 | description: str 86 | url: str 87 | timestamp: str 88 | color: int 89 | footer: EmbedFooter 90 | image: EmbedImage 91 | thumbnail: EmbedThumbnail 92 | video: EmbedVideo 93 | provider: EmbedProvider 94 | author: EmbedAuthor 95 | fields: List[EmbedField] 96 | -------------------------------------------------------------------------------- /docs/_static/custom.js: -------------------------------------------------------------------------------- 1 | 'use-strict'; 2 | 3 | let activeModal = null; 4 | let bottomHeightThreshold, sections; 5 | let hamburgerToggle; 6 | let mobileSearch; 7 | let sidebar; 8 | let toTop; 9 | 10 | class Modal { 11 | constructor(element) { 12 | this.element = element; 13 | } 14 | 15 | close() { 16 | activeModal = null; 17 | this.element.style.display = 'none' 18 | } 19 | 20 | open() { 21 | if (activeModal) { 22 | activeModal.close(); 23 | } 24 | activeModal = this; 25 | this.element.style.display = 'flex' 26 | } 27 | } 28 | 29 | class SearchBar { 30 | 31 | constructor() { 32 | this.box = document.querySelector('nav.mobile-only'); 33 | this.bar = document.querySelector('nav.mobile-only input[type="search"]'); 34 | this.openButton = document.getElementById('open-search'); 35 | this.closeButton = document.getElementById('close-search'); 36 | } 37 | 38 | open() { 39 | this.openButton.hidden = true; 40 | this.closeButton.hidden = false; 41 | this.box.style.top = "100%"; 42 | this.bar.focus(); 43 | } 44 | 45 | close() { 46 | this.openButton.hidden = false; 47 | this.closeButton.hidden = true; 48 | this.box.style.top = "0"; 49 | } 50 | 51 | } 52 | 53 | function scrollToTop() { 54 | window.scrollTo({ top: 0, behavior: 'smooth' }); 55 | } 56 | 57 | document.addEventListener('DOMContentLoaded', () => { 58 | mobileSearch = new SearchBar(); 59 | 60 | bottomHeightThreshold = document.documentElement.scrollHeight - 30; 61 | sections = document.querySelectorAll('section'); 62 | hamburgerToggle = document.getElementById('hamburger-toggle'); 63 | 64 | toTop = document.getElementById('to-top'); 65 | toTop.hidden = !(window.scrollY > 0); 66 | 67 | if (hamburgerToggle) { 68 | hamburgerToggle.addEventListener('click', (e) => { 69 | sidebar.element.classList.toggle('sidebar-toggle'); 70 | let button = hamburgerToggle.firstElementChild; 71 | if (button.textContent == 'menu') { 72 | button.textContent = 'close'; 73 | } 74 | else { 75 | button.textContent = 'menu'; 76 | } 77 | }); 78 | } 79 | 80 | const tables = document.querySelectorAll('.py-attribute-table[data-move-to-id]'); 81 | tables.forEach(table => { 82 | let element = document.getElementById(table.getAttribute('data-move-to-id')); 83 | let parent = element.parentNode; 84 | // insert ourselves after the element 85 | parent.insertBefore(table, element.nextSibling); 86 | }); 87 | 88 | window.addEventListener('scroll', () => { 89 | toTop.hidden = !(window.scrollY > 0); 90 | }); 91 | }); 92 | 93 | document.addEventListener('keydown', (event) => { 94 | if (event.code == "Escape" && activeModal) { 95 | activeModal.close(); 96 | } 97 | }); 98 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report broken or incorrect behaviour of this library. 3 | labels: unconfirmed bug 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: > 8 | Thanks for taking the time to fill out a bug. 9 | If you want real-time support, consider joining our Discord at https://discord.gg/V6VxfDYHkB instead. 10 | 11 | Please note that this form is for bugs only! 12 | - type: input 13 | attributes: 14 | label: Summary 15 | description: A simple summary of your bug. 16 | validations: 17 | required: true 18 | - type: textarea 19 | attributes: 20 | label: Reproduction Steps 21 | description: > 22 | What you did to make it happen. Explain the steps so we can reproduce it. 23 | validations: 24 | required: true 25 | - type: textarea 26 | attributes: 27 | label: Minimal Reproducible Code 28 | description: > 29 | A short snippet of code that potentially reproduces the bug. If any. 30 | render: python 31 | - type: textarea 32 | attributes: 33 | label: Expected Results 34 | description: > 35 | What did you expect to happen? 36 | validations: 37 | required: true 38 | - type: textarea 39 | attributes: 40 | label: Actual Results 41 | description: > 42 | What actually happened? 43 | validations: 44 | required: true 45 | - type: input 46 | attributes: 47 | label: Intents 48 | description: > 49 | What intents are you using for your bot? 50 | This is the `diskord.Intents` class you pass to the client. 51 | validations: 52 | required: true 53 | - type: textarea 54 | attributes: 55 | label: System Information 56 | description: > 57 | Run `python -m diskord -v` and paste this information below. 58 | 59 | This command required v1.1.0 or higher of the library. If this errors out then show some basic 60 | information involving your system such as operating system and Python version. 61 | validations: 62 | required: true 63 | - type: checkboxes 64 | attributes: 65 | label: Checklist 66 | description: > 67 | Let's make sure you've properly done due dilligence when reporting this issue! 68 | options: 69 | - label: I have searched the open issues for duplicates. 70 | required: true 71 | - label: I have shown the entire traceback, if possible. 72 | required: true 73 | - label: I have removed my token from display, if visible. 74 | required: true 75 | - type: textarea 76 | attributes: 77 | label: Additional Context 78 | description: If there is anything else to say, please do so here. 79 | -------------------------------------------------------------------------------- /diskord/types/sticker.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 List, Literal, TypedDict, Union 28 | from .snowflake import Snowflake 29 | from .user import User 30 | 31 | StickerFormatType = Literal[1, 2, 3] 32 | 33 | 34 | class StickerItem(TypedDict): 35 | id: Snowflake 36 | name: str 37 | format_type: StickerFormatType 38 | 39 | 40 | class BaseSticker(TypedDict): 41 | id: Snowflake 42 | name: str 43 | description: str 44 | tags: str 45 | format_type: StickerFormatType 46 | 47 | 48 | class StandardSticker(BaseSticker): 49 | type: Literal[1] 50 | sort_value: int 51 | pack_id: Snowflake 52 | 53 | 54 | class _GuildStickerOptional(TypedDict, total=False): 55 | user: User 56 | 57 | 58 | class GuildSticker(BaseSticker, _GuildStickerOptional): 59 | type: Literal[2] 60 | available: bool 61 | guild_id: Snowflake 62 | 63 | 64 | Sticker = Union[BaseSticker, StandardSticker, GuildSticker] 65 | 66 | 67 | class StickerPack(TypedDict): 68 | id: Snowflake 69 | stickers: List[StandardSticker] 70 | name: str 71 | sku_id: Snowflake 72 | cover_sticker_id: Snowflake 73 | description: str 74 | banner_asset_id: Snowflake 75 | 76 | 77 | class _CreateGuildStickerOptional(TypedDict, total=False): 78 | description: str 79 | 80 | 81 | class CreateGuildSticker(_CreateGuildStickerOptional): 82 | name: str 83 | tags: str 84 | 85 | 86 | class EditGuildSticker(TypedDict, total=False): 87 | name: str 88 | tags: str 89 | description: str 90 | 91 | 92 | class ListPremiumStickerPacks(TypedDict): 93 | sticker_packs: List[StickerPack] 94 | -------------------------------------------------------------------------------- /docs/ext/commands/extensions.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: discord 2 | 3 | .. _ext_commands_extensions: 4 | 5 | Extensions 6 | ============= 7 | 8 | There comes a time in the bot development when you want to extend the bot functionality at run-time and quickly unload and reload code (also called hot-reloading). The command framework comes with this ability built-in, with a concept called **extensions**. 9 | 10 | Primer 11 | -------- 12 | 13 | An extension at its core is a python file with an entry point called ``setup``. This setup must be a plain Python function (not a coroutine). It takes a single parameter -- the :class:`~.commands.Bot` that loads the extension. 14 | 15 | An example extension looks like this: 16 | 17 | .. code-block:: python3 18 | :caption: hello.py 19 | :emphasize-lines: 7,8 20 | 21 | from diskord.ext import commands 22 | 23 | @commands.command() 24 | async def hello(ctx): 25 | await ctx.send(f'Hello {ctx.author.display_name}.') 26 | 27 | def setup(bot): 28 | bot.add_command(hello) 29 | 30 | In this example we define a simple command, and when the extension is loaded this command is added to the bot. Now the final step to this is loading the extension, which we do by calling :meth:`.Bot.load_extension`. To load this extension we call ``bot.load_extension('hello')``. 31 | 32 | .. admonition:: Cogs 33 | :class: helpful 34 | 35 | Extensions are usually used in conjunction with cogs. To read more about them, check out the documentation, :ref:`ext_commands_cogs`. 36 | 37 | .. note:: 38 | 39 | Extension paths are ultimately similar to the import mechanism. What this means is that if there is a folder, then it must be dot-qualified. For example to load an extension in ``plugins/hello.py`` then we use the string ``plugins.hello``. 40 | 41 | Reloading 42 | ----------- 43 | 44 | When you make a change to the extension and want to reload the references, the library comes with a function to do this for you, :meth:`.Bot.reload_extension`. 45 | 46 | .. code-block:: python3 47 | 48 | >>> bot.reload_extension('hello') 49 | 50 | Once the extension reloads, any changes that we did will be applied. This is useful if we want to add or remove functionality without restarting our bot. If an error occurred during the reloading process, the bot will pretend as if the reload never happened. 51 | 52 | Cleaning Up 53 | ------------- 54 | 55 | Although rare, sometimes an extension needs to clean-up or know when it's being unloaded. For cases like these, there is another entry point named ``teardown`` which is similar to ``setup`` except called when the extension is unloaded. 56 | 57 | .. code-block:: python3 58 | :caption: basic_ext.py 59 | 60 | def setup(bot): 61 | print('I am being loaded!') 62 | 63 | def teardown(bot): 64 | print('I am being unloaded!') 65 | -------------------------------------------------------------------------------- /docs/_static/scorer.js: -------------------------------------------------------------------------------- 1 | 'use-strict'; 2 | 3 | let queryBeingDone = null; 4 | let pattern = null; 5 | 6 | const escapedRegex = /[-\/\\^$*+?.()|[\]{}]/g; 7 | function escapeRegex(e) { 8 | return e.replace(escapedRegex, '\\$&'); 9 | } 10 | 11 | // for some reason Sphinx shows some entries twice 12 | // if something has been scored already I'd rather sort it to the bottom 13 | const beenScored = new Set(); 14 | 15 | function __score(haystack, regex) { 16 | let match = regex.exec(haystack); 17 | if (match == null) { 18 | return Number.MAX_VALUE; 19 | } 20 | let subLength = match[0].length; 21 | let start = match.index; 22 | return (subLength * 1000 + start) / 1000.0; 23 | } 24 | 25 | // unused for now 26 | function __cleanNamespaces(query) { 27 | return query.replace(/(discord\.(ext\.)?)?(.+)/, '$3'); 28 | } 29 | 30 | Scorer = { 31 | 32 | // Implement the following function to further tweak the score for each result 33 | // The function takes a result array [filename, title, anchor, descr, score] 34 | // and returns the new score. 35 | score: (result) => { 36 | // only inflate the score of things that are actual API reference things 37 | const [, title, , , score] = result; 38 | 39 | if (pattern !== null && title.startsWith('discord.')) { 40 | let _score = __score(title, pattern); 41 | if (_score === Number.MAX_VALUE) { 42 | return score; 43 | } 44 | if (beenScored.has(title)) { 45 | return 0; 46 | } 47 | beenScored.add(title); 48 | let newScore = 100 + queryBeingDone.length - _score; 49 | // console.log(`${title}: ${score} -> ${newScore} (${_score})`); 50 | return newScore; 51 | } 52 | return score; 53 | }, 54 | 55 | // query matches the full name of an object 56 | objNameMatch: 15, 57 | // or matches in the last dotted part of the object name 58 | objPartialMatch: 11, 59 | // Additive scores depending on the priority of the object 60 | objPrio: { 61 | 0: 15, // used to be importantResults 62 | 1: 7, // used to be objectResults 63 | 2: -5 // used to be unimportantResults 64 | }, 65 | // Used when the priority is not in the mapping. 66 | objPrioDefault: 0, 67 | 68 | // query found in title 69 | title: 15, 70 | partialTitle: 7, 71 | // query found in terms 72 | term: 5, 73 | partialTerm: 2 74 | }; 75 | 76 | document.addEventListener('DOMContentLoaded', () => { 77 | const params = new URLSearchParams(window.location.search); 78 | queryBeingDone = params.get('q'); 79 | if (queryBeingDone) { 80 | let pattern = Array.from(queryBeingDone).map(escapeRegex).join('.*?'); 81 | pattern = new RegExp(pattern, 'i'); 82 | } 83 | }); 84 | -------------------------------------------------------------------------------- /docs/locale/ja/LC_MESSAGES/logging.po: -------------------------------------------------------------------------------- 1 | 2 | msgid "" 3 | msgstr "" 4 | "Project-Id-Version: discordpy\n" 5 | "Report-Msgid-Bugs-To: \n" 6 | "POT-Creation-Date: 2020-10-23 22:41-0400\n" 7 | "PO-Revision-Date: 2020-10-24 02:41+0000\n" 8 | "Last-Translator: \n" 9 | "Language: ja_JP\n" 10 | "Language-Team: Japanese\n" 11 | "Plural-Forms: nplurals=1; plural=0\n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=utf-8\n" 14 | "Content-Transfer-Encoding: 8bit\n" 15 | "Generated-By: Babel 2.5.3\n" 16 | 17 | #: ../../logging.rst:5 18 | msgid "Setting Up Logging" 19 | msgstr "ログの設定" 20 | 21 | #: ../../logging.rst:7 22 | msgid "" 23 | "*discord.py* logs errors and debug information via the :mod:`logging` " 24 | "python module. It is strongly recommended that the logging module is " 25 | "configured, as no errors or warnings will be output if it is not set up. " 26 | "Configuration of the ``logging`` module can be as simple as::" 27 | msgstr "" 28 | "*discord.py* はPythonの :mod:`logging` " 29 | "モジュールを介してエラーやデバッグの情報を記録します。loggingモジュールが設定されていない場合は、エラーや警告が出力されないため、設定するとを強くおすすめします。loggingモジュールの設定は下記手順で簡単に実装が可能です。" 30 | 31 | #: ../../logging.rst:16 32 | #, fuzzy 33 | msgid "" 34 | "Placed at the start of the application. This will output the logs from " 35 | "discord as well as other libraries that use the ``logging`` module " 36 | "directly to the console." 37 | msgstr "" 38 | "アプリケーションの始めにこれを書き加えるだけです。これはdiscordからのログを ``logging`` " 39 | "モジュールを用いた他のライブラリ同様、コンソールに出力します。" 40 | 41 | #: ../../logging.rst:20 42 | #, fuzzy 43 | msgid "" 44 | "The optional ``level`` argument specifies what level of events to log out" 45 | " and can be any of ``CRITICAL``, ``ERROR``, ``WARNING``, ``INFO``, and " 46 | "``DEBUG`` and if not specified defaults to ``WARNING``." 47 | msgstr "" 48 | "オプションである ``level`` の引数は出力するイベントのレベルを指定するためのもので、 ``CRITICAL``, ``ERROR``, " 49 | "``WARNING``, ``INFO`` そして ``DEBUG`` を指定することが可能です。指定されていない場合はデフォルトである " 50 | "``WARNING`` に設定されます。" 51 | 52 | #: ../../logging.rst:24 53 | #, fuzzy 54 | msgid "" 55 | "More advanced setups are possible with the :mod:`logging` module. For " 56 | "example to write the logs to a file called ``discord.log`` instead of " 57 | "outputting them to the console the following snippet can be used::" 58 | msgstr "" 59 | "また、 :mod:`logging` モジュールでは更に高度な設定が可能です。たとえば、コンソールへ出力するのではなく、 " 60 | "``discord.log`` というファイルにログを出力するには、以下のスニペットが利用できます。" 61 | 62 | #: ../../logging.rst:37 63 | #, fuzzy 64 | msgid "" 65 | "This is recommended, especially at verbose levels such as ``INFO`` and " 66 | "``DEBUG``, as there are a lot of events logged and it would clog the " 67 | "stdout of your program." 68 | msgstr "" 69 | "特に、 ``INFO`` や ``DEBUG`` " 70 | "といった冗長なイベントレベルを設定している場合、プログラムの標準出力をつまらせてしまう原因になるため、ファイルへの出力が推奨されます。" 71 | 72 | #: ../../logging.rst:43 73 | msgid "" 74 | "For more information, check the documentation and tutorial of the " 75 | ":mod:`logging` module." 76 | msgstr "詳細は、:mod:`logging` モジュールのドキュメントを参照してください。" 77 | 78 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | ## Discontinued 2 | [Discord.py](https://github.com/Rapptz/discord.py) was resumed as such this fork will no longer be receiving any updates. Please use discord.py instead. 3 | 4 |
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"" 90 | 91 | @property 92 | def created_at(self) -> datetime.datetime: 93 | """:class:`datetime.datetime`: Returns the snowflake's creation time in UTC.""" 94 | return utils.snowflake_time(self.id) 95 | -------------------------------------------------------------------------------- /docs/extensions/builder.py: -------------------------------------------------------------------------------- 1 | from sphinx.builders.html import StandaloneHTMLBuilder 2 | from sphinx.environment.adapters.indexentries import IndexEntries 3 | from sphinx.writers.html5 import HTML5Translator 4 | 5 | class DPYHTML5Translator(HTML5Translator): 6 | def visit_section(self, node): 7 | self.section_level += 1 8 | self.body.append( 9 | self.starttag(node, 'section')) 10 | 11 | def depart_section(self, node): 12 | self.section_level -= 1 13 | self.body.append('\n') 14 | 15 | def visit_table(self, node): 16 | self.body.append('
') 17 | super().visit_table(node) 18 | 19 | def depart_table(self, node): 20 | super().depart_table(node) 21 | self.body.append('
') 22 | 23 | class DPYStandaloneHTMLBuilder(StandaloneHTMLBuilder): 24 | # This is mostly copy pasted from Sphinx. 25 | def write_genindex(self) -> None: 26 | # the total count of lines for each index letter, used to distribute 27 | # the entries into two columns 28 | genindex = IndexEntries(self.env).create_index(self, group_entries=False) 29 | indexcounts = [] 30 | for _k, entries in genindex: 31 | indexcounts.append(sum(1 + len(subitems) 32 | for _, (_, subitems, _) in entries)) 33 | 34 | genindexcontext = { 35 | 'genindexentries': genindex, 36 | 'genindexcounts': indexcounts, 37 | 'split_index': self.config.html_split_index, 38 | } 39 | 40 | if self.config.html_split_index: 41 | self.handle_page('genindex', genindexcontext, 42 | 'genindex-split.html') 43 | self.handle_page('genindex-all', genindexcontext, 44 | 'genindex.html') 45 | for (key, entries), count in zip(genindex, indexcounts): 46 | ctx = {'key': key, 'entries': entries, 'count': count, 47 | 'genindexentries': genindex} 48 | self.handle_page('genindex-' + key, ctx, 49 | 'genindex-single.html') 50 | else: 51 | self.handle_page('genindex', genindexcontext, 'genindex.html') 52 | 53 | 54 | def add_custom_jinja2(app): 55 | env = app.builder.templates.environment 56 | env.tests['prefixedwith'] = str.startswith 57 | env.tests['suffixedwith'] = str.endswith 58 | 59 | def add_builders(app): 60 | """This is necessary because RTD injects their own for some reason.""" 61 | app.set_translator('html', DPYHTML5Translator, override=True) 62 | app.add_builder(DPYStandaloneHTMLBuilder, override=True) 63 | 64 | try: 65 | original = app.registry.builders['readthedocs'] 66 | except KeyError: 67 | pass 68 | else: 69 | injected_mro = tuple(base if base is not StandaloneHTMLBuilder else DPYStandaloneHTMLBuilder 70 | for base in original.mro()[1:]) 71 | new_builder = type(original.__name__, injected_mro, {'name': 'readthedocs'}) 72 | app.set_translator('readthedocs', DPYHTML5Translator, override=True) 73 | app.add_builder(new_builder, override=True) 74 | 75 | def setup(app): 76 | add_builders(app) 77 | app.connect('builder-inited', add_custom_jinja2) 78 | -------------------------------------------------------------------------------- /diskord/types/activity.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 List, Literal, Optional, TypedDict 28 | from .user import PartialUser 29 | from .snowflake import Snowflake 30 | 31 | 32 | StatusType = Literal["idle", "dnd", "online", "offline"] 33 | 34 | 35 | class PartialPresenceUpdate(TypedDict): 36 | user: PartialUser 37 | guild_id: Snowflake 38 | status: StatusType 39 | activities: List[Activity] 40 | client_status: ClientStatus 41 | 42 | 43 | class ClientStatus(TypedDict, total=False): 44 | desktop: str 45 | mobile: str 46 | web: str 47 | 48 | 49 | class ActivityTimestamps(TypedDict, total=False): 50 | start: int 51 | end: int 52 | 53 | 54 | class ActivityParty(TypedDict, total=False): 55 | id: str 56 | size: List[int] 57 | 58 | 59 | class ActivityAssets(TypedDict, total=False): 60 | large_image: str 61 | large_text: str 62 | small_image: str 63 | small_text: str 64 | 65 | 66 | class ActivitySecrets(TypedDict, total=False): 67 | join: str 68 | spectate: str 69 | match: str 70 | 71 | 72 | class _ActivityEmojiOptional(TypedDict, total=False): 73 | id: Snowflake 74 | animated: bool 75 | 76 | 77 | class ActivityEmoji(_ActivityEmojiOptional): 78 | name: str 79 | 80 | 81 | class ActivityButton(TypedDict): 82 | label: str 83 | url: str 84 | 85 | 86 | class _SendableActivityOptional(TypedDict, total=False): 87 | url: Optional[str] 88 | 89 | 90 | ActivityType = Literal[0, 1, 2, 4, 5] 91 | 92 | 93 | class SendableActivity(_SendableActivityOptional): 94 | name: str 95 | type: ActivityType 96 | 97 | 98 | class _BaseActivity(SendableActivity): 99 | created_at: int 100 | 101 | 102 | class Activity(_BaseActivity, total=False): 103 | state: Optional[str] 104 | details: Optional[str] 105 | timestamps: ActivityTimestamps 106 | assets: ActivityAssets 107 | party: ActivityParty 108 | application_id: Snowflake 109 | flags: int 110 | emoji: Optional[ActivityEmoji] 111 | secrets: ActivitySecrets 112 | session_id: Optional[str] 113 | instance: bool 114 | buttons: List[ActivityButton] 115 | -------------------------------------------------------------------------------- /docs/intro.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | .. currentmodule:: diskord 4 | 5 | .. _intro: 6 | 7 | Introduction 8 | ============== 9 | 10 | This is the documentation for diskord, a library for Python to aid 11 | in creating applications that utilise the Discord API. 12 | 13 | Prerequisites 14 | --------------- 15 | 16 | diskord works with Python 3.8 or higher. Support for earlier versions of Python 17 | is not provided. Python 2.7 or lower is not supported. Python 3.7 or lower is not supported. 18 | 19 | 20 | .. _installing: 21 | 22 | Installing 23 | ----------- 24 | 25 | You can get the library directly from PyPI: :: 26 | 27 | python3 -m pip install -U diskord 28 | 29 | If you are using Windows, then the following should be used instead: :: 30 | 31 | py -3 -m pip install -U diskord 32 | 33 | 34 | To get voice support, you should use ``diskord[voice]`` instead of ``diskord``, e.g. :: 35 | 36 | python3 -m pip install -U diskord[voice] 37 | 38 | On Linux environments, installing voice requires getting the following dependencies: 39 | 40 | - `libffi `_ 41 | - `libnacl `_ 42 | - `python3-dev `_ 43 | 44 | For a Debian-based system, the following command will get these dependencies: 45 | 46 | .. code-block:: shell 47 | 48 | $ apt install libffi-dev libnacl-dev python3-dev 49 | 50 | Remember to check your permissions! 51 | 52 | Virtual Environments 53 | ~~~~~~~~~~~~~~~~~~~~~ 54 | 55 | Sometimes you want to keep libraries from polluting system installs or use a different version of 56 | libraries than the ones installed on the system. You might also not have permissions to install libraries system-wide. 57 | For this purpose, the standard library as of Python 3.3 comes with a concept called "Virtual Environment"s to 58 | help maintain these separate versions. 59 | 60 | A more in-depth tutorial is found on :doc:`py:tutorial/venv`. 61 | 62 | However, for the quick and dirty: 63 | 64 | 1. Go to your project's working directory: 65 | 66 | .. code-block:: shell 67 | 68 | $ cd your-bot-source 69 | $ python3 -m venv bot-env 70 | 71 | 2. Activate the virtual environment: 72 | 73 | .. code-block:: shell 74 | 75 | $ source bot-env/bin/activate 76 | 77 | On Windows you activate it with: 78 | 79 | .. code-block:: shell 80 | 81 | $ bot-env\Scripts\activate.bat 82 | 83 | 3. Use pip like usual: 84 | 85 | .. code-block:: shell 86 | 87 | $ pip install -U diskord 88 | 89 | Congratulations. You now have a virtual environment all set up. 90 | 91 | Basic Concepts 92 | --------------- 93 | 94 | diskord revolves around the concept of :ref:`events `. 95 | An event is something you listen to and then respond to. For example, when a message 96 | happens, you will receive an event about it that you can respond to. 97 | 98 | A quick example to showcase how events work: 99 | 100 | .. code-block:: python3 101 | 102 | import diskord 103 | 104 | class MyClient(diskord.Client): 105 | async def on_ready(self): 106 | print(f'Logged on as {self.user}!') 107 | 108 | async def on_message(self, message): 109 | print(f'Message from {message.author}: {message.content}') 110 | 111 | client = MyClient() 112 | client.run('my token goes here') 113 | 114 | -------------------------------------------------------------------------------- /examples/reaction_roles.py: -------------------------------------------------------------------------------- 1 | # This example requires the 'members' privileged intents 2 | 3 | import diskord 4 | 5 | class MyClient(diskord.Client): 6 | def __init__(self, *args, **kwargs): 7 | super().__init__(*args, **kwargs) 8 | 9 | self.role_message_id = 0 # ID of the message that can be reacted to to add/remove a role. 10 | self.emoji_to_role = { 11 | diskord.PartialEmoji(name='🔴'): 0, # ID of the role associated with unicode emoji '🔴'. 12 | diskord.PartialEmoji(name='🟡'): 0, # ID of the role associated with unicode emoji '🟡'. 13 | diskord.PartialEmoji(name='green', id=0): 0, # ID of the role associated with a partial emoji's ID. 14 | } 15 | 16 | async def on_raw_reaction_add(self, payload: diskord.RawReactionActionEvent): 17 | """Gives a role based on a reaction emoji.""" 18 | # Make sure that the message the user is reacting to is the one we care about. 19 | if payload.message_id != self.role_message_id: 20 | return 21 | 22 | guild = self.get_guild(payload.guild_id) 23 | if guild is None: 24 | # Check if we're still in the guild and it's cached. 25 | return 26 | 27 | try: 28 | role_id = self.emoji_to_role[payload.emoji] 29 | except KeyError: 30 | # If the emoji isn't the one we care about then exit as well. 31 | return 32 | 33 | role = guild.get_role(role_id) 34 | if role is None: 35 | # Make sure the role still exists and is valid. 36 | return 37 | 38 | try: 39 | # Finally, add the role. 40 | await payload.member.add_roles(role) 41 | except diskord.HTTPException: 42 | # If we want to do something in case of errors we'd do it here. 43 | pass 44 | 45 | async def on_raw_reaction_remove(self, payload: diskord.RawReactionActionEvent): 46 | """Removes a role based on a reaction emoji.""" 47 | # Make sure that the message the user is reacting to is the one we care about. 48 | if payload.message_id != self.role_message_id: 49 | return 50 | 51 | guild = self.get_guild(payload.guild_id) 52 | if guild is None: 53 | # Check if we're still in the guild and it's cached. 54 | return 55 | 56 | try: 57 | role_id = self.emoji_to_role[payload.emoji] 58 | except KeyError: 59 | # If the emoji isn't the one we care about then exit as well. 60 | return 61 | 62 | role = guild.get_role(role_id) 63 | if role is None: 64 | # Make sure the role still exists and is valid. 65 | return 66 | 67 | # The payload for `on_raw_reaction_remove` does not provide `.member` 68 | # so we must get the member ourselves from the payload's `.user_id`. 69 | member = guild.get_member(payload.user_id) 70 | if member is None: 71 | # Make sure the member still exists and is valid. 72 | return 73 | 74 | try: 75 | # Finally, remove the role. 76 | await member.remove_roles(role) 77 | except diskord.HTTPException: 78 | # If we want to do something in case of errors we'd do it here. 79 | pass 80 | 81 | intents = diskord.Intents.default() 82 | intents.members = True 83 | 84 | client = MyClient(intents=intents) 85 | client.run('token') 86 | -------------------------------------------------------------------------------- /docs/locale/ja/LC_MESSAGES/version_guarantees.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: discordpy\n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "POT-Creation-Date: 2019-06-22 09:35-0400\n" 6 | "PO-Revision-Date: 2020-10-24 02:41\n" 7 | "Last-Translator: \n" 8 | "Language-Team: Japanese\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "Plural-Forms: nplurals=1; plural=0;\n" 13 | "X-Crowdin-Project: discordpy\n" 14 | "X-Crowdin-Project-ID: 362783\n" 15 | "X-Crowdin-Language: ja\n" 16 | "X-Crowdin-File: version_guarantees.pot\n" 17 | "X-Crowdin-File-ID: 46\n" 18 | "Language: ja_JP\n" 19 | 20 | #: ../../version_guarantees.rst:4 21 | msgid "Version Guarantees" 22 | msgstr "バージョン保証" 23 | 24 | #: ../../version_guarantees.rst:6 25 | msgid "The library follows a `semantic versioning principle `_ which means that the major version is updated every time there is an incompatible API change. However due to the lack of guarantees on the Discord side when it comes to breaking changes along with the fairly dynamic nature of Python it can be hard to discern what can be considered a breaking change and what isn't." 26 | msgstr "このライブラリは `セマンティック バージョニングの原則 `_ に従います。それが意味するのは、互換性のないAPIの変更が行われるたびにメジャーバージョンが更新されるということです。しかしながら、Discord側にはPythonの非常に動的な性質とともに破壊的変更を行う際の保証がないため、破壊的変更とみなされるもの、そうでないものを区別するのは困難です。" 27 | 28 | #: ../../version_guarantees.rst:8 29 | msgid "The first thing to keep in mind is that breaking changes only apply to **publicly documented functions and classes**. If it's not listed in the documentation here then it is not part of the public API and is thus bound to change. This includes attributes that start with an underscore or functions without an underscore that are not documented." 30 | msgstr "最初に覚えておくべきことは、破壊的変更は **公開ドキュメント化してある関数とクラス** のみに適用されるということです。ドキュメントにないものはパブリックAPIの一部ではないため、変更される可能性があります。これにはドキュメントに載っていないアンダースコアから始まる関数や、通常の関数が含まれます。" 31 | 32 | #: ../../version_guarantees.rst:12 33 | msgid "The examples below are non-exhaustive." 34 | msgstr "以下の例は網羅的なものではありません。" 35 | 36 | #: ../../version_guarantees.rst:15 37 | msgid "Examples of Breaking Changes" 38 | msgstr "破壊的変更の例" 39 | 40 | #: ../../version_guarantees.rst:17 41 | msgid "Changing the default parameter value to something else." 42 | msgstr "デフォルトのパラメータ値を別のものに変更。" 43 | 44 | #: ../../version_guarantees.rst:18 45 | msgid "Renaming a function without an alias to an old function." 46 | msgstr "古い関数へのエイリアスのない関数の名称を変更。" 47 | 48 | #: ../../version_guarantees.rst:19 49 | msgid "Adding or removing parameters to an event." 50 | msgstr "イベントへのパラメータの追加、あるいは削除。" 51 | 52 | #: ../../version_guarantees.rst:22 53 | msgid "Examples of Non-Breaking Changes" 54 | msgstr "破壊的変更ではないものの例" 55 | 56 | #: ../../version_guarantees.rst:24 57 | msgid "Adding or removing private underscored attributes." 58 | msgstr "アンダースコア付きのプライベート関数の追加、あるいは削除。" 59 | 60 | #: ../../version_guarantees.rst:25 61 | msgid "Adding an element into the ``__slots__`` of a data class." 62 | msgstr "データクラスの ``__slots__`` への要素の追加。" 63 | 64 | #: ../../version_guarantees.rst:26 65 | msgid "Changing the behaviour of a function to fix a bug." 66 | msgstr "バグ修正のための関数の動作の変更。" 67 | 68 | #: ../../version_guarantees.rst:27 69 | msgid "Changes in the documentation." 70 | msgstr "ドキュメントの変更。" 71 | 72 | #: ../../version_guarantees.rst:28 73 | msgid "Modifying the internal HTTP handling." 74 | msgstr "内部HTTP処理の変更。" 75 | 76 | #: ../../version_guarantees.rst:29 77 | msgid "Upgrading the dependencies to a new version, major or otherwise." 78 | msgstr "依存関係をメジャー、またはそれ以外の新しいバージョンへアップグレード。" 79 | 80 | -------------------------------------------------------------------------------- /examples/secret.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | import diskord 4 | from diskord.ext import commands 5 | 6 | bot = commands.Bot(command_prefix=commands.when_mentioned, description="Nothing to see here!") 7 | 8 | # the `hidden` keyword argument hides it from the help command. 9 | @bot.group(hidden=True) 10 | async def secret(ctx: commands.Context): 11 | """What is this "secret" you speak of?""" 12 | if ctx.invoked_subcommand is None: 13 | await ctx.send('Shh!', delete_after=5) 14 | 15 | def create_overwrites(ctx, *objects): 16 | """This is just a helper function that creates the overwrites for the 17 | voice/text channels. 18 | 19 | A `diskord.PermissionOverwrite` allows you to determine the permissions 20 | of an object, whether it be a `diskord.Role` or a `diskord.Member`. 21 | 22 | In this case, the `view_channel` permission is being used to hide the channel 23 | from being viewed by whoever does not meet the criteria, thus creating a 24 | secret channel. 25 | """ 26 | 27 | # a dict comprehension is being utilised here to set the same permission overwrites 28 | # for each `diskord.Role` or `diskord.Member`. 29 | overwrites = { 30 | obj: diskord.PermissionOverwrite(view_channel=True) 31 | for obj in objects 32 | } 33 | 34 | # prevents the default role (@everyone) from viewing the channel 35 | # if it isn't already allowed to view the channel. 36 | overwrites.setdefault(ctx.guild.default_role, diskord.PermissionOverwrite(view_channel=False)) 37 | 38 | # makes sure the client is always allowed to view the channel. 39 | overwrites[ctx.guild.me] = diskord.PermissionOverwrite(view_channel=True) 40 | 41 | return overwrites 42 | 43 | # since these commands rely on guild related features, 44 | # it is best to lock it to be guild-only. 45 | @secret.command() 46 | @commands.guild_only() 47 | async def text(ctx: commands.Context, name: str, *objects: typing.Union[diskord.Role, diskord.Member]): 48 | """This makes a text channel with a specified name 49 | that is only visible to roles or members that are specified. 50 | """ 51 | 52 | overwrites = create_overwrites(ctx, *objects) 53 | 54 | await ctx.guild.create_text_channel( 55 | name, 56 | overwrites=overwrites, 57 | topic='Top secret text channel. Any leakage of this channel may result in serious trouble.', 58 | reason='Very secret business.', 59 | ) 60 | 61 | @secret.command() 62 | @commands.guild_only() 63 | async def voice(ctx: commands.Context, name: str, *objects: typing.Union[diskord.Role, diskord.Member]): 64 | """This does the same thing as the `text` subcommand 65 | but instead creates a voice channel. 66 | """ 67 | 68 | overwrites = create_overwrites(ctx, *objects) 69 | 70 | await ctx.guild.create_voice_channel( 71 | name, 72 | overwrites=overwrites, 73 | reason='Very secret business.' 74 | ) 75 | 76 | @secret.command() 77 | @commands.guild_only() 78 | async def emoji(ctx: commands.Context, emoji: diskord.PartialEmoji, *roles: diskord.Role): 79 | """This clones a specified emoji that only specified roles 80 | are allowed to use. 81 | """ 82 | 83 | # fetch the emoji asset and read it as bytes. 84 | emoji_bytes = await emoji.read() 85 | 86 | # the key parameter here is `roles`, which controls 87 | # what roles are able to use the emoji. 88 | await ctx.guild.create_custom_emoji( 89 | name=emoji.name, 90 | image=emoji_bytes, 91 | roles=roles, 92 | reason='Very secret business.' 93 | ) 94 | 95 | 96 | bot.run('token') 97 | -------------------------------------------------------------------------------- /docs/discord.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | .. _discord-intro: 4 | 5 | Creating a Bot Account 6 | ======================== 7 | 8 | In order to work with the library and the Discord API in general, we must first create a Discord Bot account. 9 | 10 | Creating a Bot account is a pretty straightforward process. 11 | 12 | 1. Make sure you're logged on to the `Discord website `_. 13 | 2. Navigate to the `application page `_ 14 | 3. Click on the "New Application" button. 15 | 16 | .. image:: /images/discord_create_app_button.png 17 | :alt: The new application button. 18 | 19 | 4. Give the application a name and click "Create". 20 | 21 | .. image:: /images/discord_create_app_form.png 22 | :alt: The new application form filled in. 23 | 24 | 5. Create a Bot User by navigating to the "Bot" tab and clicking "Add Bot". 25 | 26 | - Click "Yes, do it!" to continue. 27 | 28 | .. image:: /images/discord_create_bot_user.png 29 | :alt: The Add Bot button. 30 | 6. Make sure that **Public Bot** is ticked if you want others to invite your bot. 31 | 32 | - You should also make sure that **Require OAuth2 Code Grant** is unchecked unless you 33 | are developing a service that needs it. If you're unsure, then **leave it unchecked**. 34 | 35 | .. image:: /images/discord_bot_user_options.png 36 | :alt: How the Bot User options should look like for most people. 37 | 38 | 7. Copy the token using the "Copy" button. 39 | 40 | - **This is not the Client Secret at the General Information page.** 41 | 42 | .. warning:: 43 | 44 | It should be worth noting that this token is essentially your bot's 45 | password. You should **never** share this with someone else. In doing so, 46 | someone can log in to your bot and do malicious things, such as leaving 47 | servers, ban all members inside a server, or pinging everyone maliciously. 48 | 49 | The possibilities are endless, so **do not share this token.** 50 | 51 | If you accidentally leaked your token, click the "Regenerate" button as soon 52 | as possible. This revokes your old token and re-generates a new one. 53 | Now you need to use the new token to login. 54 | 55 | And that's it. You now have a bot account and you can login with that token. 56 | 57 | .. _discord_invite_bot: 58 | 59 | Inviting Your Bot 60 | ------------------- 61 | 62 | So you've made a Bot User but it's not actually in any server. 63 | 64 | If you want to invite your bot you must create an invite URL for it. 65 | 66 | 1. Make sure you're logged on to the `Discord website `_. 67 | 2. Navigate to the `application page `_ 68 | 3. Click on your bot's page. 69 | 4. Go to the "OAuth2" tab. 70 | 71 | .. image:: /images/discord_oauth2.png 72 | :alt: How the OAuth2 page should look like. 73 | 74 | 5. Tick the "bot" checkbox under "scopes". 75 | 76 | .. image:: /images/discord_oauth2_scope.png 77 | :alt: The scopes checkbox with "bot" ticked. 78 | 79 | 6. Tick the permissions required for your bot to function under "Bot Permissions". 80 | 81 | - Please be aware of the consequences of requiring your bot to have the "Administrator" permission. 82 | 83 | - Bot owners must have 2FA enabled for certain actions and permissions when added in servers that have Server-Wide 2FA enabled. Check the `2FA support page `_ for more information. 84 | 85 | .. image:: /images/discord_oauth2_perms.png 86 | :alt: The permission checkboxes with some permissions checked. 87 | 88 | 7. Now the resulting URL can be used to add your bot to a server. Copy and paste the URL into your browser, choose a server to invite the bot to, and click "Authorize". 89 | 90 | 91 | .. note:: 92 | 93 | The person adding the bot needs "Manage Server" permissions to do so. 94 | 95 | If you want to generate this URL dynamically at run-time inside your bot and using the 96 | :class:`diskord.Permissions` interface, you can use :func:`diskord.utils.oauth_url`. 97 | -------------------------------------------------------------------------------- /diskord/backoff.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 | 28 | import time 29 | import random 30 | from typing import Callable, Generic, Literal, TypeVar, overload, Union 31 | 32 | T = TypeVar("T", bool, Literal[True], Literal[False]) 33 | 34 | __all__ = ("ExponentialBackoff",) 35 | 36 | 37 | class ExponentialBackoff(Generic[T]): 38 | """An implementation of the exponential backoff algorithm 39 | 40 | Provides a convenient interface to implement an exponential backoff 41 | for reconnecting or retrying transmissions in a distributed network. 42 | 43 | Once instantiated, the delay method will return the next interval to 44 | wait for when retrying a connection or transmission. The maximum 45 | delay increases exponentially with each retry up to a maximum of 46 | 2^10 * base, and is reset if no more attempts are needed in a period 47 | of 2^11 * base seconds. 48 | 49 | Parameters 50 | ---------- 51 | base: :class:`int` 52 | The base delay in seconds. The first retry-delay will be up to 53 | this many seconds. 54 | integral: :class:`bool` 55 | Set to ``True`` if whole periods of base is desirable, otherwise any 56 | number in between may be returned. 57 | """ 58 | 59 | def __init__(self, base: int = 1, *, integral: T = False): 60 | self._base: int = base 61 | 62 | self._exp: int = 0 63 | self._max: int = 10 64 | self._reset_time: int = base * 2 ** 11 65 | self._last_invocation: float = time.monotonic() 66 | 67 | # Use our own random instance to avoid messing with global one 68 | rand = random.Random() 69 | rand.seed() 70 | 71 | self._randfunc: Callable[..., Union[int, float]] = rand.randrange if integral else rand.uniform # type: ignore 72 | 73 | @overload 74 | def delay(self: ExponentialBackoff[Literal[False]]) -> float: 75 | ... 76 | 77 | @overload 78 | def delay(self: ExponentialBackoff[Literal[True]]) -> int: 79 | ... 80 | 81 | @overload 82 | def delay(self: ExponentialBackoff[bool]) -> Union[int, float]: 83 | ... 84 | 85 | def delay(self) -> Union[int, float]: 86 | """Compute the next delay 87 | 88 | Returns the next delay to wait according to the exponential 89 | backoff algorithm. This is a value between 0 and base * 2^exp 90 | where exponent starts off at 1 and is incremented at every 91 | invocation of this method up to a maximum of 10. 92 | 93 | If a period of more than base * 2^11 has passed since the last 94 | retry, the exponent is reset to 1. 95 | """ 96 | invocation = time.monotonic() 97 | interval = invocation - self._last_invocation 98 | self._last_invocation = invocation 99 | 100 | if interval > self._reset_time: 101 | self._exp = 0 102 | 103 | self._exp = min(self._exp + 1, self._max) 104 | return self._randfunc(0, self._base * 2 ** self._exp) 105 | -------------------------------------------------------------------------------- /diskord/oggparse.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 struct 28 | 29 | from typing import TYPE_CHECKING, ClassVar, IO, Generator, Tuple, Optional 30 | 31 | from .errors import DiscordException 32 | 33 | __all__ = ( 34 | "OggError", 35 | "OggPage", 36 | "OggStream", 37 | ) 38 | 39 | 40 | class OggError(DiscordException): 41 | """An exception that is thrown for Ogg stream parsing errors.""" 42 | 43 | pass 44 | 45 | 46 | # https://tools.ietf.org/html/rfc3533 47 | # https://tools.ietf.org/html/rfc7845 48 | 49 | 50 | class OggPage: 51 | _header: ClassVar[struct.Struct] = struct.Struct(" None: 61 | try: 62 | header = stream.read(struct.calcsize(self._header.format)) 63 | 64 | ( 65 | self.flag, 66 | self.gran_pos, 67 | self.serial, 68 | self.pagenum, 69 | self.crc, 70 | self.segnum, 71 | ) = self._header.unpack(header) 72 | 73 | self.segtable: bytes = stream.read(self.segnum) 74 | bodylen = sum(struct.unpack("B" * self.segnum, self.segtable)) 75 | self.data: bytes = stream.read(bodylen) 76 | except Exception: 77 | raise OggError("bad data stream") from None 78 | 79 | def iter_packets(self) -> Generator[Tuple[bytes, bool], None, None]: 80 | packetlen = offset = 0 81 | partial = True 82 | 83 | for seg in self.segtable: 84 | if seg == 255: 85 | packetlen += 255 86 | partial = True 87 | else: 88 | packetlen += seg 89 | yield self.data[offset : offset + packetlen], True 90 | offset += packetlen 91 | packetlen = 0 92 | partial = False 93 | 94 | if partial: 95 | yield self.data[offset:], False 96 | 97 | 98 | class OggStream: 99 | def __init__(self, stream: IO[bytes]) -> None: 100 | self.stream: IO[bytes] = stream 101 | 102 | def _next_page(self) -> Optional[OggPage]: 103 | head = self.stream.read(4) 104 | if head == b"OggS": 105 | return OggPage(self.stream) 106 | elif not head: 107 | return None 108 | else: 109 | raise OggError("invalid header magic") 110 | 111 | def _iter_pages(self) -> Generator[OggPage, None, None]: 112 | page = self._next_page() 113 | while page: 114 | yield page 115 | page = self._next_page() 116 | 117 | def iter_packets(self) -> Generator[bytes, None, None]: 118 | partial = b"" 119 | for page in self._iter_pages(): 120 | for data, complete in page.iter_packets(): 121 | partial += data 122 | if complete: 123 | yield partial 124 | partial = b"" 125 | -------------------------------------------------------------------------------- /diskord/types/message.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 List, Literal, Optional, TypedDict, Union 28 | from .snowflake import Snowflake, SnowflakeList 29 | from .member import Member, UserWithMember 30 | from .user import User 31 | from .emoji import PartialEmoji 32 | from .embed import Embed 33 | from .channel import ChannelType 34 | from .components import Component 35 | from .interactions import MessageInteraction 36 | from .sticker import StickerItem 37 | 38 | 39 | class ChannelMention(TypedDict): 40 | id: Snowflake 41 | guild_id: Snowflake 42 | type: ChannelType 43 | name: str 44 | 45 | 46 | class Reaction(TypedDict): 47 | count: int 48 | me: bool 49 | emoji: PartialEmoji 50 | 51 | 52 | class _AttachmentOptional(TypedDict, total=False): 53 | height: Optional[int] 54 | width: Optional[int] 55 | content_type: str 56 | spoiler: bool 57 | ephemeral: bool 58 | 59 | 60 | class Attachment(_AttachmentOptional): 61 | id: Snowflake 62 | filename: str 63 | size: int 64 | url: str 65 | proxy_url: str 66 | 67 | 68 | MessageActivityType = Literal[1, 2, 3, 5] 69 | 70 | 71 | class MessageActivity(TypedDict): 72 | type: MessageActivityType 73 | party_id: str 74 | 75 | 76 | class _MessageApplicationOptional(TypedDict, total=False): 77 | cover_image: str 78 | 79 | 80 | class MessageApplication(_MessageApplicationOptional): 81 | id: Snowflake 82 | description: str 83 | icon: Optional[str] 84 | name: str 85 | 86 | 87 | class MessageReference(TypedDict, total=False): 88 | message_id: Snowflake 89 | channel_id: Snowflake 90 | guild_id: Snowflake 91 | fail_if_not_exists: bool 92 | 93 | 94 | class _MessageOptional(TypedDict, total=False): 95 | guild_id: Snowflake 96 | member: Member 97 | mention_channels: List[ChannelMention] 98 | reactions: List[Reaction] 99 | nonce: Union[int, str] 100 | webhook_id: Snowflake 101 | activity: MessageActivity 102 | application: MessageApplication 103 | application_id: Snowflake 104 | message_reference: MessageReference 105 | flags: int 106 | sticker_items: List[StickerItem] 107 | referenced_message: Optional[Message] 108 | interaction: MessageInteraction 109 | components: List[Component] 110 | 111 | 112 | MessageType = Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 18, 19, 20, 21] 113 | 114 | 115 | class Message(_MessageOptional): 116 | id: Snowflake 117 | channel_id: Snowflake 118 | author: User 119 | content: str 120 | timestamp: str 121 | edited_timestamp: Optional[str] 122 | tts: bool 123 | mention_everyone: bool 124 | mentions: List[UserWithMember] 125 | mention_roles: SnowflakeList 126 | attachments: List[Attachment] 127 | embeds: List[Embed] 128 | pinned: bool 129 | type: MessageType 130 | 131 | 132 | AllowedMentionType = Literal["roles", "users", "everyone"] 133 | 134 | 135 | class AllowedMentions(TypedDict): 136 | parse: List[AllowedMentionType] 137 | roles: SnowflakeList 138 | users: SnowflakeList 139 | replied_user: bool 140 | -------------------------------------------------------------------------------- /docs/_static/sidebar.js: -------------------------------------------------------------------------------- 1 | class Sidebar { 2 | constructor(element) { 3 | this.element = element; 4 | this.activeLink = null; 5 | 6 | this.element.addEventListener('click', (e) => { 7 | // If we click a navigation, close the hamburger menu 8 | if (e.target.tagName == 'A' && this.element.classList.contains('sidebar-toggle')) { 9 | this.element.classList.remove('sidebar-toggle'); 10 | let button = hamburgerToggle.firstElementChild; 11 | button.textContent = 'menu'; 12 | 13 | // Scroll a little up to actually see the header 14 | // Note: this is generally around ~55px 15 | // A proper solution is getComputedStyle but it can be slow 16 | // Instead let's just rely on this quirk and call it a day 17 | // This has to be done after the browser actually processes 18 | // the section movement 19 | setTimeout(() => window.scrollBy(0, -100), 75); 20 | } 21 | }); 22 | } 23 | 24 | createCollapsableSections() { 25 | let toc = this.element.querySelector('ul'); 26 | if (!toc) { 27 | return 28 | } 29 | let allReferences = toc.querySelectorAll('a.reference.internal:not([href="#"])'); 30 | 31 | for (let ref of allReferences) { 32 | 33 | let next = ref.nextElementSibling; 34 | 35 | if (next && next.tagName === "UL") { 36 | 37 | let icon = document.createElement('span'); 38 | icon.className = 'material-icons collapsible-arrow expanded'; 39 | icon.innerText = 'expand_more'; 40 | 41 | if (next.parentElement.tagName == "LI") { 42 | next.parentElement.classList.add('no-list-style') 43 | } 44 | 45 | icon.addEventListener('click', () => { 46 | if (icon.classList.contains('expanded')) { 47 | this.collapseSection(icon); 48 | } else { 49 | this.expandSection(icon); 50 | } 51 | }) 52 | 53 | ref.classList.add('ref-internal-padding') 54 | ref.parentNode.insertBefore(icon, ref); 55 | } 56 | } 57 | } 58 | 59 | resize() { 60 | let rect = this.element.getBoundingClientRect(); 61 | this.element.style.height = `calc(100vh - 1em - ${rect.top + document.body.offsetTop}px)`; 62 | } 63 | 64 | collapseSection(icon) { 65 | icon.classList.remove('expanded'); 66 | icon.classList.add('collapsed'); 67 | let children = icon.nextElementSibling.nextElementSibling; 68 | // 69 | // --> 70 | setTimeout(() => children.style.display = "none", 75) 71 | } 72 | 73 | expandSection(icon) { 74 | icon.classList.remove('collapse'); 75 | icon.classList.add('expanded'); 76 | let children = icon.nextElementSibling.nextElementSibling; 77 | setTimeout(() => children.style.display = "block", 75) 78 | } 79 | 80 | setActiveLink(section) { 81 | if (this.activeLink) { 82 | this.activeLink.parentElement.classList.remove('active'); 83 | } 84 | if (section) { 85 | this.activeLink = document.querySelector(`#sidebar a[href="#${section.id}"]`); 86 | if (this.activeLink) { 87 | let headingChildren = this.activeLink.parentElement.parentElement; 88 | let heading = headingChildren.previousElementSibling.previousElementSibling; 89 | 90 | if (heading && headingChildren.style.display === 'none') { 91 | this.activeLink = heading; 92 | } 93 | this.activeLink.parentElement.classList.add('active'); 94 | } 95 | } 96 | } 97 | 98 | } 99 | 100 | function getCurrentSection() { 101 | let currentSection; 102 | if (window.scrollY + window.innerHeight > bottomHeightThreshold) { 103 | currentSection = sections[sections.length - 1]; 104 | } 105 | else { 106 | if (sections) { 107 | sections.forEach(section => { 108 | let rect = section.getBoundingClientRect(); 109 | if (rect.top + document.body.offsetTop < 1) { 110 | currentSection = section; 111 | } 112 | }); 113 | } 114 | } 115 | return currentSection; 116 | } 117 | 118 | document.addEventListener('DOMContentLoaded', () => { 119 | sidebar = new Sidebar(document.getElementById('sidebar')); 120 | sidebar.resize(); 121 | sidebar.createCollapsableSections(); 122 | 123 | window.addEventListener('scroll', () => { 124 | sidebar.setActiveLink(getCurrentSection()); 125 | sidebar.resize(); 126 | }); 127 | }); 128 | -------------------------------------------------------------------------------- /docs/ext/tasks/index.rst: -------------------------------------------------------------------------------- 1 | .. _diskord_ext_tasks: 2 | 3 | ``diskord.ext.tasks`` -- asyncio.Task helpers 4 | ==================================================== 5 | 6 | .. versionadded:: 1.1.0 7 | 8 | One of the most common operations when making a bot is having a loop run in the background at a specified interval. This pattern is very common but has a lot of things you need to look out for: 9 | 10 | - How do I handle :exc:`asyncio.CancelledError`? 11 | - What do I do if the internet goes out? 12 | - What is the maximum number of seconds I can sleep anyway? 13 | 14 | The goal of this diskord.py extension is to abstract all these worries away from you. 15 | 16 | Recipes 17 | --------- 18 | 19 | A simple background task in a :class:`~diskord.ext.commands.Cog`: 20 | 21 | .. code-block:: python3 22 | 23 | from diskord.ext import tasks, commands 24 | 25 | class MyCog(commands.Cog): 26 | def __init__(self): 27 | self.index = 0 28 | self.printer.start() 29 | 30 | def cog_unload(self): 31 | self.printer.cancel() 32 | 33 | @tasks.loop(seconds=5.0) 34 | async def printer(self): 35 | print(self.index) 36 | self.index += 1 37 | 38 | Adding an exception to handle during reconnect: 39 | 40 | .. code-block:: python3 41 | 42 | import asyncpg 43 | from diskord.ext import tasks, commands 44 | 45 | class MyCog(commands.Cog): 46 | def __init__(self, bot): 47 | self.bot = bot 48 | self.data = [] 49 | self.batch_update.add_exception_type(asyncpg.PostgresConnectionError) 50 | self.batch_update.start() 51 | 52 | def cog_unload(self): 53 | self.batch_update.cancel() 54 | 55 | @tasks.loop(minutes=5.0) 56 | async def batch_update(self): 57 | async with self.bot.pool.acquire() as con: 58 | # batch update here... 59 | pass 60 | 61 | Looping a certain amount of times before exiting: 62 | 63 | .. code-block:: python3 64 | 65 | from diskord.ext import tasks 66 | 67 | @tasks.loop(seconds=5.0, count=5) 68 | async def slow_count(): 69 | print(slow_count.current_loop) 70 | 71 | @slow_count.after_loop 72 | async def after_slow_count(): 73 | print('done!') 74 | 75 | slow_count.start() 76 | 77 | Waiting until the bot is ready before the loop starts: 78 | 79 | .. code-block:: python3 80 | 81 | from diskord.ext import tasks, commands 82 | 83 | class MyCog(commands.Cog): 84 | def __init__(self, bot): 85 | self.index = 0 86 | self.bot = bot 87 | self.printer.start() 88 | 89 | def cog_unload(self): 90 | self.printer.cancel() 91 | 92 | @tasks.loop(seconds=5.0) 93 | async def printer(self): 94 | print(self.index) 95 | self.index += 1 96 | 97 | @printer.before_loop 98 | async def before_printer(self): 99 | print('waiting...') 100 | await self.bot.wait_until_ready() 101 | 102 | Doing something during cancellation: 103 | 104 | .. code-block:: python3 105 | 106 | from diskord.ext import tasks, commands 107 | import asyncio 108 | 109 | class MyCog(commands.Cog): 110 | def __init__(self, bot): 111 | self.bot= bot 112 | self._batch = [] 113 | self.lock = asyncio.Lock() 114 | self.bulker.start() 115 | 116 | async def do_bulk(self): 117 | # bulk insert data here 118 | ... 119 | 120 | @tasks.loop(seconds=10.0) 121 | async def bulker(self): 122 | async with self.lock: 123 | await self.do_bulk() 124 | 125 | @bulker.after_loop 126 | async def on_bulker_cancel(self): 127 | if self.bulker.is_being_cancelled() and len(self._batch) != 0: 128 | # if we're cancelled and we have some data left... 129 | # let's insert it to our database 130 | await self.do_bulk() 131 | 132 | 133 | .. _ext_tasks_api: 134 | 135 | API Reference 136 | --------------- 137 | 138 | .. attributetable:: diskord.ext.tasks.Loop 139 | 140 | .. autoclass:: diskord.ext.tasks.Loop() 141 | :members: 142 | :special-members: __call__ 143 | :exclude-members: after_loop, before_loop, error 144 | 145 | .. automethod:: Loop.after_loop() 146 | :decorator: 147 | 148 | .. automethod:: Loop.before_loop() 149 | :decorator: 150 | 151 | .. automethod:: Loop.error() 152 | :decorator: 153 | 154 | .. autofunction:: diskord.ext.tasks.loop 155 | -------------------------------------------------------------------------------- /docs/locale/ja/LC_MESSAGES/quickstart.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: discordpy\n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "POT-Creation-Date: 2019-06-22 09:35-0400\n" 6 | "PO-Revision-Date: 2020-10-24 02:41\n" 7 | "Last-Translator: \n" 8 | "Language-Team: Japanese\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "Plural-Forms: nplurals=1; plural=0;\n" 13 | "X-Crowdin-Project: discordpy\n" 14 | "X-Crowdin-Project-ID: 362783\n" 15 | "X-Crowdin-Language: ja\n" 16 | "X-Crowdin-File: quickstart.pot\n" 17 | "X-Crowdin-File-ID: 50\n" 18 | "Language: ja_JP\n" 19 | 20 | #: ../../quickstart.rst:6 21 | msgid "Quickstart" 22 | msgstr "クイックスタート" 23 | 24 | #: ../../quickstart.rst:8 25 | msgid "This page gives a brief introduction to the library. It assumes you have the library installed, if you don't check the :ref:`installing` portion." 26 | msgstr "ここでは、ライブラリの概要を説明します。ライブラリがインストールされていることを前提としているので、インストールを終えていない人は :ref:`installing` を参照してください。" 27 | 28 | #: ../../quickstart.rst:12 29 | msgid "A Minimal Bot" 30 | msgstr "最小限のBot" 31 | 32 | #: ../../quickstart.rst:14 33 | msgid "Let's make a bot that replies to a specific message and walk you through it." 34 | msgstr "では早速、特定のメッセージに対して、返事をするBotを作ってみましょう。" 35 | 36 | #: ../../quickstart.rst:16 37 | msgid "It looks something like this:" 38 | msgstr "結論から書くと、このように書くことができます。" 39 | 40 | #: ../../quickstart.rst:38 41 | msgid "Let's name this file ``example_bot.py``. Make sure not to name it ``discord.py`` as that'll conflict with the library." 42 | msgstr "ファイルの名前を ``example_bot.py`` としましょう。ライブラリと競合してしまうので、 ``discord.py`` というファイル名にはしないでください。" 43 | 44 | #: ../../quickstart.rst:41 45 | msgid "There's a lot going on here, so let's walk you through it step by step." 46 | msgstr "さて、では順を追って一つづつ説明していきます。" 47 | 48 | #: ../../quickstart.rst:43 49 | msgid "The first line just imports the library, if this raises a `ModuleNotFoundError` or `ImportError` then head on over to :ref:`installing` section to properly install." 50 | msgstr "最初の行は、ただライブラリをインポートしただけです。 `ModuleNotFoundError` や `ImportError` が発生した場合は :ref:`installing` を見て、ライブラリをきちんとインストールしましょう。" 51 | 52 | #: ../../quickstart.rst:45 53 | msgid "Next, we create an instance of a :class:`Client`. This client is our connection to Discord." 54 | msgstr "次に、 :class:`Client` のインスタンスを作成します。クライアントはDiscordへの接続を行います。" 55 | 56 | #: ../../quickstart.rst:46 57 | msgid "We then use the :meth:`Client.event` decorator to register an event. This library has many events. Since this library is asynchronous, we do things in a \"callback\" style manner." 58 | msgstr "続いて、 :meth:`Client.event` デコレータを使用してイベントを登録します。ライブラリにはたくさんのイベントが用意されています。このライブラリは非同期のため、「コールバック」のスタイルで処理を行います。" 59 | 60 | #: ../../quickstart.rst:49 61 | msgid "A callback is essentially a function that is called when something happens. In our case, the :func:`on_ready` event is called when the bot has finished logging in and setting things up and the :func:`on_message` event is called when the bot has received a message." 62 | msgstr "コールバックは基本的に、何かが発生した場合に呼び出される関数です。今回の場合だと、Botがログインして、設定などを終えたときに :func:`on_ready` が、メッセージを受信したときに :func:`on_message` が呼び出されます。" 63 | 64 | #: ../../quickstart.rst:52 65 | msgid "Since the :func:`on_message` event triggers for *every* message received, we have to make sure that we ignore messages from ourselves. We do this by checking if the :attr:`Message.author` is the same as the :attr:`Client.user`." 66 | msgstr ":func:`on_message` イベントは受信したメッセージすべてに対して呼び出されるため、Bot自身からのメッセージは無視するように設定する必要があります。これは、メッセージの送信者である :attr:`Message.author` と、Bot自身を表す :attr:`Client.user` が等しいか比較することで実装できます。" 67 | 68 | #: ../../quickstart.rst:55 69 | msgid "Afterwards, we check if the :class:`Message.content` starts with ``'$hello'``. If it is, then we reply in the channel it was used in with ``'Hello!'``." 70 | msgstr "その後、 :class:`Message.content` が ``'$hello'`` から始まるかどうかを確認し、当てはまればそのチャンネルに ``'Hello!'`` という返事を送信します。" 71 | 72 | #: ../../quickstart.rst:57 73 | msgid "Finally, we run the bot with our login token. If you need help getting your token or creating a bot, look in the :ref:`discord-intro` section." 74 | msgstr "最後に、ログイン用トークンを用いてBotを起動します。トークンの取得やBotの作成について分からないことがあれば、 :ref:`discord-intro` を参照してください。" 75 | 76 | #: ../../quickstart.rst:61 77 | msgid "Now that we've made a bot, we have to *run* the bot. Luckily, this is simple since this is just a Python script, we can run it directly." 78 | msgstr "さて、これでBotは完成なので、Botを *実行* してみましょう。幸いにも、これはただのPythonスクリプトなので実行は簡単です。直接実行が可能です。" 79 | 80 | #: ../../quickstart.rst:64 81 | msgid "On Windows:" 82 | msgstr "Windowsの場合:" 83 | 84 | #: ../../quickstart.rst:70 85 | msgid "On other systems:" 86 | msgstr "その他のシステムの場合:" 87 | 88 | #: ../../quickstart.rst:76 89 | msgid "Now you can try playing around with your basic bot." 90 | msgstr "これで、あなたが作ったBotと遊ぶことができます。" 91 | 92 | -------------------------------------------------------------------------------- /docs/locale/ja/LC_MESSAGES/ext/commands/extensions.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: discordpy\n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "POT-Creation-Date: 2019-06-22 09:35-0400\n" 6 | "PO-Revision-Date: 2020-10-24 02:41\n" 7 | "Last-Translator: \n" 8 | "Language-Team: Japanese\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "Plural-Forms: nplurals=1; plural=0;\n" 13 | "X-Crowdin-Project: discordpy\n" 14 | "X-Crowdin-Project-ID: 362783\n" 15 | "X-Crowdin-Language: ja\n" 16 | "X-Crowdin-File: /ext/commands/extensions.pot\n" 17 | "X-Crowdin-File-ID: 68\n" 18 | "Language: ja_JP\n" 19 | 20 | #: ../../ext/commands/extensions.rst:6 21 | msgid "Extensions" 22 | msgstr "エクステンション" 23 | 24 | #: ../../ext/commands/extensions.rst:8 25 | msgid "There comes a time in the bot development when you want to extend the bot functionality at run-time and quickly unload and reload code (also called hot-reloading). The command framework comes with this ability built-in, with a concept called **extensions**." 26 | msgstr "Bot開発ではBotを起動している間にコードを素早くアンロードし、再度ロードし直したい (ホットリロードとも呼ばれます) という時があります。コマンドフレームワークでは **エクステンション** と呼ばれる概念でこの機能が組み込まれています。" 27 | 28 | #: ../../ext/commands/extensions.rst:11 29 | msgid "Primer" 30 | msgstr "はじめに" 31 | 32 | #: ../../ext/commands/extensions.rst:13 33 | msgid "An extension at its core is a python file with an entry point called ``setup``. This setup must be a plain Python function (not a coroutine). It takes a single parameter -- the :class:`~.commands.Bot` that loads the extension." 34 | msgstr "その中核となるエクステンションは ``setup`` というエントリポイントを持つPythonファイルです。このsetupは通常のPython関数である必要があります (コルーチンではありません)。この関数はエクステンションをロードする :class:`~.commands.Bot` を受け取るための単一のパラメータを持ちます。" 35 | 36 | #: ../../ext/commands/extensions.rst:15 37 | msgid "An example extension looks like this:" 38 | msgstr "エクステンションの例は以下のとおりです:" 39 | 40 | #: ../../ext/commands/extensions.rst:17 41 | msgid "hello.py" 42 | msgstr "hello.py" 43 | 44 | #: ../../ext/commands/extensions.rst:30 45 | msgid "In this example we define a simple command, and when the extension is loaded this command is added to the bot. Now the final step to this is loading the extension, which we do by calling :meth:`.commands.Bot.load_extension`. To load this extension we call ``bot.load_extension('hello')``." 46 | msgstr "この例では単純なコマンドを実装しており、エクステンションがロードされることでこのコマンドがBotに追加されます。最後にこのエクステンションをロードする必要があります。ロードには :meth:`.commands.Bot.load_extension` を実行します。このエクステンションを読み込むために ``bot.load_extension('hello')`` を実行します。" 47 | 48 | #: ../../ext/commands/extensions.rst:32 49 | msgid "Cogs" 50 | msgstr "コグ" 51 | 52 | #: ../../ext/commands/extensions.rst:35 53 | msgid "Extensions are usually used in conjunction with cogs. To read more about them, check out the documentation, :ref:`ext_commands_cogs`." 54 | msgstr "エクステンションは通常、コグと組み合わせて使用します。詳細については :ref:`ext_commands_cogs` のドキュメントを参照してください。" 55 | 56 | #: ../../ext/commands/extensions.rst:39 57 | msgid "Extension paths are ultimately similar to the import mechanism. What this means is that if there is a folder, then it must be dot-qualified. For example to load an extension in ``plugins/hello.py`` then we use the string ``plugins.hello``." 58 | msgstr "エクステンションのパスは究極的にはimportのメカニズムと似ています。これはフォルダ等がある場合、それをドットで区切らなければならないということです。例えば ``plugins/hello.py`` というエクステンションをロードする場合は、 ``plugins.hello`` という文字列を使います。" 59 | 60 | #: ../../ext/commands/extensions.rst:42 61 | msgid "Reloading" 62 | msgstr "リロード" 63 | 64 | #: ../../ext/commands/extensions.rst:44 65 | msgid "When you make a change to the extension and want to reload the references, the library comes with a function to do this for you, :meth:`Bot.reload_extension`." 66 | msgstr "エクステンションを更新し、その参照を再読込したい場合のために、ライブラリには :meth:`Bot.reload_extension` が用意されています。" 67 | 68 | #: ../../ext/commands/extensions.rst:50 69 | msgid "Once the extension reloads, any changes that we did will be applied. This is useful if we want to add or remove functionality without restarting our bot. If an error occurred during the reloading process, the bot will pretend as if the reload never happened." 70 | msgstr "エクステンションを再読込すると、その変更が適用されます。Botを再起動せずに機能の追加や削除を行いたい場合に便利です。再読込処理中にエラーが発生した場合、Botは再読込処理をする前の状態に戻ります。" 71 | 72 | #: ../../ext/commands/extensions.rst:53 73 | msgid "Cleaning Up" 74 | msgstr "クリーンアップ" 75 | 76 | #: ../../ext/commands/extensions.rst:55 77 | msgid "Although rare, sometimes an extension needs to clean-up or know when it's being unloaded. For cases like these, there is another entry point named ``teardown`` which is similar to ``setup`` except called when the extension is unloaded." 78 | msgstr "稀ではありますが、エクステンションにクリーンアップが必要だったり、いつアンロードするかを確認したい場合があります。このために ``setup`` に似たエクステンションがアンロードされるときに呼び出される ``teardown`` というエントリポイントが用意されています。" 79 | 80 | #: ../../ext/commands/extensions.rst:57 81 | msgid "basic_ext.py" 82 | msgstr "basic_ext.py" 83 | 84 | -------------------------------------------------------------------------------- /diskord/types/channel.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 List, Literal, Optional, TypedDict, Union 26 | from .user import PartialUser 27 | from .snowflake import Snowflake 28 | from .threads import ThreadMetadata, ThreadMember, ThreadArchiveDuration 29 | 30 | 31 | OverwriteType = Literal[0, 1] 32 | 33 | 34 | class PermissionOverwrite(TypedDict): 35 | id: Snowflake 36 | type: OverwriteType 37 | allow: str 38 | deny: str 39 | 40 | 41 | ChannelType = Literal[0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13] 42 | 43 | 44 | class _BaseChannel(TypedDict): 45 | id: Snowflake 46 | name: str 47 | 48 | 49 | class _BaseGuildChannel(_BaseChannel): 50 | guild_id: Snowflake 51 | position: int 52 | permission_overwrites: List[PermissionOverwrite] 53 | nsfw: bool 54 | parent_id: Optional[Snowflake] 55 | 56 | 57 | class PartialChannel(_BaseChannel): 58 | type: ChannelType 59 | 60 | 61 | class _TextChannelOptional(TypedDict, total=False): 62 | topic: str 63 | last_message_id: Optional[Snowflake] 64 | last_pin_timestamp: str 65 | rate_limit_per_user: int 66 | default_auto_archive_duration: ThreadArchiveDuration 67 | 68 | 69 | class TextChannel(_BaseGuildChannel, _TextChannelOptional): 70 | type: Literal[0] 71 | 72 | 73 | class NewsChannel(_BaseGuildChannel, _TextChannelOptional): 74 | type: Literal[5] 75 | 76 | 77 | VideoQualityMode = Literal[1, 2] 78 | 79 | 80 | class _VoiceChannelOptional(TypedDict, total=False): 81 | rtc_region: Optional[str] 82 | video_quality_mode: VideoQualityMode 83 | 84 | 85 | class VoiceChannel(_BaseGuildChannel, _VoiceChannelOptional): 86 | type: Literal[2] 87 | bitrate: int 88 | user_limit: int 89 | 90 | 91 | class CategoryChannel(_BaseGuildChannel): 92 | type: Literal[4] 93 | 94 | 95 | class StoreChannel(_BaseGuildChannel): 96 | type: Literal[6] 97 | 98 | 99 | class _StageChannelOptional(TypedDict, total=False): 100 | rtc_region: Optional[str] 101 | topic: str 102 | 103 | 104 | class StageChannel(_BaseGuildChannel, _StageChannelOptional): 105 | type: Literal[13] 106 | bitrate: int 107 | user_limit: int 108 | 109 | 110 | class _ThreadChannelOptional(TypedDict, total=False): 111 | member: ThreadMember 112 | owner_id: Snowflake 113 | rate_limit_per_user: int 114 | last_message_id: Optional[Snowflake] 115 | last_pin_timestamp: str 116 | 117 | 118 | class ThreadChannel(_BaseChannel, _ThreadChannelOptional): 119 | type: Literal[10, 11, 12] 120 | guild_id: Snowflake 121 | parent_id: Snowflake 122 | owner_id: Snowflake 123 | nsfw: bool 124 | last_message_id: Optional[Snowflake] 125 | rate_limit_per_user: int 126 | message_count: int 127 | member_count: int 128 | thread_metadata: ThreadMetadata 129 | 130 | 131 | GuildChannel = Union[ 132 | TextChannel, 133 | NewsChannel, 134 | VoiceChannel, 135 | CategoryChannel, 136 | StoreChannel, 137 | StageChannel, 138 | ThreadChannel, 139 | ] 140 | 141 | 142 | class DMChannel(_BaseChannel): 143 | type: Literal[1] 144 | last_message_id: Optional[Snowflake] 145 | recipients: List[PartialUser] 146 | 147 | 148 | class GroupDMChannel(_BaseChannel): 149 | type: Literal[3] 150 | icon: Optional[str] 151 | owner_id: Snowflake 152 | 153 | 154 | Channel = Union[GuildChannel, DMChannel, GroupDMChannel] 155 | 156 | PrivacyLevel = Literal[1, 2] 157 | 158 | 159 | class StageInstance(TypedDict): 160 | id: Snowflake 161 | guild_id: Snowflake 162 | channel_id: Snowflake 163 | topic: str 164 | privacy_level: PrivacyLevel 165 | discoverable_disabled: bool 166 | --------------------------------------------------------------------------------