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/migrating.rst | |
| 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/migrating.rst')
| -rw-r--r-- | docs/migrating.rst | 1113 |
1 files changed, 880 insertions, 233 deletions
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` |