Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ These changes are available on the `master` branch, but have not yet been releas
([#2747](https://github.com/Pycord-Development/pycord/pull/2747))
- Added `discord.Interaction.created_at`.
([#2801](https://github.com/Pycord-Development/pycord/pull/2801))
- Added role gradients support with `Role.colours` and the `RoleColours` class.
([#2818](https://github.com/Pycord-Development/pycord/pull/2818))

### Fixed

Expand Down
29 changes: 25 additions & 4 deletions discord/guild.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
from .monetization import Entitlement
from .onboarding import Onboarding
from .permissions import PermissionOverwrite
from .role import Role
from .role import Role, RoleColours
from .scheduled_events import ScheduledEvent, ScheduledEventLocation
from .stage_instance import StageInstance
from .sticker import GuildSticker
Expand Down Expand Up @@ -2881,6 +2881,7 @@ async def create_role(
name: str = ...,
permissions: Permissions = ...,
colour: Colour | int = ...,
colours: RoleColours = ...,
hoist: bool = ...,
mentionable: bool = ...,
icon: bytes | None = MISSING,
Expand All @@ -2895,6 +2896,7 @@ async def create_role(
name: str = ...,
permissions: Permissions = ...,
color: Colour | int = ...,
colors: RoleColours = ...,
hoist: bool = ...,
mentionable: bool = ...,
icon: bytes | None = ...,
Expand All @@ -2908,6 +2910,8 @@ async def create_role(
permissions: Permissions = MISSING,
color: Colour | int = MISSING,
colour: Colour | int = MISSING,
colors: RoleColours = MISSING,
colours: RoleColours = MISSING,
hoist: bool = MISSING,
mentionable: bool = MISSING,
reason: str | None = None,
Expand Down Expand Up @@ -2971,11 +2975,28 @@ async def create_role(
else:
fields["permissions"] = "0"

actual_colour = colour or color or Colour.default()
actual_colour = colour if colour not in (MISSING, None) else color

if isinstance(actual_colour, int):
fields["color"] = actual_colour
actual_colour = Colour(actual_colour)

if actual_colour not in (MISSING, None):
actual_colours = RoleColours(primary=actual_colour)
else:
actual_colours = colours or colors or RoleColours.default()

if isinstance(actual_colours, RoleColours):
if "ENHANCED_ROLE_COLORS" not in self.features:
actual_colours.secondary = None
actual_colours.tertiary = None
fields["colors"] = actual_colours._to_dict()

else:
fields["color"] = actual_colour.value
raise InvalidArgument(
"colours parameter must be of type RoleColours, not {0.__class__.__name__}".format(
actual_colours
)
)

if hoist is not MISSING:
fields["hoist"] = hoist
Expand Down
1 change: 1 addition & 0 deletions discord/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -2159,6 +2159,7 @@ def edit_role(
"name",
"permissions",
"color",
"colors",
"hoist",
"mentionable",
"icon",
Expand Down
128 changes: 119 additions & 9 deletions discord/role.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

from typing import TYPE_CHECKING, Any, TypeVar

from typing_extensions import Self

from .asset import Asset
from .colour import Colour
from .errors import InvalidArgument
Expand All @@ -35,10 +37,7 @@
from .permissions import Permissions
from .utils import MISSING, _bytes_to_base64_data, _get_as_snowflake, snowflake_time

__all__ = (
"RoleTags",
"Role",
)
__all__ = ("RoleTags", "Role", "RoleColours")

if TYPE_CHECKING:
import datetime
Expand All @@ -48,6 +47,7 @@
from .state import ConnectionState
from .types.guild import RolePositionUpdate
from .types.role import Role as RolePayload
from .types.role import RoleColours as RoleColoursPayload
from .types.role import RoleTags as RoleTagPayload


Expand Down Expand Up @@ -149,6 +149,76 @@ def __repr__(self) -> str:
R = TypeVar("R", bound="Role")


class RoleColours:
"""Represents a role's gradient colours.

.. versionadded:: 2.7

Attributes
----------
primary: :class:`Colour`
The primary colour of the role.
secondary: Optional[:class:`Colour`]
The secondary colour of the role.
tertiary: Optional[:class:`Colour`]
The tertiary colour of the role.
"""

def __init__(
self,
primary: Colour,
secondary: Colour | None = None,
tertiary: Colour | None = None,
):
"""Initialises a :class:`RoleColours` object.

.. versionadded:: 2.7

Parameters
----------
primary: :class:`Colour`
The primary colour of the role.
secondary: Optional[:class:`Colour`]
The secondary colour of the role.
tertiary: Optional[:class:`Colour`]
The tertiary colour of the role.
"""
self.primary: Colour = primary
self.secondary: Colour | None = secondary
self.tertiary: Colour | None = tertiary

@classmethod
def _from_payload(cls, data: RoleColoursPayload) -> Self:
primary = Colour(data["primary_color"])
secondary = (
Colour(data["secondary_color"]) if data.get("secondary_color") else None
)
tertiary = (
Colour(data["tertiary_color"]) if data.get("tertiary_color") else None
)
return cls(primary, secondary, tertiary)

def _to_dict(self) -> RoleColoursPayload:
"""Converts the role colours to a dictionary."""
return {
"primary_color": self.primary.value,
"secondary_color": self.secondary.value if self.secondary else None,
"tertiary_color": self.tertiary.value if self.tertiary else None,
}

@classmethod
def default(cls) -> RoleColours:
"""Returns a default :class:`RoleColours` object with no colours set."""
return cls(Colour.default(), None, None)

def __repr__(self) -> str:
return (
f"<RoleColours primary={self.primary!r} "
f"secondary={self.secondary!r} "
f"tertiary={self.tertiary!r}>"
)


class Role(Hashable):
"""Represents a Discord role in a :class:`Guild`.

Expand Down Expand Up @@ -227,13 +297,19 @@ class Role(Hashable):
Extra attributes of the role.

.. versionadded:: 2.6

colours: :class:`RoleColours`
The role's colours.

.. versionadded:: 2.7
"""

__slots__ = (
"id",
"name",
"_permissions",
"_colour",
"colours",
"position",
"managed",
"mentionable",
Expand Down Expand Up @@ -299,6 +375,7 @@ def _update(self, data: RolePayload):
self._permissions: int = int(data.get("permissions", 0))
self.position: int = data.get("position", 0)
self._colour: int = data.get("color", 0)
self.colours: RoleColours | None = RoleColours._from_payload(data["colors"])
self.hoist: bool = data.get("hoist", False)
self.managed: bool = data.get("managed", False)
self.mentionable: bool = data.get("mentionable", False)
Expand Down Expand Up @@ -375,13 +452,29 @@ def permissions(self) -> Permissions:

@property
def colour(self) -> Colour:
"""Returns the role colour. An alias exists under ``color``."""
return Colour(self._colour)
"""Returns the role colour. Equivalent to :attr:`colours.primary`.
An alias exists under ``color``.

.. versionchanged:: 2.7
"""
return self.colours.primary

@property
def color(self) -> Colour:
"""Returns the role color. An alias exists under ``colour``."""
return self.colour
"""Returns the role's primary color. Equivalent to :attr:`colors.primary`.
An alias exists under ``colour``.

.. versionchanged:: 2.7
"""
return self.colours.primary

@property
def colors(self) -> RoleColours:
"""Returns the role's colours. Equivalent to :attr:`colours`.

.. versionadded:: 2.7
"""
return self.colours

@property
def created_at(self) -> datetime.datetime:
Expand Down Expand Up @@ -452,6 +545,8 @@ async def edit(
permissions: Permissions = MISSING,
colour: Colour | int = MISSING,
color: Colour | int = MISSING,
colours: RoleColours | None = MISSING,
colors: RoleColours | None = MISSING,
hoist: bool = MISSING,
mentionable: bool = MISSING,
position: int = MISSING,
Expand Down Expand Up @@ -523,8 +618,23 @@ async def edit(
if color is not MISSING:
colour = color

if colors is not MISSING:
colours = colors

if colour is not MISSING:
payload["color"] = colour if isinstance(colour, int) else colour.value
if isinstance(colour, int):
colour = Colour(colour)
colours = RoleColours(primary=colour)

if colours is not MISSING:
if not isinstance(colours, RoleColours):
raise InvalidArgument("colours must be a RoleColours object")
if "ENHANCED_ROLE_COLORS" not in self.guild.features:
colours.secondary = None
colours.tertiary = None

payload["colors"] = colours._to_dict()

if name is not MISSING:
payload["name"] = name

Expand Down
1 change: 1 addition & 0 deletions discord/types/guild.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ class UnavailableGuild(TypedDict):
"VERIFIED",
"VIP_REGIONS",
"WELCOME_SCREEN_ENABLED",
"ENHANCED_ROLE_COLORS",
]


Expand Down
7 changes: 7 additions & 0 deletions discord/types/role.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,18 @@
from .snowflake import Snowflake


class RoleColours(TypedDict):
primary_color: int
secondary_color: int | None
tertiary_color: int | None


class Role(TypedDict):
tags: NotRequired[RoleTags]
id: Snowflake
name: str
color: int
colors: RoleColours
hoist: bool
position: int
permissions: str
Expand Down
5 changes: 5 additions & 0 deletions docs/api/models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,11 @@ Role
.. autoclass:: RoleTags()
:members:

.. attributetable:: RoleColours

.. autoclass:: RoleColours
:members:

Scheduled Event
~~~~~~~~~~~~~~~

Expand Down