From 470323493e7e3b790cb1f9d74bf45dd02df5f578 Mon Sep 17 00:00:00 2001 From: Willi <83978878+itswilliboy@users.noreply.github.com> Date: Wed, 19 Feb 2025 23:14:06 +0100 Subject: [PATCH 01/18] Pass BotT type argument to DeferTyping --- discord/ext/commands/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/ext/commands/context.py b/discord/ext/commands/context.py index 933039735..7198c1206 100644 --- a/discord/ext/commands/context.py +++ b/discord/ext/commands/context.py @@ -751,7 +751,7 @@ class Context(discord.abc.Messageable, Generic[BotT]): else: return await self.send(content, **kwargs) - def typing(self, *, ephemeral: bool = False) -> Union[Typing, DeferTyping]: + def typing(self, *, ephemeral: bool = False) -> Union[Typing, DeferTyping[BotT]]: """Returns an asynchronous context manager that allows you to send a typing indicator to the destination for an indefinite period of time, or 10 seconds if the context manager is called using ``await``. From 0e4f06103ee20d06fb6c0d64f75b1fc475905b95 Mon Sep 17 00:00:00 2001 From: DA344 <108473820+DA-344@users.noreply.github.com> Date: Wed, 19 Feb 2025 23:15:07 +0100 Subject: [PATCH 02/18] Fix InteractionCallbackResponse.resource having incorrect state Fix InteractionCallbackResponse.resource being created with a ConnectionState instead of _InteractionMessageState --- discord/interactions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/interactions.py b/discord/interactions.py index b9d9a4d11..a983d8ab0 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -727,7 +727,7 @@ class InteractionCallbackResponse(Generic[ClientT]): activity_instance = resource.get('activity_instance') if message is not None: self.resource = InteractionMessage( - state=self._state, + state=_InteractionMessageState(self._parent, self._state), # pyright: ignore[reportArgumentType] channel=self._parent.channel, # type: ignore # channel should be the correct type here data=message, ) From 19f02c40b371c2caf6bff686d42966a24e79cccd Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Mon, 24 Feb 2025 11:03:24 +0100 Subject: [PATCH 03/18] Document message types that can have a default message reference --- discord/message.py | 18 +++++++++++++++--- docs/api.rst | 16 ++++++++++++---- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/discord/message.py b/discord/message.py index 44431b595..05b0da183 100644 --- a/discord/message.py +++ b/discord/message.py @@ -610,6 +610,11 @@ class MessageReference: .. versionadded:: 2.5 message_id: Optional[:class:`int`] The id of the message referenced. + This can be ``None`` when this message reference was retrieved from + a system message of one of the following types: + + - :attr:`MessageType.channel_follow_add` + - :attr:`MessageType.thread_created` channel_id: :class:`int` The channel id of the message referenced. guild_id: Optional[:class:`int`] @@ -2010,9 +2015,16 @@ class Message(PartialMessage, Hashable): 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`] - The message that this message references. This is only applicable to messages of - type :attr:`MessageType.pins_add`, crossposted messages created by a - followed channel integration, or message replies. + The message that this message references. This is only applicable to + message replies (:attr:`MessageType.reply`), crossposted messages created by + a followed channel integration, forwarded messages, and messages of type: + + - :attr:`MessageType.pins_add` + - :attr:`MessageType.channel_follow_add` + - :attr:`MessageType.thread_created` + - :attr:`MessageType.thread_starter_message` + - :attr:`MessageType.poll_result` + - :attr:`MessageType.context_menu_command` .. versionadded:: 1.5 diff --git a/docs/api.rst b/docs/api.rst index 934335c5a..73e0238fc 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3851,17 +3851,25 @@ of :class:`enum.Enum`. .. versionadded:: 2.5 - .. attribute:: reply + .. attribute:: default + + A standard reference used by message replies (:attr:`MessageType.reply`), + crossposted messaged created by a followed channel integration, and messages of type: - A message reply. + - :attr:`MessageType.pins_add` + - :attr:`MessageType.channel_follow_add` + - :attr:`MessageType.thread_created` + - :attr:`MessageType.thread_starter_message` + - :attr:`MessageType.poll_result` + - :attr:`MessageType.context_menu_command` .. attribute:: forward A forwarded message. - .. attribute:: default + .. attribute:: reply - An alias for :attr:`.reply`. + An alias for :attr:`.default`. .. _discord-api-audit-logs: From a8b4eb1e9b9bd83dedce6b23f055ca02a0b59aae Mon Sep 17 00:00:00 2001 From: dolfies Date: Mon, 24 Feb 2025 05:07:21 -0500 Subject: [PATCH 04/18] Create ScheduledEvent on cache miss in SCHEDULED_EVENT_DELETE --- discord/state.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/discord/state.py b/discord/state.py index c4b71b368..0fbeadea2 100644 --- a/discord/state.py +++ b/discord/state.py @@ -1553,12 +1553,8 @@ class ConnectionState(Generic[ClientT]): def parse_guild_scheduled_event_delete(self, data: gw.GuildScheduledEventDeleteEvent) -> None: guild = self._get_guild(int(data['guild_id'])) if guild is not None: - try: - scheduled_event = guild._scheduled_events.pop(int(data['id'])) - except KeyError: - pass - else: - self.dispatch('scheduled_event_delete', scheduled_event) + scheduled_event = guild._scheduled_events.pop(int(data['id']), ScheduledEvent(state=self, data=data)) + self.dispatch('scheduled_event_delete', scheduled_event) else: _log.debug('SCHEDULED_EVENT_DELETE referencing unknown guild ID: %s. Discarding.', data['guild_id']) From 93426da37b0878aee54ca24a6d290b8edb4d06bb Mon Sep 17 00:00:00 2001 From: Soheab <33902984+Soheab@users.noreply.github.com> Date: Mon, 24 Feb 2025 12:48:29 +0100 Subject: [PATCH 05/18] Improve on_timeout FAQ Co-authored-by: DA344 <108473820+DA-344@users.noreply.github.com> Co-authored-by: Danny <1695103+Rapptz@users.noreply.github.com> --- docs/faq.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/faq.rst b/docs/faq.rst index 0cd8b8ad6..16d03362a 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -439,7 +439,7 @@ How can I disable all items on timeout? This requires three steps. -1. Attach a message to the :class:`~discord.ui.View` using either the return type of :meth:`~abc.Messageable.send` or retrieving it via :meth:`Interaction.original_response`. +1. Attach a message to the :class:`~discord.ui.View` using either the return type of :meth:`~abc.Messageable.send` or retrieving it via :attr:`InteractionCallbackResponse.resource`. 2. Inside :meth:`~ui.View.on_timeout`, loop over all items inside the view and mark them disabled. 3. Edit the message we retrieved in step 1 with the newly modified view. @@ -467,7 +467,7 @@ Putting it all together, we can do this in a text command: # Step 1 view.message = await ctx.send('Press me!', view=view) -Application commands do not return a message when you respond with :meth:`InteractionResponse.send_message`, therefore in order to reliably do this we should retrieve the message using :meth:`Interaction.original_response`. +Application commands, when you respond with :meth:`InteractionResponse.send_message`, return an instance of :class:`InteractionCallbackResponse` which contains the message you sent. This is the message you should attach to the view. Putting it all together, using the previous view definition: @@ -477,10 +477,13 @@ Putting it all together, using the previous view definition: async def more_timeout_example(interaction): """Another example to showcase disabling buttons on timing out""" view = MyView() - await interaction.response.send_message('Press me!', view=view) + callback = await interaction.response.send_message('Press me!', view=view) # Step 1 - view.message = await interaction.original_response() + resource = callback.resource + # making sure it's an interaction response message + if isinstance(resource, discord.InteractionMessage): + view.message = resource Application Commands From 66f3548f3a84a1272c5afa6c6abfff799269adf4 Mon Sep 17 00:00:00 2001 From: dolfies Date: Fri, 28 Feb 2025 18:00:33 -0500 Subject: [PATCH 06/18] Add defaults for message object parsing --- discord/message.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/discord/message.py b/discord/message.py index 05b0da183..e7752fb8d 100644 --- a/discord/message.py +++ b/discord/message.py @@ -2194,15 +2194,15 @@ class Message(PartialMessage, Hashable): self._state: ConnectionState = state self.webhook_id: Optional[int] = utils._get_as_snowflake(data, 'webhook_id') self.reactions: List[Reaction] = [Reaction(message=self, data=d) for d in data.get('reactions', [])] - self.attachments: List[Attachment] = [Attachment(data=a, state=self._state) for a in data['attachments']] - self.embeds: List[Embed] = [Embed.from_dict(a) for a in data['embeds']] + self.attachments: List[Attachment] = [Attachment(data=a, state=self._state) for a in data.get('attachments', [])] + self.embeds: List[Embed] = [Embed.from_dict(a) for a in data.get('embeds', [])] self.activity: Optional[MessageActivityPayload] = data.get('activity') - self._edited_timestamp: Optional[datetime.datetime] = utils.parse_time(data['edited_timestamp']) + self._edited_timestamp: Optional[datetime.datetime] = utils.parse_time(data.get('edited_timestamp')) self.type: MessageType = try_enum(MessageType, data['type']) - self.pinned: bool = data['pinned'] + self.pinned: bool = data.get('pinned', False) self.flags: MessageFlags = MessageFlags._from_value(data.get('flags', 0)) - self.mention_everyone: bool = data['mention_everyone'] - self.tts: bool = data['tts'] + self.mention_everyone: bool = data.get('mention_everyone', False) + self.tts: bool = data.get('tts', False) self.content: str = data['content'] self.nonce: Optional[Union[int, str]] = data.get('nonce') self.position: Optional[int] = data.get('position') From fbe2b358fc3129902f79c4747d8e21eabf5e0518 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Fri, 28 Feb 2025 18:03:47 -0500 Subject: [PATCH 07/18] Add note about NotFound for Messageable.send Fix #10116 --- discord/abc.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/discord/abc.py b/discord/abc.py index 70531fb20..692472f8f 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -1574,6 +1574,9 @@ class Messageable: Sending the message failed. ~discord.Forbidden You do not have the proper permissions to send the message. + ~discord.NotFound + You sent a message with the same nonce as one that has been explicitly + deleted shortly earlier. ValueError The ``files`` or ``embeds`` list is not of the appropriate size. TypeError From de5720e6593922dcbe2b03b6046dee2e18531b02 Mon Sep 17 00:00:00 2001 From: dolfies Date: Sun, 2 Mar 2025 22:38:53 -0500 Subject: [PATCH 08/18] Fix attachment is_spoiler() and is_voice_message() --- discord/message.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/discord/message.py b/discord/message.py index e7752fb8d..547e9c433 100644 --- a/discord/message.py +++ b/discord/message.py @@ -254,11 +254,12 @@ class Attachment(Hashable): def is_spoiler(self) -> bool: """:class:`bool`: Whether this attachment contains a spoiler.""" - return self.filename.startswith('SPOILER_') + # The flag is technically always present but no harm to check both + return self.filename.startswith('SPOILER_') or self.flags.spoiler def is_voice_message(self) -> bool: """:class:`bool`: Whether this attachment is a voice message.""" - return self.duration is not None and 'voice-message' in self.url + return self.duration is not None and self.waveform is not None def __repr__(self) -> str: return f'' From cab4732b7ea8008ace32f9bd6285f7d4b3a99299 Mon Sep 17 00:00:00 2001 From: dolfies Date: Tue, 4 Mar 2025 02:07:51 -0500 Subject: [PATCH 09/18] Make embed flags required and add them to all media fields --- discord/embeds.py | 64 ++++++++++++++++++++++-------------------- discord/types/embed.py | 22 +++------------ 2 files changed, 37 insertions(+), 49 deletions(-) diff --git a/discord/embeds.py b/discord/embeds.py index 4be644688..b35582b9f 100644 --- a/discord/embeds.py +++ b/discord/embeds.py @@ -77,12 +77,7 @@ if TYPE_CHECKING: proxy_url: Optional[str] height: Optional[int] width: Optional[int] - flags: Optional[AttachmentFlags] - - class _EmbedVideoProxy(Protocol): - url: Optional[str] - height: Optional[int] - width: Optional[int] + flags: AttachmentFlags class _EmbedProviderProxy(Protocol): name: Optional[str] @@ -148,10 +143,6 @@ class Embed: colour: Optional[Union[:class:`Colour`, :class:`int`]] The colour code of the embed. Aliased to ``color`` as well. This can be set during initialisation. - flags: Optional[:class:`EmbedFlags`] - The flags of this embed. - - .. versionadded:: 2.5 """ __slots__ = ( @@ -168,7 +159,7 @@ class Embed: '_author', '_fields', 'description', - 'flags', + '_flags', ) def __init__( @@ -188,7 +179,7 @@ class Embed: self.type: EmbedType = type self.url: Optional[str] = url self.description: Optional[str] = description - self.flags: Optional[EmbedFlags] = None + self._flags: int = 0 if self.title is not None: self.title = str(self.title) @@ -223,6 +214,7 @@ class Embed: self.type = data.get('type', None) self.description = data.get('description', None) self.url = data.get('url', None) + self._flags = data.get('flags', 0) if self.title is not None: self.title = str(self.title) @@ -253,11 +245,6 @@ class Embed: else: setattr(self, '_' + attr, value) - try: - self.flags = EmbedFlags._from_value(data['flags']) - except KeyError: - pass - return self def copy(self) -> Self: @@ -318,8 +305,17 @@ class Embed: and self.image == other.image and self.provider == other.provider and self.video == other.video + and self._flags == other._flags ) + @property + def flags(self) -> EmbedFlags: + """:class:`EmbedFlags`: The flags of this embed. + + .. versionadded:: 2.5 + """ + return EmbedFlags._from_value(self._flags or 0) + @property def colour(self) -> Optional[Colour]: return getattr(self, '_colour', None) @@ -408,18 +404,17 @@ class Embed: Possible attributes you can access are: - - ``url`` - - ``proxy_url`` - - ``width`` - - ``height`` - - ``flags`` + - ``url`` for the image URL. + - ``proxy_url`` for the proxied image URL. + - ``width`` for the image width. + - ``height`` for the image height. + - ``flags`` for the image's attachment flags. If the attribute has no value then ``None`` is returned. """ # Lying to the type checker for better developer UX. data = getattr(self, '_image', {}) - if 'flags' in data: - data['flags'] = AttachmentFlags._from_value(data['flags']) + data['flags'] = AttachmentFlags._from_value(data.get('flags', 0)) return EmbedProxy(data) # type: ignore def set_image(self, *, url: Optional[Any]) -> Self: @@ -454,15 +449,18 @@ class Embed: Possible attributes you can access are: - - ``url`` - - ``proxy_url`` - - ``width`` - - ``height`` + - ``url`` for the thumbnail URL. + - ``proxy_url`` for the proxied thumbnail URL. + - ``width`` for the thumbnail width. + - ``height`` for the thumbnail height. + - ``flags`` for the thumbnail's attachment flags. If the attribute has no value then ``None`` is returned. """ # Lying to the type checker for better developer UX. - return EmbedProxy(getattr(self, '_thumbnail', {})) # type: ignore + data = getattr(self, '_thumbnail', {}) + data['flags'] = AttachmentFlags._from_value(data.get('flags', 0)) + return EmbedProxy(data) # type: ignore def set_thumbnail(self, *, url: Optional[Any]) -> Self: """Sets the thumbnail for the embed content. @@ -491,19 +489,23 @@ class Embed: return self @property - def video(self) -> _EmbedVideoProxy: + def video(self) -> _EmbedMediaProxy: """Returns an ``EmbedProxy`` denoting the video contents. Possible attributes include: - ``url`` for the video URL. + - ``proxy_url`` for the proxied video URL. - ``height`` for the video height. - ``width`` for the video width. + - ``flags`` for the video's attachment flags. If the attribute has no value then ``None`` is returned. """ # Lying to the type checker for better developer UX. - return EmbedProxy(getattr(self, '_video', {})) # type: ignore + data = getattr(self, '_video', {}) + data['flags'] = AttachmentFlags._from_value(data.get('flags', 0)) + return EmbedProxy(data) # type: ignore @property def provider(self) -> _EmbedProviderProxy: diff --git a/discord/types/embed.py b/discord/types/embed.py index f8354a3f3..a18912f6e 100644 --- a/discord/types/embed.py +++ b/discord/types/embed.py @@ -38,28 +38,14 @@ class EmbedField(TypedDict): inline: NotRequired[bool] -class EmbedThumbnail(TypedDict, total=False): +class EmbedMedia(TypedDict, total=False): url: Required[str] proxy_url: str height: int width: int - - -class EmbedVideo(TypedDict, total=False): - url: str - proxy_url: str - height: int - width: int flags: int -class EmbedImage(TypedDict, total=False): - url: Required[str] - proxy_url: str - height: int - width: int - - class EmbedProvider(TypedDict, total=False): name: str url: str @@ -83,9 +69,9 @@ class Embed(TypedDict, total=False): timestamp: str color: int footer: EmbedFooter - image: EmbedImage - thumbnail: EmbedThumbnail - video: EmbedVideo + image: EmbedMedia + thumbnail: EmbedMedia + video: EmbedMedia provider: EmbedProvider author: EmbedAuthor fields: List[EmbedField] From 6b0a6eea6637c5c44000aea7d8c584bf1862a4d1 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Tue, 4 Mar 2025 02:13:31 -0500 Subject: [PATCH 10/18] Add v2.5.1 changelog --- docs/whats_new.rst | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/whats_new.rst b/docs/whats_new.rst index d3763a588..975567c6c 100644 --- a/docs/whats_new.rst +++ b/docs/whats_new.rst @@ -11,6 +11,20 @@ Changelog This page keeps a detailed human friendly rendering of what's new and changed in specific versions. +.. _vp2p5p1: + +v2.5.1 +------- + +Bug Fixes +~~~~~~~~~~ + +- Fix :attr:`InteractionCallbackResponse.resource` having incorrect state (:issue:`10107`) +- Create :class:`ScheduledEvent` on cache miss for :func:`on_scheduled_event_delete` (:issue:`10113`) +- Add defaults for :class:`Message` creation preventing some crashes (:issue:`10115`) +- Fix :meth:`Attachment.is_spoiler` and :meth:`Attachment.is_voice_message` being incorrect (:issue:`10122`) + + .. _vp2p5p0: v2.5.0 @@ -63,7 +77,6 @@ New Features - Add :attr:`PartialWebhookChannel.mention` attribute (:issue:`10101`) - Add support for sending stateless views for :class:`SyncWebhook` or webhooks with no state (:issue:`10089`) -- Add - Add richer :meth:`Role.move` interface (:issue:`10100`) - Add support for :class:`EmbedFlags` via :attr:`Embed.flags` (:issue:`10085`) - Add new flags for :class:`AttachmentFlags` (:issue:`10085`) From 73f261d536715af5559059268c26515812e51be7 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Tue, 4 Mar 2025 02:14:02 -0500 Subject: [PATCH 11/18] Version bump to v2.5.1 --- discord/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/__init__.py b/discord/__init__.py index 48fe10925..56d503fc4 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.6.0a' +__version__ = '2.5.1' __path__ = __import__('pkgutil').extend_path(__path__, __name__) @@ -83,7 +83,7 @@ class VersionInfo(NamedTuple): serial: int -version_info: VersionInfo = VersionInfo(major=2, minor=6, micro=0, releaselevel='alpha', serial=0) +version_info: VersionInfo = VersionInfo(major=2, minor=5, micro=1, releaselevel='final', serial=0) logging.getLogger(__name__).addHandler(logging.NullHandler()) From 973bb5089ffa60f2db5244aca52b1c6cab43661f Mon Sep 17 00:00:00 2001 From: Rapptz Date: Tue, 4 Mar 2025 02:15:32 -0500 Subject: [PATCH 12/18] 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 56d503fc4..48fe10925 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.5.1' +__version__ = '2.6.0a' __path__ = __import__('pkgutil').extend_path(__path__, __name__) @@ -83,7 +83,7 @@ class VersionInfo(NamedTuple): serial: int -version_info: VersionInfo = VersionInfo(major=2, minor=5, micro=1, releaselevel='final', serial=0) +version_info: VersionInfo = VersionInfo(major=2, minor=6, micro=0, releaselevel='alpha', serial=0) logging.getLogger(__name__).addHandler(logging.NullHandler()) From 2f8b2624f121653841bbc69651979baac6f59cd3 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Tue, 4 Mar 2025 23:42:28 +0100 Subject: [PATCH 13/18] Fix improper class in audit log docs --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 73e0238fc..e366f63bf 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -2418,7 +2418,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: - - ``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the members were moved. + - ``channel``: An :class:`abc.Connectable` or :class:`Object` with the channel ID where the members were moved. - ``count``: An integer specifying how many members were moved. .. versionadded:: 1.3 From 8594dd1b309fd66bc2defc1f73540dfa9357e2c7 Mon Sep 17 00:00:00 2001 From: dolfies Date: Tue, 4 Mar 2025 18:24:38 -0500 Subject: [PATCH 14/18] Fix embed media flags regression --- discord/embeds.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/discord/embeds.py b/discord/embeds.py index b35582b9f..7f84e410d 100644 --- a/discord/embeds.py +++ b/discord/embeds.py @@ -46,7 +46,7 @@ class EmbedProxy: return len(self.__dict__) def __repr__(self) -> str: - inner = ', '.join((f'{k}={v!r}' for k, v in self.__dict__.items() if not k.startswith('_'))) + inner = ', '.join((f'{k}={getattr(self, k)!r}' for k in dir(self) if not k.startswith('_'))) return f'EmbedProxy({inner})' def __getattr__(self, attr: str) -> None: @@ -56,6 +56,16 @@ class EmbedProxy: return isinstance(other, EmbedProxy) and self.__dict__ == other.__dict__ +class EmbedMediaProxy(EmbedProxy): + def __init__(self, layer: Dict[str, Any]): + super().__init__(layer) + self._flags = self.__dict__.pop('flags', 0) + + @property + def flags(self) -> AttachmentFlags: + return AttachmentFlags._from_value(self._flags or 0) + + if TYPE_CHECKING: from typing_extensions import Self @@ -413,9 +423,7 @@ class Embed: If the attribute has no value then ``None`` is returned. """ # Lying to the type checker for better developer UX. - data = getattr(self, '_image', {}) - data['flags'] = AttachmentFlags._from_value(data.get('flags', 0)) - return EmbedProxy(data) # type: ignore + return EmbedMediaProxy(getattr(self, '_image', {})) # type: ignore def set_image(self, *, url: Optional[Any]) -> Self: """Sets the image for the embed content. @@ -458,9 +466,7 @@ class Embed: If the attribute has no value then ``None`` is returned. """ # Lying to the type checker for better developer UX. - data = getattr(self, '_thumbnail', {}) - data['flags'] = AttachmentFlags._from_value(data.get('flags', 0)) - return EmbedProxy(data) # type: ignore + return EmbedMediaProxy(getattr(self, '_thumbnail', {})) # type: ignore def set_thumbnail(self, *, url: Optional[Any]) -> Self: """Sets the thumbnail for the embed content. @@ -503,9 +509,7 @@ class Embed: If the attribute has no value then ``None`` is returned. """ # Lying to the type checker for better developer UX. - data = getattr(self, '_video', {}) - data['flags'] = AttachmentFlags._from_value(data.get('flags', 0)) - return EmbedProxy(data) # type: ignore + return EmbedMediaProxy(getattr(self, '_video', {})) # type: ignore @property def provider(self) -> _EmbedProviderProxy: From f4bce1caf02125b9ddad25a967358756a677add9 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Tue, 4 Mar 2025 20:12:54 -0500 Subject: [PATCH 15/18] Add changelog for v2.5.2 --- docs/whats_new.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/whats_new.rst b/docs/whats_new.rst index 975567c6c..44db8c3d4 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. +.. _vp2p5p2: + +v2.5.2 +------- + +Bug Fixes +~~~~~~~~~~ + +- Fix a serialization issue when sending embeds (:issue:`10126`) + .. _vp2p5p1: v2.5.1 From d2a6ccf715b0d7d53ef75ebc767d5696b9c967a9 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Tue, 4 Mar 2025 20:13:27 -0500 Subject: [PATCH 16/18] Version bump to v2.5.2 --- discord/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/__init__.py b/discord/__init__.py index 48fe10925..0efb6553b 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.6.0a' +__version__ = '2.5.2' __path__ = __import__('pkgutil').extend_path(__path__, __name__) @@ -83,7 +83,7 @@ class VersionInfo(NamedTuple): serial: int -version_info: VersionInfo = VersionInfo(major=2, minor=6, micro=0, releaselevel='alpha', serial=0) +version_info: VersionInfo = VersionInfo(major=2, minor=5, micro=2, releaselevel='final', serial=0) logging.getLogger(__name__).addHandler(logging.NullHandler()) From 460d188359ff43419b74a15b3376095d1443d19c Mon Sep 17 00:00:00 2001 From: Rapptz Date: Tue, 4 Mar 2025 20:16:50 -0500 Subject: [PATCH 17/18] 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 0efb6553b..48fe10925 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.5.2' +__version__ = '2.6.0a' __path__ = __import__('pkgutil').extend_path(__path__, __name__) @@ -83,7 +83,7 @@ class VersionInfo(NamedTuple): serial: int -version_info: VersionInfo = VersionInfo(major=2, minor=5, micro=2, releaselevel='final', serial=0) +version_info: VersionInfo = VersionInfo(major=2, minor=6, micro=0, releaselevel='alpha', serial=0) logging.getLogger(__name__).addHandler(logging.NullHandler()) From 26855160f8a8f0dfade609cce6b1bc97f8b8fa14 Mon Sep 17 00:00:00 2001 From: Rishit Khare Date: Wed, 5 Mar 2025 09:30:57 -0600 Subject: [PATCH 18/18] update PyNaCl minimum version dependency --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d7360731d..92ccb7381 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ Documentation = "https://discordpy.readthedocs.io/en/latest/" dependencies = { file = "requirements.txt" } [project.optional-dependencies] -voice = ["PyNaCl>=1.3.0,<1.6"] +voice = ["PyNaCl>=1.5.0,<1.6"] docs = [ "sphinx==4.4.0", "sphinxcontrib_trio==1.1.2",