From 29461ddb5f67936509a462ab0ffa0e9e4ee4d7bd Mon Sep 17 00:00:00 2001 From: Puncher <65789180+Puncher1@users.noreply.github.com> Date: Sat, 25 Feb 2023 08:44:44 +0100 Subject: [PATCH 01/50] Change max values in AutoModTrigger attributes docs --- discord/automod.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/discord/automod.py b/discord/automod.py index c90e80389..cbf3a0012 100644 --- a/discord/automod.py +++ b/discord/automod.py @@ -139,13 +139,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 `_. - 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 +153,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. From da4651c97c438d9525b5dad4b73a61d407e2ab48 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Sat, 25 Feb 2023 03:13:55 -0500 Subject: [PATCH 02/50] Implement Messageable for StageChannel Fix #9248 --- discord/abc.py | 14 +++- discord/channel.py | 205 +++++++++++++++++++++++---------------------- discord/message.py | 3 +- 3 files changed, 119 insertions(+), 103 deletions(-) diff --git a/discord/abc.py b/discord/abc.py index 6e3125f40..65db6d28c 100644 --- a/discord/abc.py +++ b/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, diff --git a/discord/channel.py b/discord/channel.py index 8aecf7838..4e955a6ae 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -878,7 +878,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 +901,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 +991,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 +1035,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 +1238,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 +1358,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 +1493,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. @@ -1665,6 +1671,7 @@ class StageChannel(VocalGuildChannel): overwrites: Mapping[OverwriteKeyT, PermissionOverwrite] = ..., rtc_region: Optional[str] = ..., video_quality_mode: VideoQualityMode = ..., + slowmode_delay: int = ..., reason: Optional[str] = ..., ) -> StageChannel: ... diff --git a/discord/message.py b/discord/message.py index 06cd9e3af..a73e882d3 100644 --- a/discord/message.py +++ b/discord/message.py @@ -737,6 +737,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 +745,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 From fd9f3e9eff86ea9130b83eb3ff568cc20fdda59e Mon Sep 17 00:00:00 2001 From: Rapptz Date: Sat, 25 Feb 2023 03:27:04 -0500 Subject: [PATCH 03/50] Update docs with references to text in stage --- discord/abc.py | 1 + discord/message.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/discord/abc.py b/discord/abc.py index 65db6d28c..c6b63920f 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -1292,6 +1292,7 @@ class Messageable: - :class:`~discord.TextChannel` - :class:`~discord.VoiceChannel` + - :class:`~discord.StageChannel` - :class:`~discord.DMChannel` - :class:`~discord.GroupChannel` - :class:`~discord.PartialMessageable` diff --git a/discord/message.py b/discord/message.py index a73e882d3..2acc50278 100644 --- a/discord/message.py +++ b/discord/message.py @@ -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. @@ -1358,7 +1359,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`] From 517d7ae4fe352b795c9c7e3717f5e6fca1230a5c Mon Sep 17 00:00:00 2001 From: Rapptz Date: Sat, 25 Feb 2023 06:05:16 -0500 Subject: [PATCH 04/50] Avoid converting target_id if it's None --- discord/audit_logs.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/discord/audit_logs.py b/discord/audit_logs.py index 72cf8f854..fe51b6106 100644 --- a/discord/audit_logs.py +++ b/discord/audit_logs.py @@ -723,11 +723,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) From 90d50bef1fa7ce63191d0e0a8b64038738d3b929 Mon Sep 17 00:00:00 2001 From: Josh Date: Sun, 26 Feb 2023 18:26:26 +1000 Subject: [PATCH 05/50] Add custom_message to automod block_message aciton --- discord/automod.py | 28 +++++++++++++++++++++------- discord/types/automod.py | 6 +++++- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/discord/automod.py b/discord/automod.py index cbf3a0012..b34bec000 100644 --- a/discord/automod.py +++ b/discord/automod.py @@ -27,7 +27,6 @@ import datetime from typing import TYPE_CHECKING, Any, Dict, Optional, List, Sequence, Set, Union, Sequence - from .enums import AutoModRuleTriggerType, AutoModRuleActionType, AutoModRuleEventType, try_enum from .flags import AutoModPresets from . import utils @@ -73,15 +72,28 @@ 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') - 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.') if channel_id: self.type = AutoModRuleActionType.send_alert_message @@ -102,11 +114,13 @@ class AutoModRuleAction: 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)} diff --git a/discord/types/automod.py b/discord/types/automod.py index 250d1e41e..9b0b13692 100644 --- a/discord/types/automod.py +++ b/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): From 6af6f824110e93a9f096df20c1fbdb6485b40348 Mon Sep 17 00:00:00 2001 From: Josh Date: Mon, 27 Feb 2023 21:15:28 +1000 Subject: [PATCH 06/50] Fix audit log pagination logic --- discord/guild.py | 15 ++++++--------- discord/http.py | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index 078a7b8be..96bd1801b 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -3630,7 +3630,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 @@ -3647,20 +3647,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 @@ -3673,8 +3672,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) diff --git a/discord/http.py b/discord/http.py index 5913f4911..3fa622967 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1717,7 +1717,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 From 8f86170767bdce8eb9ddce8c9fbe89c96bb548d6 Mon Sep 17 00:00:00 2001 From: z03h <7235242+z03h@users.noreply.github.com> Date: Mon, 27 Feb 2023 03:18:13 -0800 Subject: [PATCH 07/50] Don't require some args for ScheduledEvent.edit --- discord/scheduled_event.py | 109 ++++++++++++++++++++++++++++++++++--- 1 file changed, 101 insertions(+), 8 deletions(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 9f8bd9920..810ea2b2a 100644 --- a/discord/scheduled_event.py +++ b/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,6 +495,15 @@ class ScheduledEvent(Hashable): payload['image'] = image_as_str entity_type = entity_type or getattr(channel, '_scheduled_event_entity_type', 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 + if entity_type is None: raise TypeError( f'invalid GuildChannel type passed, must be VoiceChannel or StageChannel not {channel.__class__.__name__}' @@ -426,12 +516,14 @@ class ScheduledEvent(Hashable): payload['entity_type'] = entity_type.value _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: - raise TypeError('channel must be set when entity_type is voice or stage_instance') - - payload['channel_id'] = channel.id + 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): raise TypeError('location cannot be set when entity_type is voice or stage_instance') @@ -442,11 +534,12 @@ class ScheduledEvent(Hashable): payload['channel_id'] = None if location is MISSING or location is None: - raise TypeError('location must be set when entity_type is external') - - metadata['location'] = location + 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: From ef4240dcd70f532ca8477de294394883fb9dfbe1 Mon Sep 17 00:00:00 2001 From: Willy <19799671+Willy-C@users.noreply.github.com> Date: Mon, 27 Feb 2023 06:19:17 -0500 Subject: [PATCH 08/50] [commands] Add missing command decorator to example --- docs/ext/commands/commands.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/ext/commands/commands.rst b/docs/ext/commands/commands.rst index e479c4b08..02a9ae670 100644 --- a/docs/ext/commands/commands.rst +++ b/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.') From bd6bbdab007e0ac8b21fbdae9c91f2d1e981eaa2 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Mon, 27 Feb 2023 09:17:40 -0500 Subject: [PATCH 09/50] Add support for stage message types --- discord/enums.py | 10 +++++----- discord/message.py | 15 +++++++++++++++ docs/api.rst | 25 +++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/discord/enums.py b/discord/enums.py index 43ae5b4f7..21a38caf8 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -234,11 +234,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 diff --git a/discord/message.py b/discord/message.py index 2acc50278..719cb9697 100644 --- a/discord/message.py +++ b/discord/message.py @@ -2017,6 +2017,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 '' diff --git a/docs/api.rst b/docs/api.rst index 32e393ef2..d40974d67 100644 --- a/docs/api.rst +++ b/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 From 1e9d04bbca21d6d38f8b610b861ae9c0c746ad48 Mon Sep 17 00:00:00 2001 From: Puncher <65789180+Puncher1@users.noreply.github.com> Date: Tue, 28 Feb 2023 08:18:59 +0100 Subject: [PATCH 10/50] Add missing and fix wrong docstrings --- discord/ext/commands/hybrid.py | 3 +++ discord/guild.py | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/discord/ext/commands/hybrid.py b/discord/ext/commands/hybrid.py index fb6d39b31..d3f3c91b5 100644 --- a/discord/ext/commands/hybrid.py +++ b/discord/ext/commands/hybrid.py @@ -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. diff --git a/discord/guild.py b/discord/guild.py index 96bd1801b..cf6ddd4c3 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -1593,6 +1593,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`] @@ -2782,7 +2786,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. @@ -2846,6 +2850,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 From a5a93a85bc9eebb3b7db65b9523af11be489a23c Mon Sep 17 00:00:00 2001 From: Soheab_ <33902984+Soheab@users.noreply.github.com> Date: Tue, 28 Feb 2023 08:20:00 +0100 Subject: [PATCH 11/50] [commands] Document GroupCog.interaction_check --- discord/ext/commands/cog.py | 13 +++++++++++++ docs/ext/commands/api.rst | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/discord/ext/commands/cog.py b/discord/ext/commands/cog.py index 11f6a0393..02136bce8 100644 --- a/discord/ext/commands/cog.py +++ b/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| diff --git a/docs/ext/commands/api.rst b/docs/ext/commands/api.rst index 6420d2b0d..9bda24f6e 100644 --- a/docs/ext/commands/api.rst +++ b/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 From e4667386ea300b0981e0a1d7390e43795f26c8c7 Mon Sep 17 00:00:00 2001 From: owocado <24418520+owocado@users.noreply.github.com> Date: Tue, 28 Feb 2023 19:30:33 +0530 Subject: [PATCH 12/50] Add missing AuditLogDiff attributes for AuditLogAction.guild_update --- docs/api.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index d40974d67..c7f1b549f 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1975,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 @@ -3937,6 +3939,20 @@ AuditLogDiff :type: List[:class:`abc.GuildChannel`, :class:`Thread`, :class:`Object`] + .. attribute:: premium_progress_bar_enabled + + The guild’s display setting to show boost progress sidebar + + :type: :class:`bool` + + .. attribute:: system_channel_flags + + The guild’s system channel settings. + + See also :attr:`Guild.system_channel_flags` + + :type: :class:`SystemChannelFlags` + .. this is currently missing the following keys: reason and application_id I'm not sure how to port these From 2d6c50dbe13b20ab6dc56a20ecc1057a80f166e9 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Tue, 28 Feb 2023 08:46:44 -0500 Subject: [PATCH 13/50] Add note about colour changing --- discord/colour.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/discord/colour.py b/discord/colour.py index 6a9f7b8d5..52dca9cc0 100644 --- a/discord/colour.py +++ b/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` From f849a549141304270155b0458bb6530d374f5b85 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Tue, 28 Feb 2023 08:47:14 -0500 Subject: [PATCH 14/50] Add changelog for v2.2 --- docs/whats_new.rst | 80 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/docs/whats_new.rst b/docs/whats_new.rst index 614312c8a..f0a0f4e8f 100644 --- a/docs/whats_new.rst +++ b/docs/whats_new.rst @@ -11,6 +11,86 @@ Changelog This page keeps a detailed human friendly rendering of what's new and changed in specific versions. +.. _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 ` to not prematurely close (:issue:`9257`) + .. _vp2p1p1: v2.1.1 From 7f1b39b5b3e23b3fbc0acf7060dc1f8451f49817 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Tue, 28 Feb 2023 09:10:18 -0500 Subject: [PATCH 15/50] Version bump to v2.2.0 --- discord/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/__init__.py b/discord/__init__.py index 014615354..198efd61f 100644 --- a/discord/__init__.py +++ b/discord/__init__.py @@ -13,7 +13,7 @@ __title__ = 'discord' __author__ = 'Rapptz' __license__ = 'MIT' __copyright__ = 'Copyright 2015-present Rapptz' -__version__ = '2.2.0a' +__version__ = '2.2.0' __path__ = __import__('pkgutil').extend_path(__path__, __name__) @@ -78,7 +78,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=2, micro=0, releaselevel='final', serial=0) logging.getLogger(__name__).addHandler(logging.NullHandler()) From 7a0744c9a90a38570db631d21f2d28d626710b3e Mon Sep 17 00:00:00 2001 From: Rapptz Date: Tue, 28 Feb 2023 09:10:46 -0500 Subject: [PATCH 16/50] Version bump for development --- discord/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/__init__.py b/discord/__init__.py index 198efd61f..39fdb2673 100644 --- a/discord/__init__.py +++ b/discord/__init__.py @@ -13,7 +13,7 @@ __title__ = 'discord' __author__ = 'Rapptz' __license__ = 'MIT' __copyright__ = 'Copyright 2015-present Rapptz' -__version__ = '2.2.0' +__version__ = '2.3.0a' __path__ = __import__('pkgutil').extend_path(__path__, __name__) @@ -78,7 +78,7 @@ class VersionInfo(NamedTuple): serial: int -version_info: VersionInfo = VersionInfo(major=2, minor=2, micro=0, releaselevel='final', serial=0) +version_info: VersionInfo = VersionInfo(major=2, minor=3, micro=0, releaselevel='alpha', serial=0) logging.getLogger(__name__).addHandler(logging.NullHandler()) From 26f49512b530e728cb51125da9b87a326273f85b Mon Sep 17 00:00:00 2001 From: Flame442 <34169552+Flame442@users.noreply.github.com> Date: Tue, 28 Feb 2023 17:25:20 -0500 Subject: [PATCH 17/50] Fix member_prune extras attribute name --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index c7f1b549f..58fa5c989 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -2105,7 +2105,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. From 53ce47534d5fe513535569561d11e5a386e2d6c1 Mon Sep 17 00:00:00 2001 From: owocado <24418520+owocado@users.noreply.github.com> Date: Wed, 1 Mar 2023 19:25:16 +0530 Subject: [PATCH 18/50] Add more missing AuditLogDiff attrs --- docs/api.rst | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 58fa5c989..5dd452af0 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -2018,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 @@ -2034,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 @@ -3941,7 +3947,7 @@ AuditLogDiff .. attribute:: premium_progress_bar_enabled - The guild’s display setting to show boost progress sidebar + The guild’s display setting to show boost progress bar. :type: :class:`bool` @@ -3953,6 +3959,28 @@ AuditLogDiff :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 From 6910943703ba21aba6454d07b505a2582d9a49e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Spie=C3=9F?= Date: Thu, 2 Mar 2023 00:11:06 +0100 Subject: [PATCH 19/50] Fix udp discovery not using 74 byte layout --- discord/gateway.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/discord/gateway.py b/discord/gateway.py index a06195307..50b69acf9 100644 --- a/discord/gateway.py +++ b/discord/gateway.py @@ -948,16 +948,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') From 1de3562f3475ee29ddcbfd51351586710ec953db Mon Sep 17 00:00:00 2001 From: Bryan Forbes Date: Wed, 1 Mar 2023 18:14:50 -0600 Subject: [PATCH 20/50] Fix partially uknown typing errors --- discord/client.py | 12 ++++++------ discord/ui/item.py | 2 +- discord/ui/select.py | 14 +++++++------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/discord/client.py b/discord/client.py index c53b0f3f3..9da588f33 100644 --- a/discord/client.py +++ b/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, Any], ContextMenu]], bool]], timeout: Optional[float] = None, - ) -> Tuple[Interaction[Self], Union[Command, ContextMenu]]: + ) -> Tuple[Interaction[Self], Union[Command[Any, 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 diff --git a/discord/ui/item.py b/discord/ui/item.py index 443876c1a..2ef42fb9e 100644 --- a/discord/ui/item.py +++ b/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]] diff --git a/discord/ui/select.py b/discord/ui/select.py index 11555c7f7..bcd7b466d 100644 --- a/discord/ui/select.py +++ b/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') From 4d51f6886496099e8ffb9e622e55fd6aa36cbd9a Mon Sep 17 00:00:00 2001 From: Bryan Forbes Date: Wed, 1 Mar 2023 18:37:38 -0600 Subject: [PATCH 21/50] [commands] Fix some more partially uknown typing errors --- discord/ext/commands/hybrid.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/ext/commands/hybrid.py b/discord/ext/commands/hybrid.py index d3f3c91b5..1e26f7830 100644 --- a/discord/ext/commands/hybrid.py +++ b/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, Any]') # CHT = TypeVar('CHT', bound='Check') -GroupT = TypeVar('GroupT', bound='Group') +GroupT = TypeVar('GroupT', bound='Group[Any, Any, Any]') _NoneType = type(None) if TYPE_CHECKING: From 4661fff2a015ee53e415aedaca6b6b130ec4a265 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Wed, 1 Mar 2023 19:43:18 -0500 Subject: [PATCH 22/50] Add changelog for v2.2.1 --- docs/whats_new.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/whats_new.rst b/docs/whats_new.rst index f0a0f4e8f..b7fa32607 100644 --- a/docs/whats_new.rst +++ b/docs/whats_new.rst @@ -11,6 +11,16 @@ Changelog This page keeps a detailed human friendly rendering of what's new and changed in specific versions. +.. _vp2p2p1: + +v2.2.1 +------- + +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 From bbf2cce488b524effa614ec0c8ae347e5fafff95 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Wed, 1 Mar 2023 19:43:49 -0500 Subject: [PATCH 23/50] Version bump to v2.2.1 --- discord/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/__init__.py b/discord/__init__.py index 39fdb2673..0a4d28eb4 100644 --- a/discord/__init__.py +++ b/discord/__init__.py @@ -13,7 +13,7 @@ __title__ = 'discord' __author__ = 'Rapptz' __license__ = 'MIT' __copyright__ = 'Copyright 2015-present Rapptz' -__version__ = '2.3.0a' +__version__ = '2.2.1' __path__ = __import__('pkgutil').extend_path(__path__, __name__) @@ -78,7 +78,7 @@ class VersionInfo(NamedTuple): serial: int -version_info: VersionInfo = VersionInfo(major=2, minor=3, micro=0, releaselevel='alpha', serial=0) +version_info: VersionInfo = VersionInfo(major=2, minor=2, micro=1, releaselevel='final', serial=0) logging.getLogger(__name__).addHandler(logging.NullHandler()) From 82bfb3cf34be05695e969366459c15711b4032d7 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Wed, 1 Mar 2023 19:44:24 -0500 Subject: [PATCH 24/50] Version bump for development --- discord/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/__init__.py b/discord/__init__.py index 0a4d28eb4..39fdb2673 100644 --- a/discord/__init__.py +++ b/discord/__init__.py @@ -13,7 +13,7 @@ __title__ = 'discord' __author__ = 'Rapptz' __license__ = 'MIT' __copyright__ = 'Copyright 2015-present Rapptz' -__version__ = '2.2.1' +__version__ = '2.3.0a' __path__ = __import__('pkgutil').extend_path(__path__, __name__) @@ -78,7 +78,7 @@ class VersionInfo(NamedTuple): serial: int -version_info: VersionInfo = VersionInfo(major=2, minor=2, micro=1, releaselevel='final', serial=0) +version_info: VersionInfo = VersionInfo(major=2, minor=3, micro=0, releaselevel='alpha', serial=0) logging.getLogger(__name__).addHandler(logging.NullHandler()) From 0ea098567c760e47623ec82622fda8acab429fc5 Mon Sep 17 00:00:00 2001 From: Bryan Forbes Date: Wed, 1 Mar 2023 21:47:17 -0600 Subject: [PATCH 25/50] [commands] Use `...` for `Command` and `Group` typing --- discord/client.py | 4 ++-- discord/ext/commands/hybrid.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/discord/client.py b/discord/client.py index 9da588f33..a3b76b32a 100644 --- a/discord/client.py +++ b/discord/client.py @@ -1161,9 +1161,9 @@ class Client: event: Literal['app_command_completion'], /, *, - check: Optional[Callable[[Interaction[Self], Union[Command[Any, Any, Any], ContextMenu]], bool]], + check: Optional[Callable[[Interaction[Self], Union[Command[Any, ..., Any], ContextMenu]], bool]], timeout: Optional[float] = None, - ) -> Tuple[Interaction[Self], Union[Command[Any, Any, Any], ContextMenu]]: + ) -> Tuple[Interaction[Self], Union[Command[Any, ..., Any], ContextMenu]]: ... # AutoMod diff --git a/discord/ext/commands/hybrid.py b/discord/ext/commands/hybrid.py index 1e26f7830..41d9ffd25 100644 --- a/discord/ext/commands/hybrid.py +++ b/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[Any, Any, Any]') +CommandT = TypeVar('CommandT', bound='Command[Any, ..., Any]') # CHT = TypeVar('CHT', bound='Check') -GroupT = TypeVar('GroupT', bound='Group[Any, Any, Any]') +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( From 78175ded1b29ff73c1fb1d4bcfc85e42b572fa07 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Wed, 1 Mar 2023 22:48:19 -0500 Subject: [PATCH 26/50] Version bump to v2.2.2 --- discord/__init__.py | 4 ++-- docs/whats_new.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/discord/__init__.py b/discord/__init__.py index 39fdb2673..5afc3fccb 100644 --- a/discord/__init__.py +++ b/discord/__init__.py @@ -13,7 +13,7 @@ __title__ = 'discord' __author__ = 'Rapptz' __license__ = 'MIT' __copyright__ = 'Copyright 2015-present Rapptz' -__version__ = '2.3.0a' +__version__ = '2.2.2' __path__ = __import__('pkgutil').extend_path(__path__, __name__) @@ -78,7 +78,7 @@ class VersionInfo(NamedTuple): serial: int -version_info: VersionInfo = VersionInfo(major=2, minor=3, micro=0, releaselevel='alpha', serial=0) +version_info: VersionInfo = VersionInfo(major=2, minor=2, micro=2, releaselevel='final', serial=0) logging.getLogger(__name__).addHandler(logging.NullHandler()) diff --git a/docs/whats_new.rst b/docs/whats_new.rst index b7fa32607..760be0152 100644 --- a/docs/whats_new.rst +++ b/docs/whats_new.rst @@ -11,9 +11,9 @@ Changelog This page keeps a detailed human friendly rendering of what's new and changed in specific versions. -.. _vp2p2p1: +.. _vp2p2p2: -v2.2.1 +v2.2.2 ------- Bug Fixes From 78099992dd40d984e9cb8f6505a4e5ecd0115e57 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Wed, 1 Mar 2023 22:49:32 -0500 Subject: [PATCH 27/50] Version bump for development --- discord/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/__init__.py b/discord/__init__.py index 5afc3fccb..39fdb2673 100644 --- a/discord/__init__.py +++ b/discord/__init__.py @@ -13,7 +13,7 @@ __title__ = 'discord' __author__ = 'Rapptz' __license__ = 'MIT' __copyright__ = 'Copyright 2015-present Rapptz' -__version__ = '2.2.2' +__version__ = '2.3.0a' __path__ = __import__('pkgutil').extend_path(__path__, __name__) @@ -78,7 +78,7 @@ class VersionInfo(NamedTuple): serial: int -version_info: VersionInfo = VersionInfo(major=2, minor=2, micro=2, releaselevel='final', serial=0) +version_info: VersionInfo = VersionInfo(major=2, minor=3, micro=0, releaselevel='alpha', serial=0) logging.getLogger(__name__).addHandler(logging.NullHandler()) From a938b10e5a680405a99a5d0bb49e8d075fb0fecf Mon Sep 17 00:00:00 2001 From: Soheab_ <33902984+Soheab@users.noreply.github.com> Date: Fri, 3 Mar 2023 15:07:22 +0100 Subject: [PATCH 28/50] [commands] Pass failed argument's value to BadLiteralArgument --- discord/ext/commands/converter.py | 2 +- discord/ext/commands/errors.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/discord/ext/commands/converter.py b/discord/ext/commands/converter.py index cc7a3eb9b..e5be9d47f 100644 --- a/discord/ext/commands/converter.py +++ b/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 diff --git a/discord/ext/commands/errors.py b/discord/ext/commands/errors.py index 17dd49830..b25e6ae95 100644 --- a/discord/ext/commands/errors.py +++ b/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: From 60094b17a9861e1bf94726869d8f1f5c31a50957 Mon Sep 17 00:00:00 2001 From: Puncher <65789180+Puncher1@users.noreply.github.com> Date: Sun, 5 Mar 2023 23:28:50 +0100 Subject: [PATCH 29/50] Fix create_scheduled_event param handling --- discord/guild.py | 114 ++++++++++++++++++++++++++++++------- discord/scheduled_event.py | 11 ++-- 2 files changed, 97 insertions(+), 28 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index cf6ddd4c3..4861333d5 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -2807,6 +2807,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, *, @@ -2899,27 +2961,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') + else: + if not isinstance(entity_type, EntityType): + raise TypeError('entity_type must be of type EntityType') - entity_type = _entity_type + payload['entity_type'] = entity_type.value - if not isinstance(entity_type, EntityType): - raise TypeError('entity_type must be of type EntityType') + if entity_type is None: + raise TypeError( + 'invalid GuildChannel type passed, must be VoiceChannel or StageChannel ' f'not {channel.__class__.__name__}' + ) - payload['entity_type'] = entity_type.value + if privacy_level is not MISSING: + if not isinstance(privacy_level, PrivacyLevel): + raise TypeError('privacy_level must be of type PrivacyLevel.') - payload['privacy_level'] = PrivacyLevel.guild_only.value + payload['privacy_level'] = privacy_level.value if description is not MISSING: payload['description'] = description @@ -2929,7 +2996,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 @@ -2945,12 +3012,15 @@ class Guild(Hashable): metadata['location'] = location - if end_time is not MISSING: - 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.' - ) - payload['scheduled_end_time'] = end_time.isoformat() + 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.' + ) + payload['scheduled_end_time'] = end_time.isoformat() if metadata: payload['entity_metadata'] = metadata diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 810ea2b2a..f74ae6706 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -503,18 +503,17 @@ class ScheduledEvent(Hashable): 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__}' ) - if entity_type is not MISSING: - if not isinstance(entity_type, EntityType): - raise TypeError('entity_type must be of type EntityType') - - payload['entity_type'] = entity_type.value - _entity_type = entity_type or self.entity_type _entity_type_changed = _entity_type is not self.entity_type From a1295868a629a1307f5fd9d7dbc79ae5eb9eb991 Mon Sep 17 00:00:00 2001 From: Puncher <65789180+Puncher1@users.noreply.github.com> Date: Sun, 5 Mar 2023 23:30:23 +0100 Subject: [PATCH 30/50] Add support for default_sort_order in ForumChannel --- discord/channel.py | 32 +++++++++++++++++++++++++++++++- discord/enums.py | 6 ++++++ discord/guild.py | 16 ++++++++++++++++ discord/http.py | 2 ++ discord/types/channel.py | 2 ++ docs/api.rst | 15 +++++++++++++++ 6 files changed, 72 insertions(+), 1 deletion(-) diff --git a/discord/channel.py b/discord/channel.py index 4e955a6ae..9636bc229 100644 --- a/discord/channel.py +++ b/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 @@ -2154,6 +2154,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__ = ( @@ -2173,6 +2177,7 @@ class ForumChannel(discord.abc.GuildChannel, Hashable): 'default_thread_slowmode_delay', 'default_reaction_emoji', 'default_layout', + 'default_sort_order', '_available_tags', '_flags', ) @@ -2218,6 +2223,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) @@ -2344,6 +2354,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: ... @@ -2402,6 +2413,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. @@ -2464,6 +2479,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 diff --git a/discord/enums.py b/discord/enums.py index 21a38caf8..94ca8c726 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -67,6 +67,7 @@ __all__ = ( 'AutoModRuleEventType', 'AutoModRuleActionType', 'ForumLayoutType', + 'ForumOrderType', ) if TYPE_CHECKING: @@ -751,6 +752,11 @@ class ForumLayoutType(Enum): gallery_view = 2 +class ForumOrderType(Enum): + latest_activity = 0 + creation_date = 1 + + def create_unknown_value(cls: Type[E], val: Any) -> E: value_cls = cls._enum_value_cls_ # type: ignore # This is narrowed below name = f'unknown_{val}' diff --git a/discord/guild.py b/discord/guild.py index 4861333d5..e1cc7814e 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -73,6 +73,7 @@ from .enums import ( MFALevel, Locale, AutoModRuleEventType, + ForumOrderType, ) from .mixins import Hashable from .user import User @@ -1576,6 +1577,7 @@ class Guild(Hashable): reason: Optional[str] = None, default_auto_archive_duration: int = MISSING, default_thread_slowmode_delay: int = MISSING, + default_sort_order: Optional[ForumOrderType] = None, available_tags: Sequence[ForumTag] = MISSING, ) -> ForumChannel: """|coro| @@ -1620,6 +1622,10 @@ class Guild(Hashable): The default slowmode delay in seconds for threads created in this forum. .. versionadded:: 2.1 + default_sort_order: Optional[:class:`ForumOrderType`] + The default sort order for posts in this forum channel. + + .. versionadded:: 2.3 available_tags: Sequence[:class:`ForumTag`] The available tags for this forum channel. @@ -1659,6 +1665,16 @@ 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 None: + options['default_sort_order'] = None + else: + 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 available_tags is not MISSING: options['available_tags'] = [t.to_dict() for t in available_tags] diff --git a/discord/http.py b/discord/http.py index 3fa622967..5e8d32dcd 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1148,6 +1148,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} @@ -1189,6 +1190,7 @@ class HTTPClient: 'video_quality_mode', 'default_auto_archive_duration', 'default_thread_rate_limit_per_user', + 'default_sort_order', 'available_tags', ) payload.update({k: v for k, v in options.items() if k in valid_keys and v is not None}) diff --git a/discord/types/channel.py b/discord/types/channel.py index ad17af689..421232b45 100644 --- a/discord/types/channel.py +++ b/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] diff --git a/docs/api.rst b/docs/api.rst index 5dd452af0..316dbda14 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3310,6 +3310,21 @@ of :class:`enum.Enum`. Displays posts as a collection of tiles. +.. 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 From 79c508311fa45a7e41484c181d54a9ea9324953d Mon Sep 17 00:00:00 2001 From: Puncher <65789180+Puncher1@users.noreply.github.com> Date: Sun, 5 Mar 2023 23:31:14 +0100 Subject: [PATCH 31/50] Add support for default_thread_slowmode_delay in TextChannel --- discord/channel.py | 10 ++++++++++ discord/guild.py | 10 +++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/discord/channel.py b/discord/channel.py index 9636bc229..6cd74c71a 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -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 diff --git a/discord/guild.py b/discord/guild.py index e1cc7814e..0a901fbe3 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -1200,6 +1200,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 +1274,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 +1310,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, From 103d75540ca930152b6d39a6c6a503f67c2f2b06 Mon Sep 17 00:00:00 2001 From: Puncher <65789180+Puncher1@users.noreply.github.com> Date: Sun, 5 Mar 2023 23:32:20 +0100 Subject: [PATCH 32/50] Fix MISSING error for enabled param in create_automod_rule --- discord/guild.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index 0a901fbe3..f80429173 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -4064,7 +4064,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, @@ -4089,7 +4089,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`] From dc9fb1fd84914df64576264d9cf6c151e768fefb Mon Sep 17 00:00:00 2001 From: Puncher <65789180+Puncher1@users.noreply.github.com> Date: Tue, 7 Mar 2023 02:15:22 +0100 Subject: [PATCH 33/50] Add missing param send_start_notification to create_instance --- discord/channel.py | 14 +++++++++++++- discord/http.py | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/discord/channel.py b/discord/channel.py index 6cd74c71a..3c93832f3 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -1594,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| @@ -1610,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. @@ -1636,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) diff --git a/discord/http.py b/discord/http.py index 5e8d32dcd..86b871650 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1905,6 +1905,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} From 198425707e4bc61939a37eeab6d40b7ea2e1e949 Mon Sep 17 00:00:00 2001 From: rdrescher909 <51489753+rdrescher909@users.noreply.github.com> Date: Sun, 12 Mar 2023 22:13:08 -0400 Subject: [PATCH 34/50] Add Guild.get_emoji helper --- discord/guild.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/discord/guild.py b/discord/guild.py index f80429173..596c6865c 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -737,6 +737,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. From 7db197ef250bd397fd20e0a338cf35044a5e2751 Mon Sep 17 00:00:00 2001 From: Puncher <65789180+Puncher1@users.noreply.github.com> Date: Mon, 13 Mar 2023 03:17:48 +0100 Subject: [PATCH 35/50] Add default_reaction_emoji and default_forum_layout to create_forum --- discord/guild.py | 36 ++++++++++++++++++++++++++++++++---- discord/http.py | 2 ++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index 596c6865c..02a932edc 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -74,6 +74,7 @@ from .enums import ( Locale, AutoModRuleEventType, ForumOrderType, + ForumLayoutType, ) from .mixins import Hashable from .user import User @@ -91,6 +92,7 @@ from .audit_logs import AuditLogEntry from .object import OLDEST_OBJECT, Object from .welcome_screen import WelcomeScreen, WelcomeChannel from .automod import AutoModRule, AutoModTrigger, AutoModRuleAction +from .partial_emoji import _EmojiTag, PartialEmoji __all__ = ( @@ -130,6 +132,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] @@ -1605,7 +1608,9 @@ class Guild(Hashable): reason: Optional[str] = None, default_auto_archive_duration: int = MISSING, default_thread_slowmode_delay: int = MISSING, - default_sort_order: Optional[ForumOrderType] = None, + default_sort_order: Optional[ForumOrderType] = MISSING, + default_reaction_emoji: Optional[EmojiInputType] = MISSING, + default_layout: Optional[ForumLayoutType] = MISSING, available_tags: Sequence[ForumTag] = MISSING, ) -> ForumChannel: """|coro| @@ -1653,6 +1658,15 @@ class Guild(Hashable): default_sort_order: Optional[:class:`ForumOrderType`] The default sort order for posts in this forum channel. + .. versionadded:: 2.3 + default_reaction_emoji: Optional[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: Optional[: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. @@ -1693,9 +1707,7 @@ 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 None: - options['default_sort_order'] = None - else: + if default_sort_order not in (MISSING, None): if not isinstance(default_sort_order, ForumOrderType): raise TypeError( f'default_sort_order parameter must be a ForumOrderType not {default_sort_order.__class__.__name__}' @@ -1703,6 +1715,22 @@ class Guild(Hashable): options['default_sort_order'] = default_sort_order.value + if default_reaction_emoji not in (MISSING, None): + 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 not in (MISSING, None): + 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] diff --git a/discord/http.py b/discord/http.py index 86b871650..64f04912a 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1191,6 +1191,8 @@ class HTTPClient: '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}) From c495762aa67624f1c4e2809f98443fb56f787217 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Sun, 12 Mar 2023 22:20:50 -0400 Subject: [PATCH 36/50] Fix Widget.members type docstring --- discord/widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/widget.py b/discord/widget.py index 2c46d49ba..2a7c17a21 100644 --- a/discord/widget.py +++ b/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. From 08d668f21b606abe46a9f8c43012441fa5ca8749 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Sun, 12 Mar 2023 22:22:42 -0400 Subject: [PATCH 37/50] Fix improper usages of Optional MISSING in create_forum --- discord/guild.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index 02a932edc..ac145d679 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -1608,9 +1608,9 @@ class Guild(Hashable): reason: Optional[str] = None, default_auto_archive_duration: int = MISSING, default_thread_slowmode_delay: int = MISSING, - default_sort_order: Optional[ForumOrderType] = MISSING, - default_reaction_emoji: Optional[EmojiInputType] = MISSING, - default_layout: Optional[ForumLayoutType] = MISSING, + default_sort_order: ForumOrderType = MISSING, + default_reaction_emoji: EmojiInputType = MISSING, + default_layout: ForumLayoutType = MISSING, available_tags: Sequence[ForumTag] = MISSING, ) -> ForumChannel: """|coro| @@ -1655,16 +1655,16 @@ class Guild(Hashable): The default slowmode delay in seconds for threads created in this forum. .. versionadded:: 2.1 - default_sort_order: Optional[:class:`ForumOrderType`] + default_sort_order: :class:`ForumOrderType` The default sort order for posts in this forum channel. .. versionadded:: 2.3 - default_reaction_emoji: Optional[Union[:class:`Emoji`, :class:`PartialEmoji`, :class:`str`]] + 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: Optional[:class:`ForumLayoutType`] + default_layout: :class:`ForumLayoutType` The default layout for posts in this forum. .. versionadded:: 2.3 @@ -1707,7 +1707,7 @@ 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 not in (MISSING, None): + 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__}' @@ -1715,7 +1715,7 @@ class Guild(Hashable): options['default_sort_order'] = default_sort_order.value - if default_reaction_emoji not in (MISSING, None): + 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): @@ -1723,7 +1723,7 @@ class Guild(Hashable): else: raise ValueError(f'default_reaction_emoji parameter must be either Emoji, PartialEmoji, or str') - if default_layout not in (MISSING, None): + 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__}' From 8ba830eeb86a52e54c727e71436bfe0e9ea51526 Mon Sep 17 00:00:00 2001 From: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com> Date: Tue, 14 Mar 2023 07:18:23 +0000 Subject: [PATCH 38/50] Include ssrc parameter when sending a SPEAKING payload As it is a required parameter. Don't unnecessarily send a second SPEAKING payload after connecting to voice We do need to send a SPEAKING payload in order to set our SSRC value after connecting to voice, yet that can be with a `state` of 0 (SpeakingState.none). There is no reason to send 2 packets; one marking ourselves as speaking, and then not speaking. --- discord/gateway.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/discord/gateway.py b/discord/gateway.py index 50b69acf9..162217576 100644 --- a/discord/gateway.py +++ b/discord/gateway.py @@ -915,6 +915,7 @@ class DiscordVoiceWebSocket: 'd': { 'speaking': int(state), 'delay': 0, + 'ssrc': self._connection.ssrc, }, } @@ -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: From 879c8b9340f882b961db00de95977f3cf47c2305 Mon Sep 17 00:00:00 2001 From: Puncher <65789180+Puncher1@users.noreply.github.com> Date: Tue, 14 Mar 2023 12:34:43 +0100 Subject: [PATCH 39/50] Add overloads for AutoModRuleAction.__init__ --- discord/automod.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/discord/automod.py b/discord/automod.py index b34bec000..84a00c87e 100644 --- a/discord/automod.py +++ b/discord/automod.py @@ -25,7 +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 @@ -58,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 @@ -81,6 +84,18 @@ class AutoModRuleAction: __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, *, @@ -95,19 +110,17 @@ class AutoModRuleAction: 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'' @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)) From 7af70ac988addef82ff45422219851493121d2dd Mon Sep 17 00:00:00 2001 From: Puncher <65789180+Puncher1@users.noreply.github.com> Date: Thu, 16 Mar 2023 03:53:31 +0100 Subject: [PATCH 40/50] Add widget_channel and widget_enabled parameters in Guild.edit --- discord/guild.py | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index ac145d679..454b5eeb7 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -293,6 +293,7 @@ class Guild(Hashable): 'mfa_level', 'vanity_url_code', 'widget_enabled', + '_widget_channel_id', '_members', '_channels', '_icon', @@ -481,6 +482,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') @@ -799,6 +801,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.""" @@ -1803,6 +1817,8 @@ 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, ) -> Guild: r"""|coro| @@ -1835,6 +1851,9 @@ class Guild(Hashable): .. versionchanged:: 2.1 The ``discoverable`` and ``invites_disabled`` keyword parameters were added. + .. versionchanged:: 2.3 + The ``widget_enabled`` and ``widget_channel`` keyword parameters were added. + Parameters ---------- name: :class:`str` @@ -1898,6 +1917,10 @@ class Guild(Hashable): Whether server discovery is enabled for this guild. invites_disabled: :class:`bool` Whether joining via invites should be disabled for the guild. + widget_enabled: :class:`bool` + Whether to enable the widget for the guild. + widget_channel: Optional[:class:`abc.Snowflake`] + The new widget channel. ``None`` removes the widget channel. reason: Optional[:class:`str`] The reason for editing this guild. Shows up on the audit log. @@ -2049,6 +2072,15 @@ 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) + data = await http.edit_guild(self.id, reason=reason, **fields) return Guild(data=data, state=self._state) @@ -3892,7 +3924,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. @@ -3920,7 +3952,8 @@ class Guild(Hashable): if enabled is not MISSING: payload['enabled'] = enabled - await self._state.http.edit_widget(self.id, payload=payload, reason=reason) + if payload: + await self._state.http.edit_widget(self.id, payload=payload, reason=reason) async def chunk(self, *, cache: bool = True) -> List[Member]: """|coro| From 2ca1a3a9a48c3cc0da10c7ad2214370903c99970 Mon Sep 17 00:00:00 2001 From: Puncher <65789180+Puncher1@users.noreply.github.com> Date: Thu, 16 Mar 2023 12:15:12 +0100 Subject: [PATCH 41/50] Add mfa_level parameter to Guild.edit --- discord/guild.py | 14 ++++++++++++-- discord/http.py | 6 ++++++ discord/types/guild.py | 4 ++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index 454b5eeb7..a8fce7644 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -1819,6 +1819,7 @@ class Guild(Hashable): invites_disabled: bool = MISSING, widget_enabled: bool = MISSING, widget_channel: Optional[Snowflake] = MISSING, + mfa_level: MFALevel = MISSING, ) -> Guild: r"""|coro| @@ -1852,7 +1853,7 @@ class Guild(Hashable): The ``discoverable`` and ``invites_disabled`` keyword parameters were added. .. versionchanged:: 2.3 - The ``widget_enabled`` and ``widget_channel`` keyword parameters were added. + The ``widget_enabled``, ``widget_channel``, and ``mfa_level`` keyword parameters were added. Parameters ---------- @@ -1923,6 +1924,9 @@ class Guild(Hashable): The new widget channel. ``None`` removes the widget channel. reason: Optional[:class:`str`] The reason for editing this guild. Shows up on the audit log. + 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. Raises ------- @@ -1936,7 +1940,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 @@ -2081,6 +2085,12 @@ class Guild(Hashable): 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) diff --git a/discord/http.py b/discord/http.py index 64f04912a..22336b323 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1432,6 +1432,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)) diff --git a/discord/types/guild.py b/discord/types/guild.py index b6c2a1365..1ff2854aa 100644 --- a/discord/types/guild.py +++ b/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] From 064cbd11254e0862e4637093b969cf6cdb020608 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Fri, 17 Mar 2023 18:02:02 -0400 Subject: [PATCH 42/50] Clean up Guild.edit documentation --- discord/guild.py | 48 +++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index a8fce7644..0f91eebe1 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -1827,12 +1827,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. @@ -1843,18 +1837,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. - - .. versionchanged:: 2.3 - The ``widget_enabled``, ``widget_channel``, and ``mfa_level`` keyword parameters were added. - Parameters ---------- name: :class:`str` @@ -1880,9 +1862,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` @@ -1904,30 +1890,50 @@ 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. - reason: Optional[:class:`str`] - The reason for editing this guild. Shows up on the audit log. + + .. versionadded:: 2.3 mfa_level: :class:`MFALevel` - The new guild’s Multi-Factor Authentication requirement level. + 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. + Raises ------- Forbidden From bb7668f8a58ba4b8161edeb77f8936ff807d6537 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Fri, 24 Mar 2023 04:12:47 -0400 Subject: [PATCH 43/50] Upgrade Message.guild references if None in Interaction --- discord/interactions.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/discord/interactions.py b/discord/interactions.py index d2b900d3d..f9ed7976d 100644 --- a/discord/interactions.py +++ b/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: From ebc1bc3cbb2bccc66d9cb7fd97944507b4105303 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Tue, 28 Mar 2023 04:10:59 -0400 Subject: [PATCH 44/50] Fix crash from Discord sending null channel_id for automod audit logs --- discord/audit_logs.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/discord/audit_logs.py b/discord/audit_logs.py index fe51b6106..47f397a8a 100644 --- a/discord/audit_logs.py +++ b/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_'): From e3e0d93dd976e43774cde6309529121752eb9933 Mon Sep 17 00:00:00 2001 From: Andrin S <65789180+Puncher1@users.noreply.github.com> Date: Wed, 29 Mar 2023 03:52:11 +0200 Subject: [PATCH 45/50] Add new use_soundboard and manage_guild_expressions permissions --- discord/permissions.py | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/discord/permissions.py b/discord/permissions.py index 74c7173e4..cb92041f0 100644 --- a/discord/permissions.py +++ b/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(0b1111111111111111111111111111111111111111111) @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(0b1001000000000000011111100000000001100000000) @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,14 @@ 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 + def _augment_from_permissions(cls): cls.VALID_NAMES = set(Permissions.VALID_FLAGS) @@ -745,6 +764,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 +778,7 @@ class PermissionOverwrite: use_external_stickers: Optional[bool] use_embedded_activities: Optional[bool] moderate_members: Optional[bool] + use_soundboard: Optional[bool] def __init__(self, **kwargs: Optional[bool]): self._values: Dict[str, Optional[bool]] = {} From 53de3f9cbb9a2a5ee748a17829403e24a3c00b2b Mon Sep 17 00:00:00 2001 From: scruz Date: Thu, 30 Mar 2023 15:35:53 +0300 Subject: [PATCH 46/50] Fix return types in sync.py docstring --- discord/webhook/sync.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/discord/webhook/sync.py b/discord/webhook/sync.py index 3128246f2..71564a58d 100644 --- a/discord/webhook/sync.py +++ b/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[0-9]{17,20})/(?P[A-Za-z0-9\.\-\_]{60,68})', url) if m is None: From 2ddb9d22baeb762f5c050b434c7e3da629206fbb Mon Sep 17 00:00:00 2001 From: Andrin S <65789180+Puncher1@users.noreply.github.com> Date: Thu, 30 Mar 2023 14:36:33 +0200 Subject: [PATCH 47/50] Add overloads to reply and send --- discord/ext/commands/context.py | 170 +++++++++++++++++++++++++++++++- discord/message.py | 82 ++++++++++++++- 2 files changed, 250 insertions(+), 2 deletions(-) diff --git a/discord/ext/commands/context.py b/discord/ext/commands/context.py index 8c1f7212d..92a1a6b51 100644 --- a/discord/ext/commands/context.py +++ b/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, diff --git a/discord/message.py b/discord/message.py index 719cb9697..8c9b732f5 100644 --- a/discord/message.py +++ b/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 @@ -1254,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| From 4828355f9ea4be18abad7067217236a90d0e22ba Mon Sep 17 00:00:00 2001 From: Andrin S <65789180+Puncher1@users.noreply.github.com> Date: Thu, 30 Mar 2023 14:37:34 +0200 Subject: [PATCH 48/50] Change and add params in AppInfo and PartialAppInfo --- discord/appinfo.py | 42 +++++++++++++++++++++++++++++++++++++++- discord/client.py | 2 -- discord/types/appinfo.py | 17 ++++++++-------- 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/discord/appinfo.py b/discord/appinfo.py index 18c97228b..129e543cb 100644 --- a/discord/appinfo.py +++ b/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. diff --git a/discord/client.py b/discord/client.py index a3b76b32a..298959b21 100644 --- a/discord/client.py +++ b/discord/client.py @@ -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: diff --git a/discord/types/appinfo.py b/discord/types/appinfo.py index 640fe1be7..87bafc6d2 100644 --- a/discord/types/appinfo.py +++ b/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): From ee9e54983619bf711e52fb48b46694f8c78c4ef7 Mon Sep 17 00:00:00 2001 From: Andrin S <65789180+Puncher1@users.noreply.github.com> Date: Sat, 1 Apr 2023 03:03:47 +0200 Subject: [PATCH 49/50] Add use_external_sounds permission --- discord/permissions.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/discord/permissions.py b/discord/permissions.py index cb92041f0..9716f07c6 100644 --- a/discord/permissions.py +++ b/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(0b1111111111111111111111111111111111111111111) + return cls(0b1111111111111111111111111111111111111111111111) @classmethod def _timeout_mask(cls) -> int: @@ -268,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(0b1001000000000000011111100000000001100000000) + return cls(0b1001001000000000000011111100000000001100000000) @classmethod def stage(cls) -> Self: @@ -663,6 +663,14 @@ class Permissions(BaseFlags): """ 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) @@ -779,6 +787,7 @@ class PermissionOverwrite: 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]] = {} From ab287e71b8961cbe245e60ad220b7d0be9fd0b8a Mon Sep 17 00:00:00 2001 From: Vish M Date: Sat, 1 Apr 2023 09:57:00 +0100 Subject: [PATCH 50/50] Fix 'available' KeyError for GuildSticker Co-authored-by: Danny <1695103+Rapptz@users.noreply.github.com> --- discord/sticker.py | 2 +- discord/types/sticker.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/sticker.py b/discord/sticker.py index 2872f3663..225e7648a 100644 --- a/discord/sticker.py +++ b/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 diff --git a/discord/types/sticker.py b/discord/types/sticker.py index 7dcd0ccba..15fd034a7 100644 --- a/discord/types/sticker.py +++ b/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]