Migrating to v2.0¶
Compared to v1.0, v2.0 mostly has breaking changes related to better developer experience and API coverage. While the changes aren’t as massive to require an entire rewrite, there are still many changes that need to be accounted for.
This is the first version to have version guarantees apply.
Below is a non-exhaustive list of changes.
Python Version Change¶
In order to ease development, maintain security updates, and use newer features v2.0 drops support for Python 3.7 and earlier.
asyncio Event Loop Changes¶
Python 3.7 introduced a new helper function asyncio.run()
which automatically creates and destroys the asynchronous event loop.
In order to support this, the way discord.py-self handles the asyncio
event loop has changed.
This allows you to rather than using Client.run()
create your own asynchronous loop to setup other asynchronous code as needed.
Quick example:
client = discord.Client()
async def main():
# do other async things
await my_async_function()
# start the client
async with client:
await client.start(TOKEN)
asyncio.run(main())
A new setup_hook()
method has also been added to the Client
class.
This method is called after login but before connecting to the discord gateway.
It is intended to be used to setup various bot features in an asynchronous context.
setup_hook()
can be defined by subclassing the Client
class.
Quick example:
class MyClient(discord.Client):
async def setup_hook(self):
print('This is asynchronous!')
client = MyClient()
client.run(TOKEN)
With this change, constructor of Client
no longer accepts connector
and loop
parameters.
In parallel with this change, changes were made to loading and unloading of commands extension extensions and cogs, see Extension and Cog Loading / Unloading is Now Asynchronous for more information.
Abstract Base Classes Changes¶
Abstract Base Classes that inherited from abc.ABCMeta
now inherit from typing.Protocol
.
This results in a change of the base metaclass used by these classes but this should generally be completely transparent to the user.
All of the classes are either runtime-checkable
protocols or explicitly inherited from
and as such usage with isinstance()
and issubclass()
is not affected.
The following have been changed to runtime-checkable
Protocol
s:
The following have been changed to subclass Protocol
:
The following have been changed to use the default metaclass instead of abc.ABCMeta
:
datetime
Objects Are Now UTC-Aware¶
All usage of naive datetime.datetime
objects in the library has been replaced with aware objects using UTC timezone.
Methods that accepted naive datetime
objects now also accept timezone-aware objects.
To keep behavior inline with datetime
’s methods, this library’s methods now assume
that naive datetime
objects are local time (note that some of the methods may not accept
naive datetime
, such exceptions are listed below).
Because naive datetime
objects are treated by many of its methods as local times, the previous behavior
was more likely to result in programming errors with their usage.
To ease the migration utils.utcnow()
helper function has been added.
Warning
Using datetime.datetime.utcnow()
can be problematic since it returns a naive UTC datetime
object.
Quick example:
# before
week_ago = datetime.datetime.utcnow() - datetime.timedelta(days=7)
if member.created_at > week_ago:
print(f'Member account {member} was created less than a week ago!')
# after
# The new helper function can be used here:
week_ago = discord.utils.utcnow() - datetime.timedelta(days=7)
# ...or the equivalent result can be achieved with datetime.datetime.now():
week_ago = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=7)
if member.created_at > week_ago:
print(f'Member account {member} was created less than a week ago!')
The following have been changed from naive to aware datetime
objects in UTC:
AuditLogEntry.created_at
attributeBaseActivity.created_at
attributeClientUser.created_at
attributeDMChannel.created_at
attributeEmoji.created_at
attributeGroupChannel.created_at
attributeGuild.created_at
attributeabc.GuildChannel.created_at
attributeInvite.created_at
attributeObject.created_at
attributeMember.created_at
attributeMessage.created_at
attributePartialEmoji.created_at
attributePartialInviteChannel.created_at
attributePartialInviteGuild.created_at
attributePartialMessage.created_at
attributeRole.created_at
attributeSpotify.created_at
attributeSticker.created_at
attributeTeamMember.created_at
attributeTemplate.created_at
attributeUser.created_at
attributeWebhook.created_at
attributeWidget.created_at
attributeWidgetChannel.created_at
attributeWidgetMember.created_at
attributeMessage.edited_at
attributeInvite.expires_at
attributeActivity.end
attributeGame.end
attributeSpotify.end
attributeMember.joined_at
attributeMember.premium_since
attributeVoiceState.requested_to_speak_at
attributeActivity.start
attributeGame.start
attributeSpotify.start
attributeStreamIntegration.synced_at
attributeEmbed.timestamp
attributeTemplate.updated_at
attributetimestamp
parameter inon_typing()
eventlast_pin
parameter inon_private_channel_pins_update()
eventlast_pin
parameter inon_guild_channel_pins_update()
eventReturn type of
utils.snowflake_time()
The following now accept aware datetime
and assume that if the passed datetime
is naive, it is a local time:
abc.Messageable.history()
methodClient.fetch_guilds()
methodGuild.audit_logs()
methodGuild.fetch_members()
methodTextChannel.purge()
methodEmbed
constructorEmbed.timestamp
property setterutils.sleep_until()
functionutils.time_snowflake()
function
Currently, there’s only one place in this library that doesn’t accept naive datetime.datetime
objects:
timed_out_until
parameter inMember.edit()
This has been done to prevent users from mistakenly applying incorrect timeouts to guild members.
Major Webhook Changes¶
Webhook support has been rewritten to work better with typings and rate limits.
As a result, synchronous functionality has been split to separate classes.
Quick example for asynchronous webhooks:
# before
async with aiohttp.ClientSession() as session:
webhook = discord.Webhook.from_url('url-here', adapter=discord.AsyncWebhookAdapter(session))
await webhook.send('Hello World', username='Foo')
# after
async with aiohttp.ClientSession() as session:
webhook = discord.Webhook.from_url('url-here', session=session)
await webhook.send('Hello World', username='Foo')
Quick example for synchronous webhooks:
# before
webhook = discord.Webhook.partial(123456, 'token-here', adapter=discord.RequestsWebhookAdapter())
webhook.send('Hello World', username='Foo')
# after
webhook = discord.SyncWebhook.partial(123456, 'token-here')
webhook.send('Hello World', username='Foo')
The following breaking changes have been made:
Synchronous functionality of
Webhook
andWebhookMessage
has been split toSyncWebhook
andSyncWebhookMessage
.WebhookAdapter
class has been removed and the interfaces based on it (AsyncWebhookAdapter
andRequestsWebhookAdapter
) are now considered implementation detail and should not be depended on.execute
alias forWebhook.send()
/SyncWebhook.send()
has been removed.
Asset Redesign and Changes¶
The Asset
object now encompasses all of the methods and attributes related to a CDN asset.
This means that all models with asset-related attribute and methods have been transformed to use this new design.
As an example, here’s how these changes look for Guild.icon
(of Asset
type):
Guild.icon
(ofstr
type) has been replaced withGuild.icon.key
.Guild.is_icon_animated
has been replaced withGuild.icon.is_animated
.Guild.icon_url
has been replaced withGuild.icon
.Guild.icon_url_as
has been replaced withGuild.icon.replace
.Helper methods
Asset.with_size()
,Asset.with_format()
, andAsset.with_static_format()
have also been added.
In addition to this, Emoji
and PartialEmoji
now also share an interface similar to Asset
’s:
Emoji.url_as
has been removed.Emoji.url.read
has been replaced withEmoji.read()
.Emoji.url.save
has been replaced withEmoji.save()
.
Asset
now always represent an actually existing CDN asset. This means that:
str(x)
on anAsset
can no longer return an empty string.bool(x)
on anAsset
can no longer returnFalse
.Attributes containing an optional
Asset
can now beNone
.
The following were affected by this change:
-
AuditLogDiff.avatar
is now ofAsset
type.AuditLogDiff.icon
is now ofAsset
type.AuditLogDiff.splash
is now ofAsset
type.
-
Emoji.url_as
has been removed.Emoji.url.read
(replaced byEmoji.read()
)Emoji.url.save
(replaced byEmoji.save()
)
-
GroupChannel.icon
(replaced byGroupChannel.icon.key
)GroupChannel.icon_url
(replaced byGroupChannel.icon
)The new attribute may now be
None
.
GroupChannel.icon_url_as
(replaced byGroupChannel.icon.replace
)
-
Guild.banner
(replaced byGuild.banner.key
)Guild.banner_url
(replaced byGuild.banner
)The new attribute may now be
None
.
Guild.banner_url_as
(replaced byGuild.banner.replace
)
-
Guild.discovery_splash
(replaced byGuild.discovery_splash.key
)Guild.discovery_splash_url
(replaced byGuild.discovery_splash
)The new attribute may now be
None
.
Guild.discovery_splash_url_as
(replaced byGuild.discovery_splash.replace
)
-
Guild.icon
(replaced byGuild.icon.key
)Guild.is_icon_animated
(replaced byGuild.icon.is_animated
)Guild.icon_url
(replaced byGuild.icon
)The new attribute may now be
None
.
Guild.icon_url_as
(replaced byGuild.icon.replace
)
-
Guild.splash
(replaced byGuild.splash.key
)Guild.splash_url
(replaced byGuild.splash
)The new attribute may now be
None
.
Guild.splash_url_as
(replaced byGuild.splash.replace
)
-
Member.avatar
(replaced byMember.avatar.key
)Member.is_avatar_animated
(replaced byMember.avatar.is_animated
)Member.avatar_url
(replaced byMember.avatar
)The new attribute may now be
None
.
Member.avatar_url_as
(replaced byMember.avatar.replace
)
-
Member.default_avatar
(replaced byMember.default_avatar.key
)Member.default_avatar_url
(replaced byMember.default_avatar
)Member.default_avatar_url_as
(replaced byMember.default_avatar.replace
)
-
PartialEmoji.url
is now ofstr
type.PartialEmoji.url_as
has been removed.PartialEmoji.url.read
(replaced byPartialEmoji.read()
)PartialEmoji.url.save
(replaced byPartialEmoji.save()
)
-
PartialInviteGuild.banner
(replaced byPartialInviteGuild.banner.key
)PartialInviteGuild.banner_url
(replaced byPartialInviteGuild.banner
)The new attribute may now be
None
.
PartialInviteGuild.banner_url_as
(replaced byPartialInviteGuild.banner.replace
)
-
PartialInviteGuild.icon
(replaced byPartialInviteGuild.icon.key
)PartialInviteGuild.is_icon_animated
(replaced byPartialInviteGuild.icon.is_animated
)PartialInviteGuild.icon_url
(replaced byPartialInviteGuild.icon
)The new attribute may now be
None
.
PartialInviteGuild.icon_url_as
(replaced byPartialInviteGuild.icon.replace
)
-
PartialInviteGuild.splash
(replaced byPartialInviteGuild.splash.key
)PartialInviteGuild.splash_url
(replaced byPartialInviteGuild.splash
)The new attribute may now be
None
.
PartialInviteGuild.splash_url_as
(replaced byPartialInviteGuild.splash.replace
)
-
Team.icon
(replaced byTeam.icon.key
)Team.icon_url
(replaced byTeam.icon
)The new attribute may now be
None
.
Team.icon_url_as
(replaced byTeam.icon.replace
)
-
User.avatar
(replaced byUser.avatar.key
)User.is_avatar_animated
(replaced byUser.avatar.is_animated
)User.avatar_url
(replaced byUser.avatar
)The new attribute may now be
None
.
User.avatar_url_as
(replaced byUser.avatar.replace
)
-
User.default_avatar
(replaced byUser.default_avatar.key
)User.default_avatar_url
(replaced byUser.default_avatar
)User.default_avatar_url_as
(replaced byUser.default_avatar.replace
)
-
Webhook.avatar
(replaced byWebhook.avatar.key
)Webhook.avatar_url
(replaced byWebhook.avatar
)The new attribute may now be
None
.
Webhook.avatar_url_as
(replaced byWebhook.avatar.replace
)
Thread Support¶
v2.0 has been updated to use a newer API gateway version which supports threads and as a result of this had to make few breaking changes. Most notably messages sent in guilds can, in addition to a TextChannel
, be sent in a Thread
.
The main differences between text channels and threads are:
Threads do not have their own permissions, they inherit the permissions of their parent channel.
This means that threads do not have these attributes:
changed_roles
overwrites
permissions_synced
Note
Text channels have a few dedicated permissions for threads:
Threads do not have their own NSFW status, they inherit it from their parent channel.
This means that
Thread
does not have annsfw
attribute.
Threads do not have their own topic.
This means that
Thread
does not have atopic
attribute.
Threads do not have their own position in the channel list.
This means that
Thread
does not have aposition
attribute.
Thread.created_at
of threads created before 10 January 2022 isNone
.Thread.members
is of type List[ThreadMember
] rather than List[Member
]Most of the time, this data is not provided and a call to
Thread.fetch_members()
to fetch the partial objects is needed.
For convenience, Thread
has a set of properties and methods that return the information about the parent channel:
-
Note that this outputs the permissions of the parent channel and you might need to check for different permissions when trying to determine if a member can do something.
Here are some notable examples:
A guild member can send messages in a text channel if they have
send_messages
permission in it.- A guild member can send messages in a public thread if:
They have
send_messages_in_threads
permission in its parent channel.The thread is not
locked
.
- A guild member can send messages in a private thread if:
They have
send_messages_in_threads
permission in its parent channel.They’re either already a member of the thread or have a
manage_threads
permission in its parent channel.The thread is not
locked
.
A guild member can edit a text channel if they have
manage_channels
permission in it.A guild member can edit a thread if they have
manage_threads
permission in its parent channel.Note
A thread’s
owner
can archive a (not-locked) thread and edit itsname
andauto_archive_duration
withoutmanage_threads
permission.- A guild member can react with an emoji to messages in a text channel if:
They have
read_message_history
permission in it.They have
add_reactions
permission in it or the message already has that emoji reaction.
- A guild member can react with an emoji to messages in a public thread if:
They have
read_message_history
permission in its parent channel.They have
add_reactions
permission in its parent channel or the message already has that emoji reaction.The thread is not
archived
. Note that the guild member can unarchive a thread (if it’s notlocked
) to react to a message.
- A guild member can react with an emoji to messages in a private thread if:
They have
read_message_history
permission in its parent channel.They have
add_reactions
permission in its parent channel or the message already has that emoji reaction.They’re either already a member of the thread or have a
manage_threads
permission in its parent channel.The thread is not
archived
. Note that the guild member can unarchive a thread (if it’s notlocked
) to react to a message.
The following changes have been made:
Message.channel
may now be aThread
.Message.channel_mentions
list may now contain aThread
.AuditLogEntry.target
may now be aThread
.PartialMessage.channel
may now be aThread
.Guild.get_channel
does not returnThread
s.If you’re looking to get a channel or thread, use
Guild.get_channel_or_thread
instead.If you’re only looking to get threads, use
Guild.get_thread
orTextChannel.get_thread
instead.
channel
parameter inon_guild_channel_pins_update()
may now be aThread
.channel
parameter inon_typing()
may now be aThread
.Client.fetch_channel()
may now returnThread
.Client.get_channel()
may now returnThread
.Guild.fetch_channel()
may now returnThread
.
Removing In-Place Edits¶
Most of the model methods that previously edited the model in-place have been updated to no longer do this. Instead, these methods will now return a new instance of the newly updated model. This has been done to avoid the library running into race conditions between in-place edits and gateway events on model updates. See GH-4098 for more information.
Quick example:
# before
await member.edit(nick='new nick')
await member.send(f'Your new nick is {member.nick}')
# after
updated_member = await member.edit(nick='new nick')
await member.send(f'Your new nick is {updated_member.nick}')
The following have been changed:
-
Note that this method will return
None
instead ofCategoryChannel
if the edit was only positional.
-
Note that this method only returns the updated
Member
when certain fields are updated.
-
Note that this method will return
None
instead ofStageChannel
if the edit was only positional.
-
Note that this method will return
None
instead ofTextChannel
if the edit was only positional.
-
Note that this method will return
None
instead ofVoiceChannel
if the edit was only positional.
Sticker Changes¶
Discord has changed how their stickers work and as such, sticker support has been reworked.
The following breaking changes have been made:
Type of
Message.stickers
changed to List[StickerItem
].To get the
Sticker
fromStickerItem
, useStickerItem.fetch()
or (only for stickers from guilds the bot is in)Client.get_sticker()
.
Sticker.format
is now ofStickerFormatType
type.Sticker.tags
has been removed.Depending on type of the sticker,
StandardSticker.tags
orGuildSticker.emoji
can be used instead.
Sticker.image
and related methods have been removed.Sticker.preview_image
and related methods have been removed.AuditLogDiff.type
is now of Union[ChannelType
,StickerType
] type.The old
StickerType
enum has been renamed toStickerFormatType
.StickerType
now refers to a sticker type (official sticker vs guild-uploaded sticker) rather than its format type.
Integrations Changes¶
To support the new integration types, integration support has been reworked.
The following breaking changes have been made:
The old
Integration
class has been renamed toStreamIntegration
.Guild.integrations()
now returns subclasses of the newIntegration
class.
Presence Updates Now Have A Separate Event¶
Presence updates (changes in member’s status and activity) now have a separate on_presence_update()
event.
on_member_update()
event is now only called on member updates (changes in nickname, role, pending status, etc.).
From API perspective, these are separate events and as such, this change improves library’s consistency with the API. Presence updates usually are 90% of all handled events so splitting these should benefit listeners that were only interested in member updates.
Note that this event is also called when a Relationship
presence is updated.
Quick example:
# before
@client.event
async def on_member_update(self, before, after):
if before.nick != after.nick:
await nick_changed(before, after)
if before.status != after.status:
await status_changed(before, after)
# after
@client.event
async def on_member_update(self, before, after):
if before.nick != after.nick:
await nick_changed(before, after)
@client.event
async def on_presence_update(self, before, after):
if before.status != after.status:
await status_changed(before, after)
Moving Away From Custom AsyncIterator¶
Asynchronous iterators in v1.0 were implemented using a special class named AsyncIterator
.
v2.0 instead provides regular asynchronous iterators with no added utility methods.
This means that usage of the following utility methods is no longer possible:
AsyncIterator.next()
Usage of an explicit
async for
loop should generally be preferred:# before it = channel.history() while True: try: message = await self.next() except discord.NoMoreItems: break print(f'Found message with ID {message.id}') # after async for message in channel.history(): print(f'Found message with ID {message.id}')
If you need to get next item from an iterator without a loop, you can use
anext()
(new in Python 3.10) or__anext__()
instead:# before it = channel.history() first = await it.next() if first.content == 'do not iterate': return async for message in it: ... # after it = channel.history() first = await anext(it) # await it.__anext__() on Python<3.10 if first.content == 'do not iterate': return async for message in it: ...
AsyncIterator.get()
# before msg = await channel.history().get(author__name='Dave') # after msg = await discord.utils.get(channel.history(), author__name='Dave')
AsyncIterator.find()
def predicate(event): return event.reason is not None # before event = await guild.audit_logs().find(predicate) # after event = await discord.utils.find(predicate, guild.audit_logs())
AsyncIterator.flatten()
# before users = await reaction.users().flatten() # after users = [user async for user in reaction.users()]
AsyncIterator.chunk()
# before async for leader, *users in reaction.users().chunk(3): ... # after async for leader, *users in discord.utils.as_chunks(reaction.users(), 3): ...
AsyncIterator.map()
# before content_of_messages = [] async for content in channel.history().map(lambda m: m.content): content_of_messages.append(content) # after content_of_messages = [message.content async for message in channel.history()]
AsyncIterator.filter()
def predicate(message): return not message.author.bot # before user_messages = [] async for message in channel.history().filter(lambda m: not m.author.bot): user_messages.append(message) # after user_messages = [message async for message in channel.history() if not m.author.bot]
To ease this transition, these changes have been made:
Added
utils.as_chunks()
as an alternative forAsyncIter.chunk
.Added support for asynchronous iterator to
utils.find()
.Added support for asynchronous iterator to
utils.get()
.
The return type of the following methods has been changed to an asynchronous iterator:
The NoMoreItems
exception was removed as calling anext()
or __anext__()
on an
asynchronous iterator will now raise StopAsyncIteration
.
Changing certain lists to be lazy sequences instead¶
In order to improve performance when calculating the length of certain lists, certain attributes were changed to return a sequence rather than a list
.
A sequence is similar to a list
except it is read-only. In order to get a list again you can call list
on the resulting sequence.
The following properties were changed to return a sequence instead of a list:
This change should be transparent, unless you are modifying the sequence by doing things such as list.append
.
Embed Changes¶
Originally, embeds used a special sentinel to denote emptiness or remove an attribute from display. The Embed.Empty
sentinel was made when Discord’s embed design was in a nebulous state of flux. Since then, the embed design has stabilised and thus the sentinel is seen as legacy.
Therefore, Embed.Empty
has been removed in favour of None
.
Additionally, Embed.__eq__
has been implemented thus embeds becoming unhashable (e.g. using them in sets or dict keys).
# before
embed = discord.Embed(title='foo')
embed.title = discord.Embed.Empty
embed == embed.copy() # False
# after
embed = discord.Embed(title='foo')
embed.title = None
embed == embed.copy() # True
{embed, embed} # Raises TypeError
Removal of InvalidArgument
Exception¶
The custom InvalidArgument
exception has been removed and functions and methods that
raised it are now raising TypeError
and/or ValueError
instead.
The following methods have been changed:
AutoShardedClient.change_presence()
Logging Changes¶
The library now provides a default logging configuration if using Client.run()
. To disable it, pass None
to the log_handler
keyword parameter. Since the library now provides a default logging configuration, certain methods were changed to no longer print to sys.stderr
but use the logger instead:
For more information, check Setting Up Logging.
Text in Voice¶
In order to support text in voice functionality, a few changes had to be made:
VoiceChannel
is nowabc.Messageable
so it can have messages sent and received.Message.channel
can now beVoiceChannel
andStageChannel
.
Removal of StoreChannel
¶
Discord’s API has removed store channels as of March 10th, 2022. Therefore, the library has removed support for it as well.
This removes the following:
StoreChannel
commands.StoreChannelConverter
Change in Guild.bans
endpoint¶
Due to a breaking API change by Discord, Guild.bans()
no longer returns a list of every ban in the guild but instead is paginated using an asynchronous iterator.
# before
bans = await guild.bans()
# after
async for ban in guild.bans(limit=1000):
...
Flag classes now have a custom bool()
implementation¶
To allow library users to easily check whether an instance of a flag class has any flags enabled,
using bool on them will now only return True
if at least one flag is enabled.
This means that evaluating instances of the following classes in a bool context (such as if obj:
) may no longer return True
:
Function Signature Changes¶
Parameters in the following methods are now all positional-only:
The following parameters are now positional-only:
iterable
inutils.get()
event_method
inClient.on_error()
event
inClient.wait_for()
dt
inutils.time_snowflake()
The following are now keyword-only:
Parameters in
Reaction.users()
Parameters in
Client.create_guild()
permissions
,guild
,redirect_uri
, andscopes
parameters inutils.oauth_url()
high
inutils.snowflake_time()
The library now less often uses None
as the default value for function/method parameters.
As a result, these parameters can no longer be None
:
size
,format
, andstatic_format
inAsset.replace()
check
inTextChannel.purge()
icon
andcode
inClient.create_guild()
roles
inEmoji.edit()
topic
,position
andoverwrites
inGuild.create_text_channel()
position
andoverwrites
inGuild.create_voice_channel()
topic
,position
andoverwrites
inGuild.create_stage_channel()
position
andoverwrites
inGuild.create_category()
roles
inGuild.prune_members()
roles
inGuild.estimate_pruned_members()
description
inGuild.create_template()
roles
inGuild.create_custom_emoji()
before
,after
,oldest_first
,user
, andaction
inGuild.audit_logs()
enable_emoticons
inStreamIntegration.edit()
mute
,deafen
,suppress
, androles
inMember.edit()
position
inRole.edit()
icon
inTemplate.create_guild()
name
inTemplate.edit()
permissions
,guild
,redirect_uri
,scopes
inutils.oauth_url()
content
,username
,avatar_url
,tts
,file
,files
,embed
,embeds
, andallowed_mentions
inWebhook.send()
Allowed types for the following parameters have been changed:
rtc_region
inGuild.create_voice_channel()
is now of type Optional[str
].rtc_region
inStageChannel.edit()
is now of type Optional[str
].rtc_region
inVoiceChannel.edit()
is now of type Optional[str
].preferred_locale
inGuild.edit()
is now of typeLocale
.
Attribute Type Changes¶
The following changes have been made:
DMChannel.recipient
may now beNone
.Guild.vanity_invite()
may now beNone
. This has been done to fix an issue with the method returning a brokenInvite
object.Widget.fetch_invite()
may now beNone
.Guild.mfa_level
is now of typeMFALevel
.Guild.member_count
is now of type Optional[int
].AuditLogDiff.mfa_level
is now of typeMFALevel
.AuditLogDiff.rtc_region
is now of typestr
.StageChannel.rtc_region
is now of typestr
.VoiceChannel.rtc_region
is now of typestr
.ClientUser.avatar
is nowNone
when the default avatar is used.If you want the avatar that a user has displayed, consider
ClientUser.display_avatar
.
Member.avatar
is nowNone
when the default avatar is used.If you want the avatar that a member or user has displayed, consider
Member.display_avatar
orUser.display_avatar
.
User.avatar
is nowNone
when the default avatar is used.If you want the avatar that a user has displayed, consider
User.display_avatar
.
Webhook.avatar
is nowNone
when the default avatar is used.If you want the avatar that a webhook has displayed, consider
Webhook.display_avatar
.
AuditLogEntry.target
may now be aPartialMessageable
.PartialMessage.channel
may now be aPartialMessageable
.Guild.preferred_locale
is now of typeLocale
.abc.GuildChannel.overwrites
keys can now haveObject
in them.
Removals¶
The following deprecated functionality have been removed:
Client.request_offline_members
Use
Guild.chunk()
instead.
AutoShardedClient.request_offline_members
Use
Guild.chunk()
instead.
Client.logout
Use
Client.close()
instead.
fetch_offline_members
parameter fromClient
constructorUse
chunk_guild_at_startup
instead.
Permissions.use_slash_commands
andPermissionOverwrite.use_slash_commands
Use
Permissions.use_application_commands
andPermissionOverwrite.use_application_commands
instead.
The following have been removed:
MemberCacheFlags.online
There is no replacement for this one. The current API version no longer provides enough data for this to be possible.
User.permissions_in
andMember.permissions_in
Use
abc.GuildChannel.permissions_for()
instead.
guild_subscriptions
parameter fromClient
constructorThe current API version no longer provides this functionality.
guild_subscription_options
parameter fromClient
constructorThe auto-subscribing system has been removed.
chunk_guilds_at_startup
is now utilised properly.
VerificationLevel
aliases:VerificationLevel.table_flip
- useVerificationLevel.high
instead.VerificationLevel.extreme
- useVerificationLevel.highest
instead.VerificationLevel.double_table_flip
- useVerificationLevel.highest
instead.VerificationLevel.very_high
- useVerificationLevel.highest
instead.
topic
parameter fromStageChannel.edit()
The
topic
parameter must now be set viaStageChannel.create_instance()
.
Reaction.custom_emoji
Use
Reaction.is_custom_emoji()
instead.
AuditLogDiff.region
Guild.region
VoiceRegion
This has been marked deprecated by Discord and it was usually more or less out of date due to the pace they added them anyway.
region
parameter fromClient.create_guild()
region
parameter fromTemplate.create_guild()
region
parameter fromGuild.edit()
The undocumented private
on_socket_response
eventConsider using the newer documented
on_socket_event_type()
event instead.
abc.Messageable.trigger_typing
Use
abc.Messageable.typing()
withawait
instead.
Miscellaneous Changes¶
The following changes have been made:
on_socket_raw_receive()
is now only called ifenable_debug_events
is set onClient
.on_socket_raw_receive()
is now only called once the complete message is received and decompressed. The passedmsg
parameter is now alwaysstr
.on_socket_raw_send()
is now only called ifenable_debug_events
is set onClient
.The documented return type for
Guild.fetch_channels()
changed to Sequence[abc.GuildChannel
].utils.resolve_invite()
now returns aResolvedInvite
class.utils.oauth_url()
now defaults tobot
andapplications.commands
scopes when not given instead of justbot
.abc.Messageable.typing()
can no longer be used as a regular (non-async) context manager.created_at
is no longer part ofabc.Snowflake
.All of the existing classes still keep this attribute. It is just no longer part of this protocol. This has been done because Discord reuses IDs (snowflakes) of some models in other models. For example, if
Thread
is created from a message, itsThread.id
is equivalent to the ID of that message and as such it doesn’t contain information about creation time of the thread andThread.created_at
cannot be based on it.Embed
’s bool implementation now returnsTrue
when embed has any data set.Calling
Emoji.edit()
withoutroles
argument no longer makes the emoji available to everyone.To make the emoji available to everyone, pass an empty list to
roles
instead.
The old
Colour.blurple
has been renamed toColour.og_blurple
.Colour.blurple
refers to a different colour now.
Message.type
is now set toMessageType.reply
when a message is a reply.This is caused by a difference in behavior in the current Discord API version.
Message.edit()
now merges object passed inallowed_mentions
parameter withClient.allowed_mentions
. If the parameter isn’t provided, the defaults given byClient.allowed_mentions
are used instead.Permissions.stage_moderator()
now includes thePermissions.manage_channels
permission and thePermissions.request_to_speak
permission is no longer included.File.filename
will no longer beNone
, in situations where previously this was the case the filename is set to'untitled'
.
VoiceProtocol.connect()
signature changes.¶
VoiceProtocol.connect()
will now be passed 2 keyword only arguments, self_deaf
and self_mute
. These indicate
whether or not the client should join the voice chat being deafened or muted.
Command Extension Changes¶
Extension and Cog Loading / Unloading is Now Asynchronous¶
As an extension to the asyncio changes the loading and unloading of extensions and cogs is now asynchronous.
To accommodate this, the following changes have been made:
The
setup
andteardown
functions in extensions must now be coroutines.ext.commands.Bot.load_extension()
must now be awaited.ext.commands.Bot.unload_extension()
must now be awaited.ext.commands.Bot.reload_extension()
must now be awaited.ext.commands.Bot.add_cog()
must now be awaited.ext.commands.Bot.remove_cog()
must now be awaited.
Quick example of an extension setup function:
# before
def setup(bot):
bot.add_cog(MyCog(bot))
# after
async def setup(bot):
await bot.add_cog(MyCog(bot))
Quick example of loading an extension:
# before
bot.load_extension('my_extension')
# after using setup_hook
class MyBot(commands.Bot):
async def setup_hook(self):
await self.load_extension('my_extension')
# after using async_with
async def main():
async with bot:
await bot.load_extension('my_extension')
await bot.start(TOKEN)
asyncio.run(main())
Converters Are Now Generic Runtime Protocols¶
Converter
is now a runtime-checkable
typing.Protocol
.
This results in a change of the base metaclass used by these classes
which may affect user-created classes that inherit from Converter
.
Quick example:
# before
class SomeConverterMeta(type):
...
class SomeConverter(commands.Converter, metaclass=SomeConverterMeta):
...
# after
class SomeConverterMeta(type(commands.Converter)):
...
class SomeConverter(commands.Converter, metaclass=SomeConverterMeta):
...
In addition, Converter
is now a typing.Generic
which (optionally) allows the users to
define their type hints more accurately.
Function Signature Changes¶
Parameters in the following methods are now all positional-only:
The following parameters are now positional-only:
func
inext.commands.Bot.check()
func
inext.commands.Bot.add_check()
func
inext.commands.Bot.remove_check()
func
inext.commands.Bot.check_once()
func
inext.commands.Bot.add_listener()
message
inext.commands.Bot.get_context()
func
inext.commands.Command.add_check()
context
inext.commands.Command.__call__()
commands
inext.commands.HelpCommand.filter_commands()
commands
inext.commands.DefaultHelpCommand.add_indented_commands()
cog
inext.commands.Bot.add_cog()
name
inext.commands.Bot.get_cog()
name
inext.commands.Bot.remove_cog()
command
inext.commands.Context.invoke()
command
inext.commands.GroupMixin.add_command()
The following parameters have been added:
user_bot
inBot
Allows your bot to reply to everyone’s message.
The library now less often uses None
as the default value for function/method parameters.
As a result, these parameters can no longer be None
:
name
inext.commands.Bot.add_listener()
name
inext.commands.Bot.listen()
name
inext.commands.Cog.listener()
name
inext.commands.Command()
name
andcls
inext.commands.command()
name
andcls
inext.commands.group()
Removals¶
The following attributes have been removed:
original
from theExtensionNotFound
type
from theCooldown
class that was provided by theext.commands.CommandOnCooldown.cooldown
attributeUse
ext.commands.CommandOnCooldown.type
instead.
clean_prefix
from theHelpCommand
Use
ext.commands.Context.clean_prefix
instead.
Miscellaneous Changes¶
ext.commands.Bot.add_cog()
is now raisingClientException
when a cog with the same name is already loaded.To override a cog, the new
override
parameter can be used.
When passing a callable to
type
argument ofcooldown()
, it now needs to acceptContext
rather thanMessage
as its only argument.Metaclass of
Context
changed fromabc.ABCMeta
totype
.Changed type of
ext.commands.Command.clean_params
fromcollections.OrderedDict
todict
. as the latter is guaranteed to preserve insertion order since Python 3.7.ext.commands.ChannelNotReadable.argument
may now be aThread
due to the Thread Support changes.ext.commands.NSFWChannelRequired.channel
may now be aThread
due to the Thread Support changes.ext.commands.Context.channel
may now be aThread
due to the Thread Support changes.ext.commands.Context.channel
may now be aPartialMessageable
.MissingPermissions.missing_perms
has been renamed toext.commands.MissingPermissions.missing_permissions
.BotMissingPermissions.missing_perms
has been renamed toext.commands.BotMissingPermissions.missing_permissions
.ext.commands.Cog.cog_load()
has been added as part of the Extension and Cog Loading / Unloading is Now Asynchronous changes.ext.commands.Cog.cog_unload()
may now be a coroutine due to the Extension and Cog Loading / Unloading is Now Asynchronous changes.ext.commands.Command.clean_params
type now uses a custominspect.Parameter
to handle defaults.
Tasks Extension Changes¶
Calling
ext.tasks.Loop.stop()
inbefore_loop()
now stops the first iteration from running.Calling
ext.tasks.Loop.change_interval()
now changes the interval for the sleep time right away, rather than on the next loop iteration.loop
parameter inext.tasks.loop()
can no longer beNone
.