Skip to content

Commit 975a0d9

Browse files
authored
feat: ✨ Add support for role gradient colors in Role (#2818)
* ✨ Add support for enhanced role colors in `Role` * ✨ add RoleColours support to `Role.edit` * ✨ add RoleColours support to role creation in `Guild` and define default colors in `RoleColours` * ✨ add support for RoleColours in role creation and editing * ✨ update role attributes to use RoleColours and enhance color properties * 📝 CHANGELOG.md * 📝 add documentation for RoleColours and its attributes * ✏️ fix version annotation for primary color method in role.py * ✨ add holographic role support in RoleColours * 📝 update tertiary color documentation to specify allowed value * ✨ Finish implementing holographic support * 🗑️ Add deprecation warnings for singular colour properties in Role and Guild
1 parent bda1dfe commit 975a0d9

File tree

7 files changed

+198
-14
lines changed

7 files changed

+198
-14
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ These changes are available on the `master` branch, but have not yet been releas
6363
([#2775](https://github.com/Pycord-Development/pycord/pull/2775))
6464
- Added `discord.Interaction.created_at`.
6565
([#2801](https://github.com/Pycord-Development/pycord/pull/2801))
66+
- Added role gradients support with `Role.colours` and the `RoleColours` class.
67+
([#2818](https://github.com/Pycord-Development/pycord/pull/2818))
6668

6769
### Fixed
6870

discord/guild.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@
7979
from .monetization import Entitlement
8080
from .onboarding import Onboarding
8181
from .permissions import PermissionOverwrite
82-
from .role import Role
82+
from .role import Role, RoleColours
8383
from .scheduled_events import ScheduledEvent, ScheduledEventLocation
8484
from .stage_instance import StageInstance
8585
from .sticker import GuildSticker
@@ -2916,6 +2916,8 @@ async def create_role(
29162916
name: str = ...,
29172917
permissions: Permissions = ...,
29182918
colour: Colour | int = ...,
2919+
colours: RoleColours = ...,
2920+
holographic: bool = ...,
29192921
hoist: bool = ...,
29202922
mentionable: bool = ...,
29212923
icon: bytes | None = MISSING,
@@ -2930,6 +2932,8 @@ async def create_role(
29302932
name: str = ...,
29312933
permissions: Permissions = ...,
29322934
color: Colour | int = ...,
2935+
colors: RoleColours = ...,
2936+
holographic: bool = ...,
29332937
hoist: bool = ...,
29342938
mentionable: bool = ...,
29352939
icon: bytes | None = ...,
@@ -2943,6 +2947,9 @@ async def create_role(
29432947
permissions: Permissions = MISSING,
29442948
color: Colour | int = MISSING,
29452949
colour: Colour | int = MISSING,
2950+
colors: RoleColours = MISSING,
2951+
colours: RoleColours = MISSING,
2952+
holographic: bool = MISSING,
29462953
hoist: bool = MISSING,
29472954
mentionable: bool = MISSING,
29482955
reason: str | None = None,
@@ -3006,11 +3013,30 @@ async def create_role(
30063013
else:
30073014
fields["permissions"] = "0"
30083015

3009-
actual_colour = colour or color or Colour.default()
3016+
actual_colour = colour if colour not in (MISSING, None) else color
3017+
30103018
if isinstance(actual_colour, int):
3011-
fields["color"] = actual_colour
3019+
actual_colour = Colour(actual_colour)
3020+
3021+
if actual_colour not in (MISSING, None):
3022+
utils.warn_deprecated("colour", "colours", "2.7")
3023+
actual_colours = RoleColours(primary=actual_colour)
3024+
elif holographic:
3025+
actual_colours = RoleColours.holographic()
3026+
else:
3027+
actual_colours = colours or colors or RoleColours.default()
3028+
3029+
if isinstance(actual_colours, RoleColours):
3030+
if "ENHANCED_ROLE_COLORS" not in self.features:
3031+
actual_colours.secondary = None
3032+
actual_colours.tertiary = None
3033+
fields["colors"] = actual_colours._to_dict()
30123034
else:
3013-
fields["color"] = actual_colour.value
3035+
raise InvalidArgument(
3036+
"colours parameter must be of type RoleColours, not {0.__class__.__name__}".format(
3037+
actual_colours
3038+
)
3039+
)
30143040

30153041
if hoist is not MISSING:
30163042
fields["hoist"] = hoist

discord/http.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2163,6 +2163,7 @@ def edit_role(
21632163
"name",
21642164
"permissions",
21652165
"color",
2166+
"colors",
21662167
"hoist",
21672168
"mentionable",
21682169
"icon",

discord/role.py

Lines changed: 152 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,25 @@
2727

2828
from typing import TYPE_CHECKING, Any, TypeVar
2929

30+
from typing_extensions import Self
31+
3032
from .asset import Asset
3133
from .colour import Colour
3234
from .errors import InvalidArgument
3335
from .flags import RoleFlags
3436
from .mixins import Hashable
3537
from .permissions import Permissions
36-
from .utils import MISSING, _bytes_to_base64_data, _get_as_snowflake, snowflake_time
37-
38-
__all__ = (
39-
"RoleTags",
40-
"Role",
38+
from .utils import (
39+
MISSING,
40+
_bytes_to_base64_data,
41+
_get_as_snowflake,
42+
deprecated,
43+
snowflake_time,
44+
warn_deprecated,
4145
)
4246

47+
__all__ = ("RoleTags", "Role", "RoleColours")
48+
4349
if TYPE_CHECKING:
4450
import datetime
4551

@@ -48,6 +54,7 @@
4854
from .state import ConnectionState
4955
from .types.guild import RolePositionUpdate
5056
from .types.role import Role as RolePayload
57+
from .types.role import RoleColours as RoleColoursPayload
5158
from .types.role import RoleTags as RoleTagPayload
5259

5360

@@ -149,6 +156,96 @@ def __repr__(self) -> str:
149156
R = TypeVar("R", bound="Role")
150157

151158

159+
class RoleColours:
160+
"""Represents a role's gradient colours.
161+
162+
.. versionadded:: 2.7
163+
164+
Attributes
165+
----------
166+
primary: :class:`Colour`
167+
The primary colour of the role.
168+
secondary: Optional[:class:`Colour`]
169+
The secondary colour of the role.
170+
tertiary: Optional[:class:`Colour`]
171+
The tertiary colour of the role. At the moment, only `16761760` is allowed.
172+
"""
173+
174+
def __init__(
175+
self,
176+
primary: Colour,
177+
secondary: Colour | None = None,
178+
tertiary: Colour | None = None,
179+
):
180+
"""Initialises a :class:`RoleColours` object.
181+
182+
.. versionadded:: 2.7
183+
184+
Parameters
185+
----------
186+
primary: :class:`Colour`
187+
The primary colour of the role.
188+
secondary: Optional[:class:`Colour`]
189+
The secondary colour of the role.
190+
tertiary: Optional[:class:`Colour`]
191+
The tertiary colour of the role.
192+
"""
193+
self.primary: Colour = primary
194+
self.secondary: Colour | None = secondary
195+
self.tertiary: Colour | None = tertiary
196+
197+
@classmethod
198+
def _from_payload(cls, data: RoleColoursPayload) -> Self:
199+
primary = Colour(data["primary_color"])
200+
secondary = (
201+
Colour(data["secondary_color"]) if data.get("secondary_color") else None
202+
)
203+
tertiary = (
204+
Colour(data["tertiary_color"]) if data.get("tertiary_color") else None
205+
)
206+
return cls(primary, secondary, tertiary)
207+
208+
def _to_dict(self) -> RoleColoursPayload:
209+
"""Converts the role colours to a dictionary."""
210+
return {
211+
"primary_color": self.primary.value,
212+
"secondary_color": self.secondary.value if self.secondary else None,
213+
"tertiary_color": self.tertiary.value if self.tertiary else None,
214+
} # type: ignore
215+
216+
@classmethod
217+
def default(cls) -> RoleColours:
218+
"""Returns a default :class:`RoleColours` object with no colours set."""
219+
return cls(Colour.default(), None, None)
220+
221+
@classmethod
222+
def holographic(cls) -> RoleColours:
223+
"""Returns a :class:`RoleColours` that makes the role look holographic.
224+
225+
Currently holographic roles are only supported with colours 11127295, 16759788, and 16761760.
226+
"""
227+
return cls(Colour(11127295), Colour(16759788), Colour(16761760))
228+
229+
@property
230+
def is_holographic(self) -> bool:
231+
"""Whether the role is holographic.
232+
233+
Currently roles are holographic when colours are set to 11127295, 16759788, and 16761760.
234+
"""
235+
return (
236+
self.primary.value == 11127295
237+
and self.secondary.value == 16759788
238+
and self.tertiary.value == 16761760
239+
)
240+
241+
def __repr__(self) -> str:
242+
return (
243+
f"<RoleColours primary={self.primary!r} "
244+
f"secondary={self.secondary!r} "
245+
f"tertiary={self.tertiary!r}>"
246+
)
247+
248+
152249
class Role(Hashable):
153250
"""Represents a Discord role in a :class:`Guild`.
154251
@@ -227,13 +324,19 @@ class Role(Hashable):
227324
Extra attributes of the role.
228325
229326
.. versionadded:: 2.6
327+
328+
colours: :class:`RoleColours`
329+
The role's colours.
330+
331+
.. versionadded:: 2.7
230332
"""
231333

232334
__slots__ = (
233335
"id",
234336
"name",
235337
"_permissions",
236338
"_colour",
339+
"colours",
237340
"position",
238341
"managed",
239342
"mentionable",
@@ -299,6 +402,7 @@ def _update(self, data: RolePayload):
299402
self._permissions: int = int(data.get("permissions", 0))
300403
self.position: int = data.get("position", 0)
301404
self._colour: int = data.get("color", 0)
405+
self.colours: RoleColours | None = RoleColours._from_payload(data["colors"])
302406
self.hoist: bool = data.get("hoist", False)
303407
self.managed: bool = data.get("managed", False)
304408
self.mentionable: bool = data.get("mentionable", False)
@@ -374,14 +478,32 @@ def permissions(self) -> Permissions:
374478
return Permissions(self._permissions)
375479

376480
@property
481+
@deprecated("colours.primary", "2.7")
377482
def colour(self) -> Colour:
378-
"""Returns the role colour. An alias exists under ``color``."""
379-
return Colour(self._colour)
483+
"""Returns the role colour. Equivalent to :attr:`colours.primary`.
484+
An alias exists under ``color``.
485+
486+
.. versionchanged:: 2.7
487+
"""
488+
return self.colours.primary
380489

381490
@property
491+
@deprecated("colors.primary", "2.7")
382492
def color(self) -> Colour:
383-
"""Returns the role color. An alias exists under ``colour``."""
384-
return self.colour
493+
"""Returns the role's primary color. Equivalent to :attr:`colors.primary`.
494+
An alias exists under ``colour``.
495+
496+
.. versionchanged:: 2.7
497+
"""
498+
return self.colours.primary
499+
500+
@property
501+
def colors(self) -> RoleColours:
502+
"""Returns the role's colours. Equivalent to :attr:`colours`.
503+
504+
.. versionadded:: 2.7
505+
"""
506+
return self.colours
385507

386508
@property
387509
def created_at(self) -> datetime.datetime:
@@ -452,6 +574,9 @@ async def edit(
452574
permissions: Permissions = MISSING,
453575
colour: Colour | int = MISSING,
454576
color: Colour | int = MISSING,
577+
colours: RoleColours | None = MISSING,
578+
colors: RoleColours | None = MISSING,
579+
holographic: bool = MISSING,
455580
hoist: bool = MISSING,
456581
mentionable: bool = MISSING,
457582
position: int = MISSING,
@@ -523,8 +648,25 @@ async def edit(
523648
if color is not MISSING:
524649
colour = color
525650

651+
if colors is not MISSING:
652+
colours = colors
653+
526654
if colour is not MISSING:
527-
payload["color"] = colour if isinstance(colour, int) else colour.value
655+
warn_deprecated("colour", "colours", "2.7")
656+
if isinstance(colour, int):
657+
colour = Colour(colour)
658+
colours = RoleColours(primary=colour)
659+
if holographic:
660+
colours = RoleColours.holographic()
661+
if colours is not MISSING:
662+
if not isinstance(colours, RoleColours):
663+
raise InvalidArgument("colours must be a RoleColours object")
664+
if "ENHANCED_ROLE_COLORS" not in self.guild.features:
665+
colours.secondary = None
666+
colours.tertiary = None
667+
668+
payload["colors"] = colours._to_dict()
669+
528670
if name is not MISSING:
529671
payload["name"] = name
530672

discord/types/guild.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ class UnavailableGuild(TypedDict):
101101
"VERIFIED",
102102
"VIP_REGIONS",
103103
"WELCOME_SCREEN_ENABLED",
104+
"ENHANCED_ROLE_COLORS",
104105
]
105106

106107

discord/types/role.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,18 @@
3030
from .snowflake import Snowflake
3131

3232

33+
class RoleColours(TypedDict):
34+
primary_color: int
35+
secondary_color: int | None
36+
tertiary_color: int | None
37+
38+
3339
class Role(TypedDict):
3440
tags: NotRequired[RoleTags]
3541
id: Snowflake
3642
name: str
3743
color: int
44+
colors: RoleColours
3845
hoist: bool
3946
position: int
4047
permissions: str

docs/api/models.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,11 @@ Role
221221
.. autoclass:: RoleTags()
222222
:members:
223223

224+
.. attributetable:: RoleColours
225+
226+
.. autoclass:: RoleColours
227+
:members:
228+
224229
Scheduled Event
225230
~~~~~~~~~~~~~~~
226231

0 commit comments

Comments
 (0)