Browse Source

Merge branch 'master' into feature/guild_onboarding

pull/9260/head
Josh 2 years ago
parent
commit
5307f1a6eb
  1. 4
      discord/__init__.py
  2. 15
      discord/abc.py
  3. 42
      discord/appinfo.py
  4. 15
      discord/audit_logs.py
  5. 56
      discord/automod.py
  6. 261
      discord/channel.py
  7. 14
      discord/client.py
  8. 5
      discord/colour.py
  9. 16
      discord/enums.py
  10. 13
      discord/ext/commands/cog.py
  11. 170
      discord/ext/commands/context.py
  12. 2
      discord/ext/commands/converter.py
  13. 7
      discord/ext/commands/errors.py
  14. 11
      discord/ext/commands/hybrid.py
  15. 15
      discord/gateway.py
  16. 284
      discord/guild.py
  17. 13
      discord/http.py
  18. 5
      discord/interactions.py
  19. 105
      discord/message.py
  20. 46
      discord/permissions.py
  21. 112
      discord/scheduled_event.py
  22. 2
      discord/sticker.py
  23. 17
      discord/types/appinfo.py
  24. 6
      discord/types/automod.py
  25. 2
      discord/types/channel.py
  26. 4
      discord/types/guild.py
  27. 2
      discord/types/sticker.py
  28. 2
      discord/ui/item.py
  29. 14
      discord/ui/select.py
  30. 12
      discord/webhook/sync.py
  31. 2
      discord/widget.py
  32. 86
      docs/api.rst
  33. 2
      docs/ext/commands/api.rst
  34. 1
      docs/ext/commands/commands.rst
  35. 90
      docs/whats_new.rst

4
discord/__init__.py

@ -13,7 +13,7 @@ __title__ = 'discord'
__author__ = 'Rapptz'
__license__ = 'MIT'
__copyright__ = 'Copyright 2015-present Rapptz'
__version__ = '2.2.0a'
__version__ = '2.3.0a'
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
@ -79,7 +79,7 @@ class VersionInfo(NamedTuple):
serial: int
version_info: VersionInfo = VersionInfo(major=2, minor=2, micro=0, releaselevel='alpha', serial=0)
version_info: VersionInfo = VersionInfo(major=2, minor=3, micro=0, releaselevel='alpha', serial=0)
logging.getLogger(__name__).addHandler(logging.NullHandler())

15
discord/abc.py

