From cb7300990f656c0964ea48115354f9416e96dcd1 Mon Sep 17 00:00:00 2001 From: Mak <99765898+makerze@users.noreply.github.com> Date: Tue, 8 Jul 2025 04:07:56 +0100 Subject: [PATCH] Add role parameters to support new gradient and holographic roles Co-authored-by: dolfies Co-authored-by: DA344 <108473820+DA-344@users.noreply.github.com> Co-authored-by: Danny <1695103+Rapptz@users.noreply.github.com> --- discord/guild.py | 48 +++++++++++++++++++++++-- discord/http.py | 2 +- discord/role.py | 82 +++++++++++++++++++++++++++++++++++++++--- discord/types/guild.py | 1 + discord/types/role.py | 7 ++++ 5 files changed, 132 insertions(+), 8 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index 6b8e8814e..b03dbbea6 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -3648,6 +3648,8 @@ class Guild(Hashable): hoist: bool = ..., display_icon: Union[bytes, str] = MISSING, mentionable: bool = ..., + secondary_colour: Optional[Union[Colour, int]] = ..., + tertiary_colour: Optional[Union[Colour, int]] = ..., ) -> Role: ... @@ -3662,6 +3664,8 @@ class Guild(Hashable): hoist: bool = ..., display_icon: Union[bytes, str] = MISSING, mentionable: bool = ..., + secondary_color: Optional[Union[Colour, int]] = ..., + tertiary_color: Optional[Union[Colour, int]] = ..., ) -> Role: ... @@ -3676,6 +3680,10 @@ class Guild(Hashable): display_icon: Union[bytes, str] = MISSING, mentionable: bool = MISSING, reason: Optional[str] = None, + 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| @@ -3695,6 +3703,10 @@ class Guild(Hashable): This function will now raise :exc:`TypeError` instead of ``InvalidArgument``. + .. versionchanged:: 2.6 + The ``colour`` and ``color`` parameters now set the role's primary color. + + Parameters ----------- name: :class:`str` @@ -3704,6 +3716,15 @@ class Guild(Hashable): 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: 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``. @@ -3738,11 +3759,34 @@ class Guild(Hashable): 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 + + 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: + 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: + colours['tertiary_color'] = actual_tertiary_colour.value + + fields['colors'] = colours if hoist is not MISSING: fields['hoist'] = hoist diff --git a/discord/http.py b/discord/http.py index 71912f71b..02fd1e136 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1897,7 +1897,7 @@ class HTTPClient: 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 d7fe1e08b..acb112519 100644 --- a/discord/role.py +++ b/discord/role.py @@ -222,6 +222,8 @@ class Role(Hashable): 'tags', '_flags', '_state', + '_secondary_colour', + '_tertiary_colour', ) def __init__(self, *, guild: Guild, state: ConnectionState, data: RolePayload): @@ -273,10 +275,11 @@ class Role(Hashable): 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') @@ -284,6 +287,8 @@ class Role(Hashable): self.mentionable: bool = data.get('mentionable', False) self.tags: Optional[RoleTags] self._flags: int = data.get('flags', 0) + 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] @@ -323,6 +328,34 @@ class Role(Hashable): 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 secondary_colour(self) -> Optional[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`. + .. versionadded:: 2.6 + """ + return self.secondary_colour + + @property + def tertiary_colour(self) -> Optional[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`. + .. versionadded:: 2.6 + """ + return self.tertiary_colour + @property def permissions(self) -> Permissions: """:class:`Permissions`: Returns the role's permissions.""" @@ -330,12 +363,12 @@ class Role(Hashable): @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 @@ -425,6 +458,10 @@ class Role(Hashable): mentionable: bool = MISSING, position: int = MISSING, reason: Optional[str] = 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| @@ -447,6 +484,9 @@ class Role(Hashable): This function will now raise :exc:`ValueError` instead of ``InvalidArgument``. + .. versionchanged:: 2.6 + The ``colour`` and ``color`` parameters now set the role's primary color. + Parameters ----------- name: :class:`str` @@ -455,6 +495,15 @@ class Role(Hashable): 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: 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`]] @@ -490,14 +539,17 @@ class Role(Hashable): 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 @@ -519,6 +571,26 @@ class Role(Hashable): if mentionable is not MISSING: payload['mentionable'] = mentionable + 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 + 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: + 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 + + 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) diff --git a/discord/types/guild.py b/discord/types/guild.py index 7ac90b89e..0e328fed2 100644 --- a/discord/types/guild.py +++ b/discord/types/guild.py @@ -90,6 +90,7 @@ GuildFeature = Literal[ '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 d32de8803..dabd1c1cf 100644 --- a/discord/types/role.py +++ b/discord/types/role.py @@ -30,10 +30,17 @@ from typing_extensions import NotRequired 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 + colors: RoleColours hoist: bool position: int permissions: str