diff options
| author | Rapptz <[email protected]> | 2017-05-12 20:14:34 -0400 |
|---|---|---|
| committer | Rapptz <[email protected]> | 2017-05-12 20:14:34 -0400 |
| commit | b44bba6ee6e29b38d1e579c602821582e155ec3b (patch) | |
| tree | 355df44874b3e5f8ee4e825339cb57783e3677ca /docs | |
| parent | Rename abc.Callable to abc.Connectable. (diff) | |
| download | discord.py-b44bba6ee6e29b38d1e579c602821582e155ec3b.tar.xz discord.py-b44bba6ee6e29b38d1e579c602821582e155ec3b.zip | |
First pass at documentation reform.
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/_static/style.css | 40 | ||||
| -rw-r--r-- | docs/api.rst | 413 | ||||
| -rw-r--r-- | docs/conf.py | 10 | ||||
| -rw-r--r-- | docs/discord.rst | 92 | ||||
| -rw-r--r-- | docs/ext/commands/api.rst | 197 | ||||
| -rw-r--r-- | docs/ext/commands/index.rst | 13 | ||||
| -rw-r--r-- | docs/faq.rst | 174 | ||||
| -rw-r--r-- | docs/images/discord_client_id.png | bin | 0 -> 5648 bytes | |||
| -rw-r--r-- | docs/images/discord_create_bot_user_button.png | bin | 0 -> 32460 bytes | |||
| -rw-r--r-- | docs/images/discord_finished_bot_user.png | bin | 0 -> 25998 bytes | |||
| -rw-r--r-- | docs/images/discord_new_app_button.PNG | bin | 0 -> 3864 bytes | |||
| -rw-r--r-- | docs/images/discord_new_app_form.png | bin | 0 -> 34500 bytes | |||
| -rw-r--r-- | docs/images/discord_reveal_token.png | bin | 0 -> 5784 bytes | |||
| -rw-r--r-- | docs/images/snake.png | bin | 0 -> 28244 bytes | |||
| -rw-r--r-- | docs/index.rst | 48 | ||||
| -rw-r--r-- | docs/intro.rst | 112 | ||||
| -rw-r--r-- | docs/migrating.rst | 1113 | ||||
| -rw-r--r-- | docs/migrating_to_async.rst | 322 | ||||
| -rw-r--r-- | docs/quickstart.rst | 76 | ||||
| -rw-r--r-- | docs/whats_new.rst | 16 |
20 files changed, 2156 insertions, 470 deletions
diff --git a/docs/_static/style.css b/docs/_static/style.css new file mode 100644 index 00000000..12c1fd6d --- /dev/null +++ b/docs/_static/style.css @@ -0,0 +1,40 @@ +body { + font-family: Georgia, 'Hiragino Mincho Pro', serif; + font-size: 16px; +} + +pre, code { + font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; + font-size: 0.9em; +} + +code.descname, code.descclassname { + font-size: 0.95em; +} + +code.descname { + background-color: transparent; + font-weight: bold; +} + +pre, * pre { + padding: 7px 0 7px 30px !important; + margin: 15px 0 !important; + line-height: 1.3; +} + +div.warning { + background-color: #ffe6cc; + border: 1px solid #ffd5aa; +} + +/* don't link-ify the FAQ page */ +a.toc-backref { + text-decoration: none; + color: #3E4349; +} + +code.xref { + background-color: #ecf0f3; + border-bottom: 1px dotted #222; +} diff --git a/docs/api.rst b/docs/api.rst index 16ad04ef..55dbaa5f 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -79,21 +79,22 @@ overriding the specific events. For example: :: import discord class MyClient(discord.Client): + async def on_message(self, message): + if message.author != self.user: + return - @asyncio.coroutine - def on_message(self, message): - yield from self.send_message(message.channel, 'Hello World!') + if message.content.startswith('$hello'): + await message.channel.send('Hello World!') If an event handler raises an exception, :func:`on_error` will be called -to handle it, which defaults to print a traceback and ignore the exception. +to handle it, which defaults to print a traceback and ignoring the exception. .. warning:: All the events must be a |corourl|_. If they aren't, then you might get unexpected - errors. In order to turn a function into a coroutine they must either be decorated - with ``@asyncio.coroutine`` or in Python 3.5+ be defined using the ``async def`` - declaration. + errors. In order to turn a function into a coroutine they must either be ``async def`` + functions or in 3.4 decorated with ``@asyncio.coroutine``. The following two functions are examples of coroutine functions: :: @@ -104,14 +105,6 @@ to handle it, which defaults to print a traceback and ignore the exception. def on_ready(): pass - Since this can be a potentially common mistake, there is a helper - decorator, :meth:`Client.async_event` to convert a basic function - into a coroutine and an event at the same time. Note that it is - not necessary if you use ``async def``. - -.. versionadded:: 0.7.0 - Subclassing to listen to events. - .. function:: on_connect() Called when the client has successfully connected to Discord. This is not @@ -164,48 +157,71 @@ to handle it, which defaults to print a traceback and ignore the exception. :param kwargs: The keyword arguments for the event that raised the execption. -.. function:: on_message(message) - - Called when a message is created and sent to a guild. - - :param message: A :class:`Message` of the current message. - .. function:: on_socket_raw_receive(msg) - Called whenever a message is received from the websocket, before - it's processed.This event is always dispatched when a message is + Called whenever a message is received from the WebSocket, before + it's processed. This event is always dispatched when a message is received and the passed data is not processed in any way. - This is only really useful for grabbing the websocket stream and + This is only really useful for grabbing the WebSocket stream and debugging purposes. .. note:: This is only for the messages received from the client - websocket. The voice websocket will not trigger this event. + WebSocket. The voice WebSocket will not trigger this event. - :param msg: The message passed in from the websocket library. + :param msg: The message passed in from the WebSocket library. Could be ``bytes`` for a binary message or ``str`` for a regular message. .. function:: on_socket_raw_send(payload) - Called whenever a send operation is done on the websocket before the - message is sent. The passed parameter is the message that is to - sent to the websocket. + Called whenever a send operation is done on the WebSocket before the + message is sent. The passed parameter is the message that is being + sent to the WebSocket. - This is only really useful for grabbing the websocket stream and + This is only really useful for grabbing the WebSocket stream and debugging purposes. .. note:: This is only for the messages received from the client - websocket. The voice websocket will not trigger this event. + WebSocket. The voice WebSocket will not trigger this event. :param payload: The message that is about to be passed on to the - websocket library. It can be ``bytes`` to denote a binary + WebSocket library. It can be ``bytes`` to denote a binary message or ``str`` to denote a regular text message. +.. function:: on_typing(channel, user, when) + + Called when someone begins typing a message. + + The ``channel`` parameter can be a :class:`abc.Messageable` instance. + Which could either be :class:`TextChannel`, :class:`GroupChannel`, or + :class:`DMChannel`. + + If the ``channel`` is a :class:`TextChannel` then the ``user`` parameter + is a :class:`Member`, otherwise it is a :class:`User`. + + :param channel: The location where the typing originated from. + :param user: The user that started typing. + :param when: A ``datetime.datetime`` object representing when typing started. + +.. function:: on_message(message) + + Called when a :class:`Message` is created and sent. + + .. warning:: + + Your bot's own messages and private messages are sent through this + event. This can lead cases of 'recursion' depending on how your bot was + programmed. If you want the bot to not reply to itself, consider + checking the user IDs. Note that :class:`~ext.commands.Bot` does not + have this problem. + + :param message: A :class:`Message` of the current message. + .. function:: on_message_delete(message) Called when a message is deleted. If the message is not found in the @@ -218,7 +234,7 @@ to handle it, which defaults to print a traceback and ignore the exception. .. function:: on_message_edit(before, after) - Called when a message receives an update event. If the message is not found + Called when a :class:`Message` receives an update event. If the message is not found in the :attr:`Client.messages` cache, then these events will not be called. This happens if the message is too old or the client is participating in high traffic guilds. To fix this, increase the ``max_messages`` option of :class:`Client`. @@ -228,7 +244,9 @@ to handle it, which defaults to print a traceback and ignore the exception. - A message has been pinned or unpinned. - The message content has been changed. - The message has received an embed. - - For performance reasons, the embed guild does not do this in a "consistent" manner. + + - For performance reasons, the embed server does not do this in a "consistent" manner. + - A call message has received an update to its participants or ending time. :param before: A :class:`Message` of the previous version of the message. @@ -242,7 +260,7 @@ to handle it, which defaults to print a traceback and ignore the exception. .. note:: - To get the message being reacted, access it via :attr:`Reaction.message`. + To get the :class:`Message` being reacted, access it via :attr:`Reaction.message`. :param reaction: A :class:`Reaction` showing the current state of the reaction. :param user: A :class:`User` or :class:`Member` of the user who added the reaction. @@ -269,31 +287,51 @@ to handle it, which defaults to print a traceback and ignore the exception. :param message: The :class:`Message` that had its reactions cleared. :param reactions: A list of :class:`Reaction`\s that were removed. -.. function:: on_channel_delete(channel) - on_channel_create(channel) +.. function:: on_private_channel_delete(channel) + on_private_channel_create(channel) - Called whenever a channel is removed or added from a guild. + Called whenever a private channel is deleted or created. - Note that you can get the guild from :attr:`Channel.guild`. - :func:`on_channel_create` could also pass in a :class:`PrivateChannel` depending - on the value of :attr:`Channel.is_private`. + :param channel: The :class:`abc.PrivateChannel` that got created or deleted. - :param channel: The :class:`Channel` that got added or deleted. +.. function:: on_private_channel_update(before, after) + + Called whenever a private group DM is updated. e.g. changed name or topic. + + :param before: The :class:`GroupChannel` that got updated with the old info. + :param after: The :class:`GroupChannel` that got updated with the updated info. + +.. function:: on_private_channel_pins_update(channel, last_pin) + + Called whenever a message is pinned or unpinned from a private channel. + + :param channel: The :class:`abc.PrivateChannel` that had it's pins updated. + :param last_pin: A ``datetime.datetime`` object representing when the latest message + was pinned or ``None`` if there are no pins. -.. function:: on_channel_update(before, after) +.. function:: on_guild_channel_delete(channel) + on_guild_channel_create(channel) - Called whenever a channel is updated. e.g. changed name, topic, permissions. + Called whenever a guild channel is deleted or created. - :param before: The :class:`Channel` that got updated with the old info. - :param after: The :class:`Channel` that got updated with the updated info. + Note that you can get the guild from :attr:`~abc.GuildChannel.guild`. -.. function:: on_channel_pins_update(channel, last_pin) + :param channel: The :class:`abc.GuildChannel` that got created or deleted. - Called whenever a message is pinned or unpinned from a channel. +.. function:: on_guild_channel_update(before, after) - :param channel: The :class:`Channel` that had it's pins updated. + Called whenever a guild channel is updated. e.g. changed name, topic, permissions. + + :param before: The :class:`abc.GuildChannel` that got updated with the old info. + :param after: The :class:`abc.GuildChannel` that got updated with the updated info. + +.. function:: on_guild_channel_pins_update(channel, last_pin) + + Called whenever a message is pinned or unpinned from a guild channel. + + :param channel: The :class:`abc.GuildChannel` that had it's pins updated. :param last_pin: A ``datetime.datetime`` object representing when the latest message - was pinned or ``None`` if there are no pins. + was pinned or ``None`` if there are no pins. .. function:: on_member_join(member) on_member_remove(member) @@ -368,10 +406,11 @@ to handle it, which defaults to print a traceback and ignore the exception. :param before: The :class:`Role` that updated with the old info. :param after: The :class:`Role` that updated with the updated info. -.. function:: on_guild_emojis_update(before, after) +.. function:: on_guild_emojis_update(guild, before, after) Called when a :class:`Guild` adds or removes :class:`Emoji`. + :param guild: The :class:`Guild` who got their emojis updated. :param before: A list of :class:`Emoji` before the update. :param after: A list of :class:`Emoji` after the update. @@ -383,9 +422,9 @@ to handle it, which defaults to print a traceback and ignore the exception. :param guild: The :class:`Guild` that has changed availability. -.. function:: on_voice_state_update(before, after) +.. function:: on_voice_state_update(member, before, after) - Called when a :class:`Member` changes their voice state. + Called when a :class:`Member` changes their :class:`VoiceState`. The following, but not limited to, examples illustrate when this event is called: @@ -394,35 +433,25 @@ to handle it, which defaults to print a traceback and ignore the exception. - A member is muted or deafened by their own accord. - A member is muted or deafened by a guild administrator. - :param before: The :class:`Member` whose voice state changed prior to the changes. - :param after: The :class:`Member` whose voice state changed after the changes. - -.. function:: on_member_ban(member) + :param member: The :class:`Member` whose voice states changed. + :param before: The :class:`VoiceState` prior to the changes. + :param after: The :class:`VoiceState` after to the changes. - Called when a :class:`Member` gets banned from a :class:`Guild`. +.. function:: on_member_ban(guild, user) - You can access the guild that the member got banned from via :attr:`Member.guild`. + Called when user gets banned from a :class:`Guild`. - :param member: The member that got banned. + :param guild: The :class:`Guild` the user got banned from. + :param user: The user that got banned. + Can be either :class:`User` or :class:`Member` depending if + the user was in the guild or not at the time of removal. .. function:: on_member_unban(guild, user) Called when a :class:`User` gets unbanned from a :class:`Guild`. - :param guild: The guild the user got unbanned from. - :param user: The user that got unbanned. - -.. function:: on_typing(channel, user, when) - - Called when someone begins typing a message. - - The ``channel`` parameter could either be a :class:`PrivateChannel` or a - :class:`Channel`. If ``channel`` is a :class:`PrivateChannel` then the - ``user`` parameter is a :class:`User`, otherwise it is a :class:`Member`. - - :param channel: The location where the typing originated from. - :param user: The user that started typing. - :param when: A ``datetime.datetime`` object representing when typing started. + :param guild: The :class:`Guild` the user got unbanned from. + :param user: The :class:`User` that got unbanned. .. function:: on_group_join(channel, user) on_group_remove(channel, user) @@ -1281,15 +1310,15 @@ this goal, it must make use of a couple of data classes that aid in this goal. .. attribute:: owner - *Union[:class:`Member`, :class:`User`]`* – The guild's owner. See also :attr:`Guild.owner` + Union[:class:`Member`, :class:`User`] – The guild's owner. See also :attr:`Guild.owner` .. attribute:: region - *:class:`GuildRegion`* – The guild's voice region. See also :attr:`Guild.region`. + :class:`GuildRegion` – The guild's voice region. See also :attr:`Guild.region`. .. attribute:: afk_channel - *Union[:class:`VoiceChannel`, :class:`Object`]* – The guild's AFK channel. + Union[:class:`VoiceChannel`, :class:`Object`] – The guild's AFK channel. If this could not be found, then it falls back to a :class:`Object` with the ID being set. @@ -1310,20 +1339,20 @@ this goal, it must make use of a couple of data classes that aid in this goal. .. attribute:: widget_channel - *Union[:class:`TextChannel`, :class:`Object`]* – The widget's channel. + Union[:class:`TextChannel`, :class:`Object`] – The widget's channel. If this could not be found then it falls back to a :class:`Object` with the ID being set. .. attribute:: verification_level - *:class:`VerificationLevel`* – The guild's verification level. + :class:`VerificationLevel` – The guild's verification level. See also :attr:`Guild.verification_level`. .. attribute:: explicit_content_filter - *:class:`ContentFilter`* – The guild's content filter. + :class:`ContentFilter` – The guild's content filter. See also :attr:`Guild.explicit_content_filter`. @@ -1365,7 +1394,7 @@ this goal, it must make use of a couple of data classes that aid in this goal. .. attribute:: overwrites - *List[Tuple[target, :class:`PermissionOverwrite`]]* – A list of + List[Tuple[target, :class:`PermissionOverwrite`]] – A list of permission overwrite tuples that represents a target and a :class:`PermissionOverwrite` for said target. @@ -1377,7 +1406,7 @@ this goal, it must make use of a couple of data classes that aid in this goal. .. attribute:: roles - *List[Union[:class:`Role`, :class:`Object`]]* – A list of roles being added or removed + List[Union[:class:`Role`, :class:`Object`]] – A list of roles being added or removed from a member. If a role is not found then it is a :class:`Object` with the ID and name being @@ -1403,14 +1432,14 @@ this goal, it must make use of a couple of data classes that aid in this goal. .. attribute:: permissions - *:class:`Permissions`* – The permissions of a role. + :class:`Permissions` – The permissions of a role. See also :attr:`Role.permissions`. .. attribute:: colour color - *:class:`Colour`* – The colour of a role. + :class:`Colour` – The colour of a role. See also :attr:`Role.colour` @@ -1434,14 +1463,14 @@ this goal, it must make use of a couple of data classes that aid in this goal. .. attribute:: channel - *Union[:class:`abc.GuildChannel`, :class:`Object`]* – A guild channel. + Union[:class:`abc.GuildChannel`, :class:`Object`] – A guild channel. If the channel is not found then it is a :class:`Object` with the ID being set. In some cases the channel name is also set. .. attribute:: inviter - *:class:`User`* – The user who created the invite. + :class:`User` – The user who created the invite. See also :attr:`Invite.inviter`. @@ -1472,7 +1501,7 @@ this goal, it must make use of a couple of data classes that aid in this goal. .. attribute:: allow deny - *:class:`Permissions`* – The permissions being allowed or denied. + :class:`Permissions` – The permissions being allowed or denied. .. attribute:: id @@ -1487,43 +1516,72 @@ this goal, it must make use of a couple of data classes that aid in this goal. .. this is currently missing the following keys: reason and application_id I'm not sure how to about porting these -.. _discord_api_data: +.. _discord_api_abcs: -Data Classes --------------- +Abstract Base Classes +----------------------- -Some classes are just there to be data containers, this lists them. +An abstract base class (also known as an ``abc``) is a class that models can inherit +to get their behaviour. The Python implementation of an `abc <https://docs.python.org/3/library/abc.html>`_ is +slightly different in that you can register them at run-time. **Abstract base classes cannot be instantiated**. +They are mainly there for usage with ``isinstance`` and ``issubclass``\. -.. note:: +This library has a module related to abstract base classes, some of which are actually from the ``abc`` standard +module, others which are not. + +.. autoclass:: discord.abc.Snowflake + :members: + +.. autoclass:: discord.abc.User + :members: - With the exception of :class:`Object`, :class:`Colour`, and :class:`Permissions` the - data classes listed below are **not intended to be created by users** and are also +.. autoclass:: discord.abc.PrivateChannel + :members: + +.. autoclass:: discord.abc.GuildChannel + :members: + +.. autoclass:: discord.abc.Messageable + :members: + :exclude-members: history typing + + .. autocomethod:: discord.abc.Messageable.history + :async-for: + + .. autocomethod:: discord.abc.Messageable.typing + :async-with: + +.. autoclass:: discord.abc.Connectable + +.. _discord_api_models: + +Discord Models +--------------- + +Models are classes that are received from Discord and are not meant to be created by +the user of the library. + +.. danger:: + + The classes listed below are **not intended to be created by users** and are also **read-only**. For example, this means that you should not make your own :class:`User` instances nor should you modify the :class:`User` instance yourself. - If you want to get one of these data classes instances they'd have to be through + If you want to get one of these model classes instances they'd have to be through the cache, and a common way of doing so is through the :func:`utils.find` function - or attributes of data classes that you receive from the events specified in the + or attributes of model classes that you receive from the events specified in the :ref:`discord-api-events`. +.. note:: -.. warning:: - - Nearly all data classes here have ``__slots__`` defined which means that it is - impossible to have dynamic attributes to the data classes. The only exception - to this rule is :class:`Object` which was designed with dynamic attributes in - mind. + Nearly all classes here have ``__slots__`` defined which means that it is + impossible to have dynamic attributes to the data classes. More information about ``__slots__`` can be found `in the official python documentation <https://docs.python.org/3/reference/datamodel.html#slots>`_. -Object -~~~~~~~ - -.. autoclass:: Object - :members: ClientUser ~~~~~~~~~~~~ @@ -1544,6 +1602,13 @@ User .. autoclass:: User :members: :inherited-members: + :exclude-members: history typing + + .. autocomethod:: history + :async-for: + + .. autocomethod:: typing + :async-with: Message ~~~~~~~ @@ -1556,18 +1621,10 @@ Reaction .. autoclass:: Reaction :members: + :exclude-members: users -Embed -~~~~~~ - -.. autoclass:: Embed - :members: - -File -~~~~~ - -.. autoclass:: File - :members: + .. autocomethod:: users + :async-for: CallMessage ~~~~~~~~~~~~ @@ -1586,6 +1643,10 @@ Guild .. autoclass:: Guild :members: + :exclude-members: audit_logs + + .. autocomethod:: audit_logs + :async-for: Member ~~~~~~ @@ -1593,6 +1654,13 @@ Member .. autoclass:: Member :members: :inherited-members: + :exclude-members: history typing + + .. autocomethod:: history + :async-for: + + .. autocomethod:: typing + :async-with: VoiceState ~~~~~~~~~~~ @@ -1600,18 +1668,6 @@ VoiceState .. autoclass:: VoiceState :members: -Colour -~~~~~~ - -.. autoclass:: Colour - :members: - -Game -~~~~ - -.. autoclass:: Game - :members: - Emoji ~~~~~ @@ -1624,25 +1680,19 @@ Role .. autoclass:: Role :members: -Permissions -~~~~~~~~~~~~ - -.. autoclass:: Permissions - :members: - -PermissionOverwrite -~~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: PermissionOverwrite - :members: - - TextChannel ~~~~~~~~~~~~ .. autoclass:: TextChannel :members: :inherited-members: + :exclude-members: history typing + + .. autocomethod:: history + :async-for: + + .. autocomethod:: typing + :async-with: VoiceChannel ~~~~~~~~~~~~~ @@ -1657,6 +1707,13 @@ DMChannel .. autoclass:: DMChannel :members: :inherited-members: + :exclude-members: history typing + + .. autocomethod:: history + :async-for: + + .. autocomethod:: typing + :async-with: GroupChannel ~~~~~~~~~~~~ @@ -1664,6 +1721,13 @@ GroupChannel .. autoclass:: GroupChannel :members: :inherited-members: + :exclude-members: history typing + + .. autocomethod:: history + :async-for: + + .. autocomethod:: typing + :async-with: Invite @@ -1672,6 +1736,69 @@ Invite .. autoclass:: Invite :members: +.. _discord_api_data: + +Data Classes +-------------- + +Some classes are just there to be data containers, this lists them. + +Unlike :ref:`models <discord_api_models>` you are allowed to create +these yourself, even if they can also be used to hold attributes. + +Nearly all classes here have ``__slots__`` defined which means that it is +impossible to have dynamic attributes to the data classes. + +The only exception to this rule is :class:`Object`, which is made with +dynamic attributes in mind. + +More information about ``__slots__`` can be found +`in the official python documentation <https://docs.python.org/3/reference/datamodel.html#slots>`_. + + +Object +~~~~~~~ + +.. autoclass:: Object + :members: + +Embed +~~~~~~ + +.. autoclass:: Embed + :members: + +File +~~~~~ + +.. autoclass:: File + :members: + +Colour +~~~~~~ + +.. autoclass:: Colour + :members: + +Game +~~~~ + +.. autoclass:: Game + :members: + +Permissions +~~~~~~~~~~~~ + +.. autoclass:: Permissions + :members: + +PermissionOverwrite +~~~~~~~~~~~~~~~~~~~~ + +.. autoclass:: PermissionOverwrite + :members: + + Exceptions ------------ diff --git a/docs/conf.py b/docs/conf.py index f46aadc9..53e6b2b3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -34,6 +34,7 @@ sys.path.insert(0, os.path.abspath('..')) extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.extlinks', + 'sphinxcontrib.asyncio' ] if on_rtd: @@ -115,7 +116,7 @@ exclude_patterns = ['_build'] #show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = 'friendly' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] @@ -128,7 +129,7 @@ pygments_style = 'sphinx' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'sphinx_rtd_theme' +html_theme = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -159,7 +160,7 @@ html_theme = 'sphinx_rtd_theme' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = ['_static'] +html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied @@ -304,3 +305,6 @@ texinfo_documents = [ # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False + +def setup(app): + app.add_stylesheet('style.css') diff --git a/docs/discord.rst b/docs/discord.rst new file mode 100644 index 00000000..b9a3bad2 --- /dev/null +++ b/docs/discord.rst @@ -0,0 +1,92 @@ +.. _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://discordapp.com>`_. +2. Navigate to the `application page <https://discordapp.com/developers/applications/me>`_ +3. Click on the "New App" button. + + .. image:: /images/discord_new_app_button.png + :alt: The new app button. + +4. Give the application a name and a description if wanted and click "Create App". + + - You can also put an avatar you want your bot to use, don't worry you can change this later. + - **Leave the Redirect URI(s) blank** unless are creating a service. + + .. image:: /images/discord_new_app_form.png + :alt: The new application form filled in. +5. Create a Bot User by clicking on the accompanying button and confirming it. + + .. image:: /images/discord_create_bot_user_button.png + :alt: The Create a Bot User 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**. + + .. figure:: /images/discord_finished_bot_user.png + + How the Bot User options should look like for most people. + +7. Click to reveal the token. + + - **This is not the Client Secret** + + .. figure:: /images/discord_reveal_token.png + + How the token reveal button looks like. + +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 your bot. + +First, you must fetch the Client ID of the Bot. You can find this in the Bot's application page. + +.. image:: /images/discord_client_id.png + :alt: The Bot's Client ID. + +Copy paste that into the pre-formatted URL: + +.. code-block:: none + + https://discordapp.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&scope=bot&permissions=0 + +Replace ``YOUR_CLIENT_ID`` with the Client ID we got in the previous step. For example, +in the image above our client ID is 312718641634213889 so the resulting URL would be +https://discordapp.com/oauth2/authorize?client_id=312718641634213889&scope=bot&permissions=0 +(note that this bot has been deleted). + +Now you can click the link and invite your bot to any server you have "Manage Server" permissions on. + +Adding Permissions +~~~~~~~~~~~~~~~~~~~~ + +In the above URL, you might have noticed an interesting bit, the ``permissions=0`` fragment. + +Bot accounts can request specific permissions to be granted upon joining. When the bot joins +the guild, they will be granted a managed role that contains the permissions you requested. +If the permissions is 0, then no special role is created. + +This ``permissions`` value is calculated based on bit-wise arithmetic. Thankfully, people have +created a calculate that makes it easy to calculate the permissions necessary visually. + +- https://discordapi.com/permissions.html +- https://finitereality.github.io/permissions/ + +Feel free to use whichever is easier for you to grasp. + +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`. diff --git a/docs/ext/commands/api.rst b/docs/ext/commands/api.rst new file mode 100644 index 00000000..665a2f33 --- /dev/null +++ b/docs/ext/commands/api.rst @@ -0,0 +1,197 @@ +.. currentmodule:: discord + +API Reference +=============== + +The following section outlines the API of discord.py's command extension module. + +Bot +---- + +.. autoclass:: discord.ext.commands.Bot + :members: + :inherited-members: + +.. autoclass:: discord.ext.commands.AutoShardedBot + :members: + +Event Reference +----------------- + +These events function similar to :ref:`the regular events <discord-api-events>`, except they +are custom to the command extension module. + +.. function:: 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:: 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:: 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` + + +Command +-------- + +.. autofunction:: discord.ext.commands.command + +.. autofunction:: discord.ext.commands.group + +.. autoclass:: discord.ext.commands.Command + :members: + +.. autoclass:: discord.ext.commands.Group + :members: + :inherited-members: + +.. autoclass:: discord.ext.commands.GroupMixin + :members: + + +Formatters +----------- + +.. autoclass:: discord.ext.commands.Paginator + :members: + +.. autoclass:: discord.ext.commands.HelpFormatter + :members: + +Checks +------- + +.. autofunction:: discord.ext.commands.check + +.. autofunction:: discord.ext.commands.has_role + +.. autofunction:: discord.ext.commands.has_permissions + +.. autofunction:: discord.ext.commands.has_any_role + +.. autofunction:: discord.ext.commands.bot_has_role + +.. autofunction:: discord.ext.commands.bot_has_permissions + +.. autofunction:: discord.ext.commands.bot_has_any_role + +.. autofunction:: discord.ext.commands.cooldown + +.. autofunction:: discord.ext.commands.guild_only + +.. autofunction:: discord.ext.commands.is_owner + +.. autofunction:: discord.ext.commands.is_nsfw + +Context +-------- + +.. autoclass:: discord.ext.commands.Context + :members: + :exclude-members: history typing + + .. autocomethod:: discord.ext.commands.Context.history + :async-for: + + .. autocomethod:: discord.ext.commands.Context.typing + :async-with: + +Converters +------------ + +.. autoclass:: discord.ext.commands.Converter + :members: + +.. autoclass:: discord.ext.commands.MemberConverter + :members: + +.. autoclass:: discord.ext.commands.UserConverter + :members: + +.. autoclass:: discord.ext.commands.TextChannelConverter + :members: + +.. autoclass:: discord.ext.commands.InviteConverter + :members: + +.. autoclass:: discord.ext.commands.RoleConverter + :members: + +.. autoclass:: discord.ext.commands.GameConverter + :members: + +.. autoclass:: discord.ext.commands.ColourConverter + :members: + +.. autoclass:: discord.ext.commands.VoiceChannelConverter + :members: + +.. autoclass:: discord.ext.commands.EmojiConverter + :members: + +.. autoclass:: discord.ext.commands.clean_content + :members: + +Errors +------- + +.. autoexception:: discord.ext.commands.CommandError + :members: + +.. autoexception:: discord.ext.commands.MissingRequiredArgument + :members: + +.. autoexception:: discord.ext.commands.BadArgument + :members: + +.. autoexception:: discord.ext.commands.NoPrivateMessage + :members: + +.. autoexception:: discord.ext.commands.CheckFailure + :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.NotOwner + :members: + diff --git a/docs/ext/commands/index.rst b/docs/ext/commands/index.rst new file mode 100644 index 00000000..908a7bc1 --- /dev/null +++ b/docs/ext/commands/index.rst @@ -0,0 +1,13 @@ +``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: 1 + + api diff --git a/docs/faq.rst b/docs/faq.rst index 1525b3a6..fda5313b 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -16,7 +16,7 @@ Coroutines Questions regarding coroutines and asyncio belong here. I get a SyntaxError around the word ``async``\! What should I do? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This ``SyntaxError`` happens because you're using a Python version lower than 3.5. Python 3.4 uses ``@asyncio.coroutine`` and ``yield from`` instead of ``async def`` and ``await``. @@ -52,7 +52,7 @@ 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 @@ -78,13 +78,14 @@ Consider the following example: :: r = requests.get('http://random.cat/meow') if r.status_code == 200: js = r.json() - await client.send_message(channel, js['file']) + await channel.send(js['file']) # good - async with aiohttp.get('http://random.cat/meow') as r: - if r.status == 200: - js = await r.json() - await client.send_message(channel, js['file']) + async with aiohttp.ClientSession() as session: + async with session.get('http://random.cat/meow') as r: + if r.status == 200: + js = await r.json() + await channel.send(js['file']) General --------- @@ -103,37 +104,41 @@ following: :: How do I send a message to a specific channel? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -If you have its ID then you can do this in two ways, first is by using :class:`Object`\: :: +You must fetch the channel directly and then call the appropriate method. Example: :: - await client.send_message(discord.Object(id='12324234183172'), 'hello') + channel = client.get_channel('12324234183172') + await channel.send('hello') -The second way is by calling :meth:`Client.get_channel` directly: :: +How do I upload an image? +~~~~~~~~~~~~~~~~~~~~~~~~~~ - await client.send_message(client.get_channel('12324234183172'), 'hello') +To upload something to Discord you have to use the :class:`File` object. -I'm passing IDs as integers and things are not working! -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +A :class:`File` accepts two parameters, the file-like object (or file path) and the filename +to pass to Discord when uploading. -In the library IDs must be of type ``str`` not of type ``int``. Wrap it in quotes. +If you want to upload an image it's as simple as: :: -How do I upload an image? -~~~~~~~~~~~~~~~~~~~~~~~~~~ + await channel.send(file=discord.File('my_file.png')) -There are two ways of doing it. Both of which involve using :meth:`Client.send_file`. +If you have a file-like object you can do as follows: :: -The first is by opening the file and passing it directly: :: + with open('my_file.png', 'rb') as fp: + await channel.send(file=discord.File(fp, 'new_filename.png')) - with open('my_image.png', 'rb') as f: - await client.send_file(channel, f) +To upload multiple files, you can use the ``files`` keyword argument instead of ``file``\: :: -The second is by passing the file name directly: :: + my_files = [ + discord.File('result.zip'), + discord.File('teaser_graph.png'), + ] + await channel.send(files=my_files) - await client.send_file(channel, 'my_image.png') How can I add a reaction to a message? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You use the :meth:`Client.add_reaction` method. +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: @@ -141,16 +146,32 @@ If you want to use unicode emoji, you must pass a valid unicode code point in a - ``'\U0001F44D'`` - ``'\N{THUMBS UP SIGN}'`` -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. +Quick example: :: + + await message.add_reaction('\N{THUMBS UP SIGN}') -For custom emoji, you should pass an instance of :class:`discord.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_all_emojis`/:attr:`Server.emojis` to find the one you're looking for. +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. + +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) How do I pass a coroutine to the player's "after" function? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -A StreamPlayer is just a ``threading.Thread`` object that plays music. As a result it does not execute inside a coroutine. +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. @@ -169,7 +190,7 @@ However, this function returns a ``concurrent.Future`` and to actually call it w this together we can do the following: :: def my_after(): - coro = client.send_message(some_channel, 'Song is done!') + coro = some_channel.send('Song is done!') fut = asyncio.run_coroutine_threadsafe(coro, client.loop) try: fut.result() @@ -177,48 +198,44 @@ this together we can do the following: :: # an error happened sending the message pass - player = await voice.create_ytdl_player(url, after=my_after) - player.start() - -Why is my "after" function being called right away? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``after`` keyword argument expects a *function object* to be passed in. Similar to how ``threading.Thread`` expects a -callable in its ``target`` keyword argument. This means that the following are invalid: - -.. code-block:: python + voice.play(discord.FFmpegPCMAudio(url), after=my_after) - player = await voice.create_ytdl_player(url, after=self.foo()) - other = await voice.create_ytdl_player(url, after=self.bar(10)) +How do I run something in the background? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -However the following are correct: +`Check the background_task.py example. <https://github.com/Rapptz/discord.py/blob/rewrite/examples/background_task.py>`_ -.. code-block:: python +How do I get a specific model? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - player = await voice.create_ytdl_player(url, after=self.foo) - other = await voice.create_ytdl_player(url, after=lambda: self.bar(10)) +There are multiple ways of doing this. If you have a specific model's ID then you can use +one of the following functions: -Basically, these functions should not be called. +- :meth:`Client.get_channel` +- :meth:`Client.get_guild` +- :meth:`Client.get_user` +- :meth:`Client.get_emoji` +- :meth:`Guild.get_member` +- :meth:`Guild.get_channel` +The following use an HTTP request: -How do I run something in the background? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +- :meth:`abc.Messageable.get_message` +- :meth:`Client.get_user_info` -`Check the background_task.py example. <https://github.com/Rapptz/discord.py/blob/master/examples/background_task.py>`_ -How do I get a specific User/Role/Channel/Server? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +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. -There are multiple ways of doing this. If you have a specific entity's ID then you can use -one of the following functions: +Quick example: :: -- :meth:`Client.get_channel` -- :meth:`Client.get_server` -- :meth:`Server.get_member` -- :meth:`Server.get_channel` + # find a guild by name + guild = discord.utils.get(client.guilds, name='My Server') -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 entities. The documentation for those functions provide specific examples. + # 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') Commands Extension ------------------- @@ -229,10 +246,10 @@ Is there any documentation for this? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Not at the moment. Writing documentation for stuff takes time. A lot of people get by reading the docstrings in the source -code. Others get by via asking questions in the `Discord server <https://discord.gg/0SBTUU1wZTXZNJPa>`_. Others look at the +code. Others get by via asking questions in the `Discord server <https://discord.gg/discord-api>`_. Others look at the source code of `other existing bots <https://github.com/Rapptz/RoboDanny>`_. -There is a `basic example <https://github.com/Rapptz/discord.py/blob/master/examples/basic_bot.py>`_ showcasing some +There is a `basic example <https://github.com/Rapptz/discord.py/blob/rewrite/examples/basic_bot.py>`_ showcasing some functionality. **Documentation is being worked on, it will just take some time to polish it**. @@ -249,42 +266,36 @@ Overriding the default provided ``on_message`` forbids any extra commands from r await bot.process_commands(message) -Can I use ``bot.say`` in other places aside from commands? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -No. They only work inside commands due to the way the magic involved works. - Why do my arguments require quotes? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In a simple command defined as: :: @bot.command() - async def echo(message: str): - await bot.say(message) + 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(*, message: str): - await bot.say(message) + 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``\? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Ask the command to pass you the invocation context via ``pass_context``. This context will be passed as the first parameter. +The :class:`~ext.commands.Context` contains an attribute, :attr:`~ext.commands.Context.message` to get the original +message. Example: :: - @bot.command(pass_context=True) + @bot.command() async def joined_at(ctx, member: discord.Member = None): - if member is None: - member = ctx.message.author - - await bot.say('{0} joined at {0.joined_at}'.format(member)) + member = member or ctx.author + await ctx.send('{0} joined at {0.joined_at}'.format(member)) How do I make a subcommand? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -294,15 +305,14 @@ the group operating as "subcommands". These groups can be arbitrarily nested as Example: :: - @bot.group(pass_context=True) + @bot.group() async def git(ctx): if ctx.invoked_subcommand is None: await bot.say('Invalid git command passed...') @git.command() - async def push(remote: str, branch: str): - await bot.say('Pushing to {} {}'.format(remote, branch)) - + async def push(ctx, remote: str, branch: str): + await ctx.send('Pushing to {} {}'.format(remote, branch)) This could then be used as ``?git push origin master``. diff --git a/docs/images/discord_client_id.png b/docs/images/discord_client_id.png Binary files differnew file mode 100644 index 00000000..04710f32 --- /dev/null +++ b/docs/images/discord_client_id.png diff --git a/docs/images/discord_create_bot_user_button.png b/docs/images/discord_create_bot_user_button.png Binary files differnew file mode 100644 index 00000000..f87940ab --- /dev/null +++ b/docs/images/discord_create_bot_user_button.png diff --git a/docs/images/discord_finished_bot_user.png b/docs/images/discord_finished_bot_user.png Binary files differnew file mode 100644 index 00000000..09aff891 --- /dev/null +++ b/docs/images/discord_finished_bot_user.png diff --git a/docs/images/discord_new_app_button.PNG b/docs/images/discord_new_app_button.PNG Binary files differnew file mode 100644 index 00000000..78a099dd --- /dev/null +++ b/docs/images/discord_new_app_button.PNG diff --git a/docs/images/discord_new_app_form.png b/docs/images/discord_new_app_form.png Binary files differnew file mode 100644 index 00000000..68409a6c --- /dev/null +++ b/docs/images/discord_new_app_form.png diff --git a/docs/images/discord_reveal_token.png b/docs/images/discord_reveal_token.png Binary files differnew file mode 100644 index 00000000..92d5492a --- /dev/null +++ b/docs/images/discord_reveal_token.png diff --git a/docs/images/snake.png b/docs/images/snake.png Binary files differnew file mode 100644 index 00000000..62b44617 --- /dev/null +++ b/docs/images/snake.png diff --git a/docs/index.rst b/docs/index.rst index 30a41468..883d0146 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,23 +3,55 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to discord.py's documentation! -====================================== +Welcome to the discord.py documentation +========================================= -Contents: +.. image:: /images/snake.png + +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 +- Implements the entirety of the Discord API +- Command extension to aid with bot creation +- Easy to use with an object oriented design +- Optimised for both speed and memory + +Documentation Contents +----------------------- .. toctree:: :maxdepth: 2 - logging - whats_new + intro + quickstart migrating + logging api - faq +Extensions +----------- + +.. toctree:: + :maxdepth: 2 + + ext/commands/index.rst + + +Additional Information +----------------------- + +.. toctree:: + :maxdepth: 2 + + discord + faq + whats_new -Indices and tables -================== +If you still can't find what you're looking for, try in one of the following pages: * :ref:`genindex` * :ref:`modindex` diff --git a/docs/intro.rst b/docs/intro.rst new file mode 100644 index 00000000..b3804be1 --- /dev/null +++ b/docs/intro.rst @@ -0,0 +1,112 @@ +.. 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.4.2 or higher. Support for earlier versions of Python +is not provided. Python 2.7 or lower is not supported. Python 3.3 is not supported +due to one of the dependencies (``aiohttp``) not supporting Python 3.3. + + +.. _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 +- libnacl +- python3-dev + +For a debian-based system, the following command will help get those dependencies: + +.. code-block:: shell + + $ apt install libffi-dev libnacl-dev python3-dev + +Remember to check your permissions! + +Virtual Environments +~~~~~~~~~~~~~~~~~~~~~ + +Sometimes we don't want to pollute our system installs with a library or we want to maintain +different versions of a library than the currently system installed one. Or we don't have permissions to +install a library along side with the system installed ones. For this purpose, the standard library as +of 3.3 comes with a concept called "Virtual Environment" to help maintain these separate versions. + +A more in-depth tutorial is found on `the official documentation. <https://docs.python.org/3/tutorial/venv.html>`_ + +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 without messing with your system installation. + +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 and you can then respond to it. + +A quick example to showcase how events work: + +.. code-block:: python + + import discord + + class MyClient(discord.Client): + async def on_ready(self): + print('Logged on as {0}!'.format(self.user)) + + async def on_message(self, message): + print('Message from {0.author}: {0.content}'.format(message)) + + client = MyClient() + client.run('my token goes here') + diff --git a/docs/migrating.rst b/docs/migrating.rst index 7ad9e6a1..d7365d08 100644 --- a/docs/migrating.rst +++ b/docs/migrating.rst @@ -1,336 +1,983 @@ .. currentmodule:: discord -.. _migrating-to-async: +.. _migrating_1_0: -Migrating to v0.10.0 +Migrating to v1.0 ====================== -v0.10.0 is one of the biggest breaking changes in the library due to massive -fundamental changes in how the library operates. +v1.0 is one of the biggest breaking changes in the library due to a complete +redesign. + +The amount of changes are so massive and long that for all intents and purposes, it is a completely +new library. + +Part of the redesign involves making things more easy to use and natural. Things are done on the +:ref:`models <discord_api_models>` instead of requiring a :class:`Client` instance to do any work. + +Major Model Changes +--------------------- + +Below are major model changes that have happened in v1.0 + +Snowflakes are int +~~~~~~~~~~~~~~~~~~~~ + +Before v1.0, all snowflakes (the ``id`` attribute) were strings. This has been changed to ``int``. + +Quick example: :: + + # before + ch = client.get_channel('84319995256905728') + if message.author.id == '80528701850124288': + ... + + # after + ch = client.get_channel(84319995256905728) + if message.author.id == 80528701850124288: + ... + +This change allows for fewer errors when using the Copy ID feature in the official client since you no longer have +to wrap it in quotes and allows for optimisation opportunities by allowing ETF to be used instead of JSON internally. + +Server is now Guild +~~~~~~~~~~~~~~~~~~~~~ + +The official API documentation calls the "Server" concept a "Guild" instead. In order to be more consistent with the +API documentation when necessary, the model has been renamed to :class:`Guild` and all instances referring to it has +been changed as well. + +A list of changes is as follows: + ++-------------------------------+----------------------------------+ +| Before | After | ++-------------------------------+----------------------------------+ +| ``Message.server`` | :attr:`Message.guild` | ++-------------------------------+----------------------------------+ +| ``Channel.server`` | :attr:`abc.GuildChannel.guild` | ++-------------------------------+----------------------------------+ +| ``Client.servers`` | :attr:`Client.guilds` | ++-------------------------------+----------------------------------+ +| ``Client.get_server`` | :meth:`Client.get_guild` | ++-------------------------------+----------------------------------+ +| ``Emoji.server`` | :attr:`Emoji.guild` | ++-------------------------------+----------------------------------+ +| ``Role.server`` | :attr:`Role.guild` | ++-------------------------------+----------------------------------+ +| ``Invite.server`` | :attr:`Invite.guild` | ++-------------------------------+----------------------------------+ +| ``Member.server`` | :attr:`Member.guild` | ++-------------------------------+----------------------------------+ +| ``Permissions.manage_server`` | :attr:`Permissions.manage_guild` | ++-------------------------------+----------------------------------+ +| ``VoiceClient.server`` | :attr:`VoiceClient.guild` | ++-------------------------------+----------------------------------+ +| ``Client.create_server`` | :meth:`Client.create_guild` | ++-------------------------------+----------------------------------+ + +.. _migrating_1_0_model_state: + +Models are Stateful +~~~~~~~~~~~~~~~~~~~~~ + +As mentioned earlier, a lot of functionality was moved out of :class:`Client` and +put into their respective :ref:`model <discord_api_models>`. + +A list of these changes is enumerated below. + ++---------------------------------------+------------------------------------------------------------------------------+ +| Before | After | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.add_reaction`` | :meth:`Message.add_reaction` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.add_roles`` | :meth:`Member.add_roles` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.ban`` | :meth:`Member.ban` or :meth:`Guild.ban` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.change_nickname`` | :meth:`Member.edit` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.clear_reactions`` | :meth:`Message.clear_reactions` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.create_channel`` | :meth:`Guild.create_text_channel` and :meth:`Guild.create_voice_channel` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.create_custom_emoji`` | :meth:`Guild.create_custom_emoji` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.create_invite`` | :meth:`Guild.create_invite` or :meth:`abc.GuildChannel.create_invite` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.create_role`` | :meth:`Guild.create_role` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.delete_channel`` | :meth:`abc.GuildChannel.delete` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.delete_channel_permissions`` | :meth:`abc.GuildChannel.set_permissions` with ``overwrites`` set to ``None`` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.delete_custom_emoji`` | :meth:`Emoji.delete` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.delete_invite`` | :meth:`Invite.delete` or :meth:`Client.delete_invite` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.delete_message`` | :meth:`Message.delete` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.delete_messages`` | :meth:`TextChannel.delete_messages` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.delete_role`` | :meth:`Role.delete` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.delete_server`` | :meth:`Guild.delete` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.edit_channel`` | :meth:`TextChannel.edit` or :meth:`VoiceChannel.edit` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.edit_channel_permissions`` | :meth:`abc.GuildChannel.set_permissions` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.edit_custom_emoji`` | :meth:`Emoji.edit` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.edit_message`` | :meth:`Message.edit` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.edit_profile`` | :meth:`ClientUser.edit` (you get this from :attr:`Client.user`) | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.edit_role`` | :meth:`Role.edit` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.edit_server`` | :meth:`Guild.edit` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.estimate_pruned_members`` | :meth:`Guild.estimate_pruned_members` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.get_all_emojis`` | :meth:`Client.emojis` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.get_bans`` | :meth:`Guild.bans` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.get_message`` | :meth:`abc.Messageable.get_message` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.get_reaction_users`` | :meth:`Reaction.users` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.invites_from`` | :meth:`abc.GuildChannel.invites` or :meth:`Guild.invites` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.join_voice_channel`` | :meth:`VoiceChannel.connect` (see :ref:`migrating_1_0_voice`) | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.kick`` | :meth:`Guild.kick` or :meth:`Member.kick` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.leave_server`` | :meth:`Guild.leave` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.logs_from`` | :meth:`abc.Messageable.history` (see :ref:`migrating_1_0_async_iter`) | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.move_channel`` | :meth:`TextChannel.edit` or :meth:`VoiceChannel.edit` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.move_member`` | :meth:`Member.edit` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.move_role`` | :meth:`Role.edit` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.pin_message`` | :meth:`Message.pin` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.pins_from`` | :meth:`abc.Messageable.pins` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.prune_members`` | :meth:`Guild.prune_members` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.purge_from`` | :meth:`abc.Messageable.purge` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.remove_reaction`` | :meth:`Message.remove_reaction` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.remove_roles`` | :meth:`Member.remove_roles` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.replace_roles`` | :meth:`Member.edit` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.send_file`` | :meth:`abc.Messageable.send` (see :ref:`migrating_1_0_sending_messages`) | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.send_message`` | :meth:`abc.Messageable.send` (see :ref:`migrating_1_0_sending_messages`) | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.send_typing`` | :meth:`abc.Messageable.trigger_typing` (use :meth:`abc.Messageable.typing`) | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.server_voice_state`` | :meth:`Member.edit` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.start_private_message`` | :meth:`User.create_dm` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.unban`` | :meth:`Guild.unban` or :meth:`Member.unban` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.unpin_message`` | :meth:`Message.unpin` | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.wait_for_message`` | :meth:`Client.wait_for` (see :ref:`migrating_1_0_wait_for`) | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.wait_for_reaction`` | :meth:`Client.wait_for` (see :ref:`migrating_1_0_wait_for`) | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.wait_until_login`` | Removed | ++---------------------------------------+------------------------------------------------------------------------------+ +| ``Client.wait_until_ready`` | No change | ++---------------------------------------+------------------------------------------------------------------------------+ + +Property Changes +~~~~~~~~~~~~~~~~~~ + +In order to be a bit more consistent, certain things that were properties were changed to methods instead. + +The following are now methods instead of properties (requires parentheses): + +- :meth:`TextChannel.is_default` +- :meth:`Role.is_default` +- :meth:`Client.is_ready` +- :meth:`Client.is_closed` + +Dict Value Change +~~~~~~~~~~~~~~~~~~~~~ + +Prior to v1.0 some aggregating properties that retrieved models would return "dict view" objects. + +As a consequence, when the dict would change size while you would iterate over it, a RuntimeError would +be raised and crash the task. To alleviate this, the "dict view" objects were changed into lists. + +The following views were changed to a list: + +- :attr:`Client.guilds` +- :attr:`Client.users` (new in v1.0) +- :attr:`Client.emojis` (new in v1.0) +- :attr:`Guild.channels` +- :attr:`Guild.text_channels` (new in v1.0) +- :attr:`Guild.voice_channels` (new in v1.0) +- :attr:`Guild.emojis` +- :attr:`Guild.members` -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 ``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**. +Voice State Changes +~~~~~~~~~~~~~~~~~~~~~ -Below are all the other major changes from v0.9.0 to v0.10.0. +Earlier, in v0.11.0 a :class:`VoiceState` class was added to refer to voice states along with a +:attr:`Member.voice` attribute to refer to it. -.. _migrating-event-registration: +However, it was transparent to the user. In an effort to make the library save more memory, the +voice state change is now more visible. -Event Registration --------------------- +The only way to access voice attributes if via the :attr:`Member.voice` attribute. Note that if +the member does not have a voice state this attribute can be ``None``. -All events before were registered using :meth:`Client.event`. While this is still -possible, the events must be decorated with ``@asyncio.coroutine``. +Quick example: :: -Before: + # before + member.deaf + member.voice.voice_channel -.. code-block:: python + # after + if member.voice: # can be None + member.voice.deaf + member.voice.channel - @client.event - def on_message(message): - pass -After: +User and Member Type Split +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. code-block:: python +In v1.0 to save memory, :class:`User` and :class:`Member` are no longer inherited. Instead, they are "flattened" +by having equivalent properties that map out to the functional underlying :class:`User`. Thus, there is no functional +change in how they are used. However this breaks ``isinstance`` checks and thus is something to keep in mind. - @client.event - @asyncio.coroutine - def on_message(message): - pass +These memory savings were accomplished by having a global :class:`User` cache, and as a positive consequence you +can now easily fetch a :class:`User` by their ID by using the new :meth:`Client.get_user`. You can also get a list +of all :class:`User` your client can see with :attr:`Client.users`. -Or in Python 3.5+: +.. _migrating_1_0_channel_split: -.. code-block:: python +Channel Type Split +~~~~~~~~~~~~~~~~~~~~~ - @client.event - async def on_message(message): - pass +Prior to v1.0, channels were two different types, ``Channel`` and ``PrivateChannel`` with a ``is_private`` +property to help differentiate between them. -Because there is a lot of typing, a utility decorator (:meth:`Client.async_event`) is provided -for easier registration. For example: +In order to save memory the channels have been split into 4 different types: -.. code-block:: python +- :class:`TextChannel` for guild text channels +- :class:`VoiceChannel` for guild voice channels +- :class:`DMChannel` for DM channels with members +- :class:`GroupChannel` for Group DM channels with members - @client.async_event - def on_message(message): - pass +With this split came the removal of the ``is_private`` attribute. You should now use ``isinstance``. + +The types are split into two different :ref:`discord_api_abcs`: + +- :class:`abc.GuildChannel` for guild channels +- :class:`abc.PrivateChannel` for private channels (DMs and group DMs). + +So to check if something is a guild channel you would do: :: + + isinstance(channel, discord.abc.GuildChannel) + +And to check if it's a private channel you would do: :: + + isinstance(channel, discord.abc.PrivateChannel) + +Of course, if you're looking for only a specific type you can pass that too, e.g. :: + + isintance(channel, discord.TextChannel) + +With this type split also came event changes, which are enumerated in :ref:`migrating_1_0_event_changes`. + + +Miscellaneous Model Changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There were lots of other things added or removed in the models in general. + +They will be enumerated here. + +**Removed** + +- :meth:`Client.login` no longer accepts email and password logins. + + - Use a token and ``bot=False`` + +- ``Client.get_all_emojis`` + + - Use :attr:`Client.emojis` instead. + +- ``Client.wait_for_message`` and ``Client.wait_for_reaction`` are gone + + - Use :meth:`Client.wait_for` instead. + +- ``Channel.voice_members`` + + - Use :attr:`VoiceChannel.members` instead. + +- ``Channel.is_private`` + + - Use ``isinstance`` instead with one of the :ref:`discord_api_abcs` instead. + - e.g. ``isinstance(channel, discord.abc.GuildChannel)`` will check if it isn't a private channel. + +- ``Client.accept_invite`` + + - There is no replacement for this one. This functionality is deprecated API wise. + +- ``Message.edited_timestamp`` + + - Use :attr:`Message.edited_at` instead. + +- ``Message.timestamp`` + + - Use :attr:`Message.created_at` instead. + +**Added** + +- :attr:`VoiceChannel.members` for fetching members connected to a voice channel. +- :attr:`TextChannel.members` for fetching members that can see the channel. +- :attr:`Role.members` for fetching members that have the role. +- :attr:`Guild.text_channels` for fetching text channels only. +- :attr:`Guild.voice_channels` for fetching voice channels only. +- :attr:`Guild.chunked` to check member chunking status. +- :attr:`Guild.explicit_content_filter` to fetch the content filter +- :attr:`Guild.shard_id` to get a guild's Shard ID if you're sharding. +- :attr:`Client.users` to get all visible :class:`User` instances. +- :meth:`Client.get_user` to get a :class:`User` by ID. +- :meth:`User.avatar_url_as` to get an avatar in a specific size or format. +- :meth:`Guild.vanity_invite` to fetch the guild's vanity invite. +- :meth:`Guild.audit_logs` to fetch the guild's audit logs. +- :attr:`Message.webhook_id` to fetch the message's webhook ID. +- :meth:`TextChannel.is_nsfw` to check if a text channel is NSFW. + +.. _migrating_1_0_sending_messages: + +Sending Messages +------------------ + +One of the changes that were done was the merger of the previous ``Client.send_message`` and ``Client.send_file`` +functionality into a single method, :meth:`~abc.Messageable.send`. + +Basically: :: + + # before + await client.send_message(channel, 'Hello') + + # after + await channel.send('Hello') + +This supports everything that the old ``send_message`` supported such as embeds: :: + + e = discord.Embed(title='foo') + await channel.send('Hello', embed=e) + +There is a caveat with sending files however, as this functionality was expanded to support multiple +file attachments, you must now use a :class:`File` pseudo-namedtuple to upload a single file. :: + + # before + await client.send_file(channel, 'cool.png', filename='testing.png', content='Hello') + # after + await channel.send('Hello', file=discord.File('cool.png', 'testing.png')) -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``. +This change was to facilitate multiple file uploads: :: -.. _migrating_event_changes: + my_files = [ + discord.File('cool.png', 'testing.png'), + discord.File(some_fp, 'cool_filename.png'), + ] + + await channel.send('Your images:', files=my_files) + +.. _migrating_1_0_async_iter: + +Asynchronous Iterators +------------------------ + +Prior to v1.0, certain functions like ``Client.logs_from`` would return a different type if done in Python 3.4 or 3.5+. + +In v1.0, this change has been reverted and will now return a singular type meeting an abstract concept called +:class:`AsyncIterator`. + +This allows you to iterate over it like normal in Python 3.5+: :: + + async for msg in channel.history(): + print(msg) + +Or turn it into a list for either Python 3.4 or 3.5+: :: + + messages = yield from channel.history().flatten() + for message in messages: + print(messages) + +A handy aspect of returning :class:`AsyncIterator` is that it allows you to chain functions together such as +:meth:`AsyncIterator.map` or :meth:`AsyncIterator.filter`: :: + + async for m_id in channel.history().filter(lambda m: m.author == client.user).map(lambda m: m.id): + print(m_id) + +The functions passed to :meth:`AsyncIterator.map` or :meth:`AsyncIterator.filter` can be either coroutines or regular +functions. + +You can also get single elements a la :func:`discord.utils.find` or :func:`discord.utils.get` via +:meth:`AsyncIterator.get` or :meth:`AsyncIterator.find`: :: + + my_last_message = await channel.history().get(author=client.user) + +The following return :class:`AsyncIterator`: + +- :meth:`abc.Messageable.history` +- :meth:`Guild.audit_logs` +- :meth:`Reaction.users` + +.. _migrating_1_0_event_changes: 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. +A lot of events have gone through some changes. + +Many events with ``server`` in the name where changed to use ``guild`` instead. Before: -.. code-block:: python +- ``on_server_join`` +- ``on_server_remove`` +- ``on_server_update`` +- ``on_server_role_create`` +- ``on_server_role_delete`` +- ``on_server_role_update`` +- ``on_server_emojis_update`` +- ``on_server_available`` +- ``on_server_unavailable`` - 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: +- :func:`on_guild_join` +- :func:`on_guild_remove` +- :func:`on_guild_update` +- :func:`on_guild_role_create` +- :func:`on_guild_role_delete` +- :func:`on_guild_role_update` +- :func:`on_guild_emojis_update` +- :func:`on_guild_available` +- :func:`on_guild_unavailable` -After: -.. code-block:: python +The :func:`on_voice_state_update` event has received an argument change. - 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 +Before: :: -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``. + async def on_voice_state_update(before, after) +After: :: -.. _migrating-coroutines: + async def on_voice_state_update(member, before, after) -Coroutines ------------ +Instead of two :class:`Member` objects, the new event takes one :class:`Member` object and two :class:`VoiceState` objects. -The biggest change that the library went through is that almost every function in :class:`Client` -was changed to be a `coroutine <https://docs.python.org/3/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... +The :func:`on_guild_emojis_update` event has received an argument change. -Before: +Before: :: -.. code-block:: python + async def on_guild_emojis_update(before, after) + +After: :: + + async def on_guild_emojis_update(guild, before, after) - client.send_message(message.channel, 'Hello') +The first argument is now the :class:`Guild` that the emojis were updated from. + +The ``on_channel_`` events have received a type level split (see :ref:`migrating_1_0_channel_split`). + +Before: + +- ``on_channel_delete`` +- ``on_channel_create`` +- ``on_channel_update`` After: -.. code-block:: python +- :func:`on_guild_channel_delete` +- :func:`on_guild_channel_create` +- :func:`on_guild_channel_update` +- :func:`on_private_channel_delete` +- :func:`on_private_channel_create` +- :func:`on_private_channel_update` - yield from client.send_message(message.channel, 'Hello') +The ``on_guild_channel_`` events correspond to :class:`abc.GuildChannel` being updated (i.e. :class:`TextChannel` +and :class:`VoiceChannel`) and the ``on_private_channel_`` events correspond to :class:`abc.PrivateChannel` being +updated (i.e. :class:`DMChannel` and :class:`GroupChannel`). - # or in python 3.5+ - await client.send_message(message.channel, 'Hello') +.. _migrating_1_0_voice: -In order for you to ``yield from`` or ``await`` a coroutine then your function must be decorated -with ``@asyncio.coroutine`` or ``async def``. +Voice Changes +--------------- -.. _migrating-iterable: +Voice sending has gone through a complete redesign. -Iterables ----------- +In particular: -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. +- Connection is done through :meth:`VoiceChannel.connect` instead of ``Client.join_voice_channel``. +- You no longer create players and operate on them (you no longer store them). +- You instead request :class:`VoiceClient` to play an :class:`AudioSource` via :meth:`VoiceClient.play`. +- There are different built-in :class:`AudioSource`\s -The affected attributes are as follows: + - :class:`FFmpegPCMAudio` is the equivalent of ``create_ffmpeg_player`` -- :attr:`Client.servers` -- :attr:`Client.private_channels` -- :attr:`Server.channels` -- :attr:`Server.members` +- create_ffmpeg_player/create_stream_player/create_ytdl_player have all been removed -Some examples of previously valid behaviour that is now invalid + - The goal is to create :class:`AudioSource` instead. -.. code-block:: python +- Using :meth:`VoiceClient.play` will not return an ``AudioPlayer``. - if client.servers[0].name == "test": - # do something + - Instead, it's "flattened" like :class:`User` -> :class:`Member` is. -Since they are no longer ``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. +- The ``after`` parameter now takes a single parameter (the error). -.. code-block:: python +Basically: - servers = list(client.servers) - # work with servers +Before: :: -.. warning:: + vc = await client.join_voice_channel(channel) + player = vc.create_ffmpeg_player('testing.mp3', after=lambda: print('done')) + player.start() - Due to internal changes of the structure, the order you receive the data in - is not in a guaranteed order. + player.is_playing() + player.pause() + player.resume() + player.stop() + # ... -.. _migrating-enums: +After: :: -Enumerations ------------- + vc = await channel.connect() + vc.play(discord.FFmpegPCMAudio('testing.mp3'), after=lambda e: print('done', e)) + vc.is_playing() + vc.pause() + vc.resume() + vc.stop() + # ... -Due to dropping support for versions lower than Python 3.4.2, the library can now use -`enumerations <https://docs.python.org/3/library/enum.html>`_ in places where it makes sense. +With the changed :class:`AudioSource` design, you can now change the source that the :class:`VoiceClient` is +playing at runtime via :attr:`VoiceClient.source`. -The common places where this was changed was in the server region, member status, and channel type. +For example, you can add a :class:`PCMVolumeTransformer` to allow changing the volume: :: -Before: + vc.source = discord.PCMVolumeTransformer(vc.source) + vc.source.volume = 0.6 -.. code-block:: python +An added benefit of the redesign is that it will be much more resilient towards reconnections: - server.region == 'us-west' - member.status == 'online' - channel.type == 'text' +- The voice websocket will now automatically re-connect and re-do the handshake when disconnected. +- The initial connect handshake will now retry up to 5 times so you no longer get as many ``asyncio.TimeoutError`` +- Audio will now stop and resume when a disconnect is found. -After: + - This includes changing voice regions etc. + + +.. _migrating_1_0_wait_for: + +Waiting For Events +-------------------- + +Prior to v1.0, the machinery for waiting for an event outside of the event itself was done through two different +functions, ``Client.wait_for_message`` and ``Client.wait_for_reaction``. One problem with one such approach is that it did +not allow you to wait for events outside of the ones provided by the library. + +In v1.0 the concept of waiting for another event has been generalised to work with any event as :meth:`Client.wait_for`. + +For example, to wait for a message: :: + + # before + msg = await client.wait_for_message(author=message.author, channel=message.channel) + + # after + def pred(m): + return m.author == message.author and m.channel == message.channel + + msg = await client.wait_for('message', check=m) + +To facilitate multiple returns, :meth:`Client.wait_for` returns either a single argument, no arguments, or a tuple of +arguments. + +For example, to wait for a reaction: :: + + user, reaction = await client.wait_for('reaction_add', check=lambda u, r: u.id == 176995180300206080) + + # use user and reaction + +Since this function now can return multiple arguments, the ``timeout`` parameter will now raise a ``asyncio.TimeoutError`` +when reached instead of setting the return to ``None``. For example: .. code-block:: python - 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. - -.. _migrating-properties: - -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` | -+----------------------------------------+--------------------------------------+ - -.. _migrating-member: - -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``. - -.. _migrating-kwargs: - -Forced Keyword Arguments + def pred(m): + return m.author == message.author and m.channel == message.channel + + try: + + msg = await client.wait_for('message', check=pred, timeout=60.0) + except asyncio.TimeoutError: + await channel.send('You took too long...') + else: + await channel.send('You said {0.content}, {0.author}.'.format(msg)) + +Sharding +---------- + +The library has received significant changes on how it handles sharding and now has sharding as a first-class citizen. + +If using a Bot account and you want to shard your bot in a single process then you can use the :class:`AutoShardedClient`. + +This class allows you to use sharding without having to launch multiple processes or deal with complicated IPC. + +It should be noted that **the sharded client does not support user accounts**. This is due to the changes in connection +logic and state handling. + +Usage is as simple as doing: :: + + client = discord.AutoShardedClient() + +instead of using :class:`Client`. + +This will launch as many shards as your bot needs using the ``/gateway/bot`` endpoint, which allocates about 1000 guilds +per shard. + +If you want more control over the sharding you can specify ``shard_count`` and ``shard_ids``. :: + + # launch 10 shards regardless + client = discord.AutoShardedClient(shard_count=10) + + # launch specific shard IDs in this process + client = discord.AutoShardedClient(shard_count=10, shard_ids=(1, 2, 5, 6)) + +For users of the command extension, there is also :class:`~ext.commands.AutoShardedBot` which behaves similarly. + +Connection Improvements ------------------------- -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. +In v1.0, the auto reconnection logic has been powered up significantly. -The following parameters are now exclusively keyword arguments: +:meth:`Client.connect` has gained a new keyword argument, ``reconnect`` that defaults to ``True`` which controls +the reconnect logic. When enabled, the client will automatically reconnect in all instances of your internet going +offline or Discord going offline with exponential back-off. -- :meth:`Client.send_message` - - ``tts`` -- :meth:`Client.logs_from` - - ``before`` - - ``after`` -- :meth:`Client.edit_channel_permissions` - - ``allow`` - - ``deny`` +:meth:`Client.run` and :meth:`Client.start` gains this keyword argument as well, but for most cases you will not +need to specify it unless turning it off. -In the documentation you can tell if a function parameter is a forced keyword argument if it is after ``\*,`` -in the function signature. +.. _migrating_1_0_commands: -.. _migrating-running: +Command Extension Changes +-------------------------- -Running the Client --------------------- +Due to the :ref:`migrating_1_0_model_state` changes, some of the design of the extension module had to +undergo some design changes as well. + +Context Changes +~~~~~~~~~~~~~~~~~ -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`. +In v1.0, the :class:`~ext.commands.Context` has received a lot of changes with how it's retrieved and used. -Basically, before: +The biggest change is that ``pass_context=True`` is now the default behaviour. Ergo: .. code-block:: python - client.login('token') - client.run() + # before + @bot.command() + async def foo(): + await bot.say('Hello') -After: + # after + @bot.command() + async def foo(ctx): + await ctx.send('Hello') + +The reason for this is because :class:`~ext.commands.Context` now meets the requirements of :class:`abc.Messageable`. This +makes it have similar functionality to :class:`TextChannel` or :class:`DMChannel`. Using :meth:`~ext.commands.Context.send` +will either DM the user in a DM context or send a message in the channel it was in, similar to the old ``bot.say`` +functionality. The old helpers have been removed in favour of the new :class:`abc.Messageable` interface. See +:ref:`migrating_1_0_removed_helpers` for more information. + +Since the :class:`~ext.commands.Context` is now by default passed, several shortcuts have been added: + +**New Shortcuts** + +- :attr:`~ext.commands.Context.author` is a shortcut for ``ctx.message.author`` +- :attr:`~ext.commands.Context.guild` is a shortcut for ``ctx.message.guild`` +- :attr:`~ext.commands.Context.channel` is a shortcut for ``ctx.message.channel`` +- :attr:`~ext.commands.Context.me` is a shortcut for ``ctx.message.guild.me`` or ``ctx.bot.user``. +- :attr:`~ext.commands.Context.voice_client` is a shortcut for ``ctx.message.guild.voice_client``. + +Subclassing Context +++++++++++++++++++++ + +In v1.0, there is now the ability to subclass :class:`~ext.commands.Context` and use it instead of the default +provided one. + +For example, if you want to add some functionality to the context: .. code-block:: python - client.run('token') + class MyContext(commands.Context): + @property + def secret(self): + return 'my secret here' -.. warning:: +Then you can use :meth:`~ext.commands.Bot.get_context` inside :func:`on_message` with combination with +:meth:`~ext.commands.Bot.invoke` to use your custom context: + +.. code-block:: python - 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. + class MyBot(commands.Bot): + async def on_message(self, message): + ctx = await self.get_context(message, cls=MyContext) + await self.invoke(ctx) -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: +Now inside your commands you will have access to your custom context: .. code-block:: python - import discord - import asyncio + @bot.command() + async def secret(ctx): + await ctx.send(ctx.secret) - client = discord.Client() +.. _migrating_1_0_removed_helpers: - @asyncio.coroutine - def main_task(): - yield from client.login('token') - yield from client.connect() +Removed Helpers ++++++++++++++++++ - loop = asyncio.get_event_loop() - try: - loop.run_until_complete(main_task()) - except: - loop.run_until_complete(client.logout()) - finally: - loop.close() +.. currentmodule:: discord.ext.commands + +With the new :class:`Context` changes, a lot of message sending helpers have been removed. + +For a full list of changes, see below: + ++-----------------+----------------------------------------------------------+ +| Before | After | ++-----------------+----------------------------------------------------------+ +| ``Bot.say`` | :meth:`Context.send` | ++-----------------+----------------------------------------------------------+ +| ``Bot.upload`` | :meth:`Context.send` | ++-----------------+----------------------------------------------------------+ +| ``Bot.whisper`` | ``ctx.author.send`` | ++-----------------+----------------------------------------------------------+ +| ``Bot.type`` | :meth:`Context.typing` or :meth:`Context.trigger_typing` | ++-----------------+----------------------------------------------------------+ +| ``Bot.reply`` | No replacement. | ++-----------------+----------------------------------------------------------+ + +.. currentmodule:: discord + +Command Changes +~~~~~~~~~~~~~~~~~ + +As mentioned earlier, the first command change is that ``pass_context=True`` is now the +default, so there is no need to pass this as a parameter. + +Another change is the removal of ``no_pm=True``. Instead, use the new :func:`~ext.commands.guild_only` built-in +check. + +The ``commands`` attribute of :class:`~ext.commands.Bot` and :class:`~ext.commands.Group` have been changed from a +dictionary to a set that does not have aliases. To retrieve the previous dictionary behaviour, use ``all_commands`` instead. + +Command instances have gained a new property, :attr:`~ext.commands.Command.signature` to get the signature of command along +with a :attr:`~ext.commands.Command.usage` attribute to override the default signature. + +Check Changes +~~~~~~~~~~~~~~~ + +Prior to v1.0, :func:`~ext.command.check`\s could only be synchronous. As of v1.0 checks can now be coroutines. + +Along with this change, a couple new checks were added. + +- :func:`~ext.commands.guild_only` replaces the old ``no_pm=True`` functionality +- :func:`~ext.commands.is_owner` uses the :meth:`Client.application_info` endpoint by default to fetch owner ID. + + - This is actually powered by a different function, :meth:`~ext.commands.Bot.is_owner`. + - You can set the owner ID yourself by setting :attr:`Bot.owner_id <ext.commands.Bot.owner_id>`. + +- :func:`~ext.commands.is_nsfw` checks if the channel the command is in is a NSFW channel. + + - This is powered by the new :meth:`TextChannel.is_nsfw` check. + +Event Changes +~~~~~~~~~~~~~~~ + +All command extension events have changed. + +Before: :: + + on_command(ctx, command) + on_command_completion(ctx, command) + on_command_error(error, ctx) + +After: :: + + on_command(ctx) + on_command_completion(ctx) + on_command_error(ctx, error) + +The extraneous ``command`` parameter in :func:`~ext.commands.on_command` and :func:`~ext.commands.on_command_completion` +have been removed. The :class:`~ext.commands.Command` instance was not kept up-to date so it was incorrect. In order to get +the up to date :class:`~ext.commands.Command` instance, use the :attr:`Context.command <ext.commands.Context.command>` +attribute. + +The error handlers, either :attr:`Command.error <ext.commands.Command.error>` or :func:`~ext.commands.on_command_error`, +have been re-ordered to use the :class:`~ext.commands.Context` as its first parameter to be consistent with other events +and commands. + +Cog Changes +~~~~~~~~~~~~~ + +Cog special methods have changed slightly. + +The previous ``__check`` special method has been renamed to ``__global_check`` to make it more clear that it's a global +check. + +To complement the new ``__global_check`` there is now a new ``__local_check`` to facilitate a check that will run on +every command in the cog. + +Cogs have also gained a ``__before_invoke`` and ``__after_invoke`` cog local before and after invocation hook, which +can be seen in :ref:`migrating_1_0_before_after_hook`. + +The final addition is cog-local error handler, ``__error``, that is run on every command in the cog. + +An example cog with every special method registered is as follows: :: + + class Cog: + def __global_check(self, ctx): + print('cog global check') + return True + + async def __local_check(self, ctx): + print('cog local check') + return await ctx.bot.is_owner(ctx.author) + + async def __error(self, ctx, error): + print('Error in {0.command.qualified_name}: {1}'.format(ctx, error)) + + async def __before_invoke(self, ctx): + print('cog local before: {0.command.qualified_name}'.format(ctx)) + + async def __after_invoke(self, ctx): + print('cog local after: {0.command.qualified_name}'.format(ctx)) + + +.. _migrating_1_0_before_after_hook: + +Before and After Invocation Hooks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Commands have gained new before and after invocation hooks that allow you to do an action before and after a command is +run. + +They take a single parameter, :class:`~ext.commands.Context` and they must be a coroutine. + +They are on a global, per-cog, or per-command basis. + +Basically: :: + + + # global hooks: + + @bot.before_invoke + async def before_any_command(ctx): + # do something before a command is called + pass + + @bot.after_invoke + async def after_any_command(ctx): + # do something after a command is called + pass + +The after invocation is hook always called, **regardless of an error in the command**. This makes it ideal for some error +handling or clean up of certain resources such a database connection. + +The per-command registration is as follows: :: + + @bot.command() + async def foo(ctx): + await ctx.send('foo') + + @foo.before_invoke + async def before_foo_command(ctx): + # do something before the foo command is called + pass + + @foo.after_invoke + async def after_foo_command(ctx): + # do something after the foo command is called + pass + +The special cog method for these is ``__before_invoke`` and ``__after_invoke``, e.g.: :: + + class Cog: + async def __before_invoke(self, ctx): + ctx.secret_cog_data = 'foo' + + async def __after_invoke(self, ctx): + print('{0.command} is done...'.format(ctx)) + + @commands.command() + async def foo(self, ctx): + await ctx.send(ctx.secret_cog_data) + +To check if a command failed in the after invocation hook, you can use +:attr:`Context.command_failed <ext.commands.Context.command_failed>`. + +The invocation order is as follows: + +1. Command local before invocation hook +2. Cog local before invocation hook +3. Global before invocation hook +4. The actual command +5. Command local after invocation hooko +6. Cog local after invocation hooko +7. Global after invocation hooko + +Converter Changes +~~~~~~~~~~~~~~~~~~~ + +Prior to v1.0, a converter was a type hint that could be a callable that could be invoked +with a singular argument denoting the argument passed by the user as a string. + +This system was eventually expanded to support a :class:`~ext.commands.Converter` system to +allow plugging in the :class:`~ext.commands.Context` and do more complicated conversions such +as the built-in "discord" converters. + +In v1.0 this converter system was revamped to allow instances of :class:`~ext.commands.Converter` derived +classes to be passed. For consistency, the :meth:`~ext.commands.Converter.convert` method was changed to +always be a coroutine and will now take the two arguments as parameters. + +Essentially, before: :: + + class MyConverter(commands.Converter): + def convert(self): + return self.ctx.message.server.me + +After: :: + + class MyConverter(commands.Converter): + async def convert(self, ctx, argument): + return ctx.me +The command framework also got a couple new converters: +- :class:`~ext.commands.clean_content` this is akin to :attr:`Message.clean_content` which scrubs mentions. +- :class:`~ext.commands.UserConverter` will now appropriately convert :class:`User` only. +- ``ChannelConverter`` is now split into two different converters + - :class:`~ext.commands.TextChannelConverter` for :class:`TextChannel` + - :class:`~ext.commands.VoiceChannelConverter` for :class:`VoiceChannel` diff --git a/docs/migrating_to_async.rst b/docs/migrating_to_async.rst new file mode 100644 index 00000000..c4b7e1db --- /dev/null +++ b/docs/migrating_to_async.rst @@ -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 ``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:: python + + @client.event + def on_message(message): + pass + +After: + +.. code-block:: python + + @client.event + @asyncio.coroutine + def on_message(message): + pass + +Or in Python 3.5+: + +.. code-block:: python + + @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:: python + + @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:: python + + 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:: python + + 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 <https://docs.python.org/3/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:: python + + client.send_message(message.channel, 'Hello') + +After: + +.. code-block:: python + + 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:: python + + if client.servers[0].name == "test": + # do something + +Since they are no longer ``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:: python + + 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 +`enumerations <https://docs.python.org/3/library/enum.html>`_ 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:: python + + server.region == 'us-west' + member.status == 'online' + channel.type == 'text' + +After: + +.. code-block:: python + + 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:: python + + client.login('token') + client.run() + +After: + +.. code-block:: python + + 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:: python + + 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() + + + diff --git a/docs/quickstart.rst b/docs/quickstart.rst new file mode 100644 index 00000000..28d2f59a --- /dev/null +++ b/docs/quickstart.rst @@ -0,0 +1,76 @@ +.. _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 replies to a specific message and walk you through it. + +It looks something like this: + +.. code-block:: python + + import discord + + client = discord.Client() + + @client.event + async def on_ready(): + print('We have logged in as {0.user}'.format(self)) + + @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 is, + then we reply in the channel it was used in with ``'Hello!'``. +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. diff --git a/docs/whats_new.rst b/docs/whats_new.rst index 4832dd29..069138ae 100644 --- a/docs/whats_new.rst +++ b/docs/whats_new.rst @@ -2,12 +2,26 @@ .. _whats_new: -What's New +Changelog ============ This page keeps a detailed human friendly rendering of what's new and changed in specific versions. +.. _vp0p16p6: + +v0.16.6 +-------- + +Bug Fixes +~~~~~~~~~~ + +- Fix issue with :meth:`Client.create_server` that made it stop working. +- Fix main thread being blocked upon calling ``StreamPlayer.stop``. +- Handle HEARTBEAT_ACK and resume gracefully when it occurs. +- Fix race condition when pre-emptively rate limiting that caused releasing an already released lock. +- Fix invalid state errors when immediately cancelling a coroutine. + .. _vp0p16p1: v0.16.1 |