From 44fb360a835bd5fdcdb1d69affe3ca2f570bc49d Mon Sep 17 00:00:00 2001 From: makerze <99765898+makerze@users.noreply.github.com> Date: Fri, 20 Jun 2025 20:25:54 +0100 Subject: [PATCH 01/16] Add new role parameters for creation of gradient and holographic roles --- discord/guild.py | 50 ++++++++++++++++++++++++++++++++++++++++++ discord/http.py | 2 +- discord/role.py | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) diff --git a/discord/guild.py b/discord/guild.py index 20a50d4e932f..8badf1239076 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -3614,6 +3614,9 @@ async def create_role( hoist: bool = ..., display_icon: Union[bytes, str] = MISSING, mentionable: bool = ..., + primary_color: Union[Colour, int, None] = ..., + secondary_color: Union[Colour, int, None] = ..., + tertiary_color: Union[Colour, int, None] = ..., ) -> Role: ... @@ -3628,6 +3631,9 @@ async def create_role( hoist: bool = ..., display_icon: Union[bytes, str] = MISSING, mentionable: bool = ..., + primary_color: Union[Colour, int, None] = ..., + secondary_color: Union[Colour, int, None] = ..., + tertiary_color: Union[Colour, int, None] = ..., ) -> Role: ... @@ -3642,6 +3648,9 @@ async def create_role( display_icon: Union[bytes, str] = MISSING, mentionable: bool = MISSING, reason: Optional[str] = None, + primary_color: Union[Colour, int, None] = MISSING, + secondary_color: Union[Colour, int, None] = MISSING, + tertiary_color: Union[Colour, int, None] = MISSING, ) -> Role: """|coro| @@ -3670,6 +3679,14 @@ async def create_role( colour: Union[:class:`Colour`, :class:`int`] The colour for the role. Defaults to :meth:`Colour.default`. This is aliased to ``color`` as well. + primary_color: Union[:class:`Colour`, :class:`int`, None] + The primary color for the role. If provided, must be an integer or :class:`Colour`. + secondary_color: Union[:class:`Colour`, :class:`int`, None] + The secondary color for the role. Requires ``primary_color`` to also be set. + tertiary_color: Union[:class:`Colour`, :class:`int`, None] + The tertiary_color color for the role. Used for holographic role. + The holographic preset is: + {"primary_color": 11127295, "secondary_color": 16759788, "tertiary_color": 16761760} hoist: :class:`bool` Indicates if the role should be shown separately in the member list. Defaults to ``False``. @@ -3710,6 +3727,39 @@ async def create_role( else: fields['color'] = actual_colour.value + colors_payload: Dict[str, Any]= {} + if primary_color is not MISSING: + if primary_color is None: + colors_payload['primary_color'] = None + elif isinstance(primary_color, int): + colors_payload['primary_color'] = primary_color + else: + colors_payload['primary_color'] = primary_color.value + if secondary_color is not MISSING: + if secondary_color is None: + colors_payload['secondary_color'] = None + elif isinstance(secondary_color, int): + colors_payload['secondary_color'] = secondary_color + else: + colors_payload['secondary_color'] = secondary_color.value + if tertiary_color is not MISSING: + if tertiary_color is None: + colors_payload['tertiary_color'] = None + elif isinstance(tertiary_color, int): + colors_payload['tertiary_color'] = tertiary_color + else: + colors_payload['tertiary_color'] = tertiary_color.value + + if colors_payload: + fields['colors'] = colors_payload + + if not colors_payload: + actual_colour = colour or color or Colour.default() + if isinstance(actual_colour, int): + fields['color'] = actual_colour + else: + fields['color'] = actual_colour.value + if hoist is not MISSING: fields['hoist'] = hoist diff --git a/discord/http.py b/discord/http.py index 6617efa2708b..187126ed990d 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1889,7 +1889,7 @@ def edit_role( self, guild_id: Snowflake, role_id: Snowflake, *, reason: Optional[str] = None, **fields: Any ) -> Response[role.Role]: r = Route('PATCH', '/guilds/{guild_id}/roles/{role_id}', guild_id=guild_id, role_id=role_id) - valid_keys = ('name', 'permissions', 'color', 'hoist', 'icon', 'unicode_emoji', 'mentionable') + valid_keys = ('name', 'permissions', 'color', 'hoist', 'icon', 'unicode_emoji', 'mentionable', 'colors') payload = {k: v for k, v in fields.items() if k in valid_keys} return self.request(r, json=payload, reason=reason) diff --git a/discord/role.py b/discord/role.py index d7fe1e08bbe2..076b0b9f748e 100644 --- a/discord/role.py +++ b/discord/role.py @@ -222,6 +222,9 @@ class Role(Hashable): 'tags', '_flags', '_state', + '_primary_color', + '_secondary_color', + '_tertiary_color', ) def __init__(self, *, guild: Guild, state: ConnectionState, data: RolePayload): @@ -284,6 +287,10 @@ def _update(self, data: RolePayload): self.mentionable: bool = data.get('mentionable', False) self.tags: Optional[RoleTags] self._flags: int = data.get('flags', 0) + colors = data.get('colors', {}) + self._primary_color = colors.get('primary_color', None) + self._secondary_color = colors.get('secondary_color', None) + self._tertiary_color = colors.get('tertiary_color', None) try: self.tags = RoleTags(data['tags']) # pyright: ignore[reportTypedDictNotRequiredAccess] @@ -324,6 +331,20 @@ def is_assignable(self) -> bool: return not self.is_default() and not self.managed and (me.top_role > self or me.id == self.guild.owner_id) @property + def primary_color(self) -> Optional[Colour]: + """Optional[:class:`Colour`]: The role's primary color.""" + return Colour(self._primary_color) if self._primary_color is not None else None + + @property + def secondary_color(self) -> Optional[Colour]: + """Optional[:class:`Colour`]: The role's secondary color.""" + return Colour(self._secondary_color) if self._secondary_color is not None else None + + @property + def tertiary_color(self) -> Optional[Colour]: + """Optional[:class:`Colour`]: The role's tertiary color.""" + return Colour(self._tertiary_color) if self._tertiary_color is not None else None + @property def permissions(self) -> Permissions: """:class:`Permissions`: Returns the role's permissions.""" return Permissions(self._permissions) @@ -425,6 +446,9 @@ async def edit( mentionable: bool = MISSING, position: int = MISSING, reason: Optional[str] = MISSING, + primary_color: Union[Colour, int, None] = MISSING, + secondary_color: Union[Colour, int, None] = MISSING, + tertiary_color: Union[Colour, int, None] = MISSING, ) -> Optional[Role]: """|coro| @@ -470,6 +494,14 @@ async def edit( position or it will fail. reason: Optional[:class:`str`] The reason for editing this role. Shows up on the audit log. + primary_color: Union[:class:`Colour`, :class:`int`, None] + The primary color for the role. If provided, must be an integer or :class:`Colour`. + secondary_color: Union[:class:`Colour`, :class:`int`, None] + The secondary color for the role. + tertiary_color: Union[:class:`Colour`, :class:`int`, None] + The tertiary_color color for the role. Used for holographic role. + The holographic preset is: + {"primary_color": 11127295, "secondary_color": 16759788, "tertiary_color": 16761760} Raises ------- @@ -519,6 +551,31 @@ async def edit( if mentionable is not MISSING: payload['mentionable'] = mentionable + colors_payload: Dict[str, Any] = {} + if primary_color is not MISSING: + if primary_color is None: + colors_payload['primary_color'] = None + elif isinstance(primary_color, int): + colors_payload['primary_color'] = primary_color + else: + colors_payload['primary_color'] = primary_color.value + if secondary_color is not MISSING: + if secondary_color is None: + colors_payload['secondary_color'] = None + elif isinstance(secondary_color, int): + colors_payload['secondary_color'] = secondary_color + else: + colors_payload['secondary_color'] = secondary_color.value + if tertiary_color is not MISSING: + if tertiary_color is None: + colors_payload['tertiary_color'] = None + elif isinstance(tertiary_color, int): + colors_payload['tertiary_color'] = tertiary_color + else: + colors_payload['tertiary_color'] = tertiary_color.value + if colors_payload: + payload['colors'] = colors_payload + data = await self._state.http.edit_role(self.guild.id, self.id, reason=reason, **payload) return Role(guild=self.guild, data=data, state=self._state) From 4d058136fc88b567b9dd31f95835ca4bb3b9b414 Mon Sep 17 00:00:00 2001 From: makerze <99765898+makerze@users.noreply.github.com> Date: Fri, 20 Jun 2025 21:17:16 +0100 Subject: [PATCH 02/16] Fix: throw exception when color is used together with colors parameters --- discord/guild.py | 11 +++++++++++ discord/role.py | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/discord/guild.py b/discord/guild.py index 8badf1239076..fda921f9459a 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -3727,6 +3727,17 @@ async def create_role( else: fields['color'] = actual_colour.value + solid_color_used = color is not MISSING or colour is not MISSING + colors_used = ( + primary_color is not MISSING or + secondary_color is not MISSING or + tertiary_color is not MISSING + ) + if solid_color_used and colors_used: + raise TypeError( + "You must choose either only solid color (color/colour) or colors (primary_color/secondary_color/tertiary_color), not both." + ) + colors_payload: Dict[str, Any]= {} if primary_color is not MISSING: if primary_color is None: diff --git a/discord/role.py b/discord/role.py index 076b0b9f748e..929d4446b79c 100644 --- a/discord/role.py +++ b/discord/role.py @@ -551,6 +551,17 @@ async def edit( if mentionable is not MISSING: payload['mentionable'] = mentionable + solid_color_used = color is not MISSING or colour is not MISSING + colors_used = ( + primary_color is not MISSING or + secondary_color is not MISSING or + tertiary_color is not MISSING + ) + if solid_color_used and colors_used: + raise TypeError( + "You must choose either only solid color (color/colour) or colors (primary_color/secondary_color/tertiary_color), not both." + ) + colors_payload: Dict[str, Any] = {} if primary_color is not MISSING: if primary_color is None: From 89961e52fa31f35e6d05389faad916fbc41cf018 Mon Sep 17 00:00:00 2001 From: makerze <99765898+makerze@users.noreply.github.com> Date: Fri, 20 Jun 2025 21:27:15 +0100 Subject: [PATCH 03/16] linter --- discord/guild.py | 54 ++++++++++++++++-------------------------------- discord/role.py | 25 ++++++++-------------- 2 files changed, 27 insertions(+), 52 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index fda921f9459a..278b78cd3213 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -1293,8 +1293,7 @@ def _create_channel( overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, - ) -> Coroutine[Any, Any, TextChannelPayload]: - ... + ) -> Coroutine[Any, Any, TextChannelPayload]: ... @overload def _create_channel( @@ -1304,8 +1303,7 @@ def _create_channel( overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, - ) -> Coroutine[Any, Any, VoiceChannelPayload]: - ... + ) -> Coroutine[Any, Any, VoiceChannelPayload]: ... @overload def _create_channel( @@ -1315,8 +1313,7 @@ def _create_channel( overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, - ) -> Coroutine[Any, Any, StageChannelPayload]: - ... + ) -> Coroutine[Any, Any, StageChannelPayload]: ... @overload def _create_channel( @@ -1326,8 +1323,7 @@ def _create_channel( overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, - ) -> Coroutine[Any, Any, CategoryChannelPayload]: - ... + ) -> Coroutine[Any, Any, CategoryChannelPayload]: ... @overload def _create_channel( @@ -1337,8 +1333,7 @@ def _create_channel( overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, - ) -> Coroutine[Any, Any, NewsChannelPayload]: - ... + ) -> Coroutine[Any, Any, NewsChannelPayload]: ... @overload def _create_channel( @@ -1348,8 +1343,7 @@ def _create_channel( overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, - ) -> Coroutine[Any, Any, Union[TextChannelPayload, NewsChannelPayload]]: - ... + ) -> Coroutine[Any, Any, Union[TextChannelPayload, NewsChannelPayload]]: ... @overload def _create_channel( @@ -1359,8 +1353,7 @@ def _create_channel( overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, - ) -> Coroutine[Any, Any, ForumChannelPayload]: - ... + ) -> Coroutine[Any, Any, ForumChannelPayload]: ... @overload def _create_channel( @@ -1370,8 +1363,7 @@ def _create_channel( overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, - ) -> Coroutine[Any, Any, GuildChannelPayload]: - ... + ) -> Coroutine[Any, Any, GuildChannelPayload]: ... def _create_channel( self, @@ -3202,8 +3194,7 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., - ) -> ScheduledEvent: - ... + ) -> ScheduledEvent: ... @overload async def create_scheduled_event( @@ -3218,8 +3209,7 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., - ) -> ScheduledEvent: - ... + ) -> ScheduledEvent: ... @overload async def create_scheduled_event( @@ -3233,8 +3223,7 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., - ) -> ScheduledEvent: - ... + ) -> ScheduledEvent: ... @overload async def create_scheduled_event( @@ -3248,8 +3237,7 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., - ) -> ScheduledEvent: - ... + ) -> ScheduledEvent: ... async def create_scheduled_event( self, @@ -3617,8 +3605,7 @@ async def create_role( primary_color: Union[Colour, int, None] = ..., secondary_color: Union[Colour, int, None] = ..., tertiary_color: Union[Colour, int, None] = ..., - ) -> Role: - ... + ) -> Role: ... @overload async def create_role( @@ -3634,8 +3621,7 @@ async def create_role( primary_color: Union[Colour, int, None] = ..., secondary_color: Union[Colour, int, None] = ..., tertiary_color: Union[Colour, int, None] = ..., - ) -> Role: - ... + ) -> Role: ... async def create_role( self, @@ -3728,17 +3714,13 @@ async def create_role( fields['color'] = actual_colour.value solid_color_used = color is not MISSING or colour is not MISSING - colors_used = ( - primary_color is not MISSING or - secondary_color is not MISSING or - tertiary_color is not MISSING - ) + colors_used = primary_color is not MISSING or secondary_color is not MISSING or tertiary_color is not MISSING if solid_color_used and colors_used: raise TypeError( "You must choose either only solid color (color/colour) or colors (primary_color/secondary_color/tertiary_color), not both." ) - - colors_payload: Dict[str, Any]= {} + + colors_payload: Dict[str, Any] = {} if primary_color is not MISSING: if primary_color is None: colors_payload['primary_color'] = None @@ -3763,7 +3745,7 @@ async def create_role( if colors_payload: fields['colors'] = colors_payload - + if not colors_payload: actual_colour = colour or color or Colour.default() if isinstance(actual_colour, int): diff --git a/discord/role.py b/discord/role.py index 929d4446b79c..e3ea79a31381 100644 --- a/discord/role.py +++ b/discord/role.py @@ -224,7 +224,7 @@ class Role(Hashable): '_state', '_primary_color', '_secondary_color', - '_tertiary_color', + '_tertiary_color', ) def __init__(self, *, guild: Guild, state: ConnectionState, data: RolePayload): @@ -344,6 +344,7 @@ def secondary_color(self) -> Optional[Colour]: def tertiary_color(self) -> Optional[Colour]: """Optional[:class:`Colour`]: The role's tertiary color.""" return Colour(self._tertiary_color) if self._tertiary_color is not None else None + @property def permissions(self) -> Permissions: """:class:`Permissions`: Returns the role's permissions.""" @@ -552,16 +553,12 @@ async def edit( payload['mentionable'] = mentionable solid_color_used = color is not MISSING or colour is not MISSING - colors_used = ( - primary_color is not MISSING or - secondary_color is not MISSING or - tertiary_color is not MISSING - ) + colors_used = primary_color is not MISSING or secondary_color is not MISSING or tertiary_color is not MISSING if solid_color_used and colors_used: raise TypeError( "You must choose either only solid color (color/colour) or colors (primary_color/secondary_color/tertiary_color), not both." ) - + colors_payload: Dict[str, Any] = {} if primary_color is not MISSING: if primary_color is None: @@ -586,25 +583,21 @@ async def edit( colors_payload['tertiary_color'] = tertiary_color.value if colors_payload: payload['colors'] = colors_payload - + data = await self._state.http.edit_role(self.guild.id, self.id, reason=reason, **payload) return Role(guild=self.guild, data=data, state=self._state) @overload - async def move(self, *, beginning: bool, offset: int = ..., reason: Optional[str] = ...): - ... + async def move(self, *, beginning: bool, offset: int = ..., reason: Optional[str] = ...): ... @overload - async def move(self, *, end: bool, offset: int = ..., reason: Optional[str] = ...): - ... + async def move(self, *, end: bool, offset: int = ..., reason: Optional[str] = ...): ... @overload - async def move(self, *, above: Role, offset: int = ..., reason: Optional[str] = ...): - ... + async def move(self, *, above: Role, offset: int = ..., reason: Optional[str] = ...): ... @overload - async def move(self, *, below: Role, offset: int = ..., reason: Optional[str] = ...): - ... + async def move(self, *, below: Role, offset: int = ..., reason: Optional[str] = ...): ... async def move( self, From e2303847cf39fcf3db28e197705b5be60c7ae4a1 Mon Sep 17 00:00:00 2001 From: makerze <99765898+makerze@users.noreply.github.com> Date: Fri, 20 Jun 2025 21:43:14 +0100 Subject: [PATCH 04/16] black format --- discord/types/guild.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/discord/types/guild.py b/discord/types/guild.py index 7ac90b89ea53..a001500f263a 100644 --- a/discord/types/guild.py +++ b/discord/types/guild.py @@ -113,8 +113,7 @@ class _GuildPreviewUnique(TypedDict): approximate_presence_count: int -class GuildPreview(_BaseGuildPreview, _GuildPreviewUnique): - ... +class GuildPreview(_BaseGuildPreview, _GuildPreviewUnique): ... class Guild(_BaseGuildPreview): @@ -164,8 +163,7 @@ class InviteGuild(Guild, total=False): welcome_screen: WelcomeScreen -class GuildWithCounts(Guild, _GuildPreviewUnique): - ... +class GuildWithCounts(Guild, _GuildPreviewUnique): ... class GuildPrune(TypedDict): From 59a35a297080689e36958f3934f83096db20d026 Mon Sep 17 00:00:00 2001 From: makerze <99765898+makerze@users.noreply.github.com> Date: Fri, 20 Jun 2025 22:24:06 +0100 Subject: [PATCH 05/16] black format --- discord/guild.py | 42 ++++++++++++++++++++++++++++-------------- discord/role.py | 12 ++++++++---- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index 278b78cd3213..5e747799401b 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -1293,7 +1293,8 @@ def _create_channel( overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, - ) -> Coroutine[Any, Any, TextChannelPayload]: ... + ) -> Coroutine[Any, Any, TextChannelPayload]: + ... @overload def _create_channel( @@ -1303,7 +1304,8 @@ def _create_channel( overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, - ) -> Coroutine[Any, Any, VoiceChannelPayload]: ... + ) -> Coroutine[Any, Any, VoiceChannelPayload]: + ... @overload def _create_channel( @@ -1313,7 +1315,8 @@ def _create_channel( overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, - ) -> Coroutine[Any, Any, StageChannelPayload]: ... + ) -> Coroutine[Any, Any, StageChannelPayload]: + ... @overload def _create_channel( @@ -1323,7 +1326,8 @@ def _create_channel( overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, - ) -> Coroutine[Any, Any, CategoryChannelPayload]: ... + ) -> Coroutine[Any, Any, CategoryChannelPayload]: + ... @overload def _create_channel( @@ -1333,7 +1337,8 @@ def _create_channel( overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, - ) -> Coroutine[Any, Any, NewsChannelPayload]: ... + ) -> Coroutine[Any, Any, NewsChannelPayload]: + ... @overload def _create_channel( @@ -1343,7 +1348,8 @@ def _create_channel( overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, - ) -> Coroutine[Any, Any, Union[TextChannelPayload, NewsChannelPayload]]: ... + ) -> Coroutine[Any, Any, Union[TextChannelPayload, NewsChannelPayload]]: + ... @overload def _create_channel( @@ -1353,7 +1359,8 @@ def _create_channel( overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, - ) -> Coroutine[Any, Any, ForumChannelPayload]: ... + ) -> Coroutine[Any, Any, ForumChannelPayload]: + ... @overload def _create_channel( @@ -1363,7 +1370,8 @@ def _create_channel( overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, - ) -> Coroutine[Any, Any, GuildChannelPayload]: ... + ) -> Coroutine[Any, Any, GuildChannelPayload]: + ... def _create_channel( self, @@ -3194,7 +3202,8 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., - ) -> ScheduledEvent: ... + ) -> ScheduledEvent: + ... @overload async def create_scheduled_event( @@ -3209,7 +3218,8 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., - ) -> ScheduledEvent: ... + ) -> ScheduledEvent: + ... @overload async def create_scheduled_event( @@ -3223,7 +3233,8 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., - ) -> ScheduledEvent: ... + ) -> ScheduledEvent: + ... @overload async def create_scheduled_event( @@ -3237,7 +3248,8 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., - ) -> ScheduledEvent: ... + ) -> ScheduledEvent: + ... async def create_scheduled_event( self, @@ -3605,7 +3617,8 @@ async def create_role( primary_color: Union[Colour, int, None] = ..., secondary_color: Union[Colour, int, None] = ..., tertiary_color: Union[Colour, int, None] = ..., - ) -> Role: ... + ) -> Role: + ... @overload async def create_role( @@ -3621,7 +3634,8 @@ async def create_role( primary_color: Union[Colour, int, None] = ..., secondary_color: Union[Colour, int, None] = ..., tertiary_color: Union[Colour, int, None] = ..., - ) -> Role: ... + ) -> Role: + ... async def create_role( self, diff --git a/discord/role.py b/discord/role.py index e3ea79a31381..2940f2beea16 100644 --- a/discord/role.py +++ b/discord/role.py @@ -588,16 +588,20 @@ async def edit( return Role(guild=self.guild, data=data, state=self._state) @overload - async def move(self, *, beginning: bool, offset: int = ..., reason: Optional[str] = ...): ... + async def move(self, *, beginning: bool, offset: int = ..., reason: Optional[str] = ...): + ... @overload - async def move(self, *, end: bool, offset: int = ..., reason: Optional[str] = ...): ... + async def move(self, *, end: bool, offset: int = ..., reason: Optional[str] = ...): + ... @overload - async def move(self, *, above: Role, offset: int = ..., reason: Optional[str] = ...): ... + async def move(self, *, above: Role, offset: int = ..., reason: Optional[str] = ...): + ... @overload - async def move(self, *, below: Role, offset: int = ..., reason: Optional[str] = ...): ... + async def move(self, *, below: Role, offset: int = ..., reason: Optional[str] = ...): + ... async def move( self, From c8542c411d374907dcf6a7f265a4e546a60ccd21 Mon Sep 17 00:00:00 2001 From: makerze <99765898+makerze@users.noreply.github.com> Date: Fri, 20 Jun 2025 22:36:09 +0100 Subject: [PATCH 06/16] format --- discord/types/guild.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/discord/types/guild.py b/discord/types/guild.py index a001500f263a..7ac90b89ea53 100644 --- a/discord/types/guild.py +++ b/discord/types/guild.py @@ -113,7 +113,8 @@ class _GuildPreviewUnique(TypedDict): approximate_presence_count: int -class GuildPreview(_BaseGuildPreview, _GuildPreviewUnique): ... +class GuildPreview(_BaseGuildPreview, _GuildPreviewUnique): + ... class Guild(_BaseGuildPreview): @@ -163,7 +164,8 @@ class InviteGuild(Guild, total=False): welcome_screen: WelcomeScreen -class GuildWithCounts(Guild, _GuildPreviewUnique): ... +class GuildWithCounts(Guild, _GuildPreviewUnique): + ... class GuildPrune(TypedDict): From d26e4d67ad86d1682e04621e14958f20936afe9b Mon Sep 17 00:00:00 2001 From: Mak <99765898+makerze@users.noreply.github.com> Date: Sat, 21 Jun 2025 13:29:14 +0100 Subject: [PATCH 07/16] Update discord/guild.py Co-authored-by: dolfies --- discord/guild.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index 0517f527bc57..82a3f3ce0ad2 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -3718,9 +3718,8 @@ async def create_role( secondary_color: Union[:class:`Colour`, :class:`int`, None] The secondary color for the role. Requires ``primary_color`` to also be set. tertiary_color: Union[:class:`Colour`, :class:`int`, None] - The tertiary_color color for the role. Used for holographic role. - The holographic preset is: - {"primary_color": 11127295, "secondary_color": 16759788, "tertiary_color": 16761760} + The tertiary color for the role. Can only be used for the holographic role preset, + which is ``(11127295, 16759788, 16761760)`` hoist: :class:`bool` Indicates if the role should be shown separately in the member list. Defaults to ``False``. From f1047a59deecea85facb835db2afe96dd2f8f94a Mon Sep 17 00:00:00 2001 From: makerze <99765898+makerze@users.noreply.github.com> Date: Sat, 21 Jun 2025 15:55:53 +0100 Subject: [PATCH 08/16] Add alias to primary, secondary and tertiary color --- discord/guild.py | 65 ++++++++++++++++------------ discord/role.py | 107 +++++++++++++++++++++++++++++++---------------- 2 files changed, 109 insertions(+), 63 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index 82a3f3ce0ad2..19159326907b 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -3648,9 +3648,9 @@ async def create_role( hoist: bool = ..., display_icon: Union[bytes, str] = MISSING, mentionable: bool = ..., - primary_color: Union[Colour, int, None] = ..., - secondary_color: Union[Colour, int, None] = ..., - tertiary_color: Union[Colour, int, None] = ..., + primary_colour: Union[Colour, int, None] = ..., + secondary_colour: Union[Colour, int, None] = ..., + tertiary_colour: Union[Colour, int, None] = ..., ) -> Role: ... @@ -3685,6 +3685,9 @@ async def create_role( primary_color: Union[Colour, int, None] = MISSING, secondary_color: Union[Colour, int, None] = MISSING, tertiary_color: Union[Colour, int, None] = MISSING, + primary_colour: Union[Colour, int, None] = MISSING, + secondary_colour: Union[Colour, int, None] = MISSING, + tertiary_colour: Union[Colour, int, None] = MISSING, ) -> Role: """|coro| @@ -3713,12 +3716,12 @@ async def create_role( colour: Union[:class:`Colour`, :class:`int`] The colour for the role. Defaults to :meth:`Colour.default`. This is aliased to ``color`` as well. - primary_color: Union[:class:`Colour`, :class:`int`, None] - The primary color for the role. If provided, must be an integer or :class:`Colour`. - secondary_color: Union[:class:`Colour`, :class:`int`, None] - The secondary color for the role. Requires ``primary_color`` to also be set. - tertiary_color: Union[:class:`Colour`, :class:`int`, None] - The tertiary color for the role. Can only be used for the holographic role preset, + primary_colour: Union[:class:`Colour`, :class:`int`, None] + The primary colour for the role. If provided, must be an integer or :class:`Colour`. + secondary_colour: Union[:class:`Colour`, :class:`int`, None] + The secondary colour for the role. + tertiary_colour: Union[:class:`Colour`, :class:`int`, None] + The tertiary colour for the role. Can only be used for the holographic role preset, which is ``(11127295, 16759788, 16761760)`` hoist: :class:`bool` Indicates if the role should be shown separately in the member list. @@ -3761,34 +3764,44 @@ async def create_role( fields['color'] = actual_colour.value solid_color_used = color is not MISSING or colour is not MISSING - colors_used = primary_color is not MISSING or secondary_color is not MISSING or tertiary_color is not MISSING + colors_used = ( + primary_color is not MISSING + or secondary_color is not MISSING + or tertiary_color is not MISSING + or primary_colour is not MISSING + or secondary_colour is not MISSING + or tertiary_colour is not MISSING + ) if solid_color_used and colors_used: raise TypeError( - "You must choose either only solid color (color/colour) or colors (primary_color/secondary_color/tertiary_color), not both." + "You must choose either only solid colour (color/colour) or colours (primary_colour/secondary_colour/tertiary_colour), not both." ) + actual_primary_colour = primary_colour or primary_color + actual_secondary_colour = secondary_colour or secondary_color + actual_tertiary_colour = tertiary_colour or tertiary_color colors_payload: Dict[str, Any] = {} - if primary_color is not MISSING: - if primary_color is None: + if actual_primary_colour is not MISSING: + if actual_primary_colour is None: colors_payload['primary_color'] = None - elif isinstance(primary_color, int): - colors_payload['primary_color'] = primary_color + elif isinstance(actual_primary_colour, int): + colors_payload['primary_color'] = actual_primary_colour else: - colors_payload['primary_color'] = primary_color.value - if secondary_color is not MISSING: - if secondary_color is None: + colors_payload['primary_color'] = actual_primary_colour.value + if actual_secondary_colour is not MISSING: + if actual_secondary_colour is None: colors_payload['secondary_color'] = None - elif isinstance(secondary_color, int): - colors_payload['secondary_color'] = secondary_color + elif isinstance(actual_secondary_colour, int): + colors_payload['secondary_color'] = actual_secondary_colour else: - colors_payload['secondary_color'] = secondary_color.value - if tertiary_color is not MISSING: - if tertiary_color is None: + colors_payload['secondary_color'] = actual_secondary_colour.value + if actual_tertiary_colour is not MISSING: + if actual_tertiary_colour is None: colors_payload['tertiary_color'] = None - elif isinstance(tertiary_color, int): - colors_payload['tertiary_color'] = tertiary_color + elif isinstance(actual_tertiary_colour, int): + colors_payload['tertiary_color'] = actual_tertiary_colour else: - colors_payload['tertiary_color'] = tertiary_color.value + colors_payload['tertiary_color'] = actual_tertiary_colour.value if colors_payload: fields['colors'] = colors_payload diff --git a/discord/role.py b/discord/role.py index 2940f2beea16..1ec337db6b59 100644 --- a/discord/role.py +++ b/discord/role.py @@ -222,9 +222,9 @@ class Role(Hashable): 'tags', '_flags', '_state', - '_primary_color', - '_secondary_color', - '_tertiary_color', + '_primary_colour', + '_secondary_colour', + '_tertiary_colour', ) def __init__(self, *, guild: Guild, state: ConnectionState, data: RolePayload): @@ -288,9 +288,9 @@ def _update(self, data: RolePayload): self.tags: Optional[RoleTags] self._flags: int = data.get('flags', 0) colors = data.get('colors', {}) - self._primary_color = colors.get('primary_color', None) - self._secondary_color = colors.get('secondary_color', None) - self._tertiary_color = colors.get('tertiary_color', None) + self._primary_colour = colors.get('primary_colour', None) + self._secondary_colour = colors.get('secondary_colour', None) + self._tertiary_colour = colors.get('tertiary_colour', None) try: self.tags = RoleTags(data['tags']) # pyright: ignore[reportTypedDictNotRequiredAccess] @@ -330,20 +330,35 @@ def is_assignable(self) -> bool: me = self.guild.me return not self.is_default() and not self.managed and (me.top_role > self or me.id == self.guild.owner_id) + @property + def primary_colour(self) -> Optional[Colour]: + """Optional[:class:`Colour`]: The role's primary colour.""" + return Colour(self._primary_colour) if self._primary_colour is not None else None + @property def primary_color(self) -> Optional[Colour]: - """Optional[:class:`Colour`]: The role's primary color.""" - return Colour(self._primary_color) if self._primary_color is not None else None + """Optional[:class:`Colour`]: Alias for :attr:`primary_colour`.""" + return self.primary_colour + + @property + def secondary_colour(self) -> Optional[Colour]: + """Optional[:class:`Colour`]: The role's secondary colour.""" + return Colour(self._secondary_colour) if self._secondary_colour is not None else None @property def secondary_color(self) -> Optional[Colour]: - """Optional[:class:`Colour`]: The role's secondary color.""" - return Colour(self._secondary_color) if self._secondary_color is not None else None + """Optional[:class:`Colour`]: Alias for :attr:`secondary_colour`.""" + return self.secondary_colour + + @property + def tertiary_colour(self) -> Optional[Colour]: + """Optional[:class:`Colour`]: The role's tertiary colour.""" + return Colour(self._tertiary_colour) if self._tertiary_colour is not None else None @property def tertiary_color(self) -> Optional[Colour]: - """Optional[:class:`Colour`]: The role's tertiary color.""" - return Colour(self._tertiary_color) if self._tertiary_color is not None else None + """Optional[:class:`Colour`]: Alias for :attr:`tertiary_colour`.""" + return self.tertiary_colour @property def permissions(self) -> Permissions: @@ -450,6 +465,9 @@ async def edit( primary_color: Union[Colour, int, None] = MISSING, secondary_color: Union[Colour, int, None] = MISSING, tertiary_color: Union[Colour, int, None] = MISSING, + primary_colour: Union[Colour, int, None] = MISSING, + secondary_colour: Union[Colour, int, None] = MISSING, + tertiary_colour: Union[Colour, int, None] = MISSING, ) -> Optional[Role]: """|coro| @@ -480,6 +498,13 @@ async def edit( The new permissions to change to. colour: Union[:class:`Colour`, :class:`int`] The new colour to change to. (aliased to color as well) + primary_colour: Union[:class:`Colour`, :class:`int`, None] + The new primary colour for the role. If provided, must be an integer or :class:`Colour`. + secondary_colour: Union[:class:`Colour`, :class:`int`, None] + The new secondary colour for the role. + tertiary_colour: Union[:class:`Colour`, :class:`int`, None] + The new tertiary colour for the role. Can only be used for the holographic role preset, + which is ``(11127295, 16759788, 16761760)`` hoist: :class:`bool` Indicates if the role should be shown separately in the member list. display_icon: Optional[Union[:class:`bytes`, :class:`str`]] @@ -495,14 +520,6 @@ async def edit( position or it will fail. reason: Optional[:class:`str`] The reason for editing this role. Shows up on the audit log. - primary_color: Union[:class:`Colour`, :class:`int`, None] - The primary color for the role. If provided, must be an integer or :class:`Colour`. - secondary_color: Union[:class:`Colour`, :class:`int`, None] - The secondary color for the role. - tertiary_color: Union[:class:`Colour`, :class:`int`, None] - The tertiary_color color for the role. Used for holographic role. - The holographic preset is: - {"primary_color": 11127295, "secondary_color": 16759788, "tertiary_color": 16761760} Raises ------- @@ -553,34 +570,50 @@ async def edit( payload['mentionable'] = mentionable solid_color_used = color is not MISSING or colour is not MISSING - colors_used = primary_color is not MISSING or secondary_color is not MISSING or tertiary_color is not MISSING + colors_used = ( + primary_color is not MISSING + or secondary_color is not MISSING + or tertiary_color is not MISSING + or primary_colour is not MISSING + or secondary_colour is not MISSING + or tertiary_colour is not MISSING + ) if solid_color_used and colors_used: raise TypeError( - "You must choose either only solid color (color/colour) or colors (primary_color/secondary_color/tertiary_color), not both." + "You must choose either only solid colour (color/colour) or colours (primary_colour/secondary_colour/tertiary_colour), not both." ) - colors_payload: Dict[str, Any] = {} if primary_color is not MISSING: - if primary_color is None: + primary_colour = primary_color + + if secondary_color is not MISSING: + secondary_colour = secondary_color + + if tertiary_color is not MISSING: + tertiary_colour = tertiary_color + + colors_payload: Dict[str, Any] = {} + if primary_colour is not MISSING: + if primary_colour is None: colors_payload['primary_color'] = None - elif isinstance(primary_color, int): - colors_payload['primary_color'] = primary_color + elif isinstance(primary_colour, int): + colors_payload['primary_color'] = primary_colour else: - colors_payload['primary_color'] = primary_color.value - if secondary_color is not MISSING: - if secondary_color is None: + colors_payload['primary_color'] = primary_colour.value + if secondary_colour is not MISSING: + if secondary_colour is None: colors_payload['secondary_color'] = None - elif isinstance(secondary_color, int): - colors_payload['secondary_color'] = secondary_color + elif isinstance(secondary_colour, int): + colors_payload['secondary_color'] = secondary_colour else: - colors_payload['secondary_color'] = secondary_color.value - if tertiary_color is not MISSING: - if tertiary_color is None: + colors_payload['secondary_color'] = secondary_colour.value + if tertiary_colour is not MISSING: + if tertiary_colour is None: colors_payload['tertiary_color'] = None - elif isinstance(tertiary_color, int): - colors_payload['tertiary_color'] = tertiary_color + elif isinstance(tertiary_colour, int): + colors_payload['tertiary_color'] = tertiary_colour else: - colors_payload['tertiary_color'] = tertiary_color.value + colors_payload['tertiary_color'] = tertiary_colour.value if colors_payload: payload['colors'] = colors_payload From 8bcc0c7ad94515f90b483fff875f65c0d28cf2d6 Mon Sep 17 00:00:00 2001 From: Mak <99765898+makerze@users.noreply.github.com> Date: Mon, 23 Jun 2025 01:05:30 +0100 Subject: [PATCH 09/16] Update discord/guild.py Co-authored-by: DA344 <108473820+DA-344@users.noreply.github.com> --- discord/guild.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index 19159326907b..e3fdf386a149 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -3763,19 +3763,6 @@ async def create_role( else: fields['color'] = actual_colour.value - solid_color_used = color is not MISSING or colour is not MISSING - colors_used = ( - primary_color is not MISSING - or secondary_color is not MISSING - or tertiary_color is not MISSING - or primary_colour is not MISSING - or secondary_colour is not MISSING - or tertiary_colour is not MISSING - ) - if solid_color_used and colors_used: - raise TypeError( - "You must choose either only solid colour (color/colour) or colours (primary_colour/secondary_colour/tertiary_colour), not both." - ) actual_primary_colour = primary_colour or primary_color actual_secondary_colour = secondary_colour or secondary_color From 864437464d5f0b3301e47e5a39641eed9697501f Mon Sep 17 00:00:00 2001 From: Mak <99765898+makerze@users.noreply.github.com> Date: Mon, 23 Jun 2025 01:09:40 +0100 Subject: [PATCH 10/16] Apply suggestions from code review Co-authored-by: DA344 <108473820+DA-344@users.noreply.github.com> --- discord/guild.py | 41 +++++++++++------------------------------ 1 file changed, 11 insertions(+), 30 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index e3fdf386a149..3cfbce662dab 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -3648,7 +3648,6 @@ async def create_role( hoist: bool = ..., display_icon: Union[bytes, str] = MISSING, mentionable: bool = ..., - primary_colour: Union[Colour, int, None] = ..., secondary_colour: Union[Colour, int, None] = ..., tertiary_colour: Union[Colour, int, None] = ..., ) -> Role: @@ -3665,7 +3664,6 @@ async def create_role( hoist: bool = ..., display_icon: Union[bytes, str] = MISSING, mentionable: bool = ..., - primary_color: Union[Colour, int, None] = ..., secondary_color: Union[Colour, int, None] = ..., tertiary_color: Union[Colour, int, None] = ..., ) -> Role: @@ -3682,10 +3680,8 @@ async def create_role( display_icon: Union[bytes, str] = MISSING, mentionable: bool = MISSING, reason: Optional[str] = None, - primary_color: Union[Colour, int, None] = MISSING, secondary_color: Union[Colour, int, None] = MISSING, tertiary_color: Union[Colour, int, None] = MISSING, - primary_colour: Union[Colour, int, None] = MISSING, secondary_colour: Union[Colour, int, None] = MISSING, tertiary_colour: Union[Colour, int, None] = MISSING, ) -> Role: @@ -3716,8 +3712,6 @@ async def create_role( colour: Union[:class:`Colour`, :class:`int`] The colour for the role. Defaults to :meth:`Colour.default`. This is aliased to ``color`` as well. - primary_colour: Union[:class:`Colour`, :class:`int`, None] - The primary colour for the role. If provided, must be an integer or :class:`Colour`. secondary_colour: Union[:class:`Colour`, :class:`int`, None] The secondary colour for the role. tertiary_colour: Union[:class:`Colour`, :class:`int`, None] @@ -3764,41 +3758,28 @@ async def create_role( fields['color'] = actual_colour.value - actual_primary_colour = primary_colour or primary_color actual_secondary_colour = secondary_colour or secondary_color actual_tertiary_colour = tertiary_colour or tertiary_color - colors_payload: Dict[str, Any] = {} - if actual_primary_colour is not MISSING: - if actual_primary_colour is None: - colors_payload['primary_color'] = None - elif isinstance(actual_primary_colour, int): - colors_payload['primary_color'] = actual_primary_colour - else: - colors_payload['primary_color'] = actual_primary_colour.value + colours = { + 'primary_color': fields['color'], + } + if actual_secondary_colour is not MISSING: if actual_secondary_colour is None: - colors_payload['secondary_color'] = None + colours['secondary_color'] = None elif isinstance(actual_secondary_colour, int): - colors_payload['secondary_color'] = actual_secondary_colour + colours['secondary_color'] = actual_secondary_colour else: - colors_payload['secondary_color'] = actual_secondary_colour.value + colours['secondary_color'] = actual_secondary_colour.value if actual_tertiary_colour is not MISSING: if actual_tertiary_colour is None: - colors_payload['tertiary_color'] = None + colours['tertiary_color'] = None elif isinstance(actual_tertiary_colour, int): - colors_payload['tertiary_color'] = actual_tertiary_colour + colours['tertiary_color'] = actual_tertiary_colour else: - colors_payload['tertiary_color'] = actual_tertiary_colour.value + colours['tertiary_color'] = actual_tertiary_colour.value - if colors_payload: - fields['colors'] = colors_payload - - if not colors_payload: - actual_colour = colour or color or Colour.default() - if isinstance(actual_colour, int): - fields['color'] = actual_colour - else: - fields['color'] = actual_colour.value + fields['colors'] = colours if hoist is not MISSING: fields['hoist'] = hoist From 8b5895cffc370d918175413bdf69b0fc6235685f Mon Sep 17 00:00:00 2001 From: Mak <99765898+makerze@users.noreply.github.com> Date: Mon, 23 Jun 2025 01:15:27 +0100 Subject: [PATCH 11/16] Apply suggestions from code review Co-authored-by: DA344 <108473820+DA-344@users.noreply.github.com> --- discord/role.py | 66 +++++++++++++------------------------------------ 1 file changed, 17 insertions(+), 49 deletions(-) diff --git a/discord/role.py b/discord/role.py index 1ec337db6b59..9a119a79af23 100644 --- a/discord/role.py +++ b/discord/role.py @@ -462,10 +462,8 @@ async def edit( mentionable: bool = MISSING, position: int = MISSING, reason: Optional[str] = MISSING, - primary_color: Union[Colour, int, None] = MISSING, secondary_color: Union[Colour, int, None] = MISSING, tertiary_color: Union[Colour, int, None] = MISSING, - primary_colour: Union[Colour, int, None] = MISSING, secondary_colour: Union[Colour, int, None] = MISSING, tertiary_colour: Union[Colour, int, None] = MISSING, ) -> Optional[Role]: @@ -498,8 +496,6 @@ async def edit( The new permissions to change to. colour: Union[:class:`Colour`, :class:`int`] The new colour to change to. (aliased to color as well) - primary_colour: Union[:class:`Colour`, :class:`int`, None] - The new primary colour for the role. If provided, must be an integer or :class:`Colour`. secondary_colour: Union[:class:`Colour`, :class:`int`, None] The new secondary colour for the role. tertiary_colour: Union[:class:`Colour`, :class:`int`, None] @@ -569,54 +565,26 @@ async def edit( if mentionable is not MISSING: payload['mentionable'] = mentionable - solid_color_used = color is not MISSING or colour is not MISSING - colors_used = ( - primary_color is not MISSING - or secondary_color is not MISSING - or tertiary_color is not MISSING - or primary_colour is not MISSING - or secondary_colour is not MISSING - or tertiary_colour is not MISSING - ) - if solid_color_used and colors_used: - raise TypeError( - "You must choose either only solid colour (color/colour) or colours (primary_colour/secondary_colour/tertiary_colour), not both." - ) - - if primary_color is not MISSING: - primary_colour = primary_color - - if secondary_color is not MISSING: - secondary_colour = secondary_color - - if tertiary_color is not MISSING: - tertiary_colour = tertiary_color - - colors_payload: Dict[str, Any] = {} - if primary_colour is not MISSING: - if primary_colour is None: - colors_payload['primary_color'] = None - elif isinstance(primary_colour, int): - colors_payload['primary_color'] = primary_colour - else: - colors_payload['primary_color'] = primary_colour.value - if secondary_colour is not MISSING: - if secondary_colour is None: - colors_payload['secondary_color'] = None - elif isinstance(secondary_colour, int): - colors_payload['secondary_color'] = secondary_colour + colours = { + 'primary_color': payload['color'], + } + + if actual_secondary_colour is not MISSING: + if actual_secondary_colour is None: + colours['secondary_color'] = None + elif isinstance(actual_secondary_colour, int): + colours['secondary_color'] = actual_secondary_colour else: - colors_payload['secondary_color'] = secondary_colour.value - if tertiary_colour is not MISSING: - if tertiary_colour is None: - colors_payload['tertiary_color'] = None - elif isinstance(tertiary_colour, int): - colors_payload['tertiary_color'] = tertiary_colour + colours['secondary_color'] = actual_secondary_colour.value + if actual_tertiary_colour is not MISSING: + if actual_tertiary_colour is None: + colours['tertiary_color'] = None + elif isinstance(actual_tertiary_colour, int): + colours['tertiary_color'] = actual_tertiary_colour else: - colors_payload['tertiary_color'] = tertiary_colour.value - if colors_payload: - payload['colors'] = colors_payload + colours['tertiary_color'] = actual_tertiary_colour.value + payload['colors'] = colours data = await self._state.http.edit_role(self.guild.id, self.id, reason=reason, **payload) return Role(guild=self.guild, data=data, state=self._state) From ee963d05a682ce58d7a995dc558527791717e7fb Mon Sep 17 00:00:00 2001 From: makerze <99765898+makerze@users.noreply.github.com> Date: Mon, 23 Jun 2025 02:11:00 +0100 Subject: [PATCH 12/16] Fix: color=primary_color and add types --- discord/guild.py | 16 ++++++---------- discord/role.py | 17 ++++------------- discord/types/role.py | 2 ++ 3 files changed, 12 insertions(+), 23 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index 3cfbce662dab..7d9e7a4734e2 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -3757,24 +3757,20 @@ async def create_role( else: fields['color'] = actual_colour.value - - actual_secondary_colour = secondary_colour or secondary_color + actual_secondary_colour = secondary_colour or secondary_color or Colour.default() actual_tertiary_colour = tertiary_colour or tertiary_color colours = { 'primary_color': fields['color'], } - + if actual_secondary_colour is not MISSING: - if actual_secondary_colour is None: - colours['secondary_color'] = None - elif isinstance(actual_secondary_colour, int): + if isinstance(actual_secondary_colour, int): colours['secondary_color'] = actual_secondary_colour else: colours['secondary_color'] = actual_secondary_colour.value - if actual_tertiary_colour is not MISSING: - if actual_tertiary_colour is None: - colours['tertiary_color'] = None - elif isinstance(actual_tertiary_colour, int): + + if actual_tertiary_colour is not MISSING and actual_tertiary_colour is not None: + if isinstance(actual_tertiary_colour, int): colours['tertiary_color'] = actual_tertiary_colour else: colours['tertiary_color'] = actual_tertiary_colour.value diff --git a/discord/role.py b/discord/role.py index 9a119a79af23..ed0bdf4307b1 100644 --- a/discord/role.py +++ b/discord/role.py @@ -222,7 +222,6 @@ class Role(Hashable): 'tags', '_flags', '_state', - '_primary_colour', '_secondary_colour', '_tertiary_colour', ) @@ -288,7 +287,6 @@ def _update(self, data: RolePayload): self.tags: Optional[RoleTags] self._flags: int = data.get('flags', 0) colors = data.get('colors', {}) - self._primary_colour = colors.get('primary_colour', None) self._secondary_colour = colors.get('secondary_colour', None) self._tertiary_colour = colors.get('tertiary_colour', None) @@ -330,16 +328,6 @@ def is_assignable(self) -> bool: me = self.guild.me return not self.is_default() and not self.managed and (me.top_role > self or me.id == self.guild.owner_id) - @property - def primary_colour(self) -> Optional[Colour]: - """Optional[:class:`Colour`]: The role's primary colour.""" - return Colour(self._primary_colour) if self._primary_colour is not None else None - - @property - def primary_color(self) -> Optional[Colour]: - """Optional[:class:`Colour`]: Alias for :attr:`primary_colour`.""" - return self.primary_colour - @property def secondary_colour(self) -> Optional[Colour]: """Optional[:class:`Colour`]: The role's secondary colour.""" @@ -568,7 +556,10 @@ async def edit( colours = { 'primary_color': payload['color'], } - + + actual_secondary_colour = secondary_colour or secondary_color + actual_tertiary_colour = tertiary_colour or tertiary_color + if actual_secondary_colour is not MISSING: if actual_secondary_colour is None: colours['secondary_color'] = None diff --git a/discord/types/role.py b/discord/types/role.py index d32de88032de..9614aa07a19e 100644 --- a/discord/types/role.py +++ b/discord/types/role.py @@ -34,6 +34,8 @@ class Role(TypedDict): id: Snowflake name: str color: int + secondary_color: NotRequired[Optional[int]] + tertiary_color: NotRequired[Optional[int]] hoist: bool position: int permissions: str From 95c4744a304297849e75dbfde6489b06d8976ae8 Mon Sep 17 00:00:00 2001 From: makerze <99765898+makerze@users.noreply.github.com> Date: Thu, 26 Jun 2025 19:29:40 +0100 Subject: [PATCH 13/16] Fix: types and broken none --- discord/guild.py | 35 ++++++++++++++++++++--------------- discord/role.py | 18 +++++++++--------- discord/types/guild.py | 1 + discord/types/role.py | 9 +++++++-- 4 files changed, 37 insertions(+), 26 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index 7d9e7a4734e2..ff6d53516add 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -3648,8 +3648,8 @@ async def create_role( hoist: bool = ..., display_icon: Union[bytes, str] = MISSING, mentionable: bool = ..., - secondary_colour: Union[Colour, int, None] = ..., - tertiary_colour: Union[Colour, int, None] = ..., + secondary_colour: Optional[Union[Colour, int]] = ..., + tertiary_colour: Optional[Union[Colour, int]] = ..., ) -> Role: ... @@ -3664,8 +3664,8 @@ async def create_role( hoist: bool = ..., display_icon: Union[bytes, str] = MISSING, mentionable: bool = ..., - secondary_color: Union[Colour, int, None] = ..., - tertiary_color: Union[Colour, int, None] = ..., + secondary_color: Optional[Union[Colour, int]] = ..., + tertiary_color: Optional[Union[Colour, int]] = ..., ) -> Role: ... @@ -3680,10 +3680,10 @@ async def create_role( display_icon: Union[bytes, str] = MISSING, mentionable: bool = MISSING, reason: Optional[str] = None, - secondary_color: Union[Colour, int, None] = MISSING, - tertiary_color: Union[Colour, int, None] = MISSING, - secondary_colour: Union[Colour, int, None] = MISSING, - tertiary_colour: Union[Colour, int, None] = MISSING, + secondary_color: Optional[Union[Colour, int]] = MISSING, + tertiary_color: Optional[Union[Colour, int]] = MISSING, + secondary_colour: Optional[Union[Colour, int]] = MISSING, + tertiary_colour: Optional[Union[Colour, int]] = MISSING, ) -> Role: """|coro| @@ -3712,9 +3712,9 @@ async def create_role( colour: Union[:class:`Colour`, :class:`int`] The colour for the role. Defaults to :meth:`Colour.default`. This is aliased to ``color`` as well. - secondary_colour: Union[:class:`Colour`, :class:`int`, None] + secondary_colour: Optional[Union[:class:`Colour`, :class:`int`]] The secondary colour for the role. - tertiary_colour: Union[:class:`Colour`, :class:`int`, None] + tertiary_colour: Optional[Union[:class:`Colour`, :class:`int`]] The tertiary colour for the role. Can only be used for the holographic role preset, which is ``(11127295, 16759788, 16761760)`` hoist: :class:`bool` @@ -3757,20 +3757,25 @@ async def create_role( else: fields['color'] = actual_colour.value - actual_secondary_colour = secondary_colour or secondary_color or Colour.default() + actual_secondary_colour = secondary_colour or secondary_color actual_tertiary_colour = tertiary_colour or tertiary_color - colours = { + + colours: Dict[str, Any] = { 'primary_color': fields['color'], } if actual_secondary_colour is not MISSING: - if isinstance(actual_secondary_colour, int): + if actual_secondary_colour is None: + colours['secondary_color'] = None + elif isinstance(actual_secondary_colour, int): colours['secondary_color'] = actual_secondary_colour else: colours['secondary_color'] = actual_secondary_colour.value - if actual_tertiary_colour is not MISSING and actual_tertiary_colour is not None: - if isinstance(actual_tertiary_colour, int): + if actual_tertiary_colour is not MISSING: + if actual_tertiary_colour is None: + colours['tertiary_color'] = None + elif isinstance(actual_tertiary_colour, int): colours['tertiary_color'] = actual_tertiary_colour else: colours['tertiary_color'] = actual_tertiary_colour.value diff --git a/discord/role.py b/discord/role.py index ed0bdf4307b1..7f51782c5488 100644 --- a/discord/role.py +++ b/discord/role.py @@ -287,8 +287,8 @@ def _update(self, data: RolePayload): self.tags: Optional[RoleTags] self._flags: int = data.get('flags', 0) colors = data.get('colors', {}) - self._secondary_colour = colors.get('secondary_colour', None) - self._tertiary_colour = colors.get('tertiary_colour', None) + self._secondary_colour = colors.get('secondary_color', None) + self._tertiary_colour = colors.get('tertiary_color', None) try: self.tags = RoleTags(data['tags']) # pyright: ignore[reportTypedDictNotRequiredAccess] @@ -450,10 +450,10 @@ async def edit( mentionable: bool = MISSING, position: int = MISSING, reason: Optional[str] = MISSING, - secondary_color: Union[Colour, int, None] = MISSING, - tertiary_color: Union[Colour, int, None] = MISSING, - secondary_colour: Union[Colour, int, None] = MISSING, - tertiary_colour: Union[Colour, int, None] = MISSING, + secondary_color: Optional[Union[Colour, int]] = MISSING, + tertiary_color: Optional[Union[Colour, int]] = MISSING, + secondary_colour: Optional[Union[Colour, int]] = MISSING, + tertiary_colour: Optional[Union[Colour, int]] = MISSING, ) -> Optional[Role]: """|coro| @@ -484,9 +484,9 @@ async def edit( The new permissions to change to. colour: Union[:class:`Colour`, :class:`int`] The new colour to change to. (aliased to color as well) - secondary_colour: Union[:class:`Colour`, :class:`int`, None] + secondary_colour: Optional[Union[:class:`Colour`, :class:`int`]] The new secondary colour for the role. - tertiary_colour: Union[:class:`Colour`, :class:`int`, None] + tertiary_colour: Optional[Union[:class:`Colour`, :class:`int`]] The new tertiary colour for the role. Can only be used for the holographic role preset, which is ``(11127295, 16759788, 16761760)`` hoist: :class:`bool` @@ -553,7 +553,7 @@ async def edit( if mentionable is not MISSING: payload['mentionable'] = mentionable - colours = { + colours: Dict[str, Any] = { 'primary_color': payload['color'], } diff --git a/discord/types/guild.py b/discord/types/guild.py index 7ac90b89ea53..0e328fed23f5 100644 --- a/discord/types/guild.py +++ b/discord/types/guild.py @@ -90,6 +90,7 @@ class IncidentData(TypedDict): 'VERIFIED', 'VIP_REGIONS', 'WELCOME_SCREEN_ENABLED', + 'ENHANCED_ROLE_COLORS', 'RAID_ALERTS_DISABLED', 'SOUNDBOARD', 'MORE_SOUNDBOARD', diff --git a/discord/types/role.py b/discord/types/role.py index 9614aa07a19e..dabd1c1cfc30 100644 --- a/discord/types/role.py +++ b/discord/types/role.py @@ -30,12 +30,17 @@ from .snowflake import Snowflake +class RoleColours(TypedDict): + primary_color: int + secondary_color: Optional[int] + tertiary_color: Optional[int] + + class Role(TypedDict): id: Snowflake name: str color: int - secondary_color: NotRequired[Optional[int]] - tertiary_color: NotRequired[Optional[int]] + colors: RoleColours hoist: bool position: int permissions: str From 98e36682149fa4abd56fa837f8ff3433e240228f Mon Sep 17 00:00:00 2001 From: makerze <99765898+makerze@users.noreply.github.com> Date: Sun, 6 Jul 2025 02:07:36 +0100 Subject: [PATCH 14/16] refactor: Colour assigns only primary_color and added version strings --- discord/guild.py | 17 +++++++++++------ discord/role.py | 41 +++++++++++++++++++++++++++-------------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index ff6d53516add..1d3679ad3db2 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -3703,6 +3703,13 @@ async def create_role( This function will now raise :exc:`TypeError` instead of ``InvalidArgument``. + .. versionadded:: 2.6 + The ``secondary_color``, ``tertiary_color``, ``secondary_colour``, and ``tertiary_colour`` keyword-only parameters were added. + + .. versionchanged:: 2.6 + The ``colour`` and ``color`` parameters now set the role's primary color. + + Parameters ----------- name: :class:`str` @@ -3751,19 +3758,17 @@ async def create_role( else: fields['permissions'] = '0' + colours: Dict[str, Any] = {} + actual_colour = colour or color or Colour.default() if isinstance(actual_colour, int): - fields['color'] = actual_colour + colours['primary_color'] = actual_colour else: - fields['color'] = actual_colour.value + colours['primary_color'] = actual_colour.value actual_secondary_colour = secondary_colour or secondary_color actual_tertiary_colour = tertiary_colour or tertiary_color - colours: Dict[str, Any] = { - 'primary_color': fields['color'], - } - if actual_secondary_colour is not MISSING: if actual_secondary_colour is None: colours['secondary_color'] = None diff --git a/discord/role.py b/discord/role.py index 7f51782c5488..881864ad8bdb 100644 --- a/discord/role.py +++ b/discord/role.py @@ -275,10 +275,11 @@ def __ge__(self, other: object) -> bool: return not r def _update(self, data: RolePayload): + colors = data.get('colors', {}) self.name: str = data['name'] self._permissions: int = int(data.get('permissions', 0)) self.position: int = data.get('position', 0) - self._colour: int = data.get('color', 0) + self._colour: int = colors.get('primary_color', 0) self.hoist: bool = data.get('hoist', False) self._icon: Optional[str] = data.get('icon') self.unicode_emoji: Optional[str] = data.get('unicode_emoji') @@ -286,7 +287,6 @@ def _update(self, data: RolePayload): self.mentionable: bool = data.get('mentionable', False) self.tags: Optional[RoleTags] self._flags: int = data.get('flags', 0) - colors = data.get('colors', {}) self._secondary_colour = colors.get('secondary_color', None) self._tertiary_colour = colors.get('tertiary_color', None) @@ -330,22 +330,30 @@ def is_assignable(self) -> bool: @property def secondary_colour(self) -> Optional[Colour]: - """Optional[:class:`Colour`]: The role's secondary colour.""" + """Optional[:class:`Colour`]: The role's secondary colour. + .. versionadded:: 2.6 + """ return Colour(self._secondary_colour) if self._secondary_colour is not None else None @property def secondary_color(self) -> Optional[Colour]: - """Optional[:class:`Colour`]: Alias for :attr:`secondary_colour`.""" + """Optional[:class:`Colour`]: Alias for :attr:`secondary_colour`. + .. versionadded:: 2.6 + """ return self.secondary_colour @property def tertiary_colour(self) -> Optional[Colour]: - """Optional[:class:`Colour`]: The role's tertiary colour.""" + """Optional[:class:`Colour`]: The role's tertiary colour. + .. versionadded:: 2.6 + """ return Colour(self._tertiary_colour) if self._tertiary_colour is not None else None @property def tertiary_color(self) -> Optional[Colour]: - """Optional[:class:`Colour`]: Alias for :attr:`tertiary_colour`.""" + """Optional[:class:`Colour`]: Alias for :attr:`tertiary_colour`. + .. versionadded:: 2.6 + """ return self.tertiary_colour @property @@ -355,12 +363,12 @@ def permissions(self) -> Permissions: @property def colour(self) -> Colour: - """:class:`Colour`: Returns the role colour. An alias exists under ``color``.""" + """:class:`Colour`: Returns the role's primary colour. An alias exists under ``color``.""" return Colour(self._colour) @property def color(self) -> Colour: - """:class:`Colour`: Returns the role color. An alias exists under ``colour``.""" + """:class:`Colour`: Returns the role's primary colour. An alias exists under ``colour``.""" return self.colour @property @@ -476,6 +484,12 @@ async def edit( This function will now raise :exc:`ValueError` instead of ``InvalidArgument``. + .. versionadded:: 2.6 + The ``secondary_color``, ``tertiary_color``, ``secondary_colour``, and ``tertiary_colour`` keyword-only parameters were added. + + .. versionchanged:: 2.6 + The ``colour`` and ``color`` parameters now set the role's primary color. + Parameters ----------- name: :class:`str` @@ -524,14 +538,17 @@ async def edit( await self._move(position, reason=reason) payload: Dict[str, Any] = {} + + colours: Dict[str, Any] = {} + if color is not MISSING: colour = color if colour is not MISSING: if isinstance(colour, int): - payload['color'] = colour + colours['primary_color'] = colour else: - payload['color'] = colour.value + colours['primary_color'] = colour.value if name is not MISSING: payload['name'] = name @@ -553,10 +570,6 @@ async def edit( if mentionable is not MISSING: payload['mentionable'] = mentionable - colours: Dict[str, Any] = { - 'primary_color': payload['color'], - } - actual_secondary_colour = secondary_colour or secondary_color actual_tertiary_colour = tertiary_colour or tertiary_color From 343130ffce34b4f8670843c95b3fc8d2168ccade Mon Sep 17 00:00:00 2001 From: Mak <99765898+makerze@users.noreply.github.com> Date: Sun, 6 Jul 2025 02:46:20 +0100 Subject: [PATCH 15/16] Apply suggestions from code review Co-authored-by: DA344 <108473820+DA-344@users.noreply.github.com> --- discord/guild.py | 7 ++++--- discord/role.py | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index 1d3679ad3db2..b03dbbea6445 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -3703,9 +3703,6 @@ async def create_role( This function will now raise :exc:`TypeError` instead of ``InvalidArgument``. - .. versionadded:: 2.6 - The ``secondary_color``, ``tertiary_color``, ``secondary_colour``, and ``tertiary_colour`` keyword-only parameters were added. - .. versionchanged:: 2.6 The ``colour`` and ``color`` parameters now set the role's primary color. @@ -3721,9 +3718,13 @@ async def create_role( This is aliased to ``color`` as well. secondary_colour: Optional[Union[:class:`Colour`, :class:`int`]] The secondary colour for the role. + + .. versionadded:: 2.6 tertiary_colour: Optional[Union[:class:`Colour`, :class:`int`]] The tertiary colour for the role. Can only be used for the holographic role preset, which is ``(11127295, 16759788, 16761760)`` + + .. versionadded:: 2.6 hoist: :class:`bool` Indicates if the role should be shown separately in the member list. Defaults to ``False``. diff --git a/discord/role.py b/discord/role.py index 881864ad8bdb..81b4e8cd90b4 100644 --- a/discord/role.py +++ b/discord/role.py @@ -484,9 +484,6 @@ async def edit( This function will now raise :exc:`ValueError` instead of ``InvalidArgument``. - .. versionadded:: 2.6 - The ``secondary_color``, ``tertiary_color``, ``secondary_colour``, and ``tertiary_colour`` keyword-only parameters were added. - .. versionchanged:: 2.6 The ``colour`` and ``color`` parameters now set the role's primary color. @@ -500,9 +497,13 @@ async def edit( The new colour to change to. (aliased to color as well) secondary_colour: Optional[Union[:class:`Colour`, :class:`int`]] The new secondary colour for the role. + + .. versionadded:: 2.6 tertiary_colour: Optional[Union[:class:`Colour`, :class:`int`]] The new tertiary colour for the role. Can only be used for the holographic role preset, which is ``(11127295, 16759788, 16761760)`` + + .. versionadded:: 2.6 hoist: :class:`bool` Indicates if the role should be shown separately in the member list. display_icon: Optional[Union[:class:`bytes`, :class:`str`]] From 70b5a1799675f469bc32c4015a20f66118124874 Mon Sep 17 00:00:00 2001 From: Mak <99765898+makerze@users.noreply.github.com> Date: Mon, 7 Jul 2025 10:32:12 +0100 Subject: [PATCH 16/16] Update discord/role.py Co-authored-by: Danny <1695103+Rapptz@users.noreply.github.com> --- discord/role.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/discord/role.py b/discord/role.py index 81b4e8cd90b4..acb112519516 100644 --- a/discord/role.py +++ b/discord/role.py @@ -589,7 +589,8 @@ async def edit( else: colours['tertiary_color'] = actual_tertiary_colour.value - payload['colors'] = colours + if colours: + payload['colors'] = colours data = await self._state.http.edit_role(self.guild.id, self.id, reason=reason, **payload) return Role(guild=self.guild, data=data, state=self._state)