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.
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.
Removal of Support For User Accounts¶
Logging on with a user token is against the Discord Terms of Service and as such all support for user-only endpoints has been removed.
The following have been removed:
botparameter toClient.login()andClient.start()afkparameter toClient.change_presence()password,new_password,email, andhouseparameters toClientUser.edit()CallMessagemodelGroupCallmodelProfilemodelRelationshipmodelRelationshipTypeenumerationHypeSquadHouseenumerationPremiumTypeenumerationUserContentFilterenumerationFriendFlagsenumerationThemeenumerationon_relationship_addeventon_relationship_removeeventon_relationship_updateeventClient.fetch_user_profilemethodClientUser.create_groupmethodClientUser.edit_settingsmethodClientUser.get_relationshipmethodGroupChannel.add_recipientsmethodGroupChannel.remove_recipientsmethodGroupChannel.editmethodGuild.ackmethodMessage.ackmethodUser.blockmethodUser.is_blockedmethodUser.is_friendmethodUser.profilemethodUser.remove_friendmethodUser.send_friend_requestmethodUser.unblockmethodClientUser.blockedattributeClientUser.emailattributeClientUser.friendsattributeClientUser.premiumattributeClientUser.premium_typeattributeClientUser.relationshipsattributeMessage.callattributeUser.mutual_friendsattributeUser.relationshipattribute
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 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.
Intents Are Now Required¶
In earlier versions, the intents keyword argument was optional and defaulted to Intents.default(). In order to better educate users on their intents and to also make it more explicit, this parameter is now required to pass in.
For example:
# before
client = discord.Client()
# after
intents = discord.Intents.default()
client = discord.Client(intents=intents)
This change applies to all subclasses of Client.
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 Protocols:
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_atattributeBaseActivity.created_atattributeClientUser.created_atattributeDMChannel.created_atattributeEmoji.created_atattributeGroupChannel.created_atattributeGuild.created_atattributeabc.GuildChannel.created_atattributeInvite.created_atattributeObject.created_atattributeMember.created_atattributeMessage.created_atattributePartialEmoji.created_atattributePartialInviteChannel.created_atattributePartialInviteGuild.created_atattributePartialMessage.created_atattributeRole.created_atattributeSpotify.created_atattributeSticker.created_atattributeTeamMember.created_atattributeTemplate.created_atattributeUser.created_atattributeWebhook.created_atattributeWidget.created_atattributeWidgetChannel.created_atattributeWidgetMember.created_atattributeMessage.edited_atattributeInvite.expires_atattributeActivity.endattributeGame.endattributeSpotify.endattributeMember.joined_atattributeMember.premium_sinceattributeVoiceState.requested_to_speak_atattributeActivity.startattributeGame.startattributeSpotify.startattributeStreamIntegration.synced_atattributeEmbed.timestampattributeTemplate.updated_atattributetimestampparameter inon_typing()eventlast_pinparameter inon_private_channel_pins_update()eventlast_pinparameter 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()methodEmbedconstructorEmbed.timestampproperty 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_untilparameter 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
WebhookandWebhookMessagehas been split toSyncWebhookandSyncWebhookMessage.WebhookAdapterclass has been removed and the interfaces based on it (AsyncWebhookAdapterandRequestsWebhookAdapter) are now considered implementation detail and should not be depended on.executealias 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(ofstrtype) has been replaced withGuild.icon.key.Guild.is_icon_animatedhas been replaced withGuild.icon.is_animated.Guild.icon_urlhas been replaced withGuild.icon.Guild.icon_url_ashas 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_ashas been removed.Emoji.url.readhas been replaced withEmoji.read().Emoji.url.savehas been replaced withEmoji.save().
Asset now always represent an actually existing CDN asset. This means that:
str(x)on anAssetcan no longer return an empty string.bool(x)on anAssetcan no longer returnFalse.Attributes containing an optional
Assetcan now beNone.
The following were affected by this change:
-
AppInfo.cover_image(replaced byAppInfo.cover_image.key)AppInfo.cover_image_url(replaced byAppInfo.cover_image)The new attribute may now be
None.
AppInfo.cover_image_url_as(replaced byAppInfo.cover_image.replace)
-
AppInfo.icon(replaced byAppInfo.icon.key)AppInfo.icon_url(replaced byAppInfo.icon)The new attribute may now be
None.
AppInfo.icon_url_as(replaced byAppInfo.icon.replace)
-
AuditLogDiff.avataris now ofAssettype.AuditLogDiff.iconis now ofAssettype.AuditLogDiff.splashis now ofAssettype.
-
Emoji.url_ashas 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.urlis now ofstrtype.PartialEmoji.url_ashas 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_rolesoverwritespermissions_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
Threaddoes not have annsfwattribute.
Threads do not have their own topic.
This means that
Threaddoes not have atopicattribute.
Threads do not have their own position in the channel list.
This means that
Threaddoes not have apositionattribute.
Thread.created_atof threads created before 10 January 2022 isNone.Thread.membersis of type List[ThreadMember] rather than List[Member]Most of the time, this data is not provided and a call to
Thread.fetch_members()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_messagespermission in it.- A guild member can send messages in a public thread if:
They have
send_messages_in_threadspermission 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_threadspermission in its parent channel.They’re either already a member of the thread or have a
manage_threadspermission in its parent channel.The thread is not
locked.
A guild member can edit a text channel if they have
manage_channelspermission in it.A guild member can edit a thread if they have
manage_threadspermission in its parent channel.Note
A thread’s
ownercan archive a (not-locked) thread and edit itsnameandauto_archive_durationwithoutmanage_threadspermission.- A guild member can react with an emoji to messages in a text channel if:
They have
read_message_historypermission in it.They have
add_reactionspermission 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_historypermission in its parent channel.They have
add_reactionspermission 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_historypermission in its parent channel.They have
add_reactionspermission 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_threadspermission 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.channelmay now be aThread.Message.channel_mentionslist may now contain aThread.AuditLogEntry.targetmay now be aThread.PartialMessage.channelmay now be aThread.Guild.get_channeldoes not returnThreads.If you’re looking to get a channel or thread, use
Guild.get_channel_or_threadinstead.If you’re only looking to get threads, use
Guild.get_threadorTextChannel.get_threadinstead.
channelparameter inon_guild_channel_pins_update()may now be aThread.channelparameter 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
Noneinstead ofCategoryChannelif the edit was only positional.
-
Note that this method only returns the updated
Memberwhen certain fields are updated.
-
Note that this method will return
Noneinstead ofStageChannelif the edit was only positional.
-
Note that this method will return
Noneinstead ofTextChannelif the edit was only positional.
-
Note that this method will return
Noneinstead ofVoiceChannelif 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.stickerschanged to List[StickerItem].To get the
StickerfromStickerItem, useStickerItem.fetch()or (only for stickers from guilds the bot is in)Client.get_sticker().
Sticker.formatis now ofStickerFormatTypetype.Sticker.tagshas been removed.Depending on type of the sticker,
StandardSticker.tagsorGuildSticker.emojican be used instead.
Sticker.imageand related methods have been removed.Sticker.preview_imageand related methods have been removed.AuditLogDiff.typeis now of Union[ChannelType,StickerType] type.The old
StickerTypeenum has been renamed toStickerFormatType.StickerTypenow 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
Integrationclass has been renamed toStreamIntegration.Guild.integrations()now returns subclasses of the newIntegrationclass.
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.
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 forloop 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:
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:
VoiceChannelis nowabc.Messageableso it can have messages sent and received.Message.channelcan now beVoiceChannel.
In the future this may include StageChannel when Discord implements it.
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:
StoreChannelcommands.StoreChannelConverterChannelType.store
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:
iterableinutils.get()event_methodinClient.on_error()eventinClient.wait_for()dtinutils.time_snowflake()
The following are now keyword-only:
Parameters in
Reaction.users()Parameters in
Client.create_guild()permissions,guild,redirect_uri, andscopesparameters inutils.oauth_url()highinutils.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_formatinAsset.replace()checkinTextChannel.purge()iconandcodeinClient.create_guild()rolesinEmoji.edit()topic,positionandoverwritesinGuild.create_text_channel()positionandoverwritesinGuild.create_voice_channel()topic,positionandoverwritesinGuild.create_stage_channel()positionandoverwritesinGuild.create_category()rolesinGuild.prune_members()rolesinGuild.estimate_pruned_members()descriptioninGuild.create_template()rolesinGuild.create_custom_emoji()before,after,oldest_first,user, andactioninGuild.audit_logs()enable_emoticonsinStreamIntegration.edit()mute,deafen,suppress, androlesinMember.edit()positioninRole.edit()iconinTemplate.create_guild()nameinTemplate.edit()permissions,guild,redirect_uri,scopesinutils.oauth_url()content,username,avatar_url,tts,file,files,embed,embeds, andallowed_mentionsinWebhook.send()
Allowed types for the following parameters have been changed:
rtc_regioninGuild.create_voice_channel()is now of type Optional[str].rtc_regioninStageChannel.edit()is now of type Optional[str].rtc_regioninVoiceChannel.edit()is now of type Optional[str].preferred_localeinGuild.edit()is now of typeLocale.
Attribute Type Changes¶
The following changes have been made:
DMChannel.recipientmay now beNone.Guild.vanity_invite()may now beNone. This has been done to fix an issue with the method returning a brokenInviteobject.Widget.fetch_invite()may now beNone.Guild.shard_idis now0instead ofNoneifAutoShardedClientis not used.Guild.mfa_levelis now of typeMFALevel.Guild.member_countis now of type Optional[int].AuditLogDiff.mfa_levelis now of typeMFALevel.AuditLogDiff.rtc_regionis now of typestr.StageChannel.rtc_regionis now of typestr.VoiceChannel.rtc_regionis now of typestr.ClientUser.avataris nowNonewhen the default avatar is used.If you want the avatar that a user has displayed, consider
ClientUser.display_avatar.
Member.avataris nowNonewhen the default avatar is used.If you want the avatar that a member or user has displayed, consider
Member.display_avatarorUser.display_avatar.
User.avataris nowNonewhen the default avatar is used.If you want the avatar that a user has displayed, consider
User.display_avatar.
Webhook.avataris nowNonewhen the default avatar is used.If you want the avatar that a webhook has displayed, consider
Webhook.display_avatar.
AuditLogEntry.targetmay now be aPartialMessageable.PartialMessage.channelmay now be aPartialMessageable.Guild.preferred_localeis now of typeLocale.abc.GuildChannel.overwriteskeys can now haveObjectin them.
Removals¶
The following deprecated functionality have been removed:
Client.request_offline_membersUse
Guild.chunk()instead.
AutoShardedClient.request_offline_membersUse
Guild.chunk()instead.
Client.logoutUse
Client.close()instead.
fetch_offline_membersparameter fromClientconstructorUse
chunk_guild_at_startupinstead.
Permissions.use_slash_commandsandPermissionOverwrite.use_slash_commandsUse
Permissions.use_application_commandsandPermissionOverwrite.use_application_commandsinstead.
The following have been removed:
MemberCacheFlags.onlineThere is no replacement for this one. The current API version no longer provides enough data for this to be possible.
AppInfo.summaryThere is no replacement for this one. The current API version no longer provides this field.
User.permissions_inandMember.permissions_inUse
abc.GuildChannel.permissions_for()instead.
guild_subscriptionsparameter fromClientconstructorThe current API version no longer provides this functionality. Use
intentsparameter instead.
VerificationLevelaliases:VerificationLevel.table_flip- useVerificationLevel.highinstead.VerificationLevel.extreme- useVerificationLevel.highestinstead.VerificationLevel.double_table_flip- useVerificationLevel.highestinstead.VerificationLevel.very_high- useVerificationLevel.highestinstead.
topicparameter fromStageChannel.edit()The
topicparameter must now be set viaStageChannel.create_instance().
Reaction.custom_emojiUse
Reaction.is_custom_emoji()instead.
AuditLogDiff.regionGuild.regionVoiceRegionThis has been marked deprecated by Discord and it was usually more or less out of date due to the pace they added them anyway.
regionparameter fromClient.create_guild()regionparameter fromTemplate.create_guild()regionparameter fromGuild.edit()on_private_channel_createeventDiscord API no longer sends channel create event for DMs.
on_private_channel_deleteeventDiscord API no longer sends channel create event for DMs.
The undocumented private
on_socket_responseeventConsider using the newer documented
on_socket_event_type()event instead.
abc.Messageable.trigger_typingUse
abc.Messageable.typing()withawaitinstead.
Miscellaneous Changes¶
The following changes have been made:
on_socket_raw_receive()is now only called ifenable_debug_eventsis set onClient.on_socket_raw_receive()is now only called once the complete message is received and decompressed. The passedmsgparameter is now alwaysstr.on_socket_raw_send()is now only called ifenable_debug_eventsis set onClient.The documented return type for
Guild.fetch_channels()changed to Sequence[abc.GuildChannel].utils.resolve_invite()now returns aResolvedInviteclass.utils.oauth_url()now defaults tobotandapplications.commandsscopes when not given instead of justbot.abc.Messageable.typing()can no longer be used as a regular (non-async) context manager.Intents.emojisis now an alias ofIntents.emojis_and_stickers.This may affect code that iterates through
(name, value)pairs in an instance of this class:# before friendly_names = { ..., 'emojis': 'Emojis Intent', ..., } for name, value in discord.Intents.all(): print(f'{friendly_names[name]}: {value}') # after friendly_names = { ..., 'emojis_and_stickers': 'Emojis Intent', ..., } for name, value in discord.Intents.all(): print(f'{friendly_names[name]}: {value}')
created_atis 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
Threadis created from a message, itsThread.idis equivalent to the ID of that message and as such it doesn’t contain information about creation time of the thread andThread.created_atcannot be based on it.Embed’s bool implementation now returnsTruewhen embed has any data set.Calling
Emoji.edit()withoutrolesargument no longer makes the emoji available to everyone.To make the emoji available to everyone, pass an empty list to
rolesinstead.
The old
Colour.blurplehas been renamed toColour.og_blurple.Colour.blurplerefers to a different colour now.
Message.typeis now set toMessageType.replywhen 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_mentionsparameter withClient.allowed_mentions. If the parameter isn’t provided, the defaults given byClient.allowed_mentionsare used instead.Permissions.stage_moderator()now includes thePermissions.manage_channelspermission and thePermissions.request_to_speakpermission is no longer included.File.filenamewill no longer beNone, in situations where previously this was the case the filename is set to'untitled'.Message.applicationwill no longer be a rawdictof the API payload and now returns an instance ofMessageApplication.
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
setupandteardownfunctions 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:
funcinext.commands.Bot.check()funcinext.commands.Bot.add_check()funcinext.commands.Bot.remove_check()funcinext.commands.Bot.check_once()funcinext.commands.Bot.add_listener()messageinext.commands.Bot.get_context()funcinext.commands.Command.add_check()contextinext.commands.Command.__call__()commandsinext.commands.HelpCommand.filter_commands()commandsinext.commands.DefaultHelpCommand.add_indented_commands()coginext.commands.Bot.add_cog()nameinext.commands.Bot.get_cog()nameinext.commands.Bot.remove_cog()commandinext.commands.Context.invoke()commandinext.commands.GroupMixin.add_command()
The following parameters have been removed:
self_botfromBotThis has been done due to the Removal of Support For User Accounts changes.
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:
nameinext.commands.Bot.add_listener()nameinext.commands.Bot.listen()nameinext.commands.Cog.listener()nameinext.commands.Command()nameandclsinext.commands.command()nameandclsinext.commands.group()
Removals¶
The following attributes have been removed:
originalfrom theExtensionNotFoundtypefrom theCooldownclass that was provided by theext.commands.CommandOnCooldown.cooldownattributeUse
ext.commands.CommandOnCooldown.typeinstead.
clean_prefixfrom theHelpCommandUse
ext.commands.Context.clean_prefixinstead.
Miscellaneous Changes¶
ext.commands.Bot.add_cog()is now raisingClientExceptionwhen a cog with the same name is already loaded.To override a cog, the new
overrideparameter can be used.
When passing a callable to
typeargument ofcooldown(), it now needs to acceptContextrather thanMessageas its only argument.Metaclass of
Contextchanged fromabc.ABCMetatotype.Changed type of
ext.commands.Command.clean_paramsfromcollections.OrderedDicttodict. As the latter is guaranteed to preserve insertion order since Python 3.7.ext.commands.ChannelNotReadable.argumentmay now be aThreaddue to the Thread Support changes.ext.commands.NSFWChannelRequired.channelmay now be aThreaddue to the Thread Support changes.ext.commands.Context.channelmay now be aThreaddue to the Thread Support changes.ext.commands.Context.channelmay now be aPartialMessageable.MissingPermissions.missing_permshas been renamed toext.commands.MissingPermissions.missing_permissions.BotMissingPermissions.missing_permshas 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_paramstype now uses a custominspect.Parameterto 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.loopparameter inext.tasks.loop()can no longer beNone.
Migrating to v1.0¶
The contents of that migration has been moved to Migrating to v1.0.