Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
16 changes: 16 additions & 0 deletions hikari/api/entity_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from hikari import audit_logs as audit_log_models
from hikari import auto_mod as auto_mod_models
from hikari import channels as channel_models
from hikari import colors as color_models
from hikari import commands
from hikari import embeds as embed_models
from hikari import emojis as emoji_models
Expand Down Expand Up @@ -125,6 +126,21 @@ class EntityFactory(abc.ABC):

__slots__: typing.Sequence[str] = ()

@abc.abstractmethod
def serialize_color_gradient(self, gradient: color_models.ColorGradient) -> data_binding.JSONObject:
"""Serialize a color gradient into json.

Parameters
----------
gradient
The color gradient to serialize.

Returns
-------
hikari.internal.data_binding.JSONObject
The serialized representation of the gradient object.
"""

######################
# APPLICATION MODELS #
######################
Expand Down
10 changes: 5 additions & 5 deletions hikari/api/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
from hikari import audit_logs
from hikari import auto_mod
from hikari import channels as channels_
from hikari import colors
from hikari import colors as colors_
from hikari import commands
from hikari import embeds as embeds_
from hikari import emojis
Expand Down Expand Up @@ -6395,8 +6395,8 @@ async def create_role(
*,
name: undefined.UndefinedOr[str] = undefined.UNDEFINED,
permissions: undefined.UndefinedOr[permissions_.Permissions] = permissions_.Permissions.NONE,
color: undefined.UndefinedOr[colors.Colorish] = undefined.UNDEFINED,
colour: undefined.UndefinedOr[colors.Colorish] = undefined.UNDEFINED,
color: undefined.UndefinedOr[colors_.Colorish | colors_.ColorGradient] = undefined.UNDEFINED,
colour: undefined.UndefinedOr[colors_.Colorish | colors_.ColorGradient] = undefined.UNDEFINED,
hoist: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
icon: undefined.UndefinedOr[files.Resourceish] = undefined.UNDEFINED,
unicode_emoji: undefined.UndefinedOr[str] = undefined.UNDEFINED,
Expand Down Expand Up @@ -6501,8 +6501,8 @@ async def edit_role(
*,
name: undefined.UndefinedOr[str] = undefined.UNDEFINED,
permissions: undefined.UndefinedOr[permissions_.Permissions] = undefined.UNDEFINED,
color: undefined.UndefinedOr[colors.Colorish] = undefined.UNDEFINED,
colour: undefined.UndefinedOr[colors.Colorish] = undefined.UNDEFINED,
color: undefined.UndefinedOr[colors_.Colorish | colors_.ColorGradient] = undefined.UNDEFINED,
colour: undefined.UndefinedOr[colors_.Colorish | colors_.ColorGradient] = undefined.UNDEFINED,
hoist: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
icon: undefined.UndefinedNoneOr[files.Resourceish] = undefined.UNDEFINED,
unicode_emoji: undefined.UndefinedNoneOr[str] = undefined.UNDEFINED,
Expand Down
83 changes: 82 additions & 1 deletion hikari/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,19 @@

from __future__ import annotations

__all__: typing.Sequence[str] = ("Color", "Colorish")
__all__: typing.Sequence[str] = ("Color", "ColorGradient", "Colorish")

import re
import string
import typing

import attrs

from hikari.internal import typing_extensions

if typing.TYPE_CHECKING:
from typing_extensions import Self


def _to_rgb_int(value: str, name: str) -> int:
# Heavy validation that is user-friendly and doesn't allow exploiting overflows, etc easily.
Expand Down Expand Up @@ -585,3 +590,79 @@

Web-safe colours are three hex-digits wide, `XYZ` becomes `XXYYZZ` in full-form.
"""


@attrs.define(unsafe_hash=True, kw_only=True, weakref_slot=False)
class ColorGradient:
"""Represents a role colors object."""

_primary_color: Colorish = attrs.field(hash=True, repr=True, alias="primary_color")
_secondary_color: Colorish | None = attrs.field(hash=True, repr=True, alias="secondary_color", default=None)
_tertiary_color: Colorish | None = attrs.field(hash=True, repr=True, alias="tertiary_color", default=None)

@property
def primary_color(self) -> Color:
"""The primary color of the role."""
return Color.of(self._primary_color)

Check warning on line 606 in hikari/colors.py

View check run for this annotation

Codecov / codecov/patch

hikari/colors.py#L606

Added line #L606 was not covered by tests

def set_primary_color(self, primary_color: Colorish) -> Self:
"""Set the primary color of the role.

Parameters
----------
primary_color
The new color to set

Returns
-------
RoleColors
The role colors obj.
"""
self._primary_color = primary_color
return self

Check warning on line 622 in hikari/colors.py

View check run for this annotation

Codecov / codecov/patch

hikari/colors.py#L621-L622

Added lines #L621 - L622 were not covered by tests

@property
def secondary_color(self) -> Color | None:
"""The secondary color of the role."""
if self._secondary_color is None:
return None
return Color.of(self._secondary_color)

Check warning on line 629 in hikari/colors.py

View check run for this annotation

Codecov / codecov/patch

hikari/colors.py#L628-L629

Added lines #L628 - L629 were not covered by tests

def set_secondary_color(self, secondary_color: Colorish | None) -> Self:
"""Set the secondary color of the role.

Parameters
----------
secondary_color
The new color to set

Returns
-------
RoleColors
The role colors obj.
"""
self._secondary_color = secondary_color
return self

Check warning on line 645 in hikari/colors.py

View check run for this annotation

Codecov / codecov/patch

hikari/colors.py#L644-L645

Added lines #L644 - L645 were not covered by tests

@property
def tertiary_color(self) -> Color | None:
"""The tertiary color of the role."""
if self._secondary_color is None:
return None
return Color.of(self._secondary_color)

Check warning on line 652 in hikari/colors.py

View check run for this annotation

Codecov / codecov/patch

hikari/colors.py#L651-L652

Added lines #L651 - L652 were not covered by tests

def set_tertiary_color(self, tertiary_color: Colorish | None) -> Self:
"""Set the secondary color of the role.

Parameters
----------
tertiary_color
The new color to set

Returns
-------
RoleColors
The role colors obj.
"""
self._tertiary_color = tertiary_color
return self

Check warning on line 668 in hikari/colors.py

View check run for this annotation

Codecov / codecov/patch

hikari/colors.py#L667-L668

Added lines #L667 - L668 were not covered by tests
5 changes: 4 additions & 1 deletion hikari/colours.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

from __future__ import annotations

__all__: typing.Sequence[str] = ("Colour", "Colourish")
__all__: typing.Sequence[str] = ("Colour", "ColourGradient", "Colourish")

import typing

Expand All @@ -33,3 +33,6 @@

Colourish = colors.Colorish
"""An alias for [`hikari.colors.Colorish`][]."""

ColourGradient = colors.ColorGradient
"""An alias for [`hikari.colors.ColorGradient`][]."""
8 changes: 7 additions & 1 deletion hikari/guilds.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
import attrs

from hikari import channels as channels_
from hikari import colors
from hikari import snowflakes
from hikari import stickers
from hikari import traits
Expand All @@ -75,7 +76,6 @@
if typing.TYPE_CHECKING:
import datetime

from hikari import colors
from hikari import colours
from hikari import emojis as emojis_
from hikari import files
Expand Down Expand Up @@ -191,6 +191,9 @@ class GuildFeature(str, enums.Enum):
RAID_ALERTS_DISABLED = "RAID_ALERTS_DISABLED"
"""Guild has disabled alerts for join raids in the configured safety alerts channel."""

ENHANCED_ROLE_COLORS = "ENHANCED_ROLE_COLORS"
"""Guild is able to set gradient colors to roles."""


@typing.final
class GuildMessageNotificationsLevel(int, enums.Enum):
Expand Down Expand Up @@ -1217,6 +1220,9 @@ class Role(PartialRole):
This will be applied to a member's name in chat if it's their top coloured role.
"""

colors: colors.ColorGradient = attrs.field(eq=False, hash=False, repr=False)
"""The colors object of this role."""

guild_id: snowflakes.Snowflake = attrs.field(eq=False, hash=False, repr=True)
"""The ID of the guild this role belongs to."""

Expand Down
23 changes: 22 additions & 1 deletion hikari/impl/entity_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,14 @@
"""Object of the application this entity factory is bound to."""
return self._app

@typing_extensions.override
def serialize_color_gradient(self, gradient: color_models.ColorGradient) -> data_binding.JSONObject:
return {

Check warning on line 642 in hikari/impl/entity_factory.py

View check run for this annotation

Codecov / codecov/patch

hikari/impl/entity_factory.py#L642

Added line #L642 was not covered by tests
"primary_color": gradient.primary_color,
"secondary_color": gradient.secondary_color,
"tertiary_color": gradient.tertiary_color,
}

######################
# APPLICATION MODELS #
######################
Expand Down Expand Up @@ -2100,6 +2108,18 @@
if "guild_connections" in tags_payload:
is_guild_linked_role = True

colors_payload = payload["colors"]
primary_color = color_models.Color(colors_payload["primary_color"])
secondary_color: color_models.Color | None = None
if (raw_secondary_color := colors_payload.get("secondary_color")) is not None:
secondary_color = color_models.Color(raw_secondary_color)
tertiary_color: color_models.Color | None = None
if (raw_tertiary_color := colors_payload.get("tertiary_color")) is not None:
tertiary_color = color_models.Color(raw_tertiary_color)

Check warning on line 2118 in hikari/impl/entity_factory.py

View check run for this annotation

Codecov / codecov/patch

hikari/impl/entity_factory.py#L2118

Added line #L2118 was not covered by tests
colors = color_models.ColorGradient(
primary_color=primary_color, secondary_color=secondary_color, tertiary_color=tertiary_color
)

emoji: emoji_models.UnicodeEmoji | None = None
if (raw_emoji := payload.get("unicode_emoji")) is not None:
emoji = emoji_models.UnicodeEmoji(raw_emoji)
Expand All @@ -2109,7 +2129,8 @@
id=snowflakes.Snowflake(payload["id"]),
guild_id=guild_id,
name=payload["name"],
color=color_models.Color(payload["color"]),
color=primary_color,
colors=colors,
is_hoisted=payload["hoist"],
icon_hash=payload.get("icon"),
unicode_emoji=emoji,
Expand Down
30 changes: 21 additions & 9 deletions hikari/impl/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
from hikari import _about as about
from hikari import applications
from hikari import channels as channels_
from hikari import colors
from hikari import colors as colors_
from hikari import commands
from hikari import components as components_
from hikari import embeds as embeds_
Expand Down Expand Up @@ -3813,8 +3813,8 @@
*,
name: undefined.UndefinedOr[str] = undefined.UNDEFINED,
permissions: undefined.UndefinedOr[permissions_.Permissions] = permissions_.Permissions.NONE,
color: undefined.UndefinedOr[colors.Colorish] = undefined.UNDEFINED,
colour: undefined.UndefinedOr[colors.Colorish] = undefined.UNDEFINED,
color: undefined.UndefinedOr[colors_.Colorish | colors_.ColorGradient] = undefined.UNDEFINED,
colour: undefined.UndefinedOr[colors_.Colorish | colors_.ColorGradient] = undefined.UNDEFINED,
hoist: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
icon: undefined.UndefinedOr[files.Resourceish] = undefined.UNDEFINED,
unicode_emoji: undefined.UndefinedOr[str] = undefined.UNDEFINED,
Expand All @@ -3833,8 +3833,14 @@
body = data_binding.JSONObjectBuilder()
body.put("name", name)
body.put("permissions", permissions)
body.put("color", color, conversion=colors.Color.of)
body.put("color", colour, conversion=colors.Color.of)
if isinstance(color, colors_.ColorGradient):
body.put("colors", color, conversion=self._entity_factory.serialize_color_gradient)

Check warning on line 3837 in hikari/impl/rest.py

View check run for this annotation

Codecov / codecov/patch

hikari/impl/rest.py#L3837

Added line #L3837 was not covered by tests
else:
body.put("color", color, conversion=colors_.Color.of)
if isinstance(colour, colors_.ColorGradient):
body.put("colors", colour, conversion=self._entity_factory.serialize_color_gradient)

Check warning on line 3841 in hikari/impl/rest.py

View check run for this annotation

Codecov / codecov/patch

hikari/impl/rest.py#L3841

Added line #L3841 was not covered by tests
else:
body.put("color", colour, conversion=colors_.Color.of)
body.put("hoist", hoist)
body.put("unicode_emoji", unicode_emoji)
body.put("mentionable", mentionable)
Expand Down Expand Up @@ -3867,8 +3873,8 @@
*,
name: undefined.UndefinedOr[str] = undefined.UNDEFINED,
permissions: undefined.UndefinedOr[permissions_.Permissions] = undefined.UNDEFINED,
color: undefined.UndefinedOr[colors.Colorish] = undefined.UNDEFINED,
colour: undefined.UndefinedOr[colors.Colorish] = undefined.UNDEFINED,
color: undefined.UndefinedOr[colors_.Colorish | colors_.ColorGradient] = undefined.UNDEFINED,
colour: undefined.UndefinedOr[colors_.Colorish | colors_.ColorGradient] = undefined.UNDEFINED,
hoist: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
icon: undefined.UndefinedNoneOr[files.Resourceish] = undefined.UNDEFINED,
unicode_emoji: undefined.UndefinedNoneOr[str] = undefined.UNDEFINED,
Expand All @@ -3888,8 +3894,14 @@
body = data_binding.JSONObjectBuilder()
body.put("name", name)
body.put("permissions", permissions)
body.put("color", color, conversion=colors.Color.of)
body.put("color", colour, conversion=colors.Color.of)
if isinstance(color, colors_.ColorGradient):
body.put("colors", color, conversion=self._entity_factory.serialize_color_gradient)

Check warning on line 3898 in hikari/impl/rest.py

View check run for this annotation

Codecov / codecov/patch

hikari/impl/rest.py#L3898

Added line #L3898 was not covered by tests
else:
body.put("color", color, conversion=colors_.Color.of)
if isinstance(colour, colors_.ColorGradient):
body.put("colors", colour, conversion=self._entity_factory.serialize_color_gradient)

Check warning on line 3902 in hikari/impl/rest.py

View check run for this annotation

Codecov / codecov/patch

hikari/impl/rest.py#L3902

Added line #L3902 was not covered by tests
else:
body.put("color", colour, conversion=colors_.Color.of)
body.put("hoist", hoist)
body.put("unicode_emoji", unicode_emoji)
body.put("mentionable", mentionable)
Expand Down
6 changes: 6 additions & 0 deletions tests/hikari/impl/test_entity_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ def guild_role_payload():
"id": "41771983423143936",
"name": "WE DEM BOYZZ!!!!!!",
"color": 3_447_003,
"colors": {"primary_color": 3_447_003, "secondary_color": 3_447_003, "tertiary_color": None},
"hoist": True,
"unicode_emoji": "\N{OK HAND SIGN}",
"icon": "abc123hash",
Expand Down Expand Up @@ -3443,6 +3444,11 @@ def test_deserialize_role(self, entity_factory_impl, mock_app, guild_role_payloa
assert guild_role.unicode_emoji == emoji_models.UnicodeEmoji("\N{OK HAND SIGN}")
assert isinstance(guild_role.unicode_emoji, emoji_models.UnicodeEmoji)
assert guild_role.color == color_models.Color(3_447_003)
assert guild_role.colors == color_models.ColorGradient(
primary_color=color_models.Color(3_447_003),
secondary_color=color_models.Color(3_447_003),
tertiary_color=None,
)
assert guild_role.is_hoisted is True
assert guild_role.position == 0
assert guild_role.permissions == permission_models.Permissions(66_321_471)
Expand Down
6 changes: 6 additions & 0 deletions tests/hikari/test_guilds.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ def model(self, mock_app):
id=snowflakes.Snowflake(979899100),
name="@everyone",
color=colors.Color(0x1A2B3C),
colors=colors.ColorGradient(
primary_color=colors.Color(0x1A2B3C), secondary_color=colors.Color(0x1A2B3C), tertiary_color=None
),
guild_id=snowflakes.Snowflake(112233),
is_hoisted=False,
icon_hash="icon_hash",
Expand All @@ -156,6 +159,9 @@ def model(self, mock_app):

def test_colour_property(self, model):
assert model.colour == colors.Color(0x1A2B3C)
assert model.colors == colors.ColorGradient(
primary_color=colors.Color(0x1A2B3C), secondary_color=colors.Color(0x1A2B3C), tertiary_color=None
)

def test_make_icon_url_format_set_to_deprecated_ext_argument_if_provided(self, model):
with mock.patch.object(
Expand Down
Loading