From 8d8d5e180adf8961cdac625e43f03bce99654f56 Mon Sep 17 00:00:00 2001 From: owocado <24418520+owocado@users.noreply.github.com> Date: Fri, 20 Jun 2025 23:53:35 +0530 Subject: [PATCH 01/12] fix docs for soundboard_sound_update event --- docs/api.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index e366f63bf..dda5553b7 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1354,8 +1354,10 @@ Soundboard .. versionadded:: 2.5 - :param sound: The soundboard sound that was updated. - :type sound: :class:`SoundboardSound` + :param before: The soundboard sound before the update. + :type before: :class:`SoundboardSound` + :param after: The soundboard sound after the update. + :type after: :class:`SoundboardSound` Stages From 7f16a06479bdae27b24916e784414613e8473f5d Mon Sep 17 00:00:00 2001 From: Leonardo Date: Fri, 20 Jun 2025 20:24:06 +0200 Subject: [PATCH 02/12] Copy Select options when creating View class --- discord/components.py | 3 +++ discord/ui/view.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/discord/components.py b/discord/components.py index b3f978eb1..b62ab6bf9 100644 --- a/discord/components.py +++ b/discord/components.py @@ -442,6 +442,9 @@ class SelectOption: return payload + def copy(self) -> SelectOption: + return self.__class__.from_dict(self.to_dict()) + class TextInput(Component): """Represents a text input from the Discord Bot UI Kit. diff --git a/discord/ui/view.py b/discord/ui/view.py index dd44944ec..f27b71eeb 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -33,6 +33,7 @@ import sys import time import os from .item import Item, ItemCallbackType +from .select import Select from .dynamic import DynamicItem from ..components import ( Component, @@ -179,6 +180,8 @@ class View: item: Item = func.__discord_ui_model_type__(**func.__discord_ui_model_kwargs__) item.callback = _ViewCallback(func, self, item) # type: ignore item._view = self + if isinstance(item, Select): + item.options = [option.copy() for option in item.options] setattr(self, func.__name__, item) children.append(item) return children From 3e48119654411d1fbf4dafaae8f06c6a0d053cfd Mon Sep 17 00:00:00 2001 From: Maxine Date: Fri, 20 Jun 2025 20:24:55 +0200 Subject: [PATCH 03/12] Fix unreachable code not triggering privileged intent error --- discord/shard.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/shard.py b/discord/shard.py index 454fd5e28..cd10cc265 100644 --- a/discord/shard.py +++ b/discord/shard.py @@ -517,10 +517,10 @@ class AutoShardedClient(Client): if item.type == EventType.close: await self.close() if isinstance(item.error, ConnectionClosed): - if item.error.code != 1000: - raise item.error if item.error.code == 4014: raise PrivilegedIntentsRequired(item.shard.id) from None + if item.error.code != 1000: + raise item.error return elif item.type in (EventType.identify, EventType.resume): await item.shard.reidentify(item.error) From 0ce11544e3a32d0d4b9d867e3b1d356c4438e196 Mon Sep 17 00:00:00 2001 From: Dep <70801324+Depreca1ed@users.noreply.github.com> Date: Fri, 20 Jun 2025 23:55:47 +0530 Subject: [PATCH 04/12] Add discord.Permissions.apps() --- discord/permissions.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/discord/permissions.py b/discord/permissions.py index b553e2578..c234ad5f3 100644 --- a/discord/permissions.py +++ b/discord/permissions.py @@ -363,6 +363,16 @@ class Permissions(BaseFlags): """ return cls(0b0000_0000_0000_0000_0000_0001_0000_0100_0111_0000_0000_0000_0010_0000_0011_1110) + @classmethod + def apps(cls) -> Self: + """A factory method that creates a :class:`Permissions` with all + "Apps" permissions from the official Discord UI set to ``True``. + + + .. versionadded:: 2.6 + """ + return cls(0b0000_0000_0000_0100_0000_0000_1000_0000_1000_0000_0000_0000_0000_0000_0000_0000) + @classmethod def events(cls) -> Self: """A factory method that creates a :class:`Permissions` with all From aa5f4bdd7a8327fdcd1d68c96c1cc97c87cca534 Mon Sep 17 00:00:00 2001 From: Soheab <33902984+Soheab@users.noreply.github.com> Date: Fri, 20 Jun 2025 20:27:37 +0200 Subject: [PATCH 05/12] Fix poll parameter defaulting to MISSING instead of None for Context.send --- discord/ext/commands/context.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/discord/ext/commands/context.py b/discord/ext/commands/context.py index 7198c1206..b5b96c15f 100644 --- a/discord/ext/commands/context.py +++ b/discord/ext/commands/context.py @@ -924,7 +924,7 @@ class Context(discord.abc.Messageable, Generic[BotT]): suppress_embeds: bool = False, ephemeral: bool = False, silent: bool = False, - poll: Poll = MISSING, + poll: Optional[Poll] = None, ) -> Message: """|coro| @@ -1014,10 +1014,12 @@ class Context(discord.abc.Messageable, Generic[BotT]): .. versionadded:: 2.2 - poll: :class:`~discord.Poll` + poll: Optional[:class:`~discord.Poll`] The poll to send with this message. .. versionadded:: 2.4 + .. versionchanged:: 2.6 + This can now be ``None`` and defaults to ``None`` instead of ``MISSING``. Raises -------- @@ -1072,7 +1074,7 @@ class Context(discord.abc.Messageable, Generic[BotT]): 'suppress_embeds': suppress_embeds, 'ephemeral': ephemeral, 'silent': silent, - 'poll': poll, + 'poll': MISSING if poll is None else poll, } if self.interaction.response.is_done(): From 680fe9bc808422cddc04968aa003ea6edf9090be Mon Sep 17 00:00:00 2001 From: Tari <65512380+Tari-dev@users.noreply.github.com> Date: Fri, 20 Jun 2025 23:58:08 +0530 Subject: [PATCH 06/12] Add channel select to component type documentation --- docs/interactions/api.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/interactions/api.rst b/docs/interactions/api.rst index feab66907..294a3b13a 100644 --- a/docs/interactions/api.rst +++ b/docs/interactions/api.rst @@ -329,6 +329,10 @@ Enumerations Represents a select in which both users and roles can be selected. + .. attribute:: channel_select + + Represents a channel select component. + .. class:: ButtonStyle Represents the style of the button component. From e84edf473c1372c7c04b60abbbde4d660ce4256d Mon Sep 17 00:00:00 2001 From: Leonardo Date: Fri, 20 Jun 2025 20:28:59 +0200 Subject: [PATCH 07/12] Add colours for new Discord themes --- discord/colour.py | 83 +++++++++++++++++++++++++++++++++++++++----- tests/test_colour.py | 13 ++++--- 2 files changed, 83 insertions(+), 13 deletions(-) diff --git a/discord/colour.py b/discord/colour.py index 7e3a37132..8c40dac35 100644 --- a/discord/colour.py +++ b/discord/colour.py @@ -21,6 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + from __future__ import annotations import colorsys @@ -457,20 +458,59 @@ class Colour: """ return cls(0x99AAB5) + @classmethod + def ash_theme(cls) -> Self: + """A factory method that returns a :class:`Colour` with a value of ``0x2E2E34``. + + This will appear transparent on Discord's ash theme. + + .. colour:: #2E2E34 + + .. versionadded:: 2.6 + """ + return cls(0x2E2E34) + @classmethod def dark_theme(cls) -> Self: - """A factory method that returns a :class:`Colour` with a value of ``0x313338``. + """A factory method that returns a :class:`Colour` with a value of ``0x1A1A1E``. This will appear transparent on Discord's dark theme. - .. colour:: #313338 + .. colour:: #1A1A1E .. versionadded:: 1.5 .. versionchanged:: 2.2 Updated colour from previous ``0x36393F`` to reflect discord theme changes. + + .. versionchanged:: 2.6 + Updated colour from previous ``0x313338`` to reflect discord theme changes. """ - return cls(0x313338) + return cls(0x1A1A1E) + + @classmethod + def onyx_theme(cls) -> Self: + """A factory method that returns a :class:`Colour` with a value of ``0x070709``. + + This will appear transparent on Discord's onyx theme. + + .. colour:: #070709 + + .. versionadded:: 2.6 + """ + return cls(0x070709) + + @classmethod + def light_theme(cls) -> Self: + """A factory method that returns a :class:`Colour` with a value of ``0xFBFBFB``. + + This will appear transparent on Discord's light theme. + + .. colour:: #FBFBFB + + .. versionadded:: 2.6 + """ + return cls(0xFBFBFB) @classmethod def fuchsia(cls) -> Self: @@ -492,25 +532,52 @@ class Colour: """ return cls(0xFEE75C) + @classmethod + def ash_embed(cls) -> Self: + """A factory method that returns a :class:`Colour` with a value of ``0x37373E``. + + .. colour:: #37373E + + .. versionadded:: 2.6 + + """ + return cls(0x37373E) + @classmethod def dark_embed(cls) -> Self: - """A factory method that returns a :class:`Colour` with a value of ``0x2B2D31``. + """A factory method that returns a :class:`Colour` with a value of ``0x242429``. - .. colour:: #2B2D31 + .. colour:: #242429 .. versionadded:: 2.2 + + .. versionchanged:: 2.6 + Updated colour from previous ``0x2B2D31`` to reflect discord theme changes. + """ + return cls(0x242429) + + @classmethod + def onyx_embed(cls) -> Self: + """A factory method that returns a :class:`Colour` with a value of ``0x131416``. + + .. colour:: #131416 + + .. versionadded:: 2.6 """ - return cls(0x2B2D31) + return cls(0x131416) @classmethod def light_embed(cls) -> Self: - """A factory method that returns a :class:`Colour` with a value of ``0xEEEFF1``. + """A factory method that returns a :class:`Colour` with a value of ``0xFFFFFF``. .. colour:: #EEEFF1 .. versionadded:: 2.2 + + .. versionchanged:: 2.6 + Updated colour from previous ``0xEEEFF1`` to reflect discord theme changes. """ - return cls(0xEEEFF1) + return cls(0xFFFFFF) @classmethod def pink(cls) -> Self: diff --git a/tests/test_colour.py b/tests/test_colour.py index b79f153f0..1515b2cb4 100644 --- a/tests/test_colour.py +++ b/tests/test_colour.py @@ -106,11 +106,16 @@ def test_from_str_failures(value): (discord.Colour.og_blurple(), 0x7289DA), (discord.Colour.blurple(), 0x5865F2), (discord.Colour.greyple(), 0x99AAB5), - (discord.Colour.dark_theme(), 0x313338), + (discord.Colour.ash_theme(), 0x2E2E34), + (discord.Colour.dark_theme(), 0x1A1A1E), + (discord.Colour.onyx_theme(), 0x070709), + (discord.Colour.light_theme(), 0xFBFBFB), (discord.Colour.fuchsia(), 0xEB459E), (discord.Colour.yellow(), 0xFEE75C), - (discord.Colour.dark_embed(), 0x2B2D31), - (discord.Colour.light_embed(), 0xEEEFF1), + (discord.Colour.ash_embed(), 0x37373E), + (discord.Colour.dark_embed(), 0x242429), + (discord.Colour.onyx_embed(), 0x131416), + (discord.Colour.light_embed(), 0xFFFFFF), (discord.Colour.pink(), 0xEB459F), ], ) @@ -118,8 +123,6 @@ def test_static_colours(value, expected): assert value.value == expected - - @pytest.mark.parametrize( ('value', 'property', 'expected'), [ From e177b4a70502cf248ae0d682ed011875999ffd1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20N=C3=B8rgaard?= Date: Fri, 20 Jun 2025 19:30:10 +0100 Subject: [PATCH 08/12] Fix EmbedMediaProxy boolean check --- discord/embeds.py | 6 ++++++ tests/test_embed.py | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/discord/embeds.py b/discord/embeds.py index 7f84e410d..6bd057ac8 100644 --- a/discord/embeds.py +++ b/discord/embeds.py @@ -61,6 +61,12 @@ class EmbedMediaProxy(EmbedProxy): super().__init__(layer) self._flags = self.__dict__.pop('flags', 0) + def __bool__(self) -> bool: + # This is a nasty check to see if we only have the `_flags` attribute which is created regardless in init. + # Had we had any of the other items, like image/video data this would be >1 and therefor + # would not be "empty". + return len(self.__dict__) > 1 + @property def flags(self) -> AttachmentFlags: return AttachmentFlags._from_value(self._flags or 0) diff --git a/tests/test_embed.py b/tests/test_embed.py index 3efedd6a5..004f73e3b 100644 --- a/tests/test_embed.py +++ b/tests/test_embed.py @@ -267,3 +267,14 @@ def test_embed_colour_setter_failure(value): embed = discord.Embed() with pytest.raises(TypeError): embed.colour = value + +@pytest.mark.parametrize( + ('title', 'return_val'), + [ + ('test', True), + (None, False) + ] +) +def test_embed_truthiness(title: str, return_val: bool) -> None: + embed = discord.Embed(title=title) + assert bool(embed) is return_val From ef06d7d9db2b868c57f45fe1252d055490ad31a9 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Fri, 20 Jun 2025 20:30:37 +0200 Subject: [PATCH 09/12] Add Interaction.filesize_limit --- discord/interactions.py | 8 +++++++- discord/types/interactions.py | 1 + tests/test_app_commands_invoke.py | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/discord/interactions.py b/discord/interactions.py index a983d8ab0..cb9a21e88 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -154,6 +154,10 @@ class Interaction(Generic[ClientT]): The context of the interaction. .. versionadded:: 2.4 + filesize_limit: int + The maximum number of bytes a file can have when responding to this interaction. + + .. versionadded:: 2.6 """ __slots__: Tuple[str, ...] = ( @@ -172,7 +176,8 @@ class Interaction(Generic[ClientT]): 'command_failed', 'entitlement_sku_ids', 'entitlements', - "context", + 'context', + 'filesize_limit', '_integration_owners', '_permissions', '_app_permissions', @@ -214,6 +219,7 @@ class Interaction(Generic[ClientT]): self.application_id: int = int(data['application_id']) self.entitlement_sku_ids: List[int] = [int(x) for x in data.get('entitlement_skus', []) or []] self.entitlements: List[Entitlement] = [Entitlement(self._state, x) for x in data.get('entitlements', [])] + self.filesize_limit: int = data['attachment_size_limit'] # This is not entirely useful currently, unsure how to expose it in a way that it is. self._integration_owners: Dict[int, Snowflake] = { int(k): int(v) for k, v in data.get('authorizing_integration_owners', {}).items() diff --git a/discord/types/interactions.py b/discord/types/interactions.py index 3f3516c3a..3e814b49d 100644 --- a/discord/types/interactions.py +++ b/discord/types/interactions.py @@ -233,6 +233,7 @@ class _BaseInteraction(TypedDict): entitlements: NotRequired[List[Entitlement]] authorizing_integration_owners: Dict[Literal['0', '1'], Snowflake] context: NotRequired[InteractionContextType] + attachment_size_limit: int class PingInteraction(_BaseInteraction): diff --git a/tests/test_app_commands_invoke.py b/tests/test_app_commands_invoke.py index 35915c19b..6366096f0 100644 --- a/tests/test_app_commands_invoke.py +++ b/tests/test_app_commands_invoke.py @@ -21,6 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + from __future__ import annotations @@ -90,6 +91,7 @@ class MockCommandInteraction(discord.Interaction): "version": 1, "type": 2, "data": self._get_command_data(command, self._get_command_options(**options)), + "attachment_size_limit": 0, } super().__init__(data=data, state=client._connection) From b28a4a115e72977a9091091983a1213e91f8a5e9 Mon Sep 17 00:00:00 2001 From: Michael H Date: Fri, 20 Jun 2025 14:31:55 -0400 Subject: [PATCH 10/12] Fix potentially stuck ratelimit buckets --- discord/http.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/discord/http.py b/discord/http.py index 6617efa27..eb1dbbd06 100644 --- a/discord/http.py +++ b/discord/http.py @@ -461,7 +461,12 @@ class Ratelimit: future = self._loop.create_future() self._pending_requests.append(future) try: - await future + while not future.done(): + # 30 matches the smallest allowed max_ratelimit_timeout + max_wait_time = self.expires - self._loop.time() if self.expires else 30 + await asyncio.wait([future], timeout=max_wait_time) + if not future.done(): + await self._refresh() except: future.cancel() if self.remaining > 0 and not future.cancelled(): From adb93f4a606ae21a87b46b3f150a75ed6c7d86bb Mon Sep 17 00:00:00 2001 From: Gooraeng <101193491+Gooraeng@users.noreply.github.com> Date: Sat, 21 Jun 2025 03:36:24 +0900 Subject: [PATCH 11/12] Add deprecation warning to create_guild functions --- discord/client.py | 6 +++++- discord/template.py | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/discord/client.py b/discord/client.py index b997bd96f..daf2b8855 100644 --- a/discord/client.py +++ b/discord/client.py @@ -67,7 +67,7 @@ from .voice_client import VoiceClient from .http import HTTPClient from .state import ConnectionState from . import utils -from .utils import MISSING, time_snowflake +from .utils import MISSING, time_snowflake, deprecated from .object import Object from .backoff import ExponentialBackoff from .webhook import Webhook @@ -2388,6 +2388,7 @@ class Client: data = await self.http.get_guild_preview(guild_id) return GuildPreview(data=data, state=self._connection) + @deprecated() async def create_guild( self, *, @@ -2408,6 +2409,9 @@ class Client: This function will now raise :exc:`ValueError` instead of ``InvalidArgument``. + .. deprecated:: 2.6 + This function is deprecated and will be removed in a future version. + Parameters ---------- name: :class:`str` diff --git a/discord/template.py b/discord/template.py index 409cdc7d9..691be2caf 100644 --- a/discord/template.py +++ b/discord/template.py @@ -25,7 +25,7 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations from typing import Any, Optional, TYPE_CHECKING, List -from .utils import parse_time, _bytes_to_base64_data, MISSING +from .utils import parse_time, _bytes_to_base64_data, MISSING, deprecated from .guild import Guild # fmt: off @@ -164,6 +164,7 @@ class Template: f' creator={self.creator!r} source_guild={self.source_guild!r} is_dirty={self.is_dirty}>' ) + @deprecated() async def create_guild(self, name: str, icon: bytes = MISSING) -> Guild: """|coro| @@ -178,6 +179,9 @@ class Template: This function will now raise :exc:`ValueError` instead of ``InvalidArgument``. + .. deprecated:: 2.6 + This function is deprecated and will be removed in a future version. + Parameters ---------- name: :class:`str` From 4a8817af2b89c9833ef4d889aabd6a57acad7a30 Mon Sep 17 00:00:00 2001 From: Soheab <33902984+Soheab@users.noreply.github.com> Date: Fri, 20 Jun 2025 20:49:20 +0200 Subject: [PATCH 12/12] Document return object for Guild.create_template --- discord/guild.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/discord/guild.py b/discord/guild.py index 20a50d4e9..291363b18 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -2921,6 +2921,11 @@ class Guild(Hashable): The name of the template. description: :class:`str` The description of the template. + + Returns + -------- + :class:`Template` + The created template. """ from .template import Template