@ -83,7 +83,15 @@ if TYPE_CHECKING:
from .channel import CategoryChannel
from .embeds import Embed
from .message import Message, MessageReference, PartialMessage
from .channel import TextChannel, DMChannel, GroupChannel, PartialMessageable, VoiceChannel
from .channel import (
TextChannel,
DMChannel,
GroupChannel,
PartialMessageable,
VocalGuildChannel,
VoiceChannel,
StageChannel,
)
from .threads import Thread
from .enums import InviteTarget
from .ui.view import View
@ -97,7 +105,7 @@ if TYPE_CHECKING:
SnowflakeList,
)
PartialMessageableChannel = Union[TextChannel, VoiceChannel, Thread, DMChannel, PartialMessageable]
PartialMessageableChannel = Union[TextChannel, VoiceChannel, StageChannel, Thread, DMChannel, PartialMessageable]
MessageableChannel = Union[PartialMessageableChannel, GroupChannel]
SnowflakeTime = Union["Snowflake", datetime]
@ -118,7 +126,7 @@ async def _single_delete_strategy(messages: Iterable[Message], *, reason: Option
async def _purge_helper(
channel: Union[Thread, TextChannel, VoiceChannel],
channel: Union[Thread, TextChannel, VocalGuildChannel],
*,
limit: Optional[int] = 100,
check: Callable[[Message], bool] = MISSING,
@ -1284,6 +1292,7 @@ class Messageable:
- :class:`~discord.TextChannel`
- :class:`~discord.VoiceChannel`
- :class:`~discord.StageChannel`
- :class:`~discord.DMChannel`
- :class:`~discord.GroupChannel`
- :class:`~discord.PartialMessageable`

42
discord/appinfo.py

@ -166,7 +166,7 @@ class AppInfo:
self.name: str = data['name']
self.description: str = data['description']
self._icon: Optional[str] = data['icon']
self.rpc_origins: List[str] = data['rpc_origins']
self.rpc_origins: Optional[List[str]] = data.get('rpc_origins')
self.bot_public: bool = data['bot_public']
self.bot_require_code_grant: bool = data['bot_require_code_grant']
self.owner: User = state.create_user(data['owner'])
@ -255,6 +255,24 @@ class PartialAppInfo:
The application's terms of service URL, if set.
privacy_policy_url: Optional[:class:`str`]
The application's privacy policy URL, if set.
approximate_guild_count: :class:`int`
The approximate count of the guilds the bot was added to.
.. versionadded:: 2.3
redirect_uris: List[:class:`str`]
A list of authentication redirect URIs.
.. versionadded:: 2.3
interactions_endpoint_url: Optional[:class:`str`]
The interactions endpoint url of the application to receive interactions over this endpoint rather than
over the gateway, if configured.
.. versionadded:: 2.3
role_connections_verification_url: Optional[:class:`str`]
The application's connection verification URL which will render the application as
a verification method in the guild's role verification configuration.
.. versionadded:: 2.3
"""
__slots__ = (
@ -268,6 +286,11 @@ class PartialAppInfo:
'privacy_policy_url',
'_icon',
'_flags',
'_cover_image',
'approximate_guild_count',
'redirect_uris',
'interactions_endpoint_url',
'role_connections_verification_url',
)
def __init__(self, *, state: ConnectionState, data: PartialAppInfoPayload):
@ -276,11 +299,16 @@ class PartialAppInfo:
self.name: str = data['name']
self._icon: Optional[str] = data.get('icon')
self._flags: int = data.get('flags', 0)
self._cover_image: Optional[str] = data.get('cover_image')
self.description: str = data['description']
self.rpc_origins: Optional[List[str]] = data.get('rpc_origins')
self.verify_key: str = data['verify_key']
self.terms_of_service_url: Optional[str] = data.get('terms_of_service_url')
self.privacy_policy_url: Optional[str] = data.get('privacy_policy_url')
self.approximate_guild_count: int = data.get('approximate_guild_count', 0)
self.redirect_uris: List[str] = data.get('redirect_uris', [])
self.interactions_endpoint_url: Optional[str] = data.get('interactions_endpoint_url')
self.role_connections_verification_url: Optional[str] = data.get('role_connections_verification_url')
def __repr__(self) -> str:
return f'<{self.__class__.__name__} id={self.id} name={self.name!r} description={self.description!r}>'
@ -292,6 +320,18 @@ class PartialAppInfo:
return None
return Asset._from_icon(self._state, self.id, self._icon, path='app')
@property
def cover_image(self) -> Optional[Asset]:
"""Optional[:class:`.Asset`]: Retrieves the cover image of the application's default rich presence.
This is only available if the application is a game sold on Discord.
.. versionadded:: 2.3
"""
if self._cover_image is None:
return None
return Asset._from_cover_image(self._state, self.id, self._cover_image)
@property
def flags(self) -> ApplicationFlags:
""":class:`ApplicationFlags`: The application's flags.

15
discord/audit_logs.py

@ -521,7 +521,7 @@ class _AuditLogProxyMessageBulkDelete(_AuditLogProxy):
class _AuditLogProxyAutoModAction(_AuditLogProxy):
automod_rule_name: str
automod_rule_trigger_type: str
channel: Union[abc.GuildChannel, Thread]
channel: Optional[Union[abc.GuildChannel, Thread]]
class AuditLogEntry(Hashable):
@ -644,13 +644,17 @@ class AuditLogEntry(Hashable):
or self.action is enums.AuditLogAction.automod_flag_message
or self.action is enums.AuditLogAction.automod_timeout_member
):
channel_id = int(extra['channel_id'])
channel_id = utils._get_as_snowflake(extra, 'channel_id')
channel = None
if channel_id is not None:
channel = self.guild.get_channel_or_thread(channel_id) or Object(id=channel_id)
self.extra = _AuditLogProxyAutoModAction(
automod_rule_name=extra['auto_moderation_rule_name'],
automod_rule_trigger_type=enums.try_enum(
enums.AutoModRuleTriggerType, extra['auto_moderation_rule_trigger_type']
),
channel=self.guild.get_channel_or_thread(channel_id) or Object(id=channel_id),
channel=channel,
)
elif self.action.name.startswith('overwrite_'):
@ -723,11 +727,12 @@ class AuditLogEntry(Hashable):
if self.action.target_type is None:
return None
if self._target_id is None:
return None
try:
converter = getattr(self, '_convert_target_' + self.action.target_type)
except AttributeError:
if self._target_id is None:
return None
return Object(id=self._target_id)
else:
return converter(self._target_id)

56
discord/automod.py

@ -25,8 +25,7 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations
import datetime
from typing import TYPE_CHECKING, Any, Dict, Optional, List, Sequence, Set, Union, Sequence
from typing import TYPE_CHECKING, Any, Dict, Optional, List, Set, Union, Sequence, overload
from .enums import AutoModRuleTriggerType, AutoModRuleActionType, AutoModRuleEventType, try_enum
from .flags import AutoModPresets
@ -59,6 +58,9 @@ __all__ = (
class AutoModRuleAction:
"""Represents an auto moderation's rule action.
.. note::
Only one of ``channel_id``, ``duration``, or ``custom_message`` can be used.
.. versionadded:: 2.0
Attributes
@ -73,40 +75,65 @@ class AutoModRuleAction:
The duration of the timeout to apply, if any.
Has a maximum of 28 days.
Passing this sets :attr:`type` to :attr:`~AutoModRuleActionType.timeout`.
custom_message: Optional[:class:`str`]
A custom message which will be shown to a user when their message is blocked.
Passing this sets :attr:`type` to :attr:`~AutoModRuleActionType.block_message`.
.. versionadded:: 2.2
"""
__slots__ = ('type', 'channel_id', 'duration')
__slots__ = ('type', 'channel_id', 'duration', 'custom_message')
@overload
def __init__(self, *, channel_id: Optional[int] = ...) -> None:
...
@overload
def __init__(self, *, duration: Optional[datetime.timedelta] = ...) -> None:
...
@overload
def __init__(self, *, custom_message: Optional[str] = ...) -> None:
...
def __init__(self, *, channel_id: Optional[int] = None, duration: Optional[datetime.timedelta] = None) -> None:
def __init__(
self,
*,
channel_id: Optional[int] = None,
duration: Optional[datetime.timedelta] = None,
custom_message: Optional[str] = None,
) -> None:
self.channel_id: Optional[int] = channel_id
self.duration: Optional[datetime.timedelta] = duration
if channel_id and duration:
raise ValueError('Please provide only one of ``channel`` or ``duration``')
self.custom_message: Optional[str] = custom_message
if sum(v is None for v in (channel_id, duration, custom_message)) < 2:
raise ValueError('Only one of channel_id, duration, or custom_message can be passed.')
self.type: AutoModRuleActionType = AutoModRuleActionType.block_message
if channel_id:
self.type = AutoModRuleActionType.send_alert_message
elif duration:
self.type = AutoModRuleActionType.timeout
else:
self.type = AutoModRuleActionType.block_message
def __repr__(self) -> str:
return f'<AutoModRuleAction type={self.type.value} channel={self.channel_id} duration={self.duration}>'
@classmethod
def from_data(cls, data: AutoModerationActionPayload) -> Self:
type_ = try_enum(AutoModRuleActionType, data['type'])
if data['type'] == AutoModRuleActionType.timeout.value:
duration_seconds = data['metadata']['duration_seconds']
return cls(duration=datetime.timedelta(seconds=duration_seconds))
elif data['type'] == AutoModRuleActionType.send_alert_message.value:
channel_id = int(data['metadata']['channel_id'])
return cls(channel_id=channel_id)
return cls()
return cls(custom_message=data.get('metadata', {}).get('custom_message'))
def to_dict(self) -> Dict[str, Any]:
ret = {'type': self.type.value, 'metadata': {}}
if self.type is AutoModRuleActionType.timeout:
if self.type is AutoModRuleActionType.block_message and self.custom_message is not None:
ret['metadata'] = {'custom_message': self.custom_message}
elif self.type is AutoModRuleActionType.timeout:
ret['metadata'] = {'duration_seconds': int(self.duration.total_seconds())} # type: ignore # duration cannot be None here
elif self.type is AutoModRuleActionType.send_alert_message:
ret['metadata'] = {'channel_id': str(self.channel_id)}
@ -139,13 +166,13 @@ class AutoModTrigger:
The type of trigger.
keyword_filter: List[:class:`str`]
The list of strings that will trigger the keyword filter. Maximum of 1000.
Keywords can only be up to 30 characters in length.
Keywords can only be up to 60 characters in length.
This could be combined with :attr:`regex_patterns`.
regex_patterns: List[:class:`str`]
The regex pattern that will trigger the filter. The syntax is based off of
`Rust's regex syntax <https://docs.rs/regex/latest/regex/#syntax>`_.
Maximum of 10. Regex strings can only be up to 250 characters in length.
Maximum of 10. Regex strings can only be up to 260 characters in length.
This could be combined with :attr:`keyword_filter` and/or :attr:`allow_list`
@ -153,7 +180,8 @@ class AutoModTrigger:
presets: :class:`AutoModPresets`
The presets used with the preset keyword filter.
allow_list: List[:class:`str`]
The list of words that are exempt from the commonly flagged words.
The list of words that are exempt from the commonly flagged words. Maximum of 100.
Keywords can only be up to 60 characters in length.
mention_limit: :class:`int`
The total number of user and role mentions a message can contain.
Has a maximum of 50.

261
discord/channel.py

@ -47,7 +47,7 @@ import datetime
import discord.abc
from .scheduled_event import ScheduledEvent
from .permissions import PermissionOverwrite, Permissions
from .enums import ChannelType, ForumLayoutType, PrivacyLevel, try_enum, VideoQualityMode, EntityType
from .enums import ChannelType, ForumLayoutType, ForumOrderType, PrivacyLevel, try_enum, VideoQualityMode, EntityType
from .mixins import Hashable
from . import utils
from .utils import MISSING
@ -160,6 +160,10 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
The default auto archive duration in minutes for threads created in this channel.
.. versionadded:: 2.0
default_thread_slowmode_delay: :class:`int`
The default slowmode delay in seconds for threads created in this channel.
.. versionadded:: 2.3
"""
__slots__ = (
@ -176,6 +180,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
'_type',
'last_message_id',
'default_auto_archive_duration',
'default_thread_slowmode_delay',
)
def __init__(self, *, state: ConnectionState, guild: Guild, data: Union[TextChannelPayload, NewsChannelPayload]):
@ -206,6 +211,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
# Does this need coercion into `int`? No idea yet.
self.slowmode_delay: int = data.get('rate_limit_per_user', 0)
self.default_auto_archive_duration: ThreadArchiveDuration = data.get('default_auto_archive_duration', 1440)
self.default_thread_slowmode_delay: int = data.get('default_thread_rate_limit_per_user', 0)
self._type: Literal[0, 5] = data.get('type', self._type)
self.last_message_id: Optional[int] = utils._get_as_snowflake(data, 'last_message_id')
self._fill_overwrites(data)
@ -301,6 +307,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
category: Optional[CategoryChannel] = ...,
slowmode_delay: int = ...,
default_auto_archive_duration: ThreadArchiveDuration = ...,
default_thread_slowmode_delay: int = ...,
type: ChannelType = ...,
overwrites: Mapping[OverwriteKeyT, PermissionOverwrite] = ...,
) -> TextChannel:
@ -359,7 +366,10 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
Must be one of ``60``, ``1440``, ``4320``, or ``10080``.
.. versionadded:: 2.0
default_thread_slowmode_delay: :class:`int`
The new default slowmode delay in seconds for threads created in this channel.
.. versionadded:: 2.3
Raises
------
ValueError
@ -878,7 +888,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
before_timestamp = update_before(threads[-1])
class VocalGuildChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable):
class VocalGuildChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc.GuildChannel, Hashable):
__slots__ = (
'name',
'id',
@ -901,6 +911,9 @@ class VocalGuildChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hasha
self.id: int = int(data['id'])
self._update(guild, data)
async def _get_channel(self) -> Self:
return self
def _get_voice_client_key(self) -> Tuple[int, str]:
return self.guild.id, 'guild_id'
@ -988,103 +1001,6 @@ class VocalGuildChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hasha
base.value &= ~denied.value
return base
class VoiceChannel(discord.abc.Messageable, VocalGuildChannel):
"""Represents a Discord guild voice channel.
.. container:: operations
.. describe:: x == y
Checks if two channels are equal.
.. describe:: x != y
Checks if two channels are not equal.
.. describe:: hash(x)
Returns the channel's hash.
.. describe:: str(x)
Returns the channel's name.
Attributes
-----------
name: :class:`str`
The channel name.
guild: :class:`Guild`
The guild the channel belongs to.
id: :class:`int`
The channel ID.
nsfw: :class:`bool`
If the channel is marked as "not safe for work" or "age restricted".
.. versionadded:: 2.0
category_id: Optional[:class:`int`]
The category channel ID this channel belongs to, if applicable.
position: :class:`int`
The position in the channel list. This is a number that starts at 0. e.g. the
top channel is position 0.
bitrate: :class:`int`
The channel's preferred audio bitrate in bits per second.
user_limit: :class:`int`
The channel's limit for number of members that can be in a voice channel.
rtc_region: Optional[:class:`str`]
The region for the voice channel's voice communication.
A value of ``None`` indicates automatic voice region detection.
.. versionadded:: 1.7
.. versionchanged:: 2.0
The type of this attribute has changed to :class:`str`.
video_quality_mode: :class:`VideoQualityMode`
The camera video quality for the voice channel's participants.
.. versionadded:: 2.0
last_message_id: Optional[:class:`int`]
The last message ID of the message sent to this channel. It may
*not* point to an existing or valid message.
.. versionadded:: 2.0
slowmode_delay: :class:`int`
The number of seconds a member must wait between sending messages
in this channel. A value of ``0`` denotes that it is disabled.
Bots and users with :attr:`~Permissions.manage_channels` or
:attr:`~Permissions.manage_messages` bypass slowmode.
.. versionadded:: 2.2
"""
__slots__ = ()
def __repr__(self) -> str:
attrs = [
('id', self.id),
('name', self.name),
('rtc_region', self.rtc_region),
('position', self.position),
('bitrate', self.bitrate),
('video_quality_mode', self.video_quality_mode),
('user_limit', self.user_limit),
('category_id', self.category_id),
]
joined = ' '.join('%s=%r' % t for t in attrs)
return f'<{self.__class__.__name__} {joined}>'
async def _get_channel(self) -> Self:
return self
@property
def _scheduled_event_entity_type(self) -> Optional[EntityType]:
return EntityType.voice
@property
def type(self) -> Literal[ChannelType.voice]:
""":class:`ChannelType`: The channel's Discord type."""
return ChannelType.voice
@property
def last_message(self) -> Optional[Message]:
"""Retrieves the last message from this channel in cache.
@ -1129,7 +1045,7 @@ class VoiceChannel(discord.abc.Messageable, VocalGuildChannel):
from .message import PartialMessage
return PartialMessage(channel=self, id=message_id)
return PartialMessage(channel=self, id=message_id) # type: ignore # VocalGuildChannel is an impl detail
async def delete_messages(self, messages: Iterable[Snowflake], *, reason: Optional[str] = None) -> None:
"""|coro|
@ -1332,6 +1248,100 @@ class VoiceChannel(discord.abc.Messageable, VocalGuildChannel):
data = await self._state.http.create_webhook(self.id, name=str(name), avatar=avatar, reason=reason)
return Webhook.from_state(data, state=self._state)
class VoiceChannel(VocalGuildChannel):
"""Represents a Discord guild voice channel.
.. container:: operations
.. describe:: x == y
Checks if two channels are equal.
.. describe:: x != y
Checks if two channels are not equal.
.. describe:: hash(x)
Returns the channel's hash.
.. describe:: str(x)
Returns the channel's name.
Attributes
-----------
name: :class:`str`
The channel name.
guild: :class:`Guild`
The guild the channel belongs to.
id: :class:`int`
The channel ID.
nsfw: :class:`bool`
If the channel is marked as "not safe for work" or "age restricted".
.. versionadded:: 2.0
category_id: Optional[:class:`int`]
The category channel ID this channel belongs to, if applicable.
position: :class:`int`
The position in the channel list. This is a number that starts at 0. e.g. the
top channel is position 0.
bitrate: :class:`int`
The channel's preferred audio bitrate in bits per second.
user_limit: :class:`int`
The channel's limit for number of members that can be in a voice channel.
rtc_region: Optional[:class:`str`]
The region for the voice channel's voice communication.
A value of ``None`` indicates automatic voice region detection.
.. versionadded:: 1.7
.. versionchanged:: 2.0
The type of this attribute has changed to :class:`str`.
video_quality_mode: :class:`VideoQualityMode`
The camera video quality for the voice channel's participants.
.. versionadded:: 2.0
last_message_id: Optional[:class:`int`]
The last message ID of the message sent to this channel. It may
*not* point to an existing or valid message.
.. versionadded:: 2.0
slowmode_delay: :class:`int`
The number of seconds a member must wait between sending messages
in this channel. A value of ``0`` denotes that it is disabled.
Bots and users with :attr:`~Permissions.manage_channels` or
:attr:`~Permissions.manage_messages` bypass slowmode.
.. versionadded:: 2.2
"""
__slots__ = ()
def __repr__(self) -> str:
attrs = [
('id', self.id),
('name', self.name),
('rtc_region', self.rtc_region),
('position', self.position),
('bitrate', self.bitrate),
('video_quality_mode', self.video_quality_mode),
('user_limit', self.user_limit),
('category_id', self.category_id),
]
joined = ' '.join('%s=%r' % t for t in attrs)
return f'<{self.__class__.__name__} {joined}>'
@property
def _scheduled_event_entity_type(self) -> Optional[EntityType]:
return EntityType.voice
@property
def type(self) -> Literal[ChannelType.voice]:
""":class:`ChannelType`: The channel's Discord type."""
return ChannelType.voice
@utils.copy_doc(discord.abc.GuildChannel.clone)
async def clone(self, *, name: Optional[str] = None, reason: Optional[str] = None) -> VoiceChannel:
return await self._clone_impl({'bitrate': self.bitrate, 'user_limit': self.user_limit}, name=name, reason=reason)
@ -1358,6 +1368,7 @@ class VoiceChannel(discord.abc.Messageable, VocalGuildChannel):
overwrites: Mapping[OverwriteKeyT, PermissionOverwrite] = ...,
rtc_region: Optional[str] = ...,
video_quality_mode: VideoQualityMode = ...,
slowmode_delay: int = ...,
reason: Optional[str] = ...,
) -> VoiceChannel:
...
@ -1492,6 +1503,11 @@ class StageChannel(VocalGuildChannel):
The camera video quality for the stage channel's participants.
.. versionadded:: 2.0
last_message_id: Optional[:class:`int`]
The last message ID of the message sent to this channel. It may
*not* point to an existing or valid message.
.. versionadded:: 2.2
slowmode_delay: :class:`int`
The number of seconds a member must wait between sending messages
in this channel. A value of ``0`` denotes that it is disabled.
@ -1578,7 +1594,12 @@ class StageChannel(VocalGuildChannel):
return utils.get(self.guild.stage_instances, channel_id=self.id)
async def create_instance(
self, *, topic: str, privacy_level: PrivacyLevel = MISSING, reason: Optional[str] = None
self,
*,
topic: str,
privacy_level: PrivacyLevel = MISSING,
send_start_notification: bool = False,
reason: Optional[str] = None,
) -> StageInstance:
"""|coro|
@ -1594,6 +1615,11 @@ class StageChannel(VocalGuildChannel):
The stage instance's topic.
privacy_level: :class:`PrivacyLevel`
The stage instance's privacy level. Defaults to :attr:`PrivacyLevel.guild_only`.
send_start_notification: :class:`bool`
Whether to send a start notification. This sends a push notification to @everyone if ``True``. Defaults to ``False``.
You must have :attr:`~Permissions.mention_everyone` to do this.
.. versionadded:: 2.3
reason: :class:`str`
The reason the stage instance was created. Shows up on the audit log.
@ -1620,6 +1646,8 @@ class StageChannel(VocalGuildChannel):
payload['privacy_level'] = privacy_level.value
payload['send_start_notification'] = send_start_notification
data = await self._state.http.create_stage_instance(**payload, reason=reason)
return StageInstance(guild=self.guild, state=self._state, data=data)
@ -1665,6 +1693,7 @@ class StageChannel(VocalGuildChannel):
overwrites: Mapping[OverwriteKeyT, PermissionOverwrite] = ...,
rtc_region: Optional[str] = ...,
video_quality_mode: VideoQualityMode = ...,
slowmode_delay: int = ...,
reason: Optional[str] = ...,
) -> StageChannel:
...
@ -2147,6 +2176,10 @@ class ForumChannel(discord.abc.GuildChannel, Hashable):
Defaults to :attr:`ForumLayoutType.not_set`.
.. versionadded:: 2.2
default_sort_order: Optional[:class:`ForumOrderType`]
The default sort order for posts in this forum channel.
.. versionadded:: 2.3
"""
__slots__ = (
@ -2166,6 +2199,7 @@ class ForumChannel(discord.abc.GuildChannel, Hashable):
'default_thread_slowmode_delay',
'default_reaction_emoji',
'default_layout',
'default_sort_order',
'_available_tags',
'_flags',
)
@ -2211,6 +2245,11 @@ class ForumChannel(discord.abc.GuildChannel, Hashable):
name=default_reaction_emoji.get('emoji_name') or '',
)
self.default_sort_order: Optional[ForumOrderType] = None
default_sort_order = data.get('default_sort_order')
if default_sort_order is not None:
self.default_sort_order = try_enum(ForumOrderType, default_sort_order)
self._flags: int = data.get('flags', 0)
self._fill_overwrites(data)
@ -2337,6 +2376,7 @@ class ForumChannel(discord.abc.GuildChannel, Hashable):
default_thread_slowmode_delay: int = ...,
default_reaction_emoji: Optional[EmojiInputType] = ...,
default_layout: ForumLayoutType = ...,
default_sort_order: ForumOrderType = ...,
require_tag: bool = ...,
) -> ForumChannel:
...
@ -2395,6 +2435,10 @@ class ForumChannel(discord.abc.GuildChannel, Hashable):
The new default layout for posts in this forum.
.. versionadded:: 2.2
default_sort_order: Optional[:class:`ForumOrderType`]
The new default sort order for posts in this forum.
.. versionadded:: 2.3
require_tag: :class:`bool`
Whether to require a tag for threads in this channel or not.
@ -2457,6 +2501,21 @@ class ForumChannel(discord.abc.GuildChannel, Hashable):
options['default_forum_layout'] = layout.value
try:
sort_order = options.pop('default_sort_order')
except KeyError:
pass
else:
if sort_order is None:
options['default_sort_order'] = None
else:
if not isinstance(sort_order, ForumOrderType):
raise TypeError(
f'default_sort_order parameter must be a ForumOrderType not {sort_order.__class__.__name__}'
)
options['default_sort_order'] = sort_order.value
payload = await self._edit(options, reason=reason)
if payload is not None:
# the payload will always be the proper channel payload

14
discord/client.py

@ -1161,9 +1161,9 @@ class Client:
event: Literal['app_command_completion'],
/,
*,
check: Optional[Callable[[Interaction[Self], Union[Command, ContextMenu]], bool]],
check: Optional[Callable[[Interaction[Self], Union[Command[Any, ..., Any], ContextMenu]], bool]],
timeout: Optional[float] = None,
) -> Tuple[Interaction[Self], Union[Command, ContextMenu]]:
) -> Tuple[Interaction[Self], Union[Command[Any, ..., Any], ContextMenu]]:
...
# AutoMod
@ -1816,9 +1816,9 @@ class Client:
event: Literal["command", "command_completion"],
/,
*,
check: Optional[Callable[[Context], bool]] = None,
check: Optional[Callable[[Context[Any]], bool]] = None,
timeout: Optional[float] = None,
) -> Context:
) -> Context[Any]:
...
@overload
@ -1827,9 +1827,9 @@ class Client:
event: Literal["command_error"],
/,
*,
check: Optional[Callable[[Context, CommandError], bool]] = None,
check: Optional[Callable[[Context[Any], CommandError], bool]] = None,
timeout: Optional[float] = None,
) -> Tuple[Context, CommandError]:
) -> Tuple[Context[Any], CommandError]:
...
@overload
@ -2486,8 +2486,6 @@ class Client:
The bot's application information.
"""
data = await self.http.application_info()
if 'rpc_origins' not in data:
data['rpc_origins'] = None
return AppInfo(self._connection, data)
async def fetch_user(self, user_id: int, /) -> User:

5
discord/colour.py

@ -104,6 +104,11 @@ class Colour:
Returns the raw colour value.
.. note::
The colour values in the classmethods are mostly provided as-is and can change between
versions should the Discord client's representation of that colour also change.
Attributes
------------
value: :class:`int`

16
discord/enums.py

@ -67,6 +67,7 @@ __all__ = (
'AutoModRuleEventType',
'AutoModRuleActionType',
'ForumLayoutType',
'ForumOrderType',
'OnboardingPromptType',
)
@ -235,11 +236,11 @@ class MessageType(Enum):
auto_moderation_action = 24
role_subscription_purchase = 25
interaction_premium_upsell = 26
# stage_start = 27
# stage_end = 28
# stage_speaker = 29
# stage_raise_hand = 30
# stage_topic = 31
stage_start = 27
stage_end = 28
stage_speaker = 29
stage_raise_hand = 30
stage_topic = 31
guild_application_premium_subscription = 32
@ -752,6 +753,11 @@ class ForumLayoutType(Enum):
gallery_view = 2
class ForumOrderType(Enum):
latest_activity = 0
creation_date = 1
class OnboardingPromptType(Enum):
multiple_choice = 0
dropdown = 1

13
discord/ext/commands/cog.py

@ -50,6 +50,7 @@ from ._types import _BaseCommand, BotT
if TYPE_CHECKING:
from typing_extensions import Self
from discord.abc import Snowflake
from discord._types import ClientT
from .bot import BotBase
from .context import Context
@ -585,6 +586,18 @@ class Cog(metaclass=CogMeta):
"""
return True
@_cog_special_method
def interaction_check(self, interaction: discord.Interaction[ClientT], /) -> bool:
"""A special method that registers as a :func:`discord.app_commands.check`
for every app command and subcommand in this cog.
This function **can** be a coroutine and must take a sole parameter,
``interaction``, to represent the :class:`~discord.Interaction`.
.. versionadded:: 2.0
"""
return True
@_cog_special_method
async def cog_command_error(self, ctx: Context[BotT], error: Exception) -> None:
"""|coro|

170
discord/ext/commands/context.py

@ -24,7 +24,7 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations
import re
from typing import TYPE_CHECKING, Any, Dict, Generator, Generic, List, Optional, TypeVar, Union, Sequence, Type
from typing import TYPE_CHECKING, Any, Dict, Generator, Generic, List, Optional, TypeVar, Union, Sequence, Type, overload
import discord.abc
import discord.utils
@ -615,6 +615,90 @@ class Context(discord.abc.Messageable, Generic[BotT]):
except CommandError as e:
await cmd.on_help_command_error(self, e)
@overload
async def reply(
self,
content: Optional[str] = ...,
*,
tts: bool = ...,
embed: Embed = ...,
file: File = ...,
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
delete_after: float = ...,
nonce: Union[str, int] = ...,
allowed_mentions: AllowedMentions = ...,
reference: Union[Message, MessageReference, PartialMessage] = ...,
mention_author: bool = ...,
view: View = ...,
suppress_embeds: bool = ...,
ephemeral: bool = ...,
silent: bool = ...,
) -> Message:
...
@overload
async def reply(
self,
content: Optional[str] = ...,
*,
tts: bool = ...,
embed: Embed = ...,
files: Sequence[File] = ...,
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
delete_after: float = ...,
nonce: Union[str, int] = ...,
allowed_mentions: AllowedMentions = ...,
reference: Union[Message, MessageReference, PartialMessage] = ...,
mention_author: bool = ...,
view: View = ...,
suppress_embeds: bool = ...,
ephemeral: bool = ...,
silent: bool = ...,
) -> Message:
...
@overload
async def reply(
self,
content: Optional[str] = ...,
*,
tts: bool = ...,
embeds: Sequence[Embed] = ...,
file: File = ...,
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
delete_after: float = ...,
nonce: Union[str, int] = ...,
allowed_mentions: AllowedMentions = ...,
reference: Union[Message, MessageReference, PartialMessage] = ...,
mention_author: bool = ...,
view: View = ...,
suppress_embeds: bool = ...,
ephemeral: bool = ...,
silent: bool = ...,
) -> Message:
...
@overload
async def reply(
self,
content: Optional[str] = ...,
*,
tts: bool = ...,
embeds: Sequence[Embed] = ...,
files: Sequence[File] = ...,
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
delete_after: float = ...,
nonce: Union[str, int] = ...,
allowed_mentions: AllowedMentions = ...,
reference: Union[Message, MessageReference, PartialMessage] = ...,
mention_author: bool = ...,
view: View = ...,
suppress_embeds: bool = ...,
ephemeral: bool = ...,
silent: bool = ...,
) -> Message:
...
async def reply(self, content: Optional[str] = None, **kwargs: Any) -> Message:
"""|coro|
@ -716,6 +800,90 @@ class Context(discord.abc.Messageable, Generic[BotT]):
if self.interaction:
await self.interaction.response.defer(ephemeral=ephemeral)
@overload
async def send(
self,
content: Optional[str] = ...,
*,
tts: bool = ...,
embed: Embed = ...,
file: File = ...,
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
delete_after: float = ...,
nonce: Union[str, int] = ...,
allowed_mentions: AllowedMentions = ...,
reference: Union[Message, MessageReference, PartialMessage] = ...,
mention_author: bool = ...,
view: View = ...,
suppress_embeds: bool = ...,
ephemeral: bool = ...,
silent: bool = ...,
) -> Message:
...
@overload
async def send(
self,
content: Optional[str] = ...,
*,
tts: bool = ...,
embed: Embed = ...,
files: Sequence[File] = ...,
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
delete_after: float = ...,
nonce: Union[str, int] = ...,
allowed_mentions: AllowedMentions = ...,
reference: Union[Message, MessageReference, PartialMessage] = ...,
mention_author: bool = ...,
view: View = ...,
suppress_embeds: bool = ...,
ephemeral: bool = ...,
silent: bool = ...,
) -> Message:
...
@overload
async def send(
self,
content: Optional[str] = ...,
*,
tts: bool = ...,
embeds: Sequence[Embed] = ...,
file: File = ...,
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
delete_after: float = ...,
nonce: Union[str, int] = ...,
allowed_mentions: AllowedMentions = ...,
reference: Union[Message, MessageReference, PartialMessage] = ...,
mention_author: bool = ...,
view: View = ...,
suppress_embeds: bool = ...,
ephemeral: bool = ...,
silent: bool = ...,
) -> Message:
...
@overload
async def send(
self,
content: Optional[str] = ...,
*,
tts: bool = ...,
embeds: Sequence[Embed] = ...,
files: Sequence[File] = ...,
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
delete_after: float = ...,
nonce: Union[str, int] = ...,
allowed_mentions: AllowedMentions = ...,
reference: Union[Message, MessageReference, PartialMessage] = ...,
mention_author: bool = ...,
view: View = ...,
suppress_embeds: bool = ...,
ephemeral: bool = ...,
silent: bool = ...,
) -> Message:
...
async def send(
self,
content: Optional[str] = None,

2
discord/ext/commands/converter.py

@ -1330,7 +1330,7 @@ async def run_converters(ctx: Context[BotT], converter: Any, argument: str, para
return value
# if we're here, then we failed to match all the literals
raise BadLiteralArgument(param, literal_args, errors)
raise BadLiteralArgument(param, literal_args, errors, argument)
# This must be the last if-clause in the chain of origin checking
# Nearly every type is a generic type within the typing library

7
discord/ext/commands/errors.py

@ -920,12 +920,17 @@ class BadLiteralArgument(UserInputError):
A tuple of values compared against in conversion, in order of failure.
errors: List[:class:`CommandError`]
A list of errors that were caught from failing the conversion.
argument: :class:`str`
The argument's value that failed to be converted. Defaults to an empty string.
.. versionadded:: 2.3
"""
def __init__(self, param: Parameter, literals: Tuple[Any, ...], errors: List[CommandError]) -> None:
def __init__(self, param: Parameter, literals: Tuple[Any, ...], errors: List[CommandError], argument: str = "") -> None:
self.param: Parameter = param
self.literals: Tuple[Any, ...] = literals
self.errors: List[CommandError] = errors
self.argument: str = argument
to_string = [repr(l) for l in literals]
if len(to_string) > 2:

11
discord/ext/commands/hybrid.py

@ -72,9 +72,9 @@ __all__ = (
T = TypeVar('T')
U = TypeVar('U')
CogT = TypeVar('CogT', bound='Cog')
CommandT = TypeVar('CommandT', bound='Command')
CommandT = TypeVar('CommandT', bound='Command[Any, ..., Any]')
# CHT = TypeVar('CHT', bound='Check')
GroupT = TypeVar('GroupT', bound='Group')
GroupT = TypeVar('GroupT', bound='Group[Any, ..., Any]')
_NoneType = type(None)
if TYPE_CHECKING:
@ -297,7 +297,7 @@ def replace_parameters(
class HybridAppCommand(discord.app_commands.Command[CogT, P, T]):
def __init__(self, wrapped: Union[HybridCommand[CogT, Any, T], HybridGroup[CogT, Any, T]]) -> None:
def __init__(self, wrapped: Union[HybridCommand[CogT, ..., T], HybridGroup[CogT, ..., T]]) -> None:
signature = inspect.signature(wrapped.callback)
params = replace_parameters(wrapped.params, wrapped.callback, signature)
wrapped.callback.__signature__ = signature.replace(parameters=params)
@ -312,7 +312,7 @@ class HybridAppCommand(discord.app_commands.Command[CogT, P, T]):
finally:
del wrapped.callback.__signature__
self.wrapped: Union[HybridCommand[CogT, Any, T], HybridGroup[CogT, Any, T]] = wrapped
self.wrapped: Union[HybridCommand[CogT, ..., T], HybridGroup[CogT, ..., T]] = wrapped
self.binding: Optional[CogT] = wrapped.cog
# This technically means only one flag converter is supported
self.flag_converter: Optional[Tuple[str, Type[FlagConverter]]] = getattr(
@ -908,6 +908,9 @@ def hybrid_group(
Parameters
-----------
name: Union[:class:`str`, :class:`~discord.app_commands.locale_str`]
The name to create the group with. By default this uses the
function name unchanged.
with_app_command: :class:`bool`
Whether to register the command also as an application command.

15
discord/gateway.py

@ -915,6 +915,7 @@ class DiscordVoiceWebSocket:
'd': {
'speaking': int(state),
'delay': 0,
'ssrc': self._connection.ssrc,
},
}
@ -948,16 +949,16 @@ class DiscordVoiceWebSocket:
state.voice_port = data['port']
state.endpoint_ip = data['ip']
packet = bytearray(70)
packet = bytearray(74)
struct.pack_into('>H', packet, 0, 1) # 1 = Send
struct.pack_into('>H', packet, 2, 70) # 70 = Length
struct.pack_into('>I', packet, 4, state.ssrc)
state.socket.sendto(packet, (state.endpoint_ip, state.voice_port))
recv = await self.loop.sock_recv(state.socket, 70)
recv = await self.loop.sock_recv(state.socket, 74)
_log.debug('received packet in initial_connection: %s', recv)
# the ip is ascii starting at the 4th byte and ending at the first null
ip_start = 4
# the ip is ascii starting at the 8th byte and ending at the first null
ip_start = 8
ip_end = recv.index(0, ip_start)
state.ip = recv[ip_start:ip_end].decode('ascii')
@ -990,7 +991,11 @@ class DiscordVoiceWebSocket:
async def load_secret_key(self, data: Dict[str, Any]) -> None:
_log.debug('received secret key for voice connection')
self.secret_key = self._connection.secret_key = data['secret_key']
await self.speak()
# Send a speak command with the "not speaking" state.
# This also tells Discord our SSRC value, which Discord requires
# before sending any voice data (and is the real reason why we
# call this here).
await self.speak(SpeakingState.none)
async def poll_event(self) -> None:

284
discord/guild.py

@ -73,6 +73,8 @@ from .enums import (
MFALevel,
Locale,
AutoModRuleEventType,
ForumOrderType,
ForumLayoutType,
)
from .mixins import Hashable
from .user import User
@ -91,6 +93,7 @@ from .object import OLDEST_OBJECT, Object
from .onboarding import Onboarding
from .welcome_screen import WelcomeScreen, WelcomeChannel
from .automod import AutoModRule, AutoModTrigger, AutoModRuleAction
from .partial_emoji import _EmojiTag, PartialEmoji
__all__ = (
@ -130,6 +133,7 @@ if TYPE_CHECKING:
from .types.integration import IntegrationType
from .types.snowflake import SnowflakeList
from .types.widget import EditWidgetSettings
from .message import EmojiInputType
VocalGuildChannel = Union[VoiceChannel, StageChannel]
GuildChannel = Union[VocalGuildChannel, ForumChannel, TextChannel, CategoryChannel]
@ -290,6 +294,7 @@ class Guild(Hashable):
'mfa_level',
'vanity_url_code',
'widget_enabled',
'_widget_channel_id',
'_members',
'_channels',
'_icon',
@ -478,6 +483,7 @@ class Guild(Hashable):
self.premium_subscription_count: int = guild.get('premium_subscription_count') or 0
self.vanity_url_code: Optional[str] = guild.get('vanity_url_code')
self.widget_enabled: bool = guild.get('widget_enabled', False)
self._widget_channel_id: Optional[int] = utils._get_as_snowflake(guild, 'widget_channel_id')
self._system_channel_flags: int = guild.get('system_channel_flags', 0)
self.preferred_locale: Locale = try_enum(Locale, guild.get('preferred_locale', 'en-US'))
self._discovery_splash: Optional[str] = guild.get('discovery_splash')
@ -737,6 +743,26 @@ class Guild(Hashable):
"""
return self._threads.get(thread_id)
def get_emoji(self, emoji_id: int, /) -> Optional[Emoji]:
"""Returns an emoji with the given ID.
.. versionadded:: 2.3
Parameters
----------
emoji_id: int
The ID to search for.
Returns
--------
Optional[:class:`Emoji`]
The returned Emoji or ``None`` if not found.
"""
emoji = self._state.get_emoji(emoji_id)
if emoji and emoji.guild == self:
return emoji
return None
@property
def system_channel(self) -> Optional[TextChannel]:
"""Optional[:class:`TextChannel`]: Returns the guild's channel used for system messages.
@ -776,6 +802,18 @@ class Guild(Hashable):
channel_id = self._public_updates_channel_id
return channel_id and self._channels.get(channel_id) # type: ignore
@property
def widget_channel(self) -> Optional[Union[TextChannel, ForumChannel, VoiceChannel, StageChannel]]:
"""Optional[Union[:class:`TextChannel`, :class:`ForumChannel`, :class:`VoiceChannel`, :class:`StageChannel`]]: Returns
the widget channel of the guild.
If no channel is set, then this returns ``None``.
.. versionadded:: 2.3
"""
channel_id = self._widget_channel_id
return channel_id and self._channels.get(channel_id) # type: ignore
@property
def emoji_limit(self) -> int:
""":class:`int`: The maximum number of emoji slots this guild has."""
@ -1200,6 +1238,7 @@ class Guild(Hashable):
nsfw: bool = MISSING,
overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = MISSING,
default_auto_archive_duration: int = MISSING,
default_thread_slowmode_delay: int = MISSING,
) -> TextChannel:
"""|coro|
@ -1273,6 +1312,10 @@ class Guild(Hashable):
Must be one of ``60``, ``1440``, ``4320``, or ``10080``.
.. versionadded:: 2.0
default_thread_slowmode_delay: :class:`int`
The default slowmode delay in seconds for threads created in the text channel.
.. versionadded:: 2.3
reason: Optional[:class:`str`]
The reason for creating this channel. Shows up on the audit log.
@ -1305,7 +1348,10 @@ class Guild(Hashable):
options['nsfw'] = nsfw
if default_auto_archive_duration is not MISSING:
options["default_auto_archive_duration"] = default_auto_archive_duration
options['default_auto_archive_duration'] = default_auto_archive_duration
if default_thread_slowmode_delay is not MISSING:
options['default_thread_rate_limit_per_user'] = default_thread_slowmode_delay
data = await self._create_channel(
name,
@ -1577,6 +1623,9 @@ class Guild(Hashable):
reason: Optional[str] = None,
default_auto_archive_duration: int = MISSING,
default_thread_slowmode_delay: int = MISSING,
default_sort_order: ForumOrderType = MISSING,
default_reaction_emoji: EmojiInputType = MISSING,
default_layout: ForumLayoutType = MISSING,
available_tags: Sequence[ForumTag] = MISSING,
) -> ForumChannel:
"""|coro|
@ -1594,6 +1643,10 @@ class Guild(Hashable):
-----------
name: :class:`str`
The channel's name.
overwrites: Dict[Union[:class:`Role`, :class:`Member`], :class:`PermissionOverwrite`]
A :class:`dict` of target (either a role or a member) to
:class:`PermissionOverwrite` to apply upon creation of a channel.
Useful for creating secret channels.
topic: :class:`str`
The channel's topic.
category: Optional[:class:`CategoryChannel`]
@ -1617,6 +1670,19 @@ class Guild(Hashable):
The default slowmode delay in seconds for threads created in this forum.
.. versionadded:: 2.1
default_sort_order: :class:`ForumOrderType`
The default sort order for posts in this forum channel.
.. versionadded:: 2.3
default_reaction_emoji: Union[:class:`Emoji`, :class:`PartialEmoji`, :class:`str`]
The default reaction emoji for threads created in this forum to show in the
add reaction button.
.. versionadded:: 2.3
default_layout: :class:`ForumLayoutType`
The default layout for posts in this forum.
.. versionadded:: 2.3
available_tags: Sequence[:class:`ForumTag`]
The available tags for this forum channel.
@ -1656,6 +1722,30 @@ class Guild(Hashable):
if default_thread_slowmode_delay is not MISSING:
options['default_thread_rate_limit_per_user'] = default_thread_slowmode_delay
if default_sort_order is not MISSING:
if not isinstance(default_sort_order, ForumOrderType):
raise TypeError(
f'default_sort_order parameter must be a ForumOrderType not {default_sort_order.__class__.__name__}'
)
options['default_sort_order'] = default_sort_order.value
if default_reaction_emoji is not MISSING:
if isinstance(default_reaction_emoji, _EmojiTag):
options['default_reaction_emoji'] = default_reaction_emoji._to_partial()._to_forum_tag_payload()
elif isinstance(default_reaction_emoji, str):
options['default_reaction_emoji'] = PartialEmoji.from_str(default_reaction_emoji)._to_forum_tag_payload()
else:
raise ValueError(f'default_reaction_emoji parameter must be either Emoji, PartialEmoji, or str')
if default_layout is not MISSING:
if not isinstance(default_layout, ForumLayoutType):
raise TypeError(
f'default_layout parameter must be a ForumLayoutType not {default_layout.__class__.__name__}'
)
options['default_forum_layout'] = default_layout.value
if available_tags is not MISSING:
options['available_tags'] = [t.to_dict() for t in available_tags]
@ -1728,6 +1818,9 @@ class Guild(Hashable):
premium_progress_bar_enabled: bool = MISSING,
discoverable: bool = MISSING,
invites_disabled: bool = MISSING,
widget_enabled: bool = MISSING,
widget_channel: Optional[Snowflake] = MISSING,
mfa_level: MFALevel = MISSING,
) -> Guild:
r"""|coro|
@ -1735,12 +1828,6 @@ class Guild(Hashable):
You must have :attr:`~Permissions.manage_guild` to edit the guild.
.. versionchanged:: 1.4
The ``rules_channel`` and ``public_updates_channel`` keyword parameters were added.
.. versionchanged:: 2.0
The ``discovery_splash`` and ``community`` keyword parameters were added.
.. versionchanged:: 2.0
The newly updated guild is returned.
@ -1751,15 +1838,6 @@ class Guild(Hashable):
This function will now raise :exc:`TypeError` or
:exc:`ValueError` instead of ``InvalidArgument``.
.. versionchanged:: 2.0
The ``preferred_locale`` keyword parameter now accepts an enum instead of :class:`str`.
.. versionchanged:: 2.0
The ``premium_progress_bar_enabled`` keyword parameter was added.
.. versionchanged:: 2.1
The ``discoverable`` and ``invites_disabled`` keyword parameters were added.
Parameters
----------
name: :class:`str`
@ -1785,9 +1863,13 @@ class Guild(Hashable):
Only PNG/JPEG supported. Could be ``None`` to denote removing the
splash. This is only available to guilds that contain ``DISCOVERABLE``
in :attr:`Guild.features`.
.. versionadded:: 2.0
community: :class:`bool`
Whether the guild should be a Community guild. If set to ``True``\, both ``rules_channel``
and ``public_updates_channel`` parameters are required.
.. versionadded:: 2.0
afk_channel: Optional[:class:`VoiceChannel`]
The new channel that is the AFK channel. Could be ``None`` for no AFK channel.
afk_timeout: :class:`int`
@ -1809,20 +1891,47 @@ class Guild(Hashable):
The new system channel settings to use with the new system channel.
preferred_locale: :class:`Locale`
The new preferred locale for the guild. Used as the primary language in the guild.
.. versionchanged:: 2.0
Now accepts an enum instead of :class:`str`.
rules_channel: Optional[:class:`TextChannel`]
The new channel that is used for rules. This is only available to
guilds that contain ``COMMUNITY`` in :attr:`Guild.features`. Could be ``None`` for no rules
channel.
.. versionadded:: 1.4
public_updates_channel: Optional[:class:`TextChannel`]
The new channel that is used for public updates from Discord. This is only available to
guilds that contain ``COMMUNITY`` in :attr:`Guild.features`. Could be ``None`` for no
public updates channel.
.. versionadded:: 1.4
premium_progress_bar_enabled: :class:`bool`
Whether the premium AKA server boost level progress bar should be enabled for the guild.
.. versionadded:: 2.0
discoverable: :class:`bool`
Whether server discovery is enabled for this guild.
.. versionadded:: 2.1
invites_disabled: :class:`bool`
Whether joining via invites should be disabled for the guild.
.. versionadded:: 2.1
widget_enabled: :class:`bool`
Whether to enable the widget for the guild.
.. versionadded:: 2.3
widget_channel: Optional[:class:`abc.Snowflake`]
The new widget channel. ``None`` removes the widget channel.
.. versionadded:: 2.3
mfa_level: :class:`MFALevel`
The new guild's Multi-Factor Authentication requirement level.
Note that you must be owner of the guild to do this.
.. versionadded:: 2.3
reason: Optional[:class:`str`]
The reason for editing this guild. Shows up on the audit log.
@ -1838,7 +1947,7 @@ class Guild(Hashable):
guild and request an ownership transfer.
TypeError
The type passed to the ``default_notifications``, ``verification_level``,
``explicit_content_filter``, or ``system_channel_flags`` parameter was
``explicit_content_filter``, ``system_channel_flags``, or ``mfa_level`` parameter was
of the incorrect type.
Returns
@ -1974,6 +2083,21 @@ class Guild(Hashable):
if premium_progress_bar_enabled is not MISSING:
fields['premium_progress_bar_enabled'] = premium_progress_bar_enabled
widget_payload: EditWidgetSettings = {}
if widget_channel is not MISSING:
widget_payload['channel_id'] = None if widget_channel is None else widget_channel.id
if widget_enabled is not MISSING:
widget_payload['enabled'] = widget_enabled
if widget_payload:
await self._state.http.edit_widget(self.id, payload=widget_payload, reason=reason)
if mfa_level is not MISSING:
if not isinstance(mfa_level, MFALevel):
raise TypeError(f'mfa_level must be of type MFALevel not {mfa_level.__class__.__name__}')
await http.edit_guild_mfa_level(self.id, mfa_level=mfa_level.value)
data = await http.edit_guild(self.id, reason=reason, **fields)
return Guild(data=data, state=self._state)
@ -2783,7 +2907,7 @@ class Guild(Hashable):
Parameters
------------
id: :class:`int`
scheduled_event_id: :class:`int`
The scheduled event ID.
with_counts: :class:`bool`
Whether to include the number of users that are subscribed to the event.
@ -2804,6 +2928,68 @@ class Guild(Hashable):
data = await self._state.http.get_scheduled_event(self.id, scheduled_event_id, with_counts)
return ScheduledEvent(state=self._state, data=data)
@overload
async def create_scheduled_event(
self,
*,
name: str,
start_time: datetime.datetime,
entity_type: Literal[EntityType.external] = ...,
privacy_level: PrivacyLevel = ...,
location: str = ...,
end_time: datetime.datetime = ...,
description: str = ...,
image: bytes = ...,
reason: Optional[str] = ...,
) -> ScheduledEvent:
...
@overload
async def create_scheduled_event(
self,
*,
name: str,
start_time: datetime.datetime,
entity_type: Literal[EntityType.stage_instance, EntityType.voice] = ...,
privacy_level: PrivacyLevel = ...,
channel: Snowflake = ...,
end_time: datetime.datetime = ...,
description: str = ...,
image: bytes = ...,
reason: Optional[str] = ...,
) -> ScheduledEvent:
...
@overload
async def create_scheduled_event(
self,
*,
name: str,
start_time: datetime.datetime,
privacy_level: PrivacyLevel = ...,
location: str = ...,
end_time: datetime.datetime = ...,
description: str = ...,
image: bytes = ...,
reason: Optional[str] = ...,
) -> ScheduledEvent:
...
@overload
async def create_scheduled_event(
self,
*,
name: str,
start_time: datetime.datetime,
privacy_level: PrivacyLevel = ...,
channel: Union[VoiceChannel, StageChannel] = ...,
end_time: datetime.datetime = ...,
description: str = ...,
image: bytes = ...,
reason: Optional[str] = ...,
) -> ScheduledEvent:
...
async def create_scheduled_event(
self,
*,
@ -2847,6 +3033,8 @@ class Guild(Hashable):
datetime object. Consider using :func:`utils.utcnow`.
Required if the entity type is :attr:`EntityType.external`.
privacy_level: :class:`PrivacyLevel`
The privacy level of the scheduled event.
entity_type: :class:`EntityType`
The entity type of the scheduled event. If the channel is a
:class:`StageInstance` or :class:`VoiceChannel` then this is
@ -2894,27 +3082,32 @@ class Guild(Hashable):
)
payload['scheduled_start_time'] = start_time.isoformat()
entity_type = entity_type or getattr(channel, '_scheduled_event_entity_type', MISSING)
if entity_type is MISSING:
if channel is MISSING:
if channel and isinstance(channel, Object):
if channel.type is VoiceChannel:
entity_type = EntityType.voice
elif channel.type is StageChannel:
entity_type = EntityType.stage_instance
elif location not in (MISSING, None):
entity_type = EntityType.external
else:
_entity_type = getattr(channel, '_scheduled_event_entity_type', MISSING)
if _entity_type is None:
raise TypeError(
'invalid GuildChannel type passed, must be VoiceChannel or StageChannel '
f'not {channel.__class__.__name__}'
)
if _entity_type is MISSING:
raise TypeError('entity_type must be passed in when passing an ambiguous channel type')
entity_type = _entity_type
if not isinstance(entity_type, EntityType):
raise TypeError('entity_type must be of type EntityType')
payload['entity_type'] = entity_type.value
payload['privacy_level'] = PrivacyLevel.guild_only.value
if entity_type is None:
raise TypeError(
'invalid GuildChannel type passed, must be VoiceChannel or StageChannel ' f'not {channel.__class__.__name__}'
)
if privacy_level is not MISSING:
if not isinstance(privacy_level, PrivacyLevel):
raise TypeError('privacy_level must be of type PrivacyLevel.')
payload['privacy_level'] = privacy_level.value
if description is not MISSING:
payload['description'] = description
@ -2924,7 +3117,7 @@ class Guild(Hashable):
payload['image'] = image_as_str
if entity_type in (EntityType.stage_instance, EntityType.voice):
if channel is MISSING or channel is None:
if channel in (MISSING, None):
raise TypeError('channel must be set when entity_type is voice or stage_instance')
payload['channel_id'] = channel.id
@ -2940,7 +3133,10 @@ class Guild(Hashable):
metadata['location'] = location
if end_time is not MISSING:
if end_time in (MISSING, None):
raise TypeError('end_time must be set when entity_type is external')
if end_time not in (MISSING, None):
if end_time.tzinfo is None:
raise ValueError(
'end_time must be an aware datetime. Consider using discord.utils.utcnow() or datetime.datetime.now().astimezone() for local time.'
@ -3631,7 +3827,7 @@ class Guild(Hashable):
if limit is not None:
limit -= len(entries)
after = Object(id=int(entries[0]['id']))
after = Object(id=int(entries[-1]['id']))
return data, entries, after, limit
@ -3648,20 +3844,19 @@ class Guild(Hashable):
if isinstance(after, datetime.datetime):
after = Object(id=utils.time_snowflake(after, high=True))
if oldest_first is MISSING:
reverse = after is not MISSING
else:
reverse = oldest_first
if oldest_first:
if after is MISSING:
after = OLDEST_OBJECT
predicate = None
if reverse:
if oldest_first:
strategy, state = _after_strategy, after
if before:
predicate = lambda m: int(m['id']) < before.id
else:
strategy, state = _before_strategy, before
if after and after != OLDEST_OBJECT:
if after:
predicate = lambda m: int(m['id']) > after.id
# avoid circular import
@ -3674,8 +3869,6 @@ class Guild(Hashable):
data, raw_entries, state, limit = await strategy(retrieve, state, limit)
if reverse:
raw_entries = reversed(raw_entries)
if predicate:
raw_entries = filter(predicate, raw_entries)
@ -3748,7 +3941,7 @@ class Guild(Hashable):
) -> None:
"""|coro|
Edits the widget of the guild.
Edits the widget of the guild. This can also be done with :attr:`~Guild.edit`.
You must have :attr:`~Permissions.manage_guild` to do this.
@ -3776,6 +3969,7 @@ class Guild(Hashable):
if enabled is not MISSING:
payload['enabled'] = enabled
if payload:
await self._state.http.edit_widget(self.id, payload=payload, reason=reason)
async def chunk(self, *, cache: bool = True) -> List[Member]:
@ -3968,7 +4162,7 @@ class Guild(Hashable):
event_type: AutoModRuleEventType,
trigger: AutoModTrigger,
actions: List[AutoModRuleAction],
enabled: bool = MISSING,
enabled: bool = False,
exempt_roles: Sequence[Snowflake] = MISSING,
exempt_channels: Sequence[Snowflake] = MISSING,
reason: str = MISSING,
@ -3993,7 +4187,7 @@ class Guild(Hashable):
The actions that will be taken when the automod rule is triggered.
enabled: :class:`bool`
Whether the automod rule is enabled.
Discord will default to ``False``.
Defaults to ``False``.
exempt_roles: Sequence[:class:`abc.Snowflake`]
A list of roles that will be exempt from the automod rule.
exempt_channels: Sequence[:class:`abc.Snowflake`]

13
discord/http.py

@ -1149,6 +1149,7 @@ class HTTPClient:
'available_tags',
'applied_tags',
'default_forum_layout',
'default_sort_order',
)
payload = {k: v for k, v in options.items() if k in valid_keys}
@ -1190,6 +1191,9 @@ class HTTPClient:
'video_quality_mode',
'default_auto_archive_duration',
'default_thread_rate_limit_per_user',
'default_sort_order',
'default_reaction_emoji',
'default_forum_layout',
'available_tags',
)
payload.update({k: v for k, v in options.items() if k in valid_keys and v is not None})
@ -1429,6 +1433,12 @@ class HTTPClient:
return self.request(Route('PATCH', '/guilds/{guild_id}', guild_id=guild_id), json=payload, reason=reason)
def edit_guild_mfa_level(
self, guild_id: Snowflake, *, mfa_level: int, reason: Optional[str] = None
) -> Response[guild.GuildMFALevel]:
payload = {'level': mfa_level}
return self.request(Route('POST', '/guilds/{guild_id}/mfa', guild_id=guild_id), json=payload, reason=reason)
def get_template(self, code: str) -> Response[template.Template]:
return self.request(Route('GET', '/guilds/templates/{code}', code=code))
@ -1718,7 +1728,7 @@ class HTTPClient:
params: Dict[str, Any] = {'limit': limit}
if before:
params['before'] = before
if after:
if after is not None:
params['after'] = after
if user_id:
params['user_id'] = user_id
@ -1904,6 +1914,7 @@ class HTTPClient:
'channel_id',
'topic',
'privacy_level',
'send_start_notification',
)
payload = {k: v for k, v in payload.items() if k in valid_keys}

5
discord/interactions.py

@ -195,6 +195,11 @@ class Interaction(Generic[ClientT]):
if self.guild_id:
guild = self._state._get_or_create_unavailable_guild(self.guild_id)
# Upgrade Message.guild in case it's missing with partial guild data
if self.message is not None and self.message.guild is None:
self.message.guild = guild
try:
member = data['member'] # type: ignore # The key is optional and handled
except KeyError:

105
discord/message.py

@ -60,7 +60,7 @@ from .utils import escape_mentions, MISSING
from .http import handle_message_parameters
from .guild import Guild
from .mixins import Hashable
from .sticker import StickerItem
from .sticker import StickerItem, GuildSticker
from .threads import Thread
from .channel import PartialMessageable
@ -700,6 +700,7 @@ class PartialMessage(Hashable):
- :meth:`TextChannel.get_partial_message`
- :meth:`VoiceChannel.get_partial_message`
- :meth:`StageChannel.get_partial_message`
- :meth:`Thread.get_partial_message`
- :meth:`DMChannel.get_partial_message`
@ -723,7 +724,7 @@ class PartialMessage(Hashable):
Attributes
-----------
channel: Union[:class:`PartialMessageable`, :class:`TextChannel`, :class:`VoiceChannel`, :class:`Thread`, :class:`DMChannel`]
channel: Union[:class:`PartialMessageable`, :class:`TextChannel`, :class:`StageChannel`, :class:`VoiceChannel`, :class:`Thread`, :class:`DMChannel`]
The channel associated with this partial message.
id: :class:`int`
The message ID.
@ -737,6 +738,7 @@ class PartialMessage(Hashable):
if not isinstance(channel, PartialMessageable) and channel.type not in (
ChannelType.text,
ChannelType.voice,
ChannelType.stage_voice,
ChannelType.news,
ChannelType.private,
ChannelType.news_thread,
@ -744,7 +746,7 @@ class PartialMessage(Hashable):
ChannelType.private_thread,
):
raise TypeError(
f'expected PartialMessageable, TextChannel, VoiceChannel, DMChannel or Thread not {type(channel)!r}'
f'expected PartialMessageable, TextChannel, StageChannel, VoiceChannel, DMChannel or Thread not {type(channel)!r}'
)
self.channel: MessageableChannel = channel
@ -1252,6 +1254,86 @@ class PartialMessage(Hashable):
)
return Thread(guild=self.guild, state=self._state, data=data)
@overload
async def reply(
self,
content: Optional[str] = ...,
*,
tts: bool = ...,
embed: Embed = ...,
file: File = ...,
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
delete_after: float = ...,
nonce: Union[str, int] = ...,
allowed_mentions: AllowedMentions = ...,
reference: Union[Message, MessageReference, PartialMessage] = ...,
mention_author: bool = ...,
view: View = ...,
suppress_embeds: bool = ...,
silent: bool = ...,
) -> Message:
...
@overload
async def reply(
self,
content: Optional[str] = ...,
*,
tts: bool = ...,
embed: Embed = ...,
files: Sequence[File] = ...,
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
delete_after: float = ...,
nonce: Union[str, int] = ...,
allowed_mentions: AllowedMentions = ...,
reference: Union[Message, MessageReference, PartialMessage] = ...,
mention_author: bool = ...,
view: View = ...,
suppress_embeds: bool = ...,
silent: bool = ...,
) -> Message:
...
@overload
async def reply(
self,
content: Optional[str] = ...,
*,
tts: bool = ...,
embeds: Sequence[Embed] = ...,
file: File = ...,
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
delete_after: float = ...,
nonce: Union[str, int] = ...,
allowed_mentions: AllowedMentions = ...,
reference: Union[Message, MessageReference, PartialMessage] = ...,
mention_author: bool = ...,
view: View = ...,
suppress_embeds: bool = ...,
silent: bool = ...,
) -> Message:
...
@overload
async def reply(
self,
content: Optional[str] = ...,
*,
tts: bool = ...,
embeds: Sequence[Embed] = ...,
files: Sequence[File] = ...,
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
delete_after: float = ...,
nonce: Union[str, int] = ...,
allowed_mentions: AllowedMentions = ...,
reference: Union[Message, MessageReference, PartialMessage] = ...,
mention_author: bool = ...,
view: View = ...,
suppress_embeds: bool = ...,
silent: bool = ...,
) -> Message:
...
async def reply(self, content: Optional[str] = None, **kwargs: Any) -> Message:
"""|coro|
@ -1357,7 +1439,7 @@ class Message(PartialMessage, Hashable):
A list of embeds the message has.
If :attr:`Intents.message_content` is not enabled this will always be an empty list
unless the bot is mentioned or the message is a direct message.
channel: Union[:class:`TextChannel`, :class:`VoiceChannel`, :class:`Thread`, :class:`DMChannel`, :class:`GroupChannel`, :class:`PartialMessageable`]
channel: Union[:class:`TextChannel`, :class:`StageChannel`, :class:`VoiceChannel`, :class:`Thread`, :class:`DMChannel`, :class:`GroupChannel`, :class:`PartialMessageable`]
The :class:`TextChannel` or :class:`Thread` that the message was sent from.
Could be a :class:`DMChannel` or :class:`GroupChannel` if it's a private message.
reference: Optional[:class:`~discord.MessageReference`]
@ -2015,6 +2097,21 @@ class Message(PartialMessage, Hashable):
months = '1 month' if total_months == 1 else f'{total_months} months'
return f'{self.author.name} joined {self.role_subscription.tier_name} and has been a subscriber of {self.guild} for {months}!'
if self.type is MessageType.stage_start:
return f'{self.author.name} started **{self.content}**.'
if self.type is MessageType.stage_end:
return f'{self.author.name} ended **{self.content}**.'
if self.type is MessageType.stage_speaker:
return f'{self.author.name} is now a speaker.'
if self.type is MessageType.stage_raise_hand:
return f'{self.author.name} requested to speak.'
if self.type is MessageType.stage_topic:
return f'{self.author.name} changed Stage topic: **{self.content}**.'
# Fallback for unknown message types
return ''

46
discord/permissions.py

@ -177,7 +177,7 @@ class Permissions(BaseFlags):
"""A factory method that creates a :class:`Permissions` with all
permissions set to ``True``.
"""
return cls(0b11111111111111111111111111111111111111111)
return cls(0b1111111111111111111111111111111111111111111111)
@classmethod
def _timeout_mask(cls) -> int:
@ -204,7 +204,7 @@ class Permissions(BaseFlags):
``True`` and the guild-specific ones set to ``False``. The guild-specific
permissions are currently:
- :attr:`manage_emojis`
- :attr:`manage_guild_expressions`
- :attr:`view_audit_log`
- :attr:`view_guild_insights`
- :attr:`manage_guild`
@ -221,8 +221,11 @@ class Permissions(BaseFlags):
Added :attr:`create_public_threads`, :attr:`create_private_threads`, :attr:`manage_threads`,
:attr:`use_external_stickers`, :attr:`send_messages_in_threads` and
:attr:`request_to_speak` permissions.
.. versionchanged:: 2.3
Added :attr:`use_soundboard`
"""
return cls(0b111110110110011111101111111111101010001)
return cls(0b1000111110110110011111101111111111101010001)
@classmethod
def general(cls) -> Self:
@ -265,7 +268,7 @@ class Permissions(BaseFlags):
def voice(cls) -> Self:
"""A factory method that creates a :class:`Permissions` with all
"Voice" permissions from the official Discord UI set to ``True``."""
return cls(0b1000000000000011111100000000001100000000)
return cls(0b1001001000000000000011111100000000001100000000)
@classmethod
def stage(cls) -> Self:
@ -305,7 +308,7 @@ class Permissions(BaseFlags):
- :attr:`manage_messages`
- :attr:`manage_roles`
- :attr:`manage_webhooks`
- :attr:`manage_emojis_and_stickers`
- :attr:`manage_guild_expressions`
- :attr:`manage_threads`
- :attr:`moderate_members`
@ -544,13 +547,21 @@ class Permissions(BaseFlags):
return 1 << 29
@flag_value
def manage_guild_expressions(self) -> int:
""":class:`bool`: Returns ``True`` if a user can create, edit, or delete emojis, stickers, and soundboard sounds.
.. versionadded:: 2.3
"""
return 1 << 30
@make_permission_alias('manage_guild_expressions')
def manage_emojis(self) -> int:
""":class:`bool`: Returns ``True`` if a user can create, edit, or delete emojis."""
""":class:`bool`: An alias for :attr:`manage_guild_expressions`."""
return 1 << 30
@make_permission_alias('manage_emojis')
@make_permission_alias('manage_guild_expressions')
def manage_emojis_and_stickers(self) -> int:
""":class:`bool`: An alias for :attr:`manage_emojis`.
""":class:`bool`: An alias for :attr:`manage_guild_expressions`.
.. versionadded:: 2.0
"""
@ -644,6 +655,22 @@ class Permissions(BaseFlags):
"""
return 1 << 40
@flag_value
def use_soundboard(self) -> int:
""":class:`bool`: Returns ``True`` if a user can use the soundboard.
.. versionadded:: 2.3
"""
return 1 << 42
@flag_value
def use_external_sounds(self) -> int:
""":class:`bool`: Returns ``True`` if a user can use sounds from other guilds.
.. versionadded:: 2.3
"""
return 1 << 45
def _augment_from_permissions(cls):
cls.VALID_NAMES = set(Permissions.VALID_FLAGS)
@ -745,6 +772,7 @@ class PermissionOverwrite:
manage_roles: Optional[bool]
manage_permissions: Optional[bool]
manage_webhooks: Optional[bool]
manage_guild_expressions: Optional[bool]
manage_emojis: Optional[bool]
manage_emojis_and_stickers: Optional[bool]
use_application_commands: Optional[bool]
@ -758,6 +786,8 @@ class PermissionOverwrite:
use_external_stickers: Optional[bool]
use_embedded_activities: Optional[bool]
moderate_members: Optional[bool]
use_soundboard: Optional[bool]
use_external_sounds: Optional[bool]
def __init__(self, **kwargs: Optional[bool]):
self._values: Dict[str, Optional[bool]] = {}

112
discord/scheduled_event.py

@ -25,7 +25,7 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations
from datetime import datetime
from typing import TYPE_CHECKING, AsyncIterator, Dict, Optional, Union
from typing import TYPE_CHECKING, AsyncIterator, Dict, Optional, Union, overload, Literal
from .asset import Asset
from .enums import EventStatus, EntityType, PrivacyLevel, try_enum
@ -298,6 +298,87 @@ class ScheduledEvent(Hashable):
return await self.__modify_status(EventStatus.cancelled, reason)
@overload
async def edit(
self,
*,
name: str = ...,
description: str = ...,
start_time: datetime = ...,
end_time: Optional[datetime] = ...,
privacy_level: PrivacyLevel = ...,
status: EventStatus = ...,
image: bytes = ...,
reason: Optional[str] = ...,
) -> ScheduledEvent:
...
@overload
async def edit(
self,
*,
name: str = ...,
description: str = ...,
channel: Snowflake,
start_time: datetime = ...,
end_time: Optional[datetime] = ...,
privacy_level: PrivacyLevel = ...,
entity_type: Literal[EntityType.voice, EntityType.stage_instance],
status: EventStatus = ...,
image: bytes = ...,
reason: Optional[str] = ...,
) -> ScheduledEvent:
...
@overload
async def edit(
self,
*,
name: str = ...,
description: str = ...,
start_time: datetime = ...,
end_time: datetime = ...,
privacy_level: PrivacyLevel = ...,
entity_type: Literal[EntityType.external],
status: EventStatus = ...,
image: bytes = ...,
location: str,
reason: Optional[str] = ...,
) -> ScheduledEvent:
...
@overload
async def edit(
self,
*,
name: str = ...,
description: str = ...,
channel: Union[VoiceChannel, StageChannel],
start_time: datetime = ...,
end_time: Optional[datetime] = ...,
privacy_level: PrivacyLevel = ...,
status: EventStatus = ...,
image: bytes = ...,
reason: Optional[str] = ...,
) -> ScheduledEvent:
...
@overload
async def edit(
self,
*,
name: str = ...,
description: str = ...,
start_time: datetime = ...,
end_time: datetime = ...,
privacy_level: PrivacyLevel = ...,
status: EventStatus = ...,
image: bytes = ...,
location: str,
reason: Optional[str] = ...,
) -> ScheduledEvent:
...
async def edit(
self,
*,
@ -414,23 +495,33 @@ class ScheduledEvent(Hashable):
payload['image'] = image_as_str
entity_type = entity_type or getattr(channel, '_scheduled_event_entity_type', MISSING)
if entity_type is None:
raise TypeError(
f'invalid GuildChannel type passed, must be VoiceChannel or StageChannel not {channel.__class__.__name__}'
)
if entity_type is not MISSING:
if entity_type is MISSING:
if channel and isinstance(channel, Object):
if channel.type is VoiceChannel:
entity_type = EntityType.voice
elif channel.type is StageChannel:
entity_type = EntityType.stage_instance
elif location not in (MISSING, None):
entity_type = EntityType.external
else:
if not isinstance(entity_type, EntityType):
raise TypeError('entity_type must be of type EntityType')
payload['entity_type'] = entity_type.value
if entity_type is None:
raise TypeError(
f'invalid GuildChannel type passed, must be VoiceChannel or StageChannel not {channel.__class__.__name__}'
)
_entity_type = entity_type or self.entity_type
_entity_type_changed = _entity_type is not self.entity_type
if _entity_type in (EntityType.stage_instance, EntityType.voice):
if channel is MISSING or channel is None:
if _entity_type_changed:
raise TypeError('channel must be set when entity_type is voice or stage_instance')
else:
payload['channel_id'] = channel.id
if location not in (MISSING, None):
@ -442,11 +533,12 @@ class ScheduledEvent(Hashable):
payload['channel_id'] = None
if location is MISSING or location is None:
if _entity_type_changed:
raise TypeError('location must be set when entity_type is external')
else:
metadata['location'] = location
if end_time is MISSING or end_time is None:
if not self.end_time and (end_time is MISSING or end_time is None):
raise TypeError('end_time must be set when entity_type is external')
if end_time is not MISSING:

2
discord/sticker.py

@ -414,7 +414,7 @@ class GuildSticker(Sticker):
def _from_data(self, data: GuildStickerPayload) -> None:
super()._from_data(data)
self.available: bool = data['available']
self.available: bool = data.get('available', True)
self.guild_id: int = int(data['guild_id'])
user = data.get('user')
self.user: Optional[User] = self._state.store_user(user) if user else None

17
discord/types/appinfo.py

@ -44,10 +44,14 @@ class BaseAppInfo(TypedDict):
icon: Optional[str]
summary: str
description: str
flags: int
cover_image: NotRequired[str]
terms_of_service_url: NotRequired[str]
privacy_policy_url: NotRequired[str]
rpc_origins: NotRequired[List[str]]
class AppInfo(BaseAppInfo):
rpc_origins: List[str]
owner: User
bot_public: bool
bot_require_code_grant: bool
@ -55,8 +59,6 @@ class AppInfo(BaseAppInfo):
guild_id: NotRequired[Snowflake]
primary_sku_id: NotRequired[Snowflake]
slug: NotRequired[str]
terms_of_service_url: NotRequired[str]
privacy_policy_url: NotRequired[str]
hook: NotRequired[bool]
max_participants: NotRequired[int]
tags: NotRequired[List[str]]
@ -66,13 +68,12 @@ class AppInfo(BaseAppInfo):
class PartialAppInfo(BaseAppInfo, total=False):
rpc_origins: List[str]
cover_image: str
hook: bool
terms_of_service_url: str
privacy_policy_url: str
max_participants: int
flags: int
approximate_guild_count: int
redirect_uris: List[str]
interactions_endpoint_url: Optional[str]
role_connections_verification_url: Optional[str]
class GatewayAppInfo(TypedDict):

6
discord/types/automod.py

@ -45,9 +45,13 @@ class _AutoModerationActionMetadataTimeout(TypedDict):
duration_seconds: int
class _AutoModerationActionMetadataCustomMessage(TypedDict):
custom_message: str
class _AutoModerationActionBlockMessage(TypedDict):
type: Literal[1]
metadata: NotRequired[Empty]
metadata: NotRequired[_AutoModerationActionMetadataCustomMessage]
class _AutoModerationActionAlert(TypedDict):

2
discord/types/channel.py

@ -134,6 +134,7 @@ class ForumTag(TypedDict):
emoji_name: Optional[str]
ForumOrderType = Literal[0, 1]
ForumLayoutType = Literal[0, 1, 2]
@ -141,6 +142,7 @@ class ForumChannel(_BaseTextChannel):
type: Literal[15]
available_tags: List[ForumTag]
default_reaction_emoji: Optional[DefaultReaction]
default_sort_order: Optional[ForumOrderType]
default_forum_layout: NotRequired[ForumLayoutType]
flags: NotRequired[int]

4
discord/types/guild.py

@ -161,6 +161,10 @@ class GuildPrune(TypedDict):
pruned: Optional[int]
class GuildMFALevel(TypedDict):
level: MFALevel
class ChannelPositionUpdate(TypedDict):
id: Snowflake
position: Optional[int]

2
discord/types/sticker.py

@ -55,7 +55,7 @@ class StandardSticker(BaseSticker):
class GuildSticker(BaseSticker):
type: Literal[2]
available: bool
available: NotRequired[bool]
guild_id: Snowflake
user: NotRequired[User]

2
discord/ui/item.py

@ -40,7 +40,7 @@ if TYPE_CHECKING:
from .view import View
from ..components import Component
I = TypeVar('I', bound='Item')
I = TypeVar('I', bound='Item[Any]')
V = TypeVar('V', bound='View', covariant=True)
ItemCallbackType = Callable[[V, Interaction[Any], I], Coroutine[Any, Any, Any]]

14
discord/ui/select.py

@ -22,7 +22,7 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from __future__ import annotations
from typing import List, Literal, Optional, TYPE_CHECKING, Tuple, Type, TypeVar, Callable, Union, Dict, overload
from typing import Any, List, Literal, Optional, TYPE_CHECKING, Tuple, Type, TypeVar, Callable, Union, Dict, overload
from contextvars import ContextVar
import inspect
import os
@ -71,12 +71,12 @@ if TYPE_CHECKING:
]
V = TypeVar('V', bound='View', covariant=True)
BaseSelectT = TypeVar('BaseSelectT', bound='BaseSelect')
SelectT = TypeVar('SelectT', bound='Select')
UserSelectT = TypeVar('UserSelectT', bound='UserSelect')
RoleSelectT = TypeVar('RoleSelectT', bound='RoleSelect')
ChannelSelectT = TypeVar('ChannelSelectT', bound='ChannelSelect')
MentionableSelectT = TypeVar('MentionableSelectT', bound='MentionableSelect')
BaseSelectT = TypeVar('BaseSelectT', bound='BaseSelect[Any]')
SelectT = TypeVar('SelectT', bound='Select[Any]')
UserSelectT = TypeVar('UserSelectT', bound='UserSelect[Any]')
RoleSelectT = TypeVar('RoleSelectT', bound='RoleSelect[Any]')
ChannelSelectT = TypeVar('ChannelSelectT', bound='ChannelSelect[Any]')
MentionableSelectT = TypeVar('MentionableSelectT', bound='MentionableSelect[Any]')
SelectCallbackDecorator: TypeAlias = Callable[[ItemCallbackType[V, BaseSelectT]], BaseSelectT]
selected_values: ContextVar[Dict[str, List[PossibleValue]]] = ContextVar('selected_values')

12
discord/webhook/sync.py

@ -636,9 +636,9 @@ class SyncWebhook(BaseWebhook):
Returns
--------
:class:`Webhook`
A partial :class:`Webhook`.
A partial webhook is just a webhook object with an ID and a token.
:class:`SyncWebhook`
A partial :class:`SyncWebhook`.
A partial :class:`SyncWebhook` is just a :class:`SyncWebhook` object with an ID and a token.
"""
data: WebhookPayload = {
'id': id,
@ -678,9 +678,9 @@ class SyncWebhook(BaseWebhook):
Returns
--------
:class:`Webhook`
A partial :class:`Webhook`.
A partial webhook is just a webhook object with an ID and a token.
:class:`SyncWebhook`
A partial :class:`SyncWebhook`.
A partial :class:`SyncWebhook` is just a :class:`SyncWebhook` object with an ID and a token.
"""
m = re.search(r'discord(?:app)?\.com/api/webhooks/(?P<id>[0-9]{17,20})/(?P<token>[A-Za-z0-9\.\-\_]{60,68})', url)
if m is None:

2
discord/widget.py

@ -226,7 +226,7 @@ class Widget:
The guild's name.
channels: List[:class:`WidgetChannel`]
The accessible voice channels in the guild.
members: List[:class:`Member`]
members: List[:class:`WidgetMember`]
The online members in the guild. Offline members
do not appear in the widget.

86
docs/api.rst

@ -1653,6 +1653,31 @@ of :class:`enum.Enum`.
The system message sent when a user is given an advertisement to purchase a premium tier for
an application during an interaction.
.. versionadded:: 2.2
.. attribute:: stage_start
The system message sent when the stage starts.
.. versionadded:: 2.2
.. attribute:: stage_end
The system message sent when the stage ends.
.. versionadded:: 2.2
.. attribute:: stage_speaker
The system message sent when the stage speaker changes.
.. versionadded:: 2.2
.. attribute:: stage_raise_hand
The system message sent when a user is requesting to speak by raising their hands.
.. versionadded:: 2.2
.. attribute:: stage_topic
The system message sent when the stage topic changes.
.. versionadded:: 2.2
.. attribute:: guild_application_premium_subscription
@ -1950,6 +1975,8 @@ of :class:`enum.Enum`.
- :attr:`~AuditLogDiff.verification_level`
- :attr:`~AuditLogDiff.widget_channel`
- :attr:`~AuditLogDiff.widget_enabled`
- :attr:`~AuditLogDiff.premium_progress_bar_enabled`
- :attr:`~AuditLogDiff.system_channel_flags`
.. attribute:: channel_create
@ -1991,6 +2018,9 @@ of :class:`enum.Enum`.
- :attr:`~AuditLogDiff.rtc_region`
- :attr:`~AuditLogDiff.video_quality_mode`
- :attr:`~AuditLogDiff.default_auto_archive_duration`
- :attr:`~AuditLogDiff.nsfw`
- :attr:`~AuditLogDiff.slowmode_delay`
- :attr:`~AuditLogDiff.user_limit`
.. attribute:: channel_delete
@ -2007,6 +2037,9 @@ of :class:`enum.Enum`.
- :attr:`~AuditLogDiff.name`
- :attr:`~AuditLogDiff.type`
- :attr:`~AuditLogDiff.overwrites`
- :attr:`~AuditLogDiff.flags`
- :attr:`~AuditLogDiff.nsfw`
- :attr:`~AuditLogDiff.slowmode_delay`
.. attribute:: overwrite_create
@ -2078,7 +2111,7 @@ of :class:`enum.Enum`.
When this is the action, the type of :attr:`~AuditLogEntry.extra` is
set to an unspecified proxy object with two attributes:
- ``delete_members_days``: An integer specifying how far the prune was.
- ``delete_member_days``: An integer specifying how far the prune was.
- ``members_removed``: An integer specifying how many members were removed.
When this is the action, :attr:`~AuditLogEntry.changes` is empty.
@ -3291,6 +3324,21 @@ of :class:`enum.Enum`.
Prompt options are displayed as a drop-down.
.. class:: ForumOrderType
Represents how a forum's posts are sorted in the client.
.. versionadded:: 2.3
.. attribute:: latest_activity
Sort forum posts by activity.
.. attribute:: creation_date
Sort forum posts by creation time (from most recent to oldest).
.. _discord-api-audit-logs:
Audit Log Data
@ -3926,6 +3974,42 @@ AuditLogDiff
:type: List[:class:`abc.GuildChannel`, :class:`Thread`, :class:`Object`]
.. attribute:: premium_progress_bar_enabled
The guild’s display setting to show boost progress bar.
:type: :class:`bool`
.. attribute:: system_channel_flags
The guild’s system channel settings.
See also :attr:`Guild.system_channel_flags`
:type: :class:`SystemChannelFlags`
.. attribute:: nsfw
Whether the channel is marked as “not safe for work” or “age restricted”.
:type: :class:`bool`
.. attribute:: user_limit
The channel’s limit for number of members that can be in a voice or stage channel.
See also :attr:`VoiceChannel.user_limit` and :attr:`StageChannel.user_limit`
:type: :class:`int`
.. attribute:: flags
The channel flags associated with this thread or forum post.
See also :attr:`ForumChannel.flags` and :attr:`Thread.flags`
:type: :class:`ChannelFlags`
.. this is currently missing the following keys: reason and application_id
I'm not sure how to port these

2
docs/ext/commands/api.rst

@ -256,7 +256,7 @@ GroupCog
.. attributetable:: discord.ext.commands.GroupCog
.. autoclass:: discord.ext.commands.GroupCog
:members:
:members: interaction_check
CogMeta

1
docs/ext/commands/commands.rst

@ -1147,6 +1147,7 @@ If you want a more robust error system, you can derive from the exception and ra
return True
return commands.check(predicate)
@bot.command()
@guild_only()
async def test(ctx):
await ctx.send('Hey this is not a DM! Nice.')

90
docs/whats_new.rst

@ -11,6 +11,96 @@ Changelog
This page keeps a detailed human friendly rendering of what's new and changed
in specific versions.
.. _vp2p2p2:
v2.2.2
-------
Bug Fixes
~~~~~~~~~~
- Fix UDP discovery in voice not using new 74 byte layout which caused voice to break (:issue:`9277`, :issue:`9278`)
.. _vp2p2p0:
v2.2.0
-------
New Features
~~~~~~~~~~~~
- Add support for new :func:`on_audit_log_entry_create` event
- Add support for silent messages via ``silent`` parameter in :meth:`abc.Messageable.send`
- This is queryable via :attr:`MessageFlags.suppress_notifications`
- Implement :class:`abc.Messageable` for :class:`StageChannel` (:issue:`9248`)
- Add setter for :attr:`discord.ui.ChannelSelect.channel_types` (:issue:`9068`)
- Add support for custom messages in automod via :attr:`AutoModRuleAction.custom_message` (:issue:`9267`)
- Add :meth:`ForumChannel.get_thread` (:issue:`9106`)
- Add :attr:`StageChannel.slowmode_delay` and :attr:`VoiceChannel.slowmode_delay` (:issue:`9111`)
- Add support for editing the slowmode for :class:`StageChannel` and :class:`VoiceChannel` (:issue:`9111`)
- Add :attr:`Locale.indonesian`
- Add ``delete_after`` keyword argument to :meth:`Interaction.edit_message` (:issue:`9415`)
- Add ``delete_after`` keyword argument to :meth:`InteractionMessage.edit` (:issue:`9206`)
- Add support for member flags (:issue:`9204`)
- Accessible via :attr:`Member.flags` and has a type of :class:`MemberFlags`
- Support ``bypass_verification`` within :meth:`Member.edit`
- Add support for passing a client to :meth:`Webhook.from_url` and :meth:`Webhook.partial`
- This allows them to use views (assuming they are "bot owned" webhooks)
- Add :meth:`Colour.dark_embed` and :meth:`Colour.light_embed` (:issue:`9219`)
- Add support for many more parameters within :meth:`Guild.create_stage_channel` (:issue:`9245`)
- Add :attr:`AppInfo.role_connections_verification_url`
- Add support for :attr:`ForumChannel.default_layout`
- Add various new :class:`MessageType` values such as ones related to stage channel and role subscriptions
- Add support for role subscription related attributes
- :class:`RoleSubscriptionInfo` within :attr:`Message.role_subscription`
- :attr:`MessageType.role_subscription_purchase`
- :attr:`SystemChannelFlags.role_subscription_purchase_notifications`
- :attr:`SystemChannelFlags.role_subscription_purchase_notification_replies`
- :attr:`RoleTags.subscription_listing_id`
- :meth:`RoleTags.is_available_for_purchase`
- Add support for checking if a role is a linked role under :meth:`RoleTags.is_guild_connection`
- Add support for GIF sticker type
- Add support for :attr:`Message.application_id` and :attr:`Message.position`
- Add :func:`utils.maybe_coroutine` helper
- Add :attr:`ScheduledEvent.creator_id` attribute
- |commands| Add support for :meth:`~ext.commands.Cog.interaction_check` for :class:`~ext.commands.GroupCog` (:issue:`9189`)
Bug Fixes
~~~~~~~~~~
- Fix views not being removed from message store backing leading to a memory leak when used from an application command context
- Fix async iterators requesting past their bounds when using ``oldest_first`` and ``after`` or ``before`` (:issue:`9093`)
- Fix :meth:`Guild.audit_logs` pagination logic being buggy when using ``after`` (:issue:`9269`)
- Fix :attr:`Message.channel` sometimes being :class:`Object` instead of :class:`PartialMessageable`
- Fix :class:`ui.View` not properly calling ``super().__init_subclass__`` (:issue:`9231`)
- Fix ``available_tags`` and ``default_thread_slowmode_delay`` not being respected in :meth:`Guild.create_forum`
- Fix :class:`AutoModTrigger` ignoring ``allow_list`` with type keyword (:issue:`9107`)
- Fix implicit permission resolution for :class:`Thread` (:issue:`9153`)
- Fix :meth:`AutoModRule.edit` to work with actual snowflake types such as :class:`Object` (:issue:`9159`)
- Fix :meth:`Webhook.send` returning :class:`ForumChannel` for :attr:`WebhookMessage.channel`
- When a lookup for :attr:`AuditLogEntry.target` fails, it will fallback to :class:`Object` with the appropriate :attr:`Object.type` (:issue:`9171`)
- Fix :attr:`AuditLogDiff.type` for integrations returning :class:`ChannelType` instead of :class:`str` (:issue:`9200`)
- Fix :attr:`AuditLogDiff.type` for webhooks returning :class:`ChannelType` instead of :class:`WebhookType` (:issue:`9251`)
- Fix webhooks and interactions not properly closing files after the request has completed
- Fix :exc:`NameError` in audit log target for app commands
- Fix :meth:`ScheduledEvent.edit` requiring some arguments to be passed in when unnecessary (:issue:`9261`, :issue:`9268`)
- |commands| Explicit set a traceback for hybrid command invocations (:issue:`9205`)
Miscellaneous
~~~~~~~~~~~~~~
- Add colour preview for the colours predefined in :class:`Colour`
- Finished views are no longer stored by the library when sending them (:issue:`9235`)
- Force enable colour logging for the default logging handler when run under Docker.
- Add various overloads for :meth:`Client.wait_for` to aid in static analysis (:issue:`9184`)
- :class:`Interaction` can now optionally take a generic parameter, ``ClientT`` to represent the type for :attr:`Interaction.client`
- |commands| Respect :attr:`~ext.commands.Command.ignore_extra` for :class:`~discord.ext.commands.FlagConverter` keyword-only parameters
- |commands| Change :attr:`Paginator.pages <ext.commands.Paginator.pages>` to not prematurely close (:issue:`9257`)
.. _vp2p1p1:
v2.1.1

Loading…
Cancel
Save