From 69f06c945620c581847531088734b05f7752474f Mon Sep 17 00:00:00 2001 From: z03h <7235242+z03h@users.noreply.github.com> Date: Mon, 25 Aug 2025 20:24:37 -0700 Subject: [PATCH 01/14] Fix Select.required not being applied --- discord/components.py | 1 + 1 file changed, 1 insertion(+) diff --git a/discord/components.py b/discord/components.py index 0986680fc..d5a1737a2 100644 --- a/discord/components.py +++ b/discord/components.py @@ -396,6 +396,7 @@ class SelectMenu(Component): 'min_values': self.min_values, 'max_values': self.max_values, 'disabled': self.disabled, + 'required': self.required, } if self.id is not None: payload['id'] = self.id From cbff6ddef9891401c53ad338315158ff8aa090c9 Mon Sep 17 00:00:00 2001 From: Soheab <33902984+Soheab@users.noreply.github.com> Date: Tue, 26 Aug 2025 09:34:18 +0200 Subject: [PATCH 02/14] Add support for user collectibles --- discord/__init__.py | 1 + discord/asset.py | 10 ++++ discord/collectible.py | 109 +++++++++++++++++++++++++++++++++++++++++ discord/enums.py | 20 ++++++++ discord/member.py | 2 + discord/types/user.py | 41 +++++++++++----- discord/user.py | 16 ++++++ docs/api.rst | 70 +++++++++++++++++++++++++- 8 files changed, 256 insertions(+), 13 deletions(-) create mode 100644 discord/collectible.py diff --git a/discord/__init__.py b/discord/__init__.py index f4d7af42e..3279f8b8c 100644 --- a/discord/__init__.py +++ b/discord/__init__.py @@ -75,6 +75,7 @@ from .subscription import * from .presences import * from .primary_guild import * from .onboarding import * +from .collectible import * class VersionInfo(NamedTuple): diff --git a/discord/asset.py b/discord/asset.py index a3ed53c6b..41bcba3cf 100644 --- a/discord/asset.py +++ b/discord/asset.py @@ -355,6 +355,16 @@ class Asset(AssetMixin): animated=False, ) + @classmethod + def _from_user_collectible(cls, state: _State, asset: str, animated: bool = False) -> Self: + name = 'static.png' if not animated else 'asset.webm' + return cls( + state, + url=f'{cls.BASE}/assets/collectibles/{asset}{name}', + key=asset, + animated=animated, + ) + def __str__(self) -> str: return self._url diff --git a/discord/collectible.py b/discord/collectible.py new file mode 100644 index 000000000..b2ad7e4e0 --- /dev/null +++ b/discord/collectible.py @@ -0,0 +1,109 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-present Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +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 + +from typing import Optional, TYPE_CHECKING + + +from .asset import Asset +from .enums import NameplatePalette, CollectibleType, try_enum +from .utils import parse_time + + +if TYPE_CHECKING: + from datetime import datetime + + from .state import ConnectionState + from .types.user import ( + Collectible as CollectiblePayload, + ) + + +__all__ = ('Collectible',) + + +class Collectible: + """Represents a user's collectible. + + .. versionadded:: 2.7 + + Attributes + ---------- + label: :class:`str` + The label of the collectible. + palette: Optional[:class:`NameplatePalette`] + The palette of the collectible. + This is only available if ``type`` is + :class:`CollectibleType.nameplate`. + sku_id: :class:`int` + The SKU ID of the collectible. + type: :class:`CollectibleType` + The type of the collectible. + expires_at: Optional[:class:`datetime.datetime`] + The expiration date of the collectible. If applicable. + """ + + __slots__ = ( + 'type', + 'sku_id', + 'label', + 'expires_at', + 'palette', + '_state', + '_asset', + ) + + def __init__(self, *, state: ConnectionState, type: str, data: CollectiblePayload) -> None: + self._state: ConnectionState = state + self.type: CollectibleType = try_enum(CollectibleType, type) + self._asset: str = data['asset'] + self.sku_id: int = int(data['sku_id']) + self.label: str = data['label'] + self.expires_at: Optional[datetime] = parse_time(data.get('expires_at')) + + # nameplate + self.palette: Optional[NameplatePalette] + try: + self.palette = try_enum(NameplatePalette, data['palette']) # type: ignore + except KeyError: + self.palette = None + + @property + def static(self) -> Asset: + """:class:`Asset`: The static asset of the collectible.""" + return Asset._from_user_collectible(self._state, self._asset) + + @property + def animated(self) -> Asset: + """:class:`Asset`: The animated asset of the collectible.""" + return Asset._from_user_collectible(self._state, self._asset, animated=True) + + def __repr__(self) -> str: + attrs = ['sku_id'] + if self.palette: + attrs.append('palette') + + joined_attrs = ' '.join(f'{attr}={getattr(self, attr)!r}' for attr in attrs) + return f'<{self.type.name.title()} {joined_attrs}>' diff --git a/discord/enums.py b/discord/enums.py index 7dc4bccd0..28b99ab03 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -83,6 +83,8 @@ __all__ = ( 'OnboardingMode', 'SeparatorSpacing', 'MediaItemLoadingState', + 'CollectibleType', + 'NameplatePalette', ) @@ -968,6 +970,24 @@ class MediaItemLoadingState(Enum): not_found = 3 +class CollectibleType(Enum): + nameplate = 'nameplate' + + +class NameplatePalette(Enum): + crimson = 'crimson' + berry = 'berry' + sky = 'sky' + teal = 'teal' + forest = 'forest' + bubble_gum = 'bubble_gum' + violet = 'violet' + cobalt = 'cobalt' + clover = 'clover' + lemon = 'lemon' + white = 'white' + + 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/member.py b/discord/member.py index 9f6b9daf2..fd2cf7edb 100644 --- a/discord/member.py +++ b/discord/member.py @@ -75,6 +75,7 @@ if TYPE_CHECKING: VoiceState as VoiceStatePayload, ) from .primary_guild import PrimaryGuild + from .collectible import Collectible VocalGuildChannel = Union[VoiceChannel, StageChannel] @@ -311,6 +312,7 @@ class Member(discord.abc.Messageable, _UserTag): avatar_decoration: Optional[Asset] avatar_decoration_sku_id: Optional[int] primary_guild: PrimaryGuild + collectibles: List[Collectible] def __init__(self, *, data: MemberWithUserPayload, guild: Guild, state: ConnectionState): self._state: ConnectionState = state diff --git a/discord/types/user.py b/discord/types/user.py index b2b213ecf..639384a56 100644 --- a/discord/types/user.py +++ b/discord/types/user.py @@ -27,11 +27,38 @@ from typing import Literal, Optional, TypedDict from typing_extensions import NotRequired -class AvatarDecorationData(TypedDict): +PremiumType = Literal[0, 1, 2, 3] +NameplatePallete = Literal['crimson', 'berry', 'sky', 'teal', 'forest', 'bubble_gum', 'violet', 'cobalt', 'clover'] + + +class _UserSKU(TypedDict): asset: str sku_id: Snowflake +AvatarDecorationData = _UserSKU + + +class PrimaryGuild(TypedDict): + identity_guild_id: Optional[int] + identity_enabled: Optional[bool] + tag: Optional[str] + badge: Optional[str] + + +class Collectible(_UserSKU): + label: str + expires_at: Optional[str] + + +class NameplateCollectible(Collectible): + palette: str + + +class UserCollectibles(TypedDict): + nameplate: NameplateCollectible + + class PartialUser(TypedDict): id: Snowflake username: str @@ -39,9 +66,8 @@ class PartialUser(TypedDict): avatar: Optional[str] global_name: Optional[str] avatar_decoration_data: NotRequired[AvatarDecorationData] - - -PremiumType = Literal[0, 1, 2, 3] + primary_guild: NotRequired[PrimaryGuild] + collectibles: NotRequired[UserCollectibles] class User(PartialUser, total=False): @@ -54,10 +80,3 @@ class User(PartialUser, total=False): flags: int premium_type: PremiumType public_flags: int - - -class PrimaryGuild(TypedDict): - identity_guild_id: Optional[int] - identity_enabled: Optional[bool] - tag: Optional[str] - badge: Optional[str] diff --git a/discord/user.py b/discord/user.py index 751437532..32edb1dc7 100644 --- a/discord/user.py +++ b/discord/user.py @@ -33,6 +33,7 @@ from .enums import DefaultAvatar from .flags import PublicUserFlags from .utils import snowflake_time, _bytes_to_base64_data, MISSING, _get_as_snowflake from .primary_guild import PrimaryGuild +from .collectible import Collectible if TYPE_CHECKING: from typing_extensions import Self @@ -49,6 +50,7 @@ if TYPE_CHECKING: User as UserPayload, AvatarDecorationData, PrimaryGuild as PrimaryGuildPayload, + UserCollectibles as UserCollectiblesPayload, ) @@ -78,6 +80,7 @@ class BaseUser(_UserTag): '_state', '_avatar_decoration_data', '_primary_guild', + '_collectibles', ) if TYPE_CHECKING: @@ -94,6 +97,7 @@ class BaseUser(_UserTag): _public_flags: int _avatar_decoration_data: Optional[AvatarDecorationData] _primary_guild: Optional[PrimaryGuildPayload] + _collectibles: Optional[UserCollectiblesPayload] def __init__(self, *, state: ConnectionState, data: Union[UserPayload, PartialUserPayload]) -> None: self._state = state @@ -132,6 +136,7 @@ class BaseUser(_UserTag): self.system = data.get('system', False) self._avatar_decoration_data = data.get('avatar_decoration_data') self._primary_guild = data.get('primary_guild', None) + self._collectibles = data.get('collectibles', None) @classmethod def _copy(cls, user: Self) -> Self: @@ -149,6 +154,7 @@ class BaseUser(_UserTag): self._public_flags = user._public_flags self._avatar_decoration_data = user._avatar_decoration_data self._primary_guild = user._primary_guild + self._collectibles = user._collectibles return self @@ -324,6 +330,16 @@ class BaseUser(_UserTag): return PrimaryGuild(state=self._state, data=self._primary_guild) return PrimaryGuild._default(self._state) + @property + def collectibles(self) -> List[Collectible]: + """List[:class:`Collectible`]: Returns a list of the user's collectibles. + + .. versionadded:: 2.7 + """ + if self._collectibles is None: + return [] + return [Collectible(state=self._state, type=key, data=value) for key, value in self._collectibles.items() if value] # type: ignore + def mentioned_in(self, message: Message) -> bool: """Checks if the user is mentioned in the specified message. diff --git a/docs/api.rst b/docs/api.rst index 53ddec06e..1a564ddca 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -4052,8 +4052,6 @@ of :class:`enum.Enum`. Default channels and questions count towards onboarding constraints. - - .. class:: MediaItemLoadingState Represents a :class:`UnfurledMediaItem` load state. @@ -4074,6 +4072,66 @@ of :class:`enum.Enum`. The media item was not found. +.. class:: CollectibleType + + Represents the type of a :class:`Collectible`. + + .. versionadded:: 2.7 + + .. attribute:: nameplate + + The collectible is a nameplate. + +.. class:: NameplatePalette + + Represents the available palettes for a nameplate. + + .. versionadded:: 2.7 + + .. attribute:: crimson + + The collectible nameplate palette is crimson. + + .. attribute:: berry + + The collectible nameplate palette is berry. + + .. attribute:: sky + + The collectible nameplate palette is sky. + + .. attribute:: teal + + The collectible nameplate palette is teal. + + .. attribute:: forest + + The collectible nameplate palette is forest. + + .. attribute:: bubble_gum + + The collectible nameplate palette is bubble gum. + + .. attribute:: violet + + The collectible nameplate palette is violet. + + .. attribute:: cobalt + + The collectible nameplate palette is cobalt. + + .. attribute:: clover + + The collectible nameplate palette is clover. + + .. attribute:: lemon + + The collectible nameplate palette is lemon. + + .. attribute:: white + + The collectible nameplate palette is white. + .. _discord-api-audit-logs: Audit Log Data @@ -5770,6 +5828,14 @@ PrimaryGuild .. autoclass:: PrimaryGuild() :members: +Collectible +~~~~~~~~~~~ + +.. attributetable:: Collectible + +.. autoclass:: Collectible() + :members: + CallMessage ~~~~~~~~~~~~~~~~~~~ From 7c52dbdba01bf2572db862381c9949d61de08f22 Mon Sep 17 00:00:00 2001 From: Sacul Date: Tue, 26 Aug 2025 23:34:23 +0800 Subject: [PATCH 03/14] Fix spelling mistake in LabelComponent slots --- discord/components.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/components.py b/discord/components.py index d5a1737a2..a13a214f7 100644 --- a/discord/components.py +++ b/discord/components.py @@ -1355,11 +1355,11 @@ class LabelComponent(Component): __slots__ = ( 'label', 'description', - 'commponent', + 'component', 'id', ) - __repr_info__ = ('label', 'description', 'commponent', 'id,') + __repr_info__ = ('label', 'description', 'component', 'id,') def __init__(self, data: LabelComponentPayload, state: Optional[ConnectionState]) -> None: self.component: Component = _component_factory(data['component'], state) # type: ignore From 116107d7ede689b1fff977b4bd89309f09ad9be6 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Tue, 26 Aug 2025 11:53:55 -0400 Subject: [PATCH 04/14] Add accessory to Section.__repr__ --- discord/ui/section.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/ui/section.py b/discord/ui/section.py index 6a4026e22..cb914612d 100644 --- a/discord/ui/section.py +++ b/discord/ui/section.py @@ -87,7 +87,7 @@ class Section(Item[V]): self.id = id def __repr__(self) -> str: - return f'<{self.__class__.__name__} children={len(self._children)}>' + return f'<{self.__class__.__name__} children={len(self._children)} accessory={self._accessory!r}>' @property def type(self) -> Literal[ComponentType.section]: From 2f1f5fe2e2bd7c20280275a0d3dee7a0662d2f96 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Tue, 26 Aug 2025 21:12:03 -0400 Subject: [PATCH 05/14] Revert "Fix context install decorators to correctly restrict commands" This reverts commit ce9f5ad1ba07d070a50bf41b9d26f68a85be610b. --- discord/app_commands/commands.py | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/discord/app_commands/commands.py b/discord/app_commands/commands.py index a23682f8b..36d07d41c 100644 --- a/discord/app_commands/commands.py +++ b/discord/app_commands/commands.py @@ -2523,10 +2523,7 @@ def guild_only(func: Optional[T] = None) -> Union[T, Callable[[T], T]]: allowed_contexts = getattr(f, '__discord_app_commands_contexts__', None) or AppCommandContext() f.__discord_app_commands_contexts__ = allowed_contexts # type: ignore # Runtime attribute assignment - # Ensure that only Guild context is allowed - allowed_contexts.guild = True # Enable guild context - allowed_contexts.private_channel = False # Disable private channel context - allowed_contexts.dm_channel = False # Disable DM context + allowed_contexts.guild = True return f @@ -2578,10 +2575,7 @@ def private_channel_only(func: Optional[T] = None) -> Union[T, Callable[[T], T]] allowed_contexts = getattr(f, '__discord_app_commands_contexts__', None) or AppCommandContext() f.__discord_app_commands_contexts__ = allowed_contexts # type: ignore # Runtime attribute assignment - # Ensure that only Private Channel context is allowed - allowed_contexts.guild = False # Disable guild context - allowed_contexts.private_channel = True # Enable private channel context - allowed_contexts.dm_channel = False # Disable DM context + allowed_contexts.private_channel = True return f @@ -2631,11 +2625,7 @@ def dm_only(func: Optional[T] = None) -> Union[T, Callable[[T], T]]: allowed_contexts = getattr(f, '__discord_app_commands_contexts__', None) or AppCommandContext() f.__discord_app_commands_contexts__ = allowed_contexts # type: ignore # Runtime attribute assignment - # Ensure that only DM context is allowed - allowed_contexts.guild = False # Disable guild context - allowed_contexts.private_channel = False # Disable private channel context - allowed_contexts.dm_channel = True # Enable DM context - + allowed_contexts.dm_channel = True return f # Check if called with parentheses or not @@ -2727,7 +2717,6 @@ def guild_install(func: Optional[T] = None) -> Union[T, Callable[[T], T]]: f.__discord_app_commands_installation_types__ = allowed_installs # type: ignore # Runtime attribute assignment allowed_installs.guild = True - allowed_installs.user = False return f @@ -2776,7 +2765,6 @@ def user_install(func: Optional[T] = None) -> Union[T, Callable[[T], T]]: f.__discord_app_commands_installation_types__ = allowed_installs # type: ignore # Runtime attribute assignment allowed_installs.user = True - allowed_installs.guild = False return f From 89d5cbd78a19dfce3d25dad488eee4a778faf519 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Sun, 31 Aug 2025 14:18:50 -0400 Subject: [PATCH 06/14] Move v2 item check from BaseView to View --- discord/ui/view.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/discord/ui/view.py b/discord/ui/view.py index cbf40a14d..9c7547e60 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -426,9 +426,6 @@ class BaseView: if not isinstance(item, Item): raise TypeError(f'expected Item not {item.__class__.__name__}') - if item._is_v2() and not self._is_layout(): - raise ValueError('v2 items cannot be added to this view') - item._update_view(self) self._add_count(item._total_count) self._children.append(item) @@ -737,6 +734,9 @@ class View(BaseView): if len(self._children) >= 25: raise ValueError('maximum number of children exceeded') + if item._is_v2(): + raise ValueError('v2 items cannot be added to this view') + super().add_item(item) try: self.__weights.add_item(item) From 6c4f8c410d9aec5d1e2b001db9c0acfe8a22048a Mon Sep 17 00:00:00 2001 From: Rapptz Date: Wed, 27 Aug 2025 23:41:00 -0400 Subject: [PATCH 07/14] Add changelog for v2.6.2 and v2.6.3 --- docs/whats_new.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/whats_new.rst b/docs/whats_new.rst index 25011bb4b..c78c44435 100644 --- a/docs/whats_new.rst +++ b/docs/whats_new.rst @@ -11,6 +11,28 @@ Changelog This page keeps a detailed human friendly rendering of what's new and changed in specific versions. +.. _vp2p6p3: + +v2.6.3 +------- + +Bug Fixes +~~~~~~~~~~ + +- Fix :attr:`ui.Select.required` not being applied properly +- Fix potential attribute error when accessing :class:`LabelComponent` +- Fix issue when stacking decorators such as :func:`app_commands.guild_install` and :func:`app_commands.user_install` + +.. _vp2p6p2: + +v2.6.2 +------- + +Bug Fixes +~~~~~~~~~~ + +- Fix a bug with :class:`ui.DynamicItem` causing it to fail at runtime when passing a generic. + .. _vp2p6p1: v2.6.1 From 25a4dbe86aa350b7f7cf58ac8279b158e6489dcd Mon Sep 17 00:00:00 2001 From: Rapptz Date: Sun, 31 Aug 2025 16:00:59 -0400 Subject: [PATCH 08/14] Remove id from LabelComponent.__repr__ --- discord/components.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/components.py b/discord/components.py index a13a214f7..08ae4f277 100644 --- a/discord/components.py +++ b/discord/components.py @@ -1359,7 +1359,7 @@ class LabelComponent(Component): 'id', ) - __repr_info__ = ('label', 'description', 'component', 'id,') + __repr_info__ = ('label', 'description', 'component') def __init__(self, data: LabelComponentPayload, state: Optional[ConnectionState]) -> None: self.component: Component = _component_factory(data['component'], state) # type: ignore From 1e165eebd95c33a66a68cf6031eb30a771f93ec5 Mon Sep 17 00:00:00 2001 From: blord0 <68508813+blord0@users.noreply.github.com> Date: Tue, 2 Sep 2025 22:05:25 +0100 Subject: [PATCH 09/14] Update docs for on_user_update to mention primary_guild --- docs/api.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api.rst b/docs/api.rst index 1a564ddca..1b762ab51 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -874,6 +874,7 @@ Members - avatar - username - discriminator + - primary guild This requires :attr:`Intents.members` to be enabled. From 44956db0331850699902b8953f871d309418542e Mon Sep 17 00:00:00 2001 From: Sacul Date: Wed, 3 Sep 2025 05:05:50 +0800 Subject: [PATCH 10/14] Update modal example in docstring --- discord/ui/modal.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/discord/ui/modal.py b/discord/ui/modal.py index c85719a9b..86c09da30 100644 --- a/discord/ui/modal.py +++ b/discord/ui/modal.py @@ -70,11 +70,11 @@ class Modal(BaseView): from discord import ui class Questionnaire(ui.Modal, title='Questionnaire Response'): - name = ui.TextInput(label='Name') - answer = ui.TextInput(label='Answer', style=discord.TextStyle.paragraph) + name = ui.Label(text='Name', component=ui.TextInput()) + answer = ui.Label(text='Answer', component=ui.TextInput(style=discord.TextStyle.paragraph)) async def on_submit(self, interaction: discord.Interaction): - await interaction.response.send_message(f'Thanks for your response, {self.name}!', ephemeral=True) + await interaction.response.send_message(f'Thanks for your response, {self.name.component.value}!', ephemeral=True) Parameters ----------- From 3f47698791412f40a93ac52034cb6ae0411e99cf Mon Sep 17 00:00:00 2001 From: Steve C Date: Wed, 3 Sep 2025 05:01:50 -0400 Subject: [PATCH 11/14] All __all__ to primary_guilds module --- discord/primary_guild.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/discord/primary_guild.py b/discord/primary_guild.py index 85e40159a..940cb77c8 100644 --- a/discord/primary_guild.py +++ b/discord/primary_guild.py @@ -35,6 +35,12 @@ if TYPE_CHECKING: from .types.user import PrimaryGuild as PrimaryGuildPayload from typing_extensions import Self +# fmt: off +__all__ = ( + 'PrimaryGuild', +) +# fmt: on + class PrimaryGuild: """Represents the primary guild identity of a :class:`User` From a7f349498a9ca711b26de6f7a0415944ee259796 Mon Sep 17 00:00:00 2001 From: Lucas Hardt Date: Wed, 3 Sep 2025 11:31:59 +0200 Subject: [PATCH 12/14] Upgrade code by using f-strings and yield from --- discord/message.py | 8 ++++---- discord/ui/action_row.py | 3 +-- discord/ui/section.py | 3 +-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/discord/message.py b/discord/message.py index 02f31198d..2c7ab17a7 100644 --- a/discord/message.py +++ b/discord/message.py @@ -2843,14 +2843,14 @@ class Message(PartialMessage, Hashable): if call_ended: duration = utils._format_call_duration(self.call.duration) # type: ignore # call can't be None here if missed: - return 'You missed a call from {0.author.name} that lasted {1}.'.format(self, duration) + return f'You missed a call from {self.author.name} that lasted {duration}.' else: - return '{0.author.name} started a call that lasted {1}.'.format(self, duration) + return f'{self.author.name} started a call that lasted {duration}.' else: if missed: - return '{0.author.name} started a call. \N{EM DASH} Join the call'.format(self) + return f'{self.author.name} started a call. \N{EM DASH} Join the call' else: - return '{0.author.name} started a call.'.format(self) + return f'{self.author.name} started a call.' if self.type is MessageType.purchase_notification and self.purchase_notification is not None: guild_product_purchase = self.purchase_notification.guild_product_purchase diff --git a/discord/ui/action_row.py b/discord/ui/action_row.py index b0598db20..c7f7a2b7b 100644 --- a/discord/ui/action_row.py +++ b/discord/ui/action_row.py @@ -227,8 +227,7 @@ class ActionRow(Item[V]): An item in the action row. """ - for child in self.children: - yield child + yield from self.children def content_length(self) -> int: """:class:`int`: Returns the total length of all text content in this action row.""" diff --git a/discord/ui/section.py b/discord/ui/section.py index cb914612d..67d35e001 100644 --- a/discord/ui/section.py +++ b/discord/ui/section.py @@ -137,8 +137,7 @@ class Section(Item[V]): An item in this section. """ - for child in self.children: - yield child + yield from self.children yield self.accessory def _update_view(self, view) -> None: From fe4bf87b232c0b870b92781b4249c7fdd49200d1 Mon Sep 17 00:00:00 2001 From: Sacul Date: Wed, 3 Sep 2025 17:33:41 +0800 Subject: [PATCH 13/14] Add channel attribute to automod quarantine user AuditLogAction --- discord/audit_logs.py | 14 +------------- docs/api.rst | 3 ++- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/discord/audit_logs.py b/discord/audit_logs.py index c27a793c3..e56f0fb3d 100644 --- a/discord/audit_logs.py +++ b/discord/audit_logs.py @@ -630,11 +630,6 @@ class _AuditLogProxyAutoModAction(_AuditLogProxy): channel: Optional[Union[abc.GuildChannel, Thread]] -class _AuditLogProxyAutoModActionQuarantineUser(_AuditLogProxy): - automod_rule_name: str - automod_rule_trigger_type: str - - class _AuditLogProxyMemberKickOrMemberRoleUpdate(_AuditLogProxy): integration_type: Optional[str] @@ -725,7 +720,6 @@ class AuditLogEntry(Hashable): _AuditLogProxyStageInstanceAction, _AuditLogProxyMessageBulkDelete, _AuditLogProxyAutoModAction, - _AuditLogProxyAutoModActionQuarantineUser, _AuditLogProxyMemberKickOrMemberRoleUpdate, Member, User, None, PartialIntegration, Role, Object @@ -766,6 +760,7 @@ class AuditLogEntry(Hashable): self.action is enums.AuditLogAction.automod_block_message or self.action is enums.AuditLogAction.automod_flag_message or self.action is enums.AuditLogAction.automod_timeout_member + or self.action is enums.AuditLogAction.automod_quarantine_user ): channel_id = utils._get_as_snowflake(extra, 'channel_id') channel = None @@ -781,13 +776,6 @@ class AuditLogEntry(Hashable): ), channel=channel, ) - elif self.action is enums.AuditLogAction.automod_quarantine_user: - self.extra = _AuditLogProxyAutoModActionQuarantineUser( - automod_rule_name=extra['auto_moderation_rule_name'], - automod_rule_trigger_type=enums.try_enum( - enums.AutoModRuleTriggerType, int(extra['auto_moderation_rule_trigger_type']) - ), - ) elif self.action.name.startswith('overwrite_'): # the overwrite_ actions have a dict with some information diff --git a/docs/api.rst b/docs/api.rst index 1b762ab51..428d9e1d9 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3064,10 +3064,11 @@ of :class:`enum.Enum`. a :class:`Member` with the ID of the person who triggered the automod rule. When this is the action, the type of :attr:`~AuditLogEntry.extra` is - set to an unspecified proxy object with 2 attributes: + set to an unspecified proxy object with 3 attributes: - ``automod_rule_name``: The name of the automod rule that was triggered. - ``automod_rule_trigger_type``: A :class:`AutoModRuleTriggerType` representation of the rule type that was triggered. + - ``channel``: The channel of the message sent by the member when they were flagged. `None` if the member was quarantined when they just joined the guild. When this is the action, :attr:`AuditLogEntry.changes` is empty. From bd329b15159976ca0a5c8da8fe12bd73ee0c23b3 Mon Sep 17 00:00:00 2001 From: Steve C Date: Wed, 3 Sep 2025 05:34:13 -0400 Subject: [PATCH 14/14] Add support for emoji_added message type --- discord/enums.py | 1 + discord/flags.py | 9 +++++++++ discord/message.py | 3 +++ discord/types/message.py | 1 + docs/api.rst | 8 ++++++++ 5 files changed, 22 insertions(+) diff --git a/discord/enums.py b/discord/enums.py index 28b99ab03..172f736a9 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -275,6 +275,7 @@ class MessageType(Enum): guild_incident_report_false_alarm = 39 purchase_notification = 44 poll_result = 46 + emoji_added = 63 class SpeakingState(Enum): diff --git a/discord/flags.py b/discord/flags.py index 5105a4156..a4878368c 100644 --- a/discord/flags.py +++ b/discord/flags.py @@ -371,6 +371,15 @@ class SystemChannelFlags(BaseFlags): """ return 32 + @flag_value + def emoji_added(self): + """:class:`bool`: Returns ``True`` if the system channel is used for + emoji added notifications. + + .. versionadded:: 2.7 + """ + return 256 + @fill_with_flags() class MessageFlags(BaseFlags): diff --git a/discord/message.py b/discord/message.py index 2c7ab17a7..9db351d54 100644 --- a/discord/message.py +++ b/discord/message.py @@ -2865,6 +2865,9 @@ class Message(PartialMessage, Hashable): ) return f"{self.author.display_name}'s poll {poll_title.value} has closed." # type: ignore + if self.type is MessageType.emoji_added: + return f'{self.author.name} added a new emoji, {self.content}' + # Fallback for unknown message types return '' diff --git a/discord/types/message.py b/discord/types/message.py index dfb251f28..c7631ffc3 100644 --- a/discord/types/message.py +++ b/discord/types/message.py @@ -175,6 +175,7 @@ MessageType = Literal[ 39, 44, 46, + 63, ] diff --git a/docs/api.rst b/docs/api.rst index 428d9e1d9..ab8f4f5ca 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1925,6 +1925,14 @@ of :class:`enum.Enum`. The system message sent when a poll has closed. + .. versionadded:: 2.5 + + .. attribute:: emoji_added + + The system message sent when a custom emoji is added to the guild. + + .. versionadded:: 2.7 + .. class:: UserFlags Represents Discord User flags.