Sort event references by categories

This commit is contained in:
Chiggy-Playz
2021-10-03 16:18:46 +05:30
parent 3260ec6643
commit 16b7cdc488
85 changed files with 85331 additions and 571 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,96 @@
:orphan:
.. _discord-intro:
Creating a Bot Account
========================
In order to work with the library and the Discord API in general, we must first create a Discord Bot account.
Creating a Bot account is a pretty straightforward process.
1. Make sure you're logged on to the `Discord website <https://discord.com>`_.
2. Navigate to the `application page <https://discord.com/developers/applications>`_
3. Click on the "New Application" button.
.. image:: /images/discord_create_app_button.png
:alt: The new application button.
4. Give the application a name and click "Create".
.. image:: /images/discord_create_app_form.png
:alt: The new application form filled in.
5. Create a Bot User by navigating to the "Bot" tab and clicking "Add Bot".
- Click "Yes, do it!" to continue.
.. image:: /images/discord_create_bot_user.png
:alt: The Add Bot button.
6. Make sure that **Public Bot** is ticked if you want others to invite your bot.
- You should also make sure that **Require OAuth2 Code Grant** is unchecked unless you
are developing a service that needs it. If you're unsure, then **leave it unchecked**.
.. image:: /images/discord_bot_user_options.png
:alt: How the Bot User options should look like for most people.
7. Copy the token using the "Copy" button.
- **This is not the Client Secret at the General Information page.**
.. warning::
It should be worth noting that this token is essentially your bot's
password. You should **never** share this with someone else. In doing so,
someone can log in to your bot and do malicious things, such as leaving
servers, ban all members inside a server, or pinging everyone maliciously.
The possibilities are endless, so **do not share this token.**
If you accidentally leaked your token, click the "Regenerate" button as soon
as possible. This revokes your old token and re-generates a new one.
Now you need to use the new token to login.
And that's it. You now have a bot account and you can login with that token.
.. _discord_invite_bot:
Inviting Your Bot
-------------------
So you've made a Bot User but it's not actually in any server.
If you want to invite your bot you must create an invite URL for it.
1. Make sure you're logged on to the `Discord website <https://discord.com>`_.
2. Navigate to the `application page <https://discord.com/developers/applications>`_
3. Click on your bot's page.
4. Go to the "OAuth2" tab.
.. image:: /images/discord_oauth2.png
:alt: How the OAuth2 page should look like.
5. Tick the "bot" checkbox under "scopes".
.. image:: /images/discord_oauth2_scope.png
:alt: The scopes checkbox with "bot" ticked.
6. Tick the permissions required for your bot to function under "Bot Permissions".
- Please be aware of the consequences of requiring your bot to have the "Administrator" permission.
- 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 <https://support.discord.com/hc/en-us/articles/219576828-Setting-up-Two-Factor-Authentication>`_ for more information.
.. image:: /images/discord_oauth2_perms.png
:alt: The permission checkboxes with some permissions checked.
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".
.. note::
The person adding the bot needs "Manage Server" permissions to do so.
If you want to generate this URL dynamically at run-time inside your bot and using the
:class:`discord.Permissions` interface, you can use :func:`discord.utils.oauth_url`.

View File

@@ -0,0 +1,681 @@
.. currentmodule:: discord
API Reference
===============
The following section outlines the API of discord.py's command extension module.
.. _ext_commands_api_bot:
Bots
------
Bot
~~~~
.. attributetable:: discord.ext.commands.Bot
.. autoclass:: discord.ext.commands.Bot
:members:
:inherited-members:
:exclude-members: after_invoke, before_invoke, check, check_once, command, event, group, listen
.. automethod:: Bot.after_invoke()
:decorator:
.. automethod:: Bot.before_invoke()
:decorator:
.. automethod:: Bot.check()
:decorator:
.. automethod:: Bot.check_once()
:decorator:
.. automethod:: Bot.command(*args, **kwargs)
:decorator:
.. automethod:: Bot.event()
:decorator:
.. automethod:: Bot.group(*args, **kwargs)
:decorator:
.. automethod:: Bot.listen(name=None)
:decorator:
AutoShardedBot
~~~~~~~~~~~~~~~~
.. attributetable:: discord.ext.commands.AutoShardedBot
.. autoclass:: discord.ext.commands.AutoShardedBot
:members:
Prefix Helpers
----------------
.. autofunction:: discord.ext.commands.when_mentioned
.. autofunction:: discord.ext.commands.when_mentioned_or
.. _ext_commands_api_events:
Event Reference
-----------------
These events function similar to :ref:`the regular events <discord-api-events>`, except they
are custom to the command extension module.
.. function:: discord.ext.commands.on_command_error(ctx, error)
An error handler that is called when an error is raised
inside a command either through user input error, check
failure, or an error in your own code.
A default one is provided (:meth:`.Bot.on_command_error`).
:param ctx: The invocation context.
:type ctx: :class:`.Context`
:param error: The error that was raised.
:type error: :class:`.CommandError` derived
.. function:: discord.ext.commands.on_command(ctx)
An event that is called when a command is found and is about to be invoked.
This event is called regardless of whether the command itself succeeds via
error or completes.
:param ctx: The invocation context.
:type ctx: :class:`.Context`
.. function:: discord.ext.commands.on_command_completion(ctx)
An event that is called when a command has completed its invocation.
This event is called only if the command succeeded, i.e. all checks have
passed and the user input it correctly.
:param ctx: The invocation context.
:type ctx: :class:`.Context`
.. _ext_commands_api_command:
Commands
----------
Decorators
~~~~~~~~~~~~
.. autofunction:: discord.ext.commands.command
:decorator:
.. autofunction:: discord.ext.commands.group
:decorator:
Command
~~~~~~~~~
.. attributetable:: discord.ext.commands.Command
.. autoclass:: discord.ext.commands.Command
:members:
:special-members: __call__
:exclude-members: after_invoke, before_invoke, error
.. automethod:: Command.after_invoke()
:decorator:
.. automethod:: Command.before_invoke()
:decorator:
.. automethod:: Command.error()
:decorator:
Group
~~~~~~
.. attributetable:: discord.ext.commands.Group
.. autoclass:: discord.ext.commands.Group
:members:
:inherited-members:
:exclude-members: after_invoke, before_invoke, command, error, group
.. automethod:: Group.after_invoke()
:decorator:
.. automethod:: Group.before_invoke()
:decorator:
.. automethod:: Group.command(*args, **kwargs)
:decorator:
.. automethod:: Group.error()
:decorator:
.. automethod:: Group.group(*args, **kwargs)
:decorator:
GroupMixin
~~~~~~~~~~~
.. attributetable:: discord.ext.commands.GroupMixin
.. autoclass:: discord.ext.commands.GroupMixin
:members:
:exclude-members: command, group
.. automethod:: GroupMixin.command(*args, **kwargs)
:decorator:
.. automethod:: GroupMixin.group(*args, **kwargs)
:decorator:
.. _ext_commands_api_cogs:
Cogs
------
Cog
~~~~
.. attributetable:: discord.ext.commands.Cog
.. autoclass:: discord.ext.commands.Cog
:members:
CogMeta
~~~~~~~~
.. attributetable:: discord.ext.commands.CogMeta
.. autoclass:: discord.ext.commands.CogMeta
:members:
.. _ext_commands_help_command:
Help Commands
---------------
HelpCommand
~~~~~~~~~~~~
.. attributetable:: discord.ext.commands.HelpCommand
.. autoclass:: discord.ext.commands.HelpCommand
:members:
DefaultHelpCommand
~~~~~~~~~~~~~~~~~~~
.. attributetable:: discord.ext.commands.DefaultHelpCommand
.. autoclass:: discord.ext.commands.DefaultHelpCommand
:members:
:exclude-members: send_bot_help, send_cog_help, send_group_help, send_command_help, prepare_help_command
MinimalHelpCommand
~~~~~~~~~~~~~~~~~~~
.. attributetable:: discord.ext.commands.MinimalHelpCommand
.. autoclass:: discord.ext.commands.MinimalHelpCommand
:members:
:exclude-members: send_bot_help, send_cog_help, send_group_help, send_command_help, prepare_help_command
Paginator
~~~~~~~~~~
.. attributetable:: discord.ext.commands.Paginator
.. autoclass:: discord.ext.commands.Paginator
:members:
Enums
------
.. class:: BucketType
:module: discord.ext.commands
Specifies a type of bucket for, e.g. a cooldown.
.. attribute:: default
The default bucket operates on a global basis.
.. attribute:: user
The user bucket operates on a per-user basis.
.. attribute:: guild
The guild bucket operates on a per-guild basis.
.. attribute:: channel
The channel bucket operates on a per-channel basis.
.. attribute:: member
The member bucket operates on a per-member basis.
.. attribute:: category
The category bucket operates on a per-category basis.
.. attribute:: role
The role bucket operates on a per-role basis.
.. versionadded:: 1.3
.. _ext_commands_api_checks:
Checks
-------
.. autofunction:: discord.ext.commands.check(predicate)
:decorator:
.. autofunction:: discord.ext.commands.check_any(*checks)
:decorator:
.. autofunction:: discord.ext.commands.has_role(item)
:decorator:
.. autofunction:: discord.ext.commands.has_permissions(**perms)
:decorator:
.. autofunction:: discord.ext.commands.has_guild_permissions(**perms)
:decorator:
.. autofunction:: discord.ext.commands.has_any_role(*items)
:decorator:
.. autofunction:: discord.ext.commands.bot_has_role(item)
:decorator:
.. autofunction:: discord.ext.commands.bot_has_permissions(**perms)
:decorator:
.. autofunction:: discord.ext.commands.bot_has_guild_permissions(**perms)
:decorator:
.. autofunction:: discord.ext.commands.bot_has_any_role(*items)
:decorator:
.. autofunction:: discord.ext.commands.cooldown(rate, per, type=discord.ext.commands.BucketType.default)
:decorator:
.. autofunction:: discord.ext.commands.dynamic_cooldown(cooldown, type=BucketType.default)
:decorator:
.. autofunction:: discord.ext.commands.max_concurrency(number, per=discord.ext.commands.BucketType.default, *, wait=False)
:decorator:
.. autofunction:: discord.ext.commands.before_invoke(coro)
:decorator:
.. autofunction:: discord.ext.commands.after_invoke(coro)
:decorator:
.. autofunction:: discord.ext.commands.guild_only(,)
:decorator:
.. autofunction:: discord.ext.commands.dm_only(,)
:decorator:
.. autofunction:: discord.ext.commands.is_owner(,)
:decorator:
.. autofunction:: discord.ext.commands.is_nsfw(,)
:decorator:
.. _ext_commands_api_context:
Cooldown
---------
.. attributetable:: discord.ext.commands.Cooldown
.. autoclass:: discord.ext.commands.Cooldown
:members:
Context
--------
.. attributetable:: discord.ext.commands.Context
.. autoclass:: discord.ext.commands.Context
:members:
:inherited-members:
:exclude-members: history, typing
.. automethod:: discord.ext.commands.Context.history
:async-for:
.. automethod:: discord.ext.commands.Context.typing
:async-with:
.. _ext_commands_api_converters:
Converters
------------
.. autoclass:: discord.ext.commands.Converter
:members:
.. autoclass:: discord.ext.commands.ObjectConverter
:members:
.. autoclass:: discord.ext.commands.MemberConverter
:members:
.. autoclass:: discord.ext.commands.UserConverter
:members:
.. autoclass:: discord.ext.commands.MessageConverter
:members:
.. autoclass:: discord.ext.commands.PartialMessageConverter
:members:
.. autoclass:: discord.ext.commands.GuildChannelConverter
:members:
.. autoclass:: discord.ext.commands.TextChannelConverter
:members:
.. autoclass:: discord.ext.commands.VoiceChannelConverter
:members:
.. autoclass:: discord.ext.commands.StoreChannelConverter
:members:
.. autoclass:: discord.ext.commands.StageChannelConverter
:members:
.. autoclass:: discord.ext.commands.CategoryChannelConverter
:members:
.. autoclass:: discord.ext.commands.InviteConverter
:members:
.. autoclass:: discord.ext.commands.GuildConverter
:members:
.. autoclass:: discord.ext.commands.RoleConverter
:members:
.. autoclass:: discord.ext.commands.GameConverter
:members:
.. autoclass:: discord.ext.commands.ColourConverter
:members:
.. autoclass:: discord.ext.commands.EmojiConverter
:members:
.. autoclass:: discord.ext.commands.PartialEmojiConverter
:members:
.. autoclass:: discord.ext.commands.ThreadConverter
:members:
.. autoclass:: discord.ext.commands.GuildStickerConverter
:members:
.. autoclass:: discord.ext.commands.clean_content
:members:
.. autoclass:: discord.ext.commands.Greedy()
.. autofunction:: discord.ext.commands.run_converters
Option
~~~~~~
.. autoclass:: discord.ext.commands.Option
:members:
Flag Converter
~~~~~~~~~~~~~~~
.. autoclass:: discord.ext.commands.FlagConverter
:members:
.. autoclass:: discord.ext.commands.Flag()
:members:
.. autofunction:: discord.ext.commands.flag
.. _ext_commands_api_errors:
Exceptions
-----------
.. autoexception:: discord.ext.commands.CommandError
:members:
.. autoexception:: discord.ext.commands.ConversionError
:members:
.. autoexception:: discord.ext.commands.MissingRequiredArgument
:members:
.. autoexception:: discord.ext.commands.ArgumentParsingError
:members:
.. autoexception:: discord.ext.commands.UnexpectedQuoteError
:members:
.. autoexception:: discord.ext.commands.InvalidEndOfQuotedStringError
:members:
.. autoexception:: discord.ext.commands.ExpectedClosingQuoteError
:members:
.. autoexception:: discord.ext.commands.BadArgument
:members:
.. autoexception:: discord.ext.commands.BadUnionArgument
:members:
.. autoexception:: discord.ext.commands.BadLiteralArgument
:members:
.. autoexception:: discord.ext.commands.PrivateMessageOnly
:members:
.. autoexception:: discord.ext.commands.NoPrivateMessage
:members:
.. autoexception:: discord.ext.commands.CheckFailure
:members:
.. autoexception:: discord.ext.commands.CheckAnyFailure
:members:
.. autoexception:: discord.ext.commands.CommandNotFound
:members:
.. autoexception:: discord.ext.commands.DisabledCommand
:members:
.. autoexception:: discord.ext.commands.CommandInvokeError
:members:
.. autoexception:: discord.ext.commands.TooManyArguments
:members:
.. autoexception:: discord.ext.commands.UserInputError
:members:
.. autoexception:: discord.ext.commands.CommandOnCooldown
:members:
.. autoexception:: discord.ext.commands.MaxConcurrencyReached
:members:
.. autoexception:: discord.ext.commands.NotOwner
:members:
.. autoexception:: discord.ext.commands.MessageNotFound
:members:
.. autoexception:: discord.ext.commands.MemberNotFound
:members:
.. autoexception:: discord.ext.commands.GuildNotFound
:members:
.. autoexception:: discord.ext.commands.UserNotFound
:members:
.. autoexception:: discord.ext.commands.ChannelNotFound
:members:
.. autoexception:: discord.ext.commands.ChannelNotReadable
:members:
.. autoexception:: discord.ext.commands.ThreadNotFound
:members:
.. autoexception:: discord.ext.commands.BadColourArgument
:members:
.. autoexception:: discord.ext.commands.RoleNotFound
:members:
.. autoexception:: discord.ext.commands.BadInviteArgument
:members:
.. autoexception:: discord.ext.commands.EmojiNotFound
:members:
.. autoexception:: discord.ext.commands.PartialEmojiConversionFailure
:members:
.. autoexception:: discord.ext.commands.GuildStickerNotFound
:members:
.. autoexception:: discord.ext.commands.BadBoolArgument
:members:
.. autoexception:: discord.ext.commands.MissingPermissions
:members:
.. autoexception:: discord.ext.commands.BotMissingPermissions
:members:
.. autoexception:: discord.ext.commands.MissingRole
:members:
.. autoexception:: discord.ext.commands.BotMissingRole
:members:
.. autoexception:: discord.ext.commands.MissingAnyRole
:members:
.. autoexception:: discord.ext.commands.BotMissingAnyRole
:members:
.. autoexception:: discord.ext.commands.NSFWChannelRequired
:members:
.. autoexception:: discord.ext.commands.FlagError
:members:
.. autoexception:: discord.ext.commands.BadFlagArgument
:members:
.. autoexception:: discord.ext.commands.MissingFlagArgument
:members:
.. autoexception:: discord.ext.commands.TooManyFlags
:members:
.. autoexception:: discord.ext.commands.MissingRequiredFlag
:members:
.. autoexception:: discord.ext.commands.ExtensionError
:members:
.. autoexception:: discord.ext.commands.ExtensionAlreadyLoaded
:members:
.. autoexception:: discord.ext.commands.ExtensionNotLoaded
:members:
.. autoexception:: discord.ext.commands.NoEntryPointError
:members:
.. autoexception:: discord.ext.commands.ExtensionFailed
:members:
.. autoexception:: discord.ext.commands.ExtensionNotFound
:members:
.. autoexception:: discord.ext.commands.CommandRegistrationError
:members:
Exception Hierarchy
~~~~~~~~~~~~~~~~~~~~~
.. exception_hierarchy::
- :exc:`~.DiscordException`
- :exc:`~.commands.CommandError`
- :exc:`~.commands.ConversionError`
- :exc:`~.commands.UserInputError`
- :exc:`~.commands.MissingRequiredArgument`
- :exc:`~.commands.TooManyArguments`
- :exc:`~.commands.BadArgument`
- :exc:`~.commands.MessageNotFound`
- :exc:`~.commands.MemberNotFound`
- :exc:`~.commands.GuildNotFound`
- :exc:`~.commands.UserNotFound`
- :exc:`~.commands.ChannelNotFound`
- :exc:`~.commands.ChannelNotReadable`
- :exc:`~.commands.BadColourArgument`
- :exc:`~.commands.RoleNotFound`
- :exc:`~.commands.BadInviteArgument`
- :exc:`~.commands.EmojiNotFound`
- :exc:`~.commands.GuildStickerNotFound`
- :exc:`~.commands.PartialEmojiConversionFailure`
- :exc:`~.commands.BadBoolArgument`
- :exc:`~.commands.ThreadNotFound`
- :exc:`~.commands.FlagError`
- :exc:`~.commands.BadFlagArgument`
- :exc:`~.commands.MissingFlagArgument`
- :exc:`~.commands.TooManyFlags`
- :exc:`~.commands.MissingRequiredFlag`
- :exc:`~.commands.BadUnionArgument`
- :exc:`~.commands.BadLiteralArgument`
- :exc:`~.commands.ArgumentParsingError`
- :exc:`~.commands.UnexpectedQuoteError`
- :exc:`~.commands.InvalidEndOfQuotedStringError`
- :exc:`~.commands.ExpectedClosingQuoteError`
- :exc:`~.commands.CommandNotFound`
- :exc:`~.commands.CheckFailure`
- :exc:`~.commands.CheckAnyFailure`
- :exc:`~.commands.PrivateMessageOnly`
- :exc:`~.commands.NoPrivateMessage`
- :exc:`~.commands.NotOwner`
- :exc:`~.commands.MissingPermissions`
- :exc:`~.commands.BotMissingPermissions`
- :exc:`~.commands.MissingRole`
- :exc:`~.commands.BotMissingRole`
- :exc:`~.commands.MissingAnyRole`
- :exc:`~.commands.BotMissingAnyRole`
- :exc:`~.commands.NSFWChannelRequired`
- :exc:`~.commands.DisabledCommand`
- :exc:`~.commands.CommandInvokeError`
- :exc:`~.commands.CommandOnCooldown`
- :exc:`~.commands.MaxConcurrencyReached`
- :exc:`~.commands.ExtensionError`
- :exc:`~.commands.ExtensionAlreadyLoaded`
- :exc:`~.commands.ExtensionNotLoaded`
- :exc:`~.commands.NoEntryPointError`
- :exc:`~.commands.ExtensionFailed`
- :exc:`~.commands.ExtensionNotFound`
- :exc:`~.ClientException`
- :exc:`~.commands.CommandRegistrationError`

View File

@@ -0,0 +1,159 @@
.. currentmodule:: discord
.. _ext_commands_cogs:
Cogs
======
There comes a point in your bot's development when you want to organize a collection of commands, listeners, and some state into one class. Cogs allow you to do just that.
The gist:
- Each cog is a Python class that subclasses :class:`.commands.Cog`.
- Every command is marked with the :func:`.commands.command` decorator.
- Every listener is marked with the :meth:`.commands.Cog.listener` decorator.
- Cogs are then registered with the :meth:`.Bot.add_cog` call.
- Cogs are subsequently removed with the :meth:`.Bot.remove_cog` call.
It should be noted that cogs are typically used alongside with :ref:`ext_commands_extensions`.
Quick Example
---------------
This example cog defines a ``Greetings`` category for your commands, with a single :ref:`command <ext_commands_commands>` named ``hello`` as well as a listener to listen to an :ref:`Event <discord-api-events>`.
.. code-block:: python3
class Greetings(commands.Cog):
def __init__(self, bot):
self.bot = bot
self._last_member = None
@commands.Cog.listener()
async def on_member_join(self, member):
channel = member.guild.system_channel
if channel is not None:
await channel.send(f'Welcome {member.mention}.')
@commands.command()
async def hello(self, ctx, *, member: discord.Member = None):
"""Says hello"""
member = member or ctx.author
if self._last_member is None or self._last_member.id != member.id:
await ctx.send(f'Hello {member.name}~')
else:
await ctx.send(f'Hello {member.name}... This feels familiar.')
self._last_member = member
A couple of technical notes to take into consideration:
- All listeners must be explicitly marked via decorator, :meth:`~.commands.Cog.listener`.
- The name of the cog is automatically derived from the class name but can be overridden. See :ref:`ext_commands_cogs_meta_options`.
- All commands must now take a ``self`` parameter to allow usage of instance attributes that can be used to maintain state.
Cog Registration
-------------------
Once you have defined your cogs, you need to tell the bot to register the cogs to be used. We do this via the :meth:`~.commands.Bot.add_cog` method.
.. code-block:: python3
bot.add_cog(Greetings(bot))
This binds the cog to the bot, adding all commands and listeners to the bot automatically.
Note that we reference the cog by name, which we can override through :ref:`ext_commands_cogs_meta_options`. So if we ever want to remove the cog eventually, we would have to do the following.
.. code-block:: python3
bot.remove_cog('Greetings')
Using Cogs
-------------
Just as we remove a cog by its name, we can also retrieve it by its name as well. This allows us to use a cog as an inter-command communication protocol to share data. For example:
.. code-block:: python3
:emphasize-lines: 22,24
class Economy(commands.Cog):
...
async def withdraw_money(self, member, money):
# implementation here
...
async def deposit_money(self, member, money):
# implementation here
...
class Gambling(commands.Cog):
def __init__(self, bot):
self.bot = bot
def coinflip(self):
return random.randint(0, 1)
@commands.command()
async def gamble(self, ctx, money: int):
"""Gambles some money."""
economy = self.bot.get_cog('Economy')
if economy is not None:
await economy.withdraw_money(ctx.author, money)
if self.coinflip() == 1:
await economy.deposit_money(ctx.author, money * 1.5)
.. _ext_commands_cogs_special_methods:
Special Methods
-----------------
As cogs get more complicated and have more commands, there comes a point where we want to customise the behaviour of the entire cog or bot.
They are as follows:
- :meth:`.Cog.cog_unload`
- :meth:`.Cog.cog_check`
- :meth:`.Cog.cog_command_error`
- :meth:`.Cog.cog_before_invoke`
- :meth:`.Cog.cog_after_invoke`
- :meth:`.Cog.bot_check`
- :meth:`.Cog.bot_check_once`
You can visit the reference to get more detail.
.. _ext_commands_cogs_meta_options:
Meta Options
--------------
At the heart of a cog resides a metaclass, :class:`.commands.CogMeta`, which can take various options to customise some of the behaviour. To do this, we pass keyword arguments to the class definition line. For example, to change the cog name we can pass the ``name`` keyword argument as follows:
.. code-block:: python3
class MyCog(commands.Cog, name='My Cog'):
pass
To see more options that you can set, see the documentation of :class:`.commands.CogMeta`.
Inspection
------------
Since cogs ultimately are classes, we have some tools to help us inspect certain properties of the cog.
To get a :class:`list` of commands, we can use :meth:`.Cog.get_commands`. ::
>>> cog = bot.get_cog('Greetings')
>>> commands = cog.get_commands()
>>> print([c.name for c in commands])
If we want to get the subcommands as well, we can use the :meth:`.Cog.walk_commands` generator. ::
>>> print([c.qualified_name for c in cog.walk_commands()])
To do the same with listeners, we can query them with :meth:`.Cog.get_listeners`. This returns a list of tuples -- the first element being the listener name and the second one being the actual function itself. ::
>>> for name, func in cog.get_listeners():
... print(name, '->', func)

View File

@@ -0,0 +1,962 @@
.. currentmodule:: discord
.. _ext_commands_commands:
Commands
==========
One of the most appealing aspects of the command extension is how easy it is to define commands and
how you can arbitrarily nest groups and commands to have a rich sub-command system.
Commands are defined by attaching it to a regular Python function. The command is then invoked by the user using a similar
signature to the Python function.
For example, in the given command definition:
.. code-block:: python3
@bot.command()
async def foo(ctx, arg):
await ctx.send(arg)
With the following prefix (``$``), it would be invoked by the user via:
.. code-block:: none
$foo abc
A command must always have at least one parameter, ``ctx``, which is the :class:`.Context` as the first one.
There are two ways of registering a command. The first one is by using :meth:`.Bot.command` decorator,
as seen in the example above. The second is using the :func:`~ext.commands.command` decorator followed by
:meth:`.Bot.add_command` on the instance.
Essentially, these two are equivalent: ::
from discord.ext import commands
bot = commands.Bot(command_prefix='$')
@bot.command()
async def test(ctx):
pass
# or:
@commands.command()
async def test(ctx):
pass
bot.add_command(test)
Since the :meth:`.Bot.command` decorator is shorter and easier to comprehend, it will be the one used throughout the
documentation here.
Any parameter that is accepted by the :class:`.Command` constructor can be passed into the decorator. For example, to change
the name to something other than the function would be as simple as doing this:
.. code-block:: python3
@bot.command(name='list')
async def _list(ctx, arg):
pass
Parameters
------------
Since we define commands by making Python functions, we also define the argument passing behaviour by the function
parameters.
Certain parameter types do different things in the user side and most forms of parameter types are supported.
Positional
++++++++++++
The most basic form of parameter passing is the positional parameter. This is where we pass a parameter as-is:
.. code-block:: python3
@bot.command()
async def test(ctx, arg):
await ctx.send(arg)
On the bot using side, you can provide positional arguments by just passing a regular string:
.. image:: /images/commands/positional1.png
To make use of a word with spaces in between, you should quote it:
.. image:: /images/commands/positional2.png
As a note of warning, if you omit the quotes, you will only get the first word:
.. image:: /images/commands/positional3.png
Since positional arguments are just regular Python arguments, you can have as many as you want:
.. code-block:: python3
@bot.command()
async def test(ctx, arg1, arg2):
await ctx.send(f'You passed {arg1} and {arg2}')
Variable
++++++++++
Sometimes you want users to pass in an undetermined number of parameters. The library supports this
similar to how variable list parameters are done in Python:
.. code-block:: python3
@bot.command()
async def test(ctx, *args):
arguments = ', '.join(args)
await ctx.send(f'{len(args)} arguments: {arguments}')
This allows our user to accept either one or many arguments as they please. This works similar to positional arguments,
so multi-word parameters should be quoted.
For example, on the bot side:
.. image:: /images/commands/variable1.png
If the user wants to input a multi-word argument, they have to quote it like earlier:
.. image:: /images/commands/variable2.png
Do note that similar to the Python function behaviour, a user can technically pass no arguments
at all:
.. image:: /images/commands/variable3.png
Since the ``args`` variable is a :class:`py:tuple`,
you can do anything you would usually do with one.
.. admonition:: Slash Command Only
This functionally is currently not supported by the slash command API, so is turned into
a single ``STRING`` parameter on discord's end which we do our own parsing on.
Keyword-Only Arguments
++++++++++++++++++++++++
When you want to handle parsing of the argument yourself or do not feel like you want to wrap multi-word user input into
quotes, you can ask the library to give you the rest as a single argument. We do this by using a **keyword-only argument**,
seen below:
.. code-block:: python3
@bot.command()
async def test(ctx, *, arg):
await ctx.send(arg)
.. warning::
You can only have one keyword-only argument due to parsing ambiguities.
On the bot side, we do not need to quote input with spaces:
.. image:: /images/commands/keyword1.png
Do keep in mind that wrapping it in quotes leaves it as-is:
.. image:: /images/commands/keyword2.png
By default, the keyword-only arguments are stripped of white space to make it easier to work with. This behaviour can be
toggled by the :attr:`.Command.rest_is_raw` argument in the decorator.
.. _ext_commands_context:
Invocation Context
-------------------
As seen earlier, every command must take at least a single parameter, called the :class:`~ext.commands.Context`.
This parameter gives you access to something called the "invocation context". Essentially all the information you need to
know how the command was executed. It contains a lot of useful information:
- :attr:`.Context.guild` to fetch the :class:`Guild` of the command, if any.
- :attr:`.Context.message` to fetch the :class:`Message` of the command.
- :attr:`.Context.author` to fetch the :class:`Member` or :class:`User` that called the command.
- :meth:`.Context.send` to send a message to the channel the command was used in.
The context implements the :class:`abc.Messageable` interface, so anything you can do on a :class:`abc.Messageable` you
can do on the :class:`~ext.commands.Context`.
.. admonition:: Slash Command Only
:attr:`.Context.message` will be fake if in a slash command, it is not
recommended to access if :attr:`.Context.interaction` is not None as most
methods will error due to the message not actually existing.
Converters
------------
Adding bot arguments with function parameters is only the first step in defining your bot's command interface. To actually
make use of the arguments, we usually want to convert the data into a target type. We call these
:ref:`ext_commands_api_converters`.
Converters come in a few flavours:
- A regular callable object that takes an argument as a sole parameter and returns a different type.
- These range from your own function, to something like :class:`bool` or :class:`int`.
- A custom class that inherits from :class:`~ext.commands.Converter`.
.. _ext_commands_basic_converters:
Basic Converters
++++++++++++++++++
At its core, a basic converter is a callable that takes in an argument and turns it into something else.
For example, if we wanted to add two numbers together, we could request that they are turned into integers
for us by specifying the converter:
.. code-block:: python3
@bot.command()
async def add(ctx, a: int, b: int):
await ctx.send(a + b)
We specify converters by using something called a **function annotation**. This is a Python 3 exclusive feature that was
introduced in :pep:`3107`.
This works with any callable, such as a function that would convert a string to all upper-case:
.. code-block:: python3
def to_upper(argument):
return argument.upper()
@bot.command()
async def up(ctx, *, content: to_upper):
await ctx.send(content)
bool
^^^^^^
Unlike the other basic converters, the :class:`bool` converter is treated slightly different. Instead of casting directly to the :class:`bool` type, which would result in any non-empty argument returning ``True``, it instead evaluates the argument as ``True`` or ``False`` based on its given content:
.. code-block:: python3
if lowered in ('yes', 'y', 'true', 't', '1', 'enable', 'on'):
return True
elif lowered in ('no', 'n', 'false', 'f', '0', 'disable', 'off'):
return False
.. _ext_commands_adv_converters:
Advanced Converters
+++++++++++++++++++++
Sometimes a basic converter doesn't have enough information that we need. For example, sometimes we want to get some
information from the :class:`Message` that called the command or we want to do some asynchronous processing.
For this, the library provides the :class:`~ext.commands.Converter` interface. This allows you to have access to the
:class:`.Context` and have the callable be asynchronous. Defining a custom converter using this interface requires
overriding a single method, :meth:`.Converter.convert`.
An example converter:
.. code-block:: python3
import random
class Slapper(commands.Converter):
async def convert(self, ctx, argument):
to_slap = random.choice(ctx.guild.members)
return f'{ctx.author} slapped {to_slap} because *{argument}*'
@bot.command()
async def slap(ctx, *, reason: Slapper):
await ctx.send(reason)
The converter provided can either be constructed or not. Essentially these two are equivalent:
.. code-block:: python3
@bot.command()
async def slap(ctx, *, reason: Slapper):
await ctx.send(reason)
# is the same as...
@bot.command()
async def slap(ctx, *, reason: Slapper()):
await ctx.send(reason)
Having the possibility of the converter be constructed allows you to set up some state in the converter's ``__init__`` for
fine tuning the converter. An example of this is actually in the library, :class:`~ext.commands.clean_content`.
.. code-block:: python3
@bot.command()
async def clean(ctx, *, content: commands.clean_content):
await ctx.send(content)
# or for fine-tuning
@bot.command()
async def clean(ctx, *, content: commands.clean_content(use_nicknames=False)):
await ctx.send(content)
If a converter fails to convert an argument to its designated target type, the :exc:`.BadArgument` exception must be
raised.
Inline Advanced Converters
+++++++++++++++++++++++++++++
If we don't want to inherit from :class:`~ext.commands.Converter`, we can still provide a converter that has the
advanced functionalities of an advanced converter and save us from specifying two types.
For example, a common idiom would be to have a class and a converter for that class:
.. code-block:: python3
class JoinDistance:
def __init__(self, joined, created):
self.joined = joined
self.created = created
@property
def delta(self):
return self.joined - self.created
class JoinDistanceConverter(commands.MemberConverter):
async def convert(self, ctx, argument):
member = await super().convert(ctx, argument)
return JoinDistance(member.joined_at, member.created_at)
@bot.command()
async def delta(ctx, *, member: JoinDistanceConverter):
is_new = member.delta.days < 100
if is_new:
await ctx.send("Hey you're pretty new!")
else:
await ctx.send("Hm you're not so new.")
This can get tedious, so an inline advanced converter is possible through a :func:`classmethod` inside the type:
.. code-block:: python3
class JoinDistance:
def __init__(self, joined, created):
self.joined = joined
self.created = created
@classmethod
async def convert(cls, ctx, argument):
member = await commands.MemberConverter().convert(ctx, argument)
return cls(member.joined_at, member.created_at)
@property
def delta(self):
return self.joined - self.created
@bot.command()
async def delta(ctx, *, member: JoinDistance):
is_new = member.delta.days < 100
if is_new:
await ctx.send("Hey you're pretty new!")
else:
await ctx.send("Hm you're not so new.")
Discord Converters
++++++++++++++++++++
Working with :ref:`discord_api_models` is a fairly common thing when defining commands, as a result the library makes
working with them easy.
For example, to receive a :class:`Member` you can just pass it as a converter:
.. code-block:: python3
@bot.command()
async def joined(ctx, *, member: discord.Member):
await ctx.send(f'{member} joined on {member.joined_at}')
When this command is executed, it attempts to convert the string given into a :class:`Member` and then passes it as a
parameter for the function. This works by checking if the string is a mention, an ID, a nickname, a username + discriminator,
or just a regular username. The default set of converters have been written to be as easy to use as possible.
A lot of discord models work out of the gate as a parameter:
- :class:`Object` (since v2.0)
- :class:`Member`
- :class:`User`
- :class:`Message` (since v1.1)
- :class:`PartialMessage` (since v1.7)
- :class:`abc.GuildChannel` (since 2.0)
- :class:`TextChannel`
- :class:`VoiceChannel`
- :class:`StageChannel` (since v1.7)
- :class:`StoreChannel` (since v1.7)
- :class:`CategoryChannel`
- :class:`Invite`
- :class:`Guild` (since v1.7)
- :class:`Role`
- :class:`Game`
- :class:`Colour`
- :class:`Emoji`
- :class:`PartialEmoji`
- :class:`Thread` (since v2.0)
Having any of these set as the converter will intelligently convert the argument to the appropriate target type you
specify.
Under the hood, these are implemented by the :ref:`ext_commands_adv_converters` interface. A table of the equivalent
converter is given below:
+--------------------------+-------------------------------------------------+-----------------------------+
| Discord Class | Converter | Supported By Slash Commands |
+--------------------------+-------------------------------------------------+-----------------------------+
| :class:`Object` | :class:`~ext.commands.ObjectConverter` | Not currently |
+--------------------------+-------------------------------------------------+-----------------------------+
| :class:`Member` | :class:`~ext.commands.MemberConverter` | Yes, as type 6 (USER) |
+--------------------------+-------------------------------------------------+-----------------------------+
| :class:`User` | :class:`~ext.commands.UserConverter` | Yes, as type 6 (USER) |
+--------------------------+-------------------------------------------------+-----------------------------+
| :class:`Message` | :class:`~ext.commands.MessageConverter` | Not currently |
+--------------------------+-------------------------------------------------+-----------------------------+
| :class:`PartialMessage` | :class:`~ext.commands.PartialMessageConverter` | Not currently |
+--------------------------+-------------------------------------------------+-----------------------------+
| :class:`.GuildChannel` | :class:`~ext.commands.GuildChannelConverter` | Yes, as type 7 (CHANNEL) |
+--------------------------+-------------------------------------------------+-----------------------------+
| :class:`TextChannel` | :class:`~ext.commands.TextChannelConverter` | Yes, as type 7 (CHANNEL) |
+--------------------------+-------------------------------------------------+-----------------------------+
| :class:`VoiceChannel` | :class:`~ext.commands.VoiceChannelConverter` | Yes, as type 7 (CHANNEL) |
+--------------------------+-------------------------------------------------+-----------------------------+
| :class:`StageChannel` | :class:`~ext.commands.StageChannelConverter` | Yes, as type 7 (CHANNEL) |
+--------------------------+-------------------------------------------------+-----------------------------+
| :class:`StoreChannel` | :class:`~ext.commands.StoreChannelConverter` | Yes, as type 7 (CHANNEL) |
+--------------------------+-------------------------------------------------+-----------------------------+
| :class:`CategoryChannel` | :class:`~ext.commands.CategoryChannelConverter` | Yes, as type 7 (CHANNEL) |
+--------------------------+-------------------------------------------------+-----------------------------+
| :class:`Thread` | :class:`~ext.commands.ThreadConverter` | Yes, as type 7 (CHANNEL) |
+--------------------------+-------------------------------------------------+-----------------------------+
| :class:`Invite` | :class:`~ext.commands.InviteConverter` | Not currently |
+--------------------------+-------------------------------------------------+-----------------------------+
| :class:`Guild` | :class:`~ext.commands.GuildConverter` | Not currently |
+--------------------------+-------------------------------------------------+-----------------------------+
| :class:`Role` | :class:`~ext.commands.RoleConverter` | Yes, as type 8 (ROLE) |
+--------------------------+-------------------------------------------------+-----------------------------+
| :class:`Game` | :class:`~ext.commands.GameConverter` | Not currently |
+--------------------------+-------------------------------------------------+-----------------------------+
| :class:`Colour` | :class:`~ext.commands.ColourConverter` | Not currently |
+--------------------------+-------------------------------------------------+-----------------------------+
| :class:`Emoji` | :class:`~ext.commands.EmojiConverter` | Not currently |
+--------------------------+-------------------------------------------------+-----------------------------+
| :class:`PartialEmoji` | :class:`~ext.commands.PartialEmojiConverter` | Not currently |
+--------------------------+-------------------------------------------------+-----------------------------+
.. admonition:: Slash Command Only
If a slash command is not marked on the table above as supported, it will be sent as type 3 (STRING)
and parsed by normal content parsing, see
`the discord documentation <https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-type>`_
for all supported types by the API.
By providing the converter it allows us to use them as building blocks for another converter:
.. code-block:: python3
class MemberRoles(commands.MemberConverter):
async def convert(self, ctx, argument):
member = await super().convert(ctx, argument)
return [role.name for role in member.roles[1:]] # Remove everyone role!
@bot.command()
async def roles(ctx, *, member: MemberRoles):
"""Tells you a member's roles."""
await ctx.send('I see the following roles: ' + ', '.join(member))
.. _ext_commands_special_converters:
Special Converters
++++++++++++++++++++
The command extension also has support for certain converters to allow for more advanced and intricate use cases that go
beyond the generic linear parsing. These converters allow you to introduce some more relaxed and dynamic grammar to your
commands in an easy to use manner.
typing.Union
^^^^^^^^^^^^^^
A :data:`typing.Union` is a special type hint that allows for the command to take in any of the specific types instead of
a singular type. For example, given the following:
.. code-block:: python3
import typing
@bot.command()
async def union(ctx, what: typing.Union[discord.TextChannel, discord.Member]):
await ctx.send(what)
The ``what`` parameter would either take a :class:`discord.TextChannel` converter or a :class:`discord.Member` converter.
The way this works is through a left-to-right order. It first attempts to convert the input to a
:class:`discord.TextChannel`, and if it fails it tries to convert it to a :class:`discord.Member`. If all converters fail,
then a special error is raised, :exc:`~ext.commands.BadUnionArgument`.
Note that any valid converter discussed above can be passed in to the argument list of a :data:`typing.Union`.
.. admonition:: Slash Command Only
These are not currently supported by the Discord API and will be sent as type 3 (STRING)
typing.Optional
^^^^^^^^^^^^^^^^^
A :data:`typing.Optional` is a special type hint that allows for "back-referencing" behaviour. If the converter fails to
parse into the specified type, the parser will skip the parameter and then either ``None`` or the specified default will be
passed into the parameter instead. The parser will then continue on to the next parameters and converters, if any.
Consider the following example:
.. code-block:: python3
import typing
@bot.command()
async def bottles(ctx, amount: typing.Optional[int] = 99, *, liquid="beer"):
await ctx.send(f'{amount} bottles of {liquid} on the wall!')
.. image:: /images/commands/optional1.png
In this example, since the argument could not be converted into an ``int``, the default of ``99`` is passed and the parser
resumes handling, which in this case would be to pass it into the ``liquid`` parameter.
.. note::
This converter only works in regular positional parameters, not variable parameters or keyword-only parameters.
typing.Literal
^^^^^^^^^^^^^^^^
A :data:`typing.Literal` is a special type hint that requires the passed parameter to be equal to one of the listed values
after being converted to the same type. For example, given the following:
.. code-block:: python3
from typing import Literal
@bot.command()
async def shop(ctx, buy_sell: Literal['buy', 'sell'], amount: Literal[1, 2], *, item: str):
await ctx.send(f'{buy_sell.capitalize()}ing {amount} {item}(s)!')
The ``buy_sell`` parameter must be either the literal string ``"buy"`` or ``"sell"`` and ``amount`` must convert to the
``int`` ``1`` or ``2``. If ``buy_sell`` or ``amount`` don't match any value, then a special error is raised,
:exc:`~.ext.commands.BadLiteralArgument`. Any literal values can be mixed and matched within the same :data:`typing.Literal` converter.
Note that ``typing.Literal[True]`` and ``typing.Literal[False]`` still follow the :class:`bool` converter rules.
Greedy
^^^^^^^^
The :class:`~ext.commands.Greedy` converter is a generalisation of the :data:`typing.Optional` converter, except applied
to a list of arguments. In simple terms, this means that it tries to convert as much as it can until it can't convert
any further.
Consider the following example:
.. code-block:: python3
@bot.command()
async def slap(ctx, members: commands.Greedy[discord.Member], *, reason='no reason'):
slapped = ", ".join(x.name for x in members)
await ctx.send(f'{slapped} just got slapped for {reason}')
When invoked, it allows for any number of members to be passed in:
.. image:: /images/commands/greedy1.png
The type passed when using this converter depends on the parameter type that it is being attached to:
- Positional parameter types will receive either the default parameter or a :class:`list` of the converted values.
- Variable parameter types will be a :class:`tuple` as usual.
- Keyword-only parameter types will be the same as if :class:`~ext.commands.Greedy` was not passed at all.
:class:`~ext.commands.Greedy` parameters can also be made optional by specifying an optional value.
When mixed with the :data:`typing.Optional` converter you can provide simple and expressive command invocation syntaxes:
.. code-block:: python3
import typing
@bot.command()
async def ban(ctx, members: commands.Greedy[discord.Member],
delete_days: typing.Optional[int] = 0, *,
reason: str):
"""Mass bans members with an optional delete_days parameter"""
for member in members:
await member.ban(delete_message_days=delete_days, reason=reason)
This command can be invoked any of the following ways:
.. code-block:: none
$ban @Member @Member2 spam bot
$ban @Member @Member2 7 spam bot
$ban @Member spam
.. warning::
The usage of :class:`~ext.commands.Greedy` and :data:`typing.Optional` are powerful and useful, however as a
price, they open you up to some parsing ambiguities that might surprise some people.
For example, a signature expecting a :data:`typing.Optional` of a :class:`discord.Member` followed by a
:class:`int` could catch a member named after a number due to the different ways a
:class:`~ext.commands.MemberConverter` decides to fetch members. You should take care to not introduce
unintended parsing ambiguities in your code. One technique would be to clamp down the expected syntaxes
allowed through custom converters or reordering the parameters to minimise clashes.
To help aid with some parsing ambiguities, :class:`str`, ``None``, :data:`typing.Optional` and
:class:`~ext.commands.Greedy` are forbidden as parameters for the :class:`~ext.commands.Greedy` converter.
.. _ext_commands_flag_converter:
FlagConverter
++++++++++++++
.. versionadded:: 2.0
A :class:`~ext.commands.FlagConverter` allows the user to specify user-friendly "flags" using :pep:`526` type annotations
or a syntax more reminiscent of the :mod:`py:dataclasses` module.
For example, the following code:
.. code-block:: python3
from discord.ext import commands
import discord
class BanFlags(commands.FlagConverter):
member: discord.Member
reason: str
days: int = 1
@commands.command()
async def ban(ctx, *, flags: BanFlags):
plural = f'{flags.days} days' if flags.days != 1 else f'{flags.days} day'
await ctx.send(f'Banned {flags.member} for {flags.reason!r} (deleted {plural} worth of messages)')
Allows the user to invoke the command using a simple flag-like syntax:
.. image:: /images/commands/flags1.png
Flags use a syntax that allows the user to not require quotes when passing in values to the flag. The goal of the
flag syntax is to be as user-friendly as possible. This makes flags a good choice for complicated commands that can have
multiple knobs to turn or simulating keyword-only parameters in your external command interface. **It is recommended to use
keyword-only parameters with the flag converter**. This ensures proper parsing and behaviour with quoting.
Internally, the :class:`~ext.commands.FlagConverter` class examines the class to find flags. A flag can either be a
class variable with a type annotation or a class variable that's been assigned the result of the :func:`~ext.commands.flag`
function. These flags are then used to define the interface that your users will use. The annotations correspond to
the converters that the flag arguments must adhere to.
For most use cases, no extra work is required to define flags. However, if customisation is needed to control the flag name
or the default value then the :func:`~ext.commands.flag` function can come in handy:
.. code-block:: python3
from typing import List
class BanFlags(commands.FlagConverter):
members: List[discord.Member] = commands.flag(name='member', default=lambda ctx: [])
This tells the parser that the ``members`` attribute is mapped to a flag named ``member`` and that
the default value is an empty list. For greater customisability, the default can either be a value or a callable
that takes the :class:`~ext.commands.Context` as a sole parameter. This callable can either be a function or a coroutine.
In order to customise the flag syntax we also have a few options that can be passed to the class parameter list:
.. code-block:: python3
# --hello world syntax
class PosixLikeFlags(commands.FlagConverter, delimiter=' ', prefix='--'):
hello: str
# /make food
class WindowsLikeFlags(commands.FlagConverter, prefix='/', delimiter=''):
make: str
# TOPIC: not allowed nsfw: yes Slowmode: 100
class Settings(commands.FlagConverter, case_insensitive=True):
topic: Optional[str]
nsfw: Optional[bool]
slowmode: Optional[int]
.. note::
Despite the similarities in these examples to command like arguments, the syntax and parser is not
a command line parser. The syntax is mainly inspired by Discord's search bar input and as a result
all flags need a corresponding value.
.. admonition:: Slash Command Only
As these are built very similar to slash command options, they are converted into options and parsed
back into flags when the slash command is executed.
The flag converter is similar to regular commands and allows you to use most types of converters
(with the exception of :class:`~ext.commands.Greedy`) as the type annotation. Some extra support is added for specific
annotations as described below.
typing.List
^^^^^^^^^^^^^
If a list is given as a flag annotation it tells the parser that the argument can be passed multiple times.
For example, augmenting the example above:
.. code-block:: python3
from discord.ext import commands
from typing import List
import discord
class BanFlags(commands.FlagConverter):
members: List[discord.Member] = commands.flag(name='member')
reason: str
days: int = 1
@commands.command()
async def ban(ctx, *, flags: BanFlags):
for member in flags.members:
await member.ban(reason=flags.reason, delete_message_days=flags.days)
members = ', '.join(str(member) for member in flags.members)
plural = f'{flags.days} days' if flags.days != 1 else f'{flags.days} day'
await ctx.send(f'Banned {members} for {flags.reason!r} (deleted {plural} worth of messages)')
This is called by repeatedly specifying the flag:
.. image:: /images/commands/flags2.png
typing.Tuple
^^^^^^^^^^^^^
Since the above syntax can be a bit repetitive when specifying a flag many times, the :class:`py:tuple` type annotation
allows for "greedy-like" semantics using a variadic tuple:
.. code-block:: python3
from discord.ext import commands
from typing import Tuple
import discord
class BanFlags(commands.FlagConverter):
members: Tuple[discord.Member, ...]
reason: str
days: int = 1
This allows the previous ``ban`` command to be called like this:
.. image:: /images/commands/flags3.png
The :class:`py:tuple` annotation also allows for parsing of pairs. For example, given the following code:
.. code-block:: python3
# point: 10 11 point: 12 13
class Coordinates(commands.FlagConverter):
point: Tuple[int, int]
.. warning::
Due to potential parsing ambiguities, the parser expects tuple arguments to be quoted
if they require spaces. So if one of the inner types is :class:`str` and the argument requires spaces
then quotes should be used to disambiguate it from the other element of the tuple.
typing.Dict
^^^^^^^^^^^^^
A :class:`dict` annotation is functionally equivalent to ``List[Tuple[K, V]]`` except with the return type
given as a :class:`dict` rather than a :class:`list`.
.. _ext_commands_error_handler:
Error Handling
----------------
When our commands fail to parse we will, by default, receive a noisy error in ``stderr`` of our console that tells us
that an error has happened and has been silently ignored.
In order to handle our errors, we must use something called an error handler. There is a global error handler, called
:func:`.on_command_error` which works like any other event in the :ref:`discord-api-events`. This global error handler is
called for every error reached.
Most of the time however, we want to handle an error local to the command itself. Luckily, commands come with local error
handlers that allow us to do just that. First we decorate an error handler function with :meth:`.Command.error`:
.. code-block:: python3
@bot.command()
async def info(ctx, *, member: discord.Member):
"""Tells you some info about the member."""
msg = f'{member} joined on {member.joined_at} and has {len(member.roles)} roles.'
await ctx.send(msg)
@info.error
async def info_error(ctx, error):
if isinstance(error, commands.BadArgument):
await ctx.send('I could not find that member...')
The first parameter of the error handler is the :class:`.Context` while the second one is an exception that is derived from
:exc:`~ext.commands.CommandError`. A list of errors is found in the :ref:`ext_commands_api_errors` page of the documentation.
Checks
-------
There are cases when we don't want a user to use our commands. They don't have permissions to do so or maybe we blocked
them from using our bot earlier. The commands extension comes with full support for these things in a concept called a
:ref:`ext_commands_api_checks`.
A check is a basic predicate that can take in a :class:`.Context` as its sole parameter. Within it, you have the following
options:
- Return ``True`` to signal that the person can run the command.
- Return ``False`` to signal that the person cannot run the command.
- Raise a :exc:`~ext.commands.CommandError` derived exception to signal the person cannot run the command.
- This allows you to have custom error messages for you to handle in the
:ref:`error handlers <ext_commands_error_handler>`.
To register a check for a command, we would have two ways of doing so. The first is using the :meth:`~ext.commands.check`
decorator. For example:
.. code-block:: python3
async def is_owner(ctx):
return ctx.author.id == 316026178463072268
@bot.command(name='eval')
@commands.check(is_owner)
async def _eval(ctx, *, code):
"""A bad example of an eval command"""
await ctx.send(eval(code))
This would only evaluate the command if the function ``is_owner`` returns ``True``. Sometimes we re-use a check often and
want to split it into its own decorator. To do that we can just add another level of depth:
.. code-block:: python3
def is_owner():
async def predicate(ctx):
return ctx.author.id == 316026178463072268
return commands.check(predicate)
@bot.command(name='eval')
@is_owner()
async def _eval(ctx, *, code):
"""A bad example of an eval command"""
await ctx.send(eval(code))
Since an owner check is so common, the library provides it for you (:func:`~ext.commands.is_owner`):
.. code-block:: python3
@bot.command(name='eval')
@commands.is_owner()
async def _eval(ctx, *, code):
"""A bad example of an eval command"""
await ctx.send(eval(code))
When multiple checks are specified, **all** of them must be ``True``:
.. code-block:: python3
def is_in_guild(guild_id):
async def predicate(ctx):
return ctx.guild and ctx.guild.id == guild_id
return commands.check(predicate)
@bot.command()
@commands.is_owner()
@is_in_guild(41771983423143937)
async def secretguilddata(ctx):
"""super secret stuff"""
await ctx.send('secret stuff')
If any of those checks fail in the example above, then the command will not be run.
When an error happens, the error is propagated to the :ref:`error handlers <ext_commands_error_handler>`. If you do not
raise a custom :exc:`~ext.commands.CommandError` derived exception, then it will get wrapped up into a
:exc:`~ext.commands.CheckFailure` exception as so:
.. code-block:: python3
@bot.command()
@commands.is_owner()
@is_in_guild(41771983423143937)
async def secretguilddata(ctx):
"""super secret stuff"""
await ctx.send('secret stuff')
@secretguilddata.error
async def secretguilddata_error(ctx, error):
if isinstance(error, commands.CheckFailure):
await ctx.send('nothing to see here comrade.')
If you want a more robust error system, you can derive from the exception and raise it instead of returning ``False``:
.. code-block:: python3
class NoPrivateMessages(commands.CheckFailure):
pass
def guild_only():
async def predicate(ctx):
if ctx.guild is None:
raise NoPrivateMessages('Hey no DMs!')
return True
return commands.check(predicate)
@guild_only()
async def test(ctx):
await ctx.send('Hey this is not a DM! Nice.')
@test.error
async def test_error(ctx, error):
if isinstance(error, NoPrivateMessages):
await ctx.send(error)
.. note::
Since having a ``guild_only`` decorator is pretty common, it comes built-in via :func:`~ext.commands.guild_only`.
Global Checks
++++++++++++++
Sometimes we want to apply a check to **every** command, not just certain commands. The library supports this as well
using the global check concept.
Global checks work similarly to regular checks except they are registered with the :meth:`.Bot.check` decorator.
For example, to block all DMs we could do the following:
.. code-block:: python3
@bot.check
async def globally_block_dms(ctx):
return ctx.guild is not None
.. warning::
Be careful on how you write your global checks, as it could also lock you out of your own bot.
.. need a note on global check once here I think

View File

@@ -0,0 +1,64 @@
.. currentmodule:: discord
.. _ext_commands_extensions:
Extensions
=============
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**.
Primer
--------
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.
An example extension looks like this:
.. code-block:: python3
:caption: hello.py
:emphasize-lines: 7,8
from discord.ext import commands
@commands.command()
async def hello(ctx):
await ctx.send(f'Hello {ctx.author.display_name}.')
def setup(bot):
bot.add_command(hello)
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')``.
.. admonition:: Cogs
:class: helpful
Extensions are usually used in conjunction with cogs. To read more about them, check out the documentation, :ref:`ext_commands_cogs`.
.. note::
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``.
Reloading
-----------
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`.
.. code-block:: python3
>>> bot.reload_extension('hello')
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.
Cleaning Up
-------------
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.
.. code-block:: python3
:caption: basic_ext.py
def setup(bot):
print('I am being loaded!')
def teardown(bot):
print('I am being unloaded!')

View File

@@ -0,0 +1,19 @@
.. _discord_ext_commands:
``discord.ext.commands`` -- Bot commands framework
====================================================
``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.
.. toctree::
:maxdepth: 2
commands
cogs
extensions
slash-commands
api

View File

@@ -0,0 +1,23 @@
.. currentmodule:: discord
.. _ext_commands_slash_commands:
Slash Commands
==============
Slash Commands are currently supported in enhanced-discord.py using a system on top of ext.commands.
This system is very simple to use, and can be enabled via :attr:`.Bot.slash_commands` globally,
or only for specific commands via :attr:`.Command.slash_command`.
There is also the parameter ``slash_command_guilds`` which can be passed to either :class:`.Bot` or the command
decorator in order to only upload the commands as guild commands to these specific guild IDs, however this
should only be used for testing or small (<10 guilds) bots.
If you want to add option descriptions to your commands, you should use :class:`.Option`
For troubleshooting, see the :ref:`FAQ <ext_commands_slash_command_troubleshooting>`
.. admonition:: Slash Command Only
For parts of the docs specific to slash commands, look for this box!

View File

@@ -0,0 +1,154 @@
.. _discord_ext_tasks:
``discord.ext.tasks`` -- asyncio.Task helpers
====================================================
.. versionadded:: 1.1.0
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:
- How do I handle :exc:`asyncio.CancelledError`?
- What do I do if the internet goes out?
- What is the maximum number of seconds I can sleep anyway?
The goal of this discord.py extension is to abstract all these worries away from you.
Recipes
---------
A simple background task in a :class:`~discord.ext.commands.Cog`:
.. code-block:: python3
from discord.ext import tasks, commands
class MyCog(commands.Cog):
def __init__(self):
self.index = 0
self.printer.start()
def cog_unload(self):
self.printer.cancel()
@tasks.loop(seconds=5.0)
async def printer(self):
print(self.index)
self.index += 1
Adding an exception to handle during reconnect:
.. code-block:: python3
import asyncpg
from discord.ext import tasks, commands
class MyCog(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.data = []
self.batch_update.add_exception_type(asyncpg.PostgresConnectionError)
self.batch_update.start()
def cog_unload(self):
self.batch_update.cancel()
@tasks.loop(minutes=5.0)
async def batch_update(self):
async with self.bot.pool.acquire() as con:
# batch update here...
pass
Looping a certain amount of times before exiting:
.. code-block:: python3
from discord.ext import tasks
@tasks.loop(seconds=5.0, count=5)
async def slow_count():
print(slow_count.current_loop)
@slow_count.after_loop
async def after_slow_count():
print('done!')
slow_count.start()
Waiting until the bot is ready before the loop starts:
.. code-block:: python3
from discord.ext import tasks, commands
class MyCog(commands.Cog):
def __init__(self, bot):
self.index = 0
self.bot = bot
self.printer.start()
def cog_unload(self):
self.printer.cancel()
@tasks.loop(seconds=5.0)
async def printer(self):
print(self.index)
self.index += 1
@printer.before_loop
async def before_printer(self):
print('waiting...')
await self.bot.wait_until_ready()
Doing something during cancellation:
.. code-block:: python3
from discord.ext import tasks, commands
import asyncio
class MyCog(commands.Cog):
def __init__(self, bot):
self.bot= bot
self._batch = []
self.lock = asyncio.Lock()
self.bulker.start()
async def do_bulk(self):
# bulk insert data here
...
@tasks.loop(seconds=10.0)
async def bulker(self):
async with self.lock:
await self.do_bulk()
@bulker.after_loop
async def on_bulker_cancel(self):
if self.bulker.is_being_cancelled() and len(self._batch) != 0:
# if we're cancelled and we have some data left...
# let's insert it to our database
await self.do_bulk()
.. _ext_tasks_api:
API Reference
---------------
.. attributetable:: discord.ext.tasks.Loop
.. autoclass:: discord.ext.tasks.Loop()
:members:
:special-members: __call__
:exclude-members: after_loop, before_loop, error
.. automethod:: Loop.after_loop()
:decorator:
.. automethod:: Loop.before_loop()
:decorator:
.. automethod:: Loop.error()
:decorator:
.. autofunction:: discord.ext.tasks.loop

View File

@@ -0,0 +1,443 @@
:orphan:
.. currentmodule:: discord
.. _faq:
Frequently Asked Questions
===========================
This is a list of Frequently Asked Questions regarding using ``discord.py`` and its extension modules. Feel free to suggest a
new question or submit one via pull requests.
.. contents:: Questions
:local:
Coroutines
------------
Questions regarding coroutines and asyncio belong here.
What is a coroutine?
~~~~~~~~~~~~~~~~~~~~~~
A |coroutine_link|_ is a function that must be invoked with ``await`` or ``yield from``. When Python encounters an ``await`` it stops
the function's execution at that point and works on other things until it comes back to that point and finishes off its work.
This allows for your program to be doing multiple things at the same time without using threads or complicated
multiprocessing.
**If you forget to await a coroutine then the coroutine will not run. Never forget to await a coroutine.**
Where can I use ``await``\?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can only use ``await`` inside ``async def`` functions and nowhere else.
What does "blocking" mean?
~~~~~~~~~~~~~~~~~~~~~~~~~~~
In asynchronous programming a blocking call is essentially all the parts of the function that are not ``await``. Do not
despair however, because not all forms of blocking are bad! Using blocking calls is inevitable, but you must work to make
sure that you don't excessively block functions. Remember, if you block for too long then your bot will freeze since it has
not stopped the function's execution at that point to do other things.
If logging is enabled, this library will attempt to warn you that blocking is occurring with the message:
``Heartbeat blocked for more than N seconds.``
See :ref:`logging_setup` for details on enabling logging.
A common source of blocking for too long is something like :func:`time.sleep`. Don't do that. Use :func:`asyncio.sleep`
instead. Similar to this example: ::
# bad
time.sleep(10)
# good
await asyncio.sleep(10)
Another common source of blocking for too long is using HTTP requests with the famous module :doc:`req:index`.
While :doc:`req:index` is an amazing module for non-asynchronous programming, it is not a good choice for
:mod:`asyncio` because certain requests can block the event loop too long. Instead, use the :doc:`aiohttp <aio:index>` library which
is installed on the side with this library.
Consider the following example: ::
# bad
r = requests.get('http://aws.random.cat/meow')
if r.status_code == 200:
js = r.json()
await channel.send(js['file'])
# good
async with aiohttp.ClientSession() as session:
async with session.get('http://aws.random.cat/meow') as r:
if r.status == 200:
js = await r.json()
await channel.send(js['file'])
General
---------
General questions regarding library usage belong here.
Where can I find usage examples?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Example code can be found in the `examples folder <https://github.com/Rapptz/discord.py/tree/master/examples>`_
in the repository.
How do I set the "Playing" status?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``activity`` keyword argument may be passed in the :class:`Client` constructor or :meth:`Client.change_presence`, given an :class:`Activity` object.
The constructor may be used for static activities, while :meth:`Client.change_presence` may be used to update the activity at runtime.
.. warning::
It is highly discouraged to use :meth:`Client.change_presence` or API calls in :func:`on_ready` as this event may be called many times while running, not just once.
There is a high chance of disconnecting if presences are changed right after connecting.
The status type (playing, listening, streaming, watching) can be set using the :class:`ActivityType` enum.
For memory optimisation purposes, some activities are offered in slimmed-down versions:
- :class:`Game`
- :class:`Streaming`
Putting both of these pieces of info together, you get the following: ::
client = discord.Client(activity=discord.Game(name='my game'))
# or, for watching:
activity = discord.Activity(name='my activity', type=discord.ActivityType.watching)
client = discord.Client(activity=activity)
How do I send a message to a specific channel?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You must fetch the channel directly and then call the appropriate method. Example: ::
channel = client.get_channel(12324234183172)
await channel.send('hello')
How do I send a DM?
~~~~~~~~~~~~~~~~~~~
Get the :class:`User` or :class:`Member` object and call :meth:`abc.Messageable.send`. For example: ::
user = client.get_user(381870129706958858)
await user.send('👀')
If you are responding to an event, such as :func:`on_message`, you already have the :class:`User` object via :attr:`Message.author`: ::
await message.author.send('👋')
How do I get the ID of a sent message?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:meth:`abc.Messageable.send` returns the :class:`Message` that was sent.
The ID of a message can be accessed via :attr:`Message.id`: ::
message = await channel.send('hmm…')
message_id = message.id
How do I upload an image?
~~~~~~~~~~~~~~~~~~~~~~~~~
To upload something to Discord you have to use the :class:`File` object.
A :class:`File` accepts two parameters, the file-like object (or file path) and the filename
to pass to Discord when uploading.
If you want to upload an image it's as simple as: ::
await channel.send(file=discord.File('my_file.png'))
If you have a file-like object you can do as follows: ::
with open('my_file.png', 'rb') as fp:
await channel.send(file=discord.File(fp, 'new_filename.png'))
To upload multiple files, you can use the ``files`` keyword argument instead of ``file``\: ::
my_files = [
discord.File('result.zip'),
discord.File('teaser_graph.png'),
]
await channel.send(files=my_files)
If you want to upload something from a URL, you will have to use an HTTP request using :doc:`aiohttp <aio:index>`
and then pass an :class:`io.BytesIO` instance to :class:`File` like so:
.. code-block:: python3
import io
import aiohttp
async with aiohttp.ClientSession() as session:
async with session.get(my_url) as resp:
if resp.status != 200:
return await channel.send('Could not download file...')
data = io.BytesIO(await resp.read())
await channel.send(file=discord.File(data, 'cool_image.png'))
How can I add a reaction to a message?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You use the :meth:`Message.add_reaction` method.
If you want to use unicode emoji, you must pass a valid unicode code point in a string. In your code, you can write this in a few different ways:
- ``'👍'``
- ``'\U0001F44D'``
- ``'\N{THUMBS UP SIGN}'``
Quick example: ::
emoji = '\N{THUMBS UP SIGN}'
# or '\U0001f44d' or '👍'
await message.add_reaction(emoji)
In case you want to use emoji that come from a message, you already get their code points in the content without needing
to do anything special. You **cannot** send ``':thumbsup:'`` style shorthands.
For custom emoji, you should pass an instance of :class:`Emoji`. You can also pass a ``'<:name:id>'`` string, but if you
can use said emoji, you should be able to use :meth:`Client.get_emoji` to get an emoji via ID or use :func:`utils.find`/
:func:`utils.get` on :attr:`Client.emojis` or :attr:`Guild.emojis` collections.
The name and ID of a custom emoji can be found with the client by prefixing ``:custom_emoji:`` with a backslash.
For example, sending the message ``\:python3:`` with the client will result in ``<:python3:232720527448342530>``.
Quick example: ::
# if you have the ID already
emoji = client.get_emoji(310177266011340803)
await message.add_reaction(emoji)
# no ID, do a lookup
emoji = discord.utils.get(guild.emojis, name='LUL')
if emoji:
await message.add_reaction(emoji)
# if you have the name and ID of a custom emoji:
emoji = '<:python3:232720527448342530>'
await message.add_reaction(emoji)
How do I pass a coroutine to the player's "after" function?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The library's music player launches on a separate thread, ergo it does not execute inside a coroutine.
This does not mean that it is not possible to call a coroutine in the ``after`` parameter. To do so you must pass a callable
that wraps up a couple of aspects.
The first gotcha that you must be aware of is that calling a coroutine is not a thread-safe operation. Since we are
technically in another thread, we must take caution in calling thread-safe operations so things do not bug out. Luckily for
us, :mod:`asyncio` comes with a :func:`asyncio.run_coroutine_threadsafe` function that allows us to call
a coroutine from another thread.
However, this function returns a :class:`~concurrent.futures.Future` and to actually call it we have to fetch its result. Putting all of
this together we can do the following: ::
def my_after(error):
coro = some_channel.send('Song is done!')
fut = asyncio.run_coroutine_threadsafe(coro, client.loop)
try:
fut.result()
except:
# an error happened sending the message
pass
voice.play(discord.FFmpegPCMAudio(url), after=my_after)
How do I run something in the background?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
`Check the background_task.py example. <https://github.com/Rapptz/discord.py/blob/master/examples/background_task.py>`_
How do I get a specific model?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are multiple ways of doing this. If you have a specific model's ID then you can use
one of the following functions:
- :meth:`Client.get_channel`
- :meth:`Client.get_guild`
- :meth:`Client.get_user`
- :meth:`Client.get_emoji`
- :meth:`Guild.get_member`
- :meth:`Guild.get_channel`
- :meth:`Guild.get_role`
The following use an HTTP request:
- :meth:`abc.Messageable.fetch_message`
- :meth:`Client.fetch_user`
- :meth:`Client.fetch_guilds`
- :meth:`Client.fetch_guild`
- :meth:`Guild.fetch_emoji`
- :meth:`Guild.fetch_emojis`
- :meth:`Guild.fetch_member`
If the functions above do not help you, then use of :func:`utils.find` or :func:`utils.get` would serve some use in finding
specific models.
Quick example: ::
# find a guild by name
guild = discord.utils.get(client.guilds, name='My Server')
# make sure to check if it's found
if guild is not None:
# find a channel by name
channel = discord.utils.get(guild.text_channels, name='cool-channel')
How do I make a web request?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To make a request, you should use a non-blocking library.
This library already uses and requires a 3rd party library for making requests, :doc:`aiohttp <aio:index>`.
Quick example: ::
async with aiohttp.ClientSession() as session:
async with session.get('http://aws.random.cat/meow') as r:
if r.status == 200:
js = await r.json()
See `aiohttp's full documentation <http://aiohttp.readthedocs.io/en/stable/>`_ for more information.
How do I use a local image file for an embed image?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Discord special-cases uploading an image attachment and using it within an embed so that it will not
display separately, but instead in the embed's thumbnail, image, footer or author icon.
To do so, upload the image normally with :meth:`abc.Messageable.send`,
and set the embed's image URL to ``attachment://image.png``,
where ``image.png`` is the filename of the image you will send.
Quick example: ::
file = discord.File("path/to/my/image.png", filename="image.png")
embed = discord.Embed()
embed.set_image(url="attachment://image.png")
await channel.send(file=file, embed=embed)
.. note ::
Due to a Discord limitation, filenames may not include underscores.
Is there an event for audit log entries being created?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Since Discord does not dispatch this information in the gateway, the library cannot provide this information.
This is currently a Discord limitation.
Commands Extension
-------------------
Questions regarding ``discord.ext.commands`` belong here.
Why does ``on_message`` make my commands stop working?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Overriding the default provided ``on_message`` forbids any extra commands from running. To fix this, add a
``bot.process_commands(message)`` line at the end of your ``on_message``. For example: ::
@bot.event
async def on_message(message):
# do some extra stuff here
await bot.process_commands(message)
Alternatively, you can place your ``on_message`` logic into a **listener**. In this setup, you should not
manually call ``bot.process_commands()``. This also allows you to do multiple things asynchronously in response
to a message. Example::
@bot.listen('on_message')
async def whatever_you_want_to_call_it(message):
# do stuff here
# do not process commands here
Why do my arguments require quotes?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In a simple command defined as: ::
@bot.command()
async def echo(ctx, message: str):
await ctx.send(message)
Calling it via ``?echo a b c`` will only fetch the first argument and disregard the rest. To fix this you should either call
it via ``?echo "a b c"`` or change the signature to have "consume rest" behaviour. Example: ::
@bot.command()
async def echo(ctx, *, message: str):
await ctx.send(message)
This will allow you to use ``?echo a b c`` without needing the quotes.
How do I get the original ``message``\?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The :class:`~ext.commands.Context` contains an attribute, :attr:`~.Context.message` to get the original
message.
Example: ::
@bot.command()
async def length(ctx):
await ctx.send(f'Your message is {len(ctx.message.content)} characters long.')
How do I make a subcommand?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Use the :func:`~ext.commands.group` decorator. This will transform the callback into a :class:`~ext.commands.Group` which will allow you to add commands into
the group operating as "subcommands". These groups can be arbitrarily nested as well.
Example: ::
@bot.group()
async def git(ctx):
if ctx.invoked_subcommand is None:
await ctx.send('Invalid git command passed...')
@git.command()
async def push(ctx, remote: str, branch: str):
await ctx.send(f'Pushing to {remote} {branch}')
This could then be used as ``?git push origin master``.
How do I make slash commands?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
See :doc:`/ext/commands/slash-commands`
My slash commands aren't showing up!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. _ext_commands_slash_command_troubleshooting:
You need to invite your bot with the ``application.commands`` scope on each guild and
you need the :attr:`Permissions.use_slash_commands` permission in order to see slash commands.
.. image:: /images/discord_oauth2_slash_scope.png
:alt: The scopes checkbox with "bot" and "applications.commands" ticked.
Global slash commands (created by not specifying :attr:`~ext.commands.Bot.slash_command_guilds`) will also take up an
hour to refresh on discord's end, so it is recommended to set :attr:`~ext.commands.Bot.slash_command_guilds` for development.
If none of this works, make sure you are actually running enhanced-discord.py by doing ``print(bot.slash_commands)``
My bot won't start after enabling slash commands!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This means some of your command metadata is invalid for slash commands.
Make sure your command names and option names are lowercase, and they have to match the regex ``^[\w-]{1,32}$``
If you cannot figure out the problem, you should disable slash commands globally (:attr:`~ext.commands.Bot.slash_commands`\=False)
then go through commands, enabling them specifically with :attr:`~.commands.Command.slash_command`\=True until it
errors, then you can debug the problem with that command specifically.

View File

@@ -0,0 +1,76 @@
.. discord.py documentation master file, created by
sphinx-quickstart on Fri Aug 21 05:43:30 2015.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to discord.py
===========================
.. image:: /images/snake.svg
.. image:: /images/snake_dark.svg
discord.py is a modern, easy to use, feature-rich, and async ready API wrapper
for Discord.
**Features:**
- Modern Pythonic API using ``async``\/``await`` syntax
- Sane rate limit handling that prevents 429s
- Command extension to aid with bot creation
- Easy to use with an object oriented design
- Optimised for both speed and memory
Getting started
-----------------
Is this your first time using the library? This is the place to get started!
- **First steps:** :doc:`intro` | :doc:`quickstart` | :doc:`logging`
- **Working with Discord:** :doc:`discord` | :doc:`intents`
- **Examples:** Many examples are available in the :resource:`repository <examples>`.
Getting help
--------------
If you're having trouble with something, these resources might help.
- Try the :doc:`faq` first, it's got answers to all common questions.
- Ask us and hang out with us in our :resource:`Discord <discord>` server.
- If you're looking for something specific, try the :ref:`index <genindex>` or :ref:`searching <search>`.
- Report bugs in the :resource:`issue tracker <issues>`.
- Ask in our :resource:`GitHub discussions page <discussions>`.
Extensions
------------
These extensions help you during development when it comes to common tasks.
.. toctree::
:maxdepth: 1
ext/commands/index.rst
ext/tasks/index.rst
Manuals
---------
These pages go into great detail about everything the API can do.
.. toctree::
:maxdepth: 1
api
discord.ext.commands API Reference <ext/commands/api.rst>
discord.ext.tasks API Reference <ext/tasks/index.rst>
Meta
------
If you're looking for something related to the project itself, it's here.
.. toctree::
:maxdepth: 1
whats_new
version_guarantees
migrating

View File

@@ -0,0 +1,192 @@
:orphan:
.. currentmodule:: discord
.. versionadded:: 1.5
.. _intents_primer:
A Primer to Gateway Intents
=============================
In version 1.5 comes the introduction of :class:`Intents`. This is a radical change in how bots are written. An intent basically allows a bot to subscribe to specific buckets of events. The events that correspond to each intent is documented in the individual attribute of the :class:`Intents` documentation.
These intents are passed to the constructor of :class:`Client` or its subclasses (:class:`AutoShardedClient`, :class:`~.AutoShardedBot`, :class:`~.Bot`) with the ``intents`` argument.
If intents are not passed, then the library defaults to every intent being enabled except the privileged intents, currently :attr:`Intents.members` and :attr:`Intents.presences`.
What intents are needed?
--------------------------
The intents that are necessary for your bot can only be dictated by yourself. Each attribute in the :class:`Intents` class documents what :ref:`events <discord-api-events>` it corresponds to and what kind of cache it enables.
For example, if you want a bot that functions without spammy events like presences or typing then we could do the following:
.. code-block:: python3
:emphasize-lines: 7,9,10
import discord
intents = discord.Intents.default()
intents.typing = False
intents.presences = False
# Somewhere else:
# client = discord.Client(intents=intents)
# or
# from discord.ext import commands
# bot = commands.Bot(command_prefix='!', intents=intents)
Note that this doesn't enable :attr:`Intents.members` since it's a privileged intent.
Another example showing a bot that only deals with messages and guild information:
.. code-block:: python3
:emphasize-lines: 7,9,10
import discord
intents = discord.Intents(messages=True, guilds=True)
# If you also want reaction events enable the following:
# intents.reactions = True
# Somewhere else:
# client = discord.Client(intents=intents)
# or
# from discord.ext import commands
# bot = commands.Bot(command_prefix='!', intents=intents)
.. _privileged_intents:
Privileged Intents
---------------------
With the API change requiring bot authors to specify intents, some intents were restricted further and require more manual steps. These intents are called **privileged intents**.
A privileged intent is one that requires you to go to the developer portal and manually enable it. To enable privileged intents do the following:
1. Make sure you're logged on to the `Discord website <https://discord.com>`_.
2. Navigate to the `application page <https://discord.com/developers/applications>`_.
3. Click on the bot you want to enable privileged intents for.
4. Navigate to the bot tab on the left side of the screen.
.. image:: /images/discord_bot_tab.png
:alt: The bot tab in the application page.
5. Scroll down to the "Privileged Gateway Intents" section and enable the ones you want.
.. image:: /images/discord_privileged_intents.png
:alt: The privileged gateway intents selector.
.. warning::
Enabling privileged intents when your bot is in over 100 guilds requires going through `bot verification <https://support.discord.com/hc/en-us/articles/360040720412>`_. If your bot is already verified and you would like to enable a privileged intent you must go through `Discord support <https://dis.gd/contact>`_ and talk to them about it.
.. note::
Even if you enable intents through the developer portal, you still have to enable the intents
through code as well.
Do I need privileged intents?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is a quick checklist to see if you need specific privileged intents.
.. _need_presence_intent:
Presence Intent
+++++++++++++++++
- Whether you use :attr:`Member.status` at all to track member statuses.
- Whether you use :attr:`Member.activity` or :attr:`Member.activities` to check member's activities.
.. _need_members_intent:
Member Intent
+++++++++++++++
- Whether you track member joins or member leaves, corresponds to :func:`on_member_join` and :func:`on_member_remove` events.
- Whether you want to track member updates such as nickname or role changes.
- Whether you want to track user updates such as usernames, avatars, discriminators, etc.
- Whether you want to request the guild member list through :meth:`Guild.chunk` or :meth:`Guild.fetch_members`.
- Whether you want high accuracy member cache under :attr:`Guild.members`.
.. _intents_member_cache:
Member Cache
-------------
Along with intents, Discord now further restricts the ability to cache members and expects bot authors to cache as little as is necessary. However, to properly maintain a cache the :attr:`Intents.members` intent is required in order to track the members who left and properly evict them.
To aid with member cache where we don't need members to be cached, the library now has a :class:`MemberCacheFlags` flag to control the member cache. The documentation page for the class goes over the specific policies that are possible.
It should be noted that certain things do not need a member cache since Discord will provide full member information if possible. For example:
- :func:`on_message` will have :attr:`Message.author` be a member even if cache is disabled.
- :func:`on_voice_state_update` will have the ``member`` parameter be a member even if cache is disabled.
- :func:`on_reaction_add` will have the ``user`` parameter be a member when in a guild even if cache is disabled.
- :func:`on_raw_reaction_add` will have :attr:`RawReactionActionEvent.member` be a member when in a guild even if cache is disabled.
- The reaction add events do not contain additional information when in direct messages. This is a Discord limitation.
- The reaction removal events do not have member information. This is a Discord limitation.
Other events that take a :class:`Member` will require the use of the member cache. If absolute accuracy over the member cache is desirable, then it is advisable to have the :attr:`Intents.members` intent enabled.
.. _retrieving_members:
Retrieving Members
--------------------
If the cache is disabled or you disable chunking guilds at startup, we might still need a way to load members. The library offers a few ways to do this:
- :meth:`Guild.query_members`
- Used to query members by a prefix matching nickname or username.
- This can also be used to query members by their user ID.
- This uses the gateway and not the HTTP.
- :meth:`Guild.chunk`
- This can be used to fetch the entire member list through the gateway.
- :meth:`Guild.fetch_member`
- Used to fetch a member by ID through the HTTP API.
- :meth:`Guild.fetch_members`
- used to fetch a large number of members through the HTTP API.
It should be noted that the gateway has a strict rate limit of 120 requests per 60 seconds.
Troubleshooting
------------------
Some common issues relating to the mandatory intent change.
Where'd my members go?
~~~~~~~~~~~~~~~~~~~~~~~~
Due to an :ref:`API change <intents_member_cache>` Discord is now forcing developers who want member caching to explicitly opt-in to it. This is a Discord mandated change and there is no way to bypass it. In order to get members back you have to explicitly enable the :ref:`members privileged intent <privileged_intents>` and change the :attr:`Intents.members` attribute to true.
For example:
.. code-block:: python3
:emphasize-lines: 3,6,8,9
import discord
intents = discord.Intents.default()
intents.members = True
# Somewhere else:
# client = discord.Client(intents=intents)
# or
# from discord.ext import commands
# bot = commands.Bot(command_prefix='!', intents=intents)
Why does ``on_ready`` take so long to fire?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As part of the API change regarding intents, Discord also changed how members are loaded in the beginning. Originally the library could request 75 guilds at once and only request members from guilds that have the :attr:`Guild.large` attribute set to ``True``. With the new intent changes, Discord mandates that we can only send 1 guild per request. This causes a 75x slowdown which is further compounded by the fact that *all* guilds, not just large guilds are being requested.
There are a few solutions to fix this.
The first solution is to request the privileged presences intent along with the privileged members intent and enable both of them. This allows the initial member list to contain online members just like the old gateway. Note that we're still limited to 1 guild per request but the number of guilds we request is significantly reduced.
The second solution is to disable member chunking by setting ``chunk_guilds_at_startup`` to ``False`` when constructing a client. Then, when chunking for a guild is necessary you can use the various techniques to :ref:`retrieve members <retrieving_members>`.
To illustrate the slowdown caused by the API change, take a bot who is in 840 guilds and 95 of these guilds are "large" (over 250 members).
Under the original system this would result in 2 requests to fetch the member list (75 guilds, 20 guilds) roughly taking 60 seconds. With :attr:`Intents.members` but not :attr:`Intents.presences` this requires 840 requests, with a rate limit of 120 requests per 60 seconds means that due to waiting for the rate limit it totals to around 7 minutes of waiting for the rate limit to fetch all the members. With both :attr:`Intents.members` and :attr:`Intents.presences` we mostly get the old behaviour so we're only required to request for the 95 guilds that are large, this is slightly less than our rate limit so it's close to the original timing to fetch the member list.
Unfortunately due to this change being required from Discord there is nothing that the library can do to mitigate this.
If you truly dislike the direction Discord is going with their API, you can contact them via `support <https://dis.gd/contact>`_.

View File

@@ -0,0 +1,113 @@
:orphan:
.. currentmodule:: discord
.. _intro:
Introduction
==============
This is the documentation for discord.py, a library for Python to aid
in creating applications that utilise the Discord API.
Prerequisites
---------------
discord.py works with Python 3.8 or higher. Support for earlier versions of Python
is not provided. Python 2.7 or lower is not supported. Python 3.7 or lower is not supported.
.. _installing:
Installing
-----------
You can get the library directly from PyPI: ::
python3 -m pip install -U discord.py
If you are using Windows, then the following should be used instead: ::
py -3 -m pip install -U discord.py
To get voice support, you should use ``discord.py[voice]`` instead of ``discord.py``, e.g. ::
python3 -m pip install -U discord.py[voice]
On Linux environments, installing voice requires getting the following dependencies:
- `libffi <https://github.com/libffi/libffi>`_
- `libnacl <https://github.com/saltstack/libnacl>`_
- `python3-dev <https://packages.debian.org/python3-dev>`_
For a Debian-based system, the following command will get these dependencies:
.. code-block:: shell
$ apt install libffi-dev libnacl-dev python3-dev
Remember to check your permissions!
Virtual Environments
~~~~~~~~~~~~~~~~~~~~~
Sometimes you want to keep libraries from polluting system installs or use a different version of
libraries than the ones installed on the system. You might also not have permissions to install libraries system-wide.
For this purpose, the standard library as of Python 3.3 comes with a concept called "Virtual Environment"s to
help maintain these separate versions.
A more in-depth tutorial is found on :doc:`py:tutorial/venv`.
However, for the quick and dirty:
1. Go to your project's working directory:
.. code-block:: shell
$ cd your-bot-source
$ python3 -m venv bot-env
2. Activate the virtual environment:
.. code-block:: shell
$ source bot-env/bin/activate
On Windows you activate it with:
.. code-block:: shell
$ bot-env\Scripts\activate.bat
3. Use pip like usual:
.. code-block:: shell
$ pip install -U discord.py
Congratulations. You now have a virtual environment all set up.
Basic Concepts
---------------
discord.py revolves around the concept of :ref:`events <discord-api-events>`.
An event is something you listen to and then respond to. For example, when a message
happens, you will receive an event about it that you can respond to.
A quick example to showcase how events work:
.. code-block:: python3
import discord
class MyClient(discord.Client):
async def on_ready(self):
print(f'Logged on as {self.user}!')
async def on_message(self, message):
print(f'Message from {messsage.author}: {message.content}')
client = MyClient()
client.run('my token goes here')

View File

@@ -0,0 +1,46 @@
:orphan:
.. versionadded:: 0.6.0
.. _logging_setup:
Setting Up Logging
===================
*discord.py* logs errors and debug information via the :mod:`logging` python
module. It is strongly recommended that the logging module is
configured, as no errors or warnings will be output if it is not set up.
Configuration of the ``logging`` module can be as simple as::
import logging
logging.basicConfig(level=logging.INFO)
Placed at the start of the application. This will output the logs from
discord as well as other libraries that use the ``logging`` module
directly to the console.
The optional ``level`` argument specifies what level of events to log
out and can be any of ``CRITICAL``, ``ERROR``, ``WARNING``, ``INFO``, and
``DEBUG`` and if not specified defaults to ``WARNING``.
More advanced setups are possible with the :mod:`logging` module. For
example to write the logs to a file called ``discord.log`` instead of
outputting them to the console the following snippet can be used::
import discord
import logging
logger = logging.getLogger('discord')
logger.setLevel(logging.DEBUG)
handler = logging.FileHandler(filename='discord.log', encoding='utf-8', mode='w')
handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s'))
logger.addHandler(handler)
This is recommended, especially at verbose levels such as ``INFO``
and ``DEBUG``, as there are a lot of events logged and it would clog the
stdout of your program.
For more information, check the documentation and tutorial of the
:mod:`logging` module.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,322 @@
:orphan:
.. currentmodule:: discord
.. _migrating-to-async:
Migrating to v0.10.0
======================
v0.10.0 is one of the biggest breaking changes in the library due to massive
fundamental changes in how the library operates.
The biggest major change is that the library has dropped support to all versions prior to
Python 3.4.2. This was made to support :mod:`asyncio`, in which more detail can be seen
:issue:`in the corresponding issue <50>`. To reiterate this, the implication is that
**python version 2.7 and 3.3 are no longer supported**.
Below are all the other major changes from v0.9.0 to v0.10.0.
Event Registration
--------------------
All events before were registered using :meth:`Client.event`. While this is still
possible, the events must be decorated with ``@asyncio.coroutine``.
Before:
.. code-block:: python3
@client.event
def on_message(message):
pass
After:
.. code-block:: python3
@client.event
@asyncio.coroutine
def on_message(message):
pass
Or in Python 3.5+:
.. code-block:: python3
@client.event
async def on_message(message):
pass
Because there is a lot of typing, a utility decorator (:meth:`Client.async_event`) is provided
for easier registration. For example:
.. code-block:: python3
@client.async_event
def on_message(message):
pass
Be aware however, that this is still a coroutine and your other functions that are coroutines must
be decorated with ``@asyncio.coroutine`` or be ``async def``.
Event Changes
--------------
Some events in v0.9.0 were considered pretty useless due to having no separate states. The main
events that were changed were the ``_update`` events since previously they had no context on what
was changed.
Before:
.. code-block:: python3
def on_channel_update(channel): pass
def on_member_update(member): pass
def on_status(member): pass
def on_server_role_update(role): pass
def on_voice_state_update(member): pass
def on_socket_raw_send(payload, is_binary): pass
After:
.. code-block:: python3
def on_channel_update(before, after): pass
def on_member_update(before, after): pass
def on_server_role_update(before, after): pass
def on_voice_state_update(before, after): pass
def on_socket_raw_send(payload): pass
Note that ``on_status`` was removed. If you want its functionality, use :func:`on_member_update`.
See :ref:`discord-api-events` for more information. Other removed events include ``on_socket_closed``, ``on_socket_receive``, and ``on_socket_opened``.
Coroutines
-----------
The biggest change that the library went through is that almost every function in :class:`Client`
was changed to be a `coroutine <py:library/asyncio-task.html>`_. Functions
that are marked as a coroutine in the documentation must be awaited from or yielded from in order
for the computation to be done. For example...
Before:
.. code-block:: python3
client.send_message(message.channel, 'Hello')
After:
.. code-block:: python3
yield from client.send_message(message.channel, 'Hello')
# or in python 3.5+
await client.send_message(message.channel, 'Hello')
In order for you to ``yield from`` or ``await`` a coroutine then your function must be decorated
with ``@asyncio.coroutine`` or ``async def``.
Iterables
----------
For performance reasons, many of the internal data structures were changed into a dictionary to support faster
lookup. As a consequence, this meant that some lists that were exposed via the API have changed into iterables
and not sequences. In short, this means that certain attributes now only support iteration and not any of the
sequence functions.
The affected attributes are as follows:
- :attr:`Client.servers`
- :attr:`Client.private_channels`
- :attr:`Server.channels`
- :attr:`Server.members`
Some examples of previously valid behaviour that is now invalid
.. code-block:: python3
if client.servers[0].name == "test":
# do something
Since they are no longer :obj:`list`\s, they no longer support indexing or any operation other than iterating.
In order to get the old behaviour you should explicitly cast it to a list.
.. code-block:: python3
servers = list(client.servers)
# work with servers
.. warning::
Due to internal changes of the structure, the order you receive the data in
is not in a guaranteed order.
Enumerations
------------
Due to dropping support for versions lower than Python 3.4.2, the library can now use
:doc:`py:library/enum` in places where it makes sense.
The common places where this was changed was in the server region, member status, and channel type.
Before:
.. code-block:: python3
server.region == 'us-west'
member.status == 'online'
channel.type == 'text'
After:
.. code-block:: python3
server.region == discord.ServerRegion.us_west
member.status = discord.Status.online
channel.type == discord.ChannelType.text
The main reason for this change was to reduce the use of finicky strings in the API as this
could give users a false sense of power. More information can be found in the :ref:`discord-api-enums` page.
Properties
-----------
A lot of function calls that returned constant values were changed into Python properties for ease of use
in format strings.
The following functions were changed into properties:
+----------------------------------------+--------------------------------------+
| Before | After |
+----------------------------------------+--------------------------------------+
| ``User.avatar_url()`` | :attr:`User.avatar_url` |
+----------------------------------------+--------------------------------------+
| ``User.mention()`` | :attr:`User.mention` |
+----------------------------------------+--------------------------------------+
| ``Channel.mention()`` | :attr:`Channel.mention` |
+----------------------------------------+--------------------------------------+
| ``Channel.is_default_channel()`` | :attr:`Channel.is_default` |
+----------------------------------------+--------------------------------------+
| ``Role.is_everyone()`` | :attr:`Role.is_everyone` |
+----------------------------------------+--------------------------------------+
| ``Server.get_default_role()`` | :attr:`Server.default_role` |
+----------------------------------------+--------------------------------------+
| ``Server.icon_url()`` | :attr:`Server.icon_url` |
+----------------------------------------+--------------------------------------+
| ``Server.get_default_channel()`` | :attr:`Server.default_channel` |
+----------------------------------------+--------------------------------------+
| ``Message.get_raw_mentions()`` | :attr:`Message.raw_mentions` |
+----------------------------------------+--------------------------------------+
| ``Message.get_raw_channel_mentions()`` | :attr:`Message.raw_channel_mentions` |
+----------------------------------------+--------------------------------------+
Member Management
-------------------
Functions that involved banning and kicking were changed.
+--------------------------------+--------------------------+
| Before | After |
+--------------------------------+--------------------------+
| ``Client.ban(server, user)`` | ``Client.ban(member)`` |
+--------------------------------+--------------------------+
| ``Client.kick(server, user)`` | ``Client.kick(member)`` |
+--------------------------------+--------------------------+
.. migrating-renames:
Renamed Functions
-------------------
Functions have been renamed.
+------------------------------------+-------------------------------------------+
| Before | After |
+------------------------------------+-------------------------------------------+
| ``Client.set_channel_permissions`` | :meth:`Client.edit_channel_permissions` |
+------------------------------------+-------------------------------------------+
All the :class:`Permissions` related attributes have been renamed and the `can_` prefix has been
dropped. So for example, ``can_manage_messages`` has become ``manage_messages``.
Forced Keyword Arguments
-------------------------
Since 3.0+ of Python, we can now force questions to take in forced keyword arguments. A keyword argument is when you
explicitly specify the name of the variable and assign to it, for example: ``foo(name='test')``. Due to this support,
some functions in the library were changed to force things to take said keyword arguments. This is to reduce errors of
knowing the argument order and the issues that could arise from them.
The following parameters are now exclusively keyword arguments:
- :meth:`Client.send_message`
- ``tts``
- :meth:`Client.logs_from`
- ``before``
- ``after``
- :meth:`Client.edit_channel_permissions`
- ``allow``
- ``deny``
In the documentation you can tell if a function parameter is a forced keyword argument if it is after ``\*,``
in the function signature.
.. _migrating-running:
Running the Client
--------------------
In earlier versions of discord.py, ``client.run()`` was a blocking call to the main thread
that called it. In v0.10.0 it is still a blocking call but it handles the event loop for you.
However, in order to do that you must pass in your credentials to :meth:`Client.run`.
Basically, before:
.. code-block:: python3
client.login('token')
client.run()
After:
.. code-block:: python3
client.run('token')
.. warning::
Like in the older ``Client.run`` function, the newer one must be the one of
the last functions to call. This is because the function is **blocking**. Registering
events or doing anything after :meth:`Client.run` will not execute until the function
returns.
This is a utility function that abstracts the event loop for you. There's no need for
the run call to be blocking and out of your control. Indeed, if you want control of the
event loop then doing so is quite straightforward:
.. code-block:: python3
import discord
import asyncio
client = discord.Client()
@asyncio.coroutine
def main_task():
yield from client.login('token')
yield from client.connect()
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(main_task())
except:
loop.run_until_complete(client.logout())
finally:
loop.close()

View File

@@ -0,0 +1,79 @@
:orphan:
.. _quickstart:
.. currentmodule:: discord
Quickstart
============
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.
A Minimal Bot
---------------
Let's make a bot that responds to a specific message and walk you through it.
It looks something like this:
.. code-block:: python3
import discord
client = discord.Client()
@client.event
async def on_ready():
print(f'We have logged in as {client.user}')
@client.event
async def on_message(message):
if message.author == client.user:
return
if message.content.startswith('$hello'):
await message.channel.send('Hello!')
client.run('your token here')
Let's name this file ``example_bot.py``. Make sure not to name it ``discord.py`` as that'll conflict
with the library.
There's a lot going on here, so let's walk you through it step by step.
1. 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.
2. Next, we create an instance of a :class:`Client`. This client is our connection to Discord.
3. 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.
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.
4. 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`.
5. Afterwards, we check if the :class:`Message.content` starts with ``'$hello'``. If it does,
then we send a message in the channel it was used in with ``'Hello!'``. This is a basic way of
handling commands, which can be later automated with the :doc:`./ext/commands/index` framework.
6. 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.
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.
On Windows:
.. code-block:: shell
$ py -3 example_bot.py
On other systems:
.. code-block:: shell
$ python3 example_bot.py
Now you can try playing around with your basic bot.

View File

@@ -0,0 +1,30 @@
.. _version_guarantees:
Version Guarantees
=====================
The library follows a `semantic versioning principle <https://semver.org/>`_ 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.
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.
.. note::
The examples below are non-exhaustive.
Examples of Breaking Changes
------------------------------
- Changing the default parameter value to something else.
- Renaming a function without an alias to an old function.
- Adding or removing parameters to an event.
Examples of Non-Breaking Changes
----------------------------------
- Adding or removing private underscored attributes.
- Adding an element into the ``__slots__`` of a data class.
- Changing the behaviour of a function to fix a bug.
- Changes in the documentation.
- Modifying the internal HTTP handling.
- Upgrading the dependencies to a new version, major or otherwise.

File diff suppressed because it is too large Load Diff