From 474b27bf5f8358569b35374be1cc148e523a09a5 Mon Sep 17 00:00:00 2001 From: Rayakame Date: Sun, 11 May 2025 11:03:53 +0000 Subject: [PATCH 1/6] chore(general): add RoleColors object and related fields to Role (issue #enhanced-guild-role-styles) --- hikari/guilds.py | 18 ++++++++++++++++++ hikari/impl/entity_factory.py | 18 +++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/hikari/guilds.py b/hikari/guilds.py index 6dabad3cea..2bc0be9452 100644 --- a/hikari/guilds.py +++ b/hikari/guilds.py @@ -50,6 +50,7 @@ "PartialRole", "RESTGuild", "Role", + "RoleColors", "WelcomeChannel", "WelcomeScreen", ) @@ -191,6 +192,8 @@ 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): @@ -1206,6 +1209,18 @@ def mention(self) -> str: def __str__(self) -> str: return self.name +@attrs.define(unsafe_hash=True, kw_only=True, weakref_slot=False) +class RoleColors: + """Represents a role colors object.""" + + primary_color: colors.Color = attrs.field(eq=False, hash=False, repr=True) + """The primary color of the role.""" + + secondary_color: colors.Color | None = attrs.field(eq=False, hash=False, repr=True) + """The secondary color of the role.""" + + tertiary_color: colors.Color | None = attrs.field(eq=False, hash=False, repr=True) + """The tertiary color of the role.""" @attrs.define(unsafe_hash=True, kw_only=True, weakref_slot=False) class Role(PartialRole): @@ -1217,6 +1232,9 @@ class Role(PartialRole): This will be applied to a member's name in chat if it's their top coloured role. """ + colors: RoleColors = 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.""" diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py index dc6b49cb1d..0b4dddf0fc 100644 --- a/hikari/impl/entity_factory.py +++ b/hikari/impl/entity_factory.py @@ -2023,6 +2023,21 @@ def deserialize_role( 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) + colors = guild_models.RoleColors( + 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) @@ -2032,7 +2047,8 @@ def deserialize_role( 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, From 50b384827fa45d9075435546f43c934d5a38f670 Mon Sep 17 00:00:00 2001 From: Rayakame Date: Sun, 11 May 2025 14:04:01 +0000 Subject: [PATCH 2/6] chore(tests): add guild role colors test (enhanced-guild-role-styles) --- hikari/guilds.py | 3 +++ hikari/impl/entity_factory.py | 5 +---- tests/hikari/impl/test_entity_factory.py | 6 ++++++ tests/hikari/test_guilds.py | 10 ++++++++++ 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/hikari/guilds.py b/hikari/guilds.py index 2bc0be9452..98a3cbf65a 100644 --- a/hikari/guilds.py +++ b/hikari/guilds.py @@ -195,6 +195,7 @@ class GuildFeature(str, enums.Enum): ENHANCED_ROLE_COLORS = "ENHANCED_ROLE_COLORS" """Guild is able to set gradient colors to roles.""" + @typing.final class GuildMessageNotificationsLevel(int, enums.Enum): """Represents the default notification level for new messages in a guild.""" @@ -1209,6 +1210,7 @@ def mention(self) -> str: def __str__(self) -> str: return self.name + @attrs.define(unsafe_hash=True, kw_only=True, weakref_slot=False) class RoleColors: """Represents a role colors object.""" @@ -1222,6 +1224,7 @@ class RoleColors: tertiary_color: colors.Color | None = attrs.field(eq=False, hash=False, repr=True) """The tertiary color of the role.""" + @attrs.define(unsafe_hash=True, kw_only=True, weakref_slot=False) class Role(PartialRole): """Represents a guild bound role object.""" diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py index 0b4dddf0fc..e162c6f822 100644 --- a/hikari/impl/entity_factory.py +++ b/hikari/impl/entity_factory.py @@ -2023,7 +2023,6 @@ def deserialize_role( 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 @@ -2033,9 +2032,7 @@ def deserialize_role( if (raw_tertiary_color := colors_payload.get("tertiary_color")) is not None: tertiary_color = color_models.Color(raw_tertiary_color) colors = guild_models.RoleColors( - primary_color=primary_color, - secondary_color=secondary_color, - tertiary_color=tertiary_color + primary_color=primary_color, secondary_color=secondary_color, tertiary_color=tertiary_color ) emoji: emoji_models.UnicodeEmoji | None = None diff --git a/tests/hikari/impl/test_entity_factory.py b/tests/hikari/impl/test_entity_factory.py index 1304723396..901c80fd97 100644 --- a/tests/hikari/impl/test_entity_factory.py +++ b/tests/hikari/impl/test_entity_factory.py @@ -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", @@ -3354,6 +3355,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 == guild_models.RoleColors( + 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) diff --git a/tests/hikari/test_guilds.py b/tests/hikari/test_guilds.py index 5db4f89f60..a2bb1d1a56 100644 --- a/tests/hikari/test_guilds.py +++ b/tests/hikari/test_guilds.py @@ -138,6 +138,11 @@ def model(self, mock_app): id=snowflakes.Snowflake(979899100), name="@everyone", color=colors.Color(0x1A2B3C), + colors=guilds.RoleColors( + 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", @@ -156,6 +161,11 @@ def model(self, mock_app): def test_colour_property(self, model): assert model.colour == colors.Color(0x1A2B3C) + assert model.colors == guilds.RoleColors( + 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( From a4980be96ea473b33b717a11195584f5ec6f599a Mon Sep 17 00:00:00 2001 From: Rayakame Date: Sun, 11 May 2025 20:14:13 +0000 Subject: [PATCH 3/6] quick little update dont mind this --- hikari/api/rest.py | 16 +++++++++++----- hikari/guilds.py | 12 +++++++----- hikari/impl/rest.py | 28 +++++++++++++++------------- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/hikari/api/rest.py b/hikari/api/rest.py index c55b37a075..3591802277 100644 --- a/hikari/api/rest.py +++ b/hikari/api/rest.py @@ -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 @@ -6259,8 +6259,9 @@ 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] = undefined.UNDEFINED, + colour: undefined.UndefinedOr[colors_.Colorish] = undefined.UNDEFINED, + colors: undefined.UndefinedOr[guilds.RoleColors] = undefined.UNDEFINED, hoist: undefined.UndefinedOr[bool] = undefined.UNDEFINED, icon: undefined.UndefinedOr[files.Resourceish] = undefined.UNDEFINED, unicode_emoji: undefined.UndefinedOr[str] = undefined.UNDEFINED, @@ -6285,6 +6286,8 @@ async def create_role( If provided, the role's color. colour An alias for `color`. + colors + TODO hoist If provided, whether to hoist the role. icon @@ -6365,8 +6368,9 @@ 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] = undefined.UNDEFINED, + colour: undefined.UndefinedOr[colors_.Colorish] = undefined.UNDEFINED, + colors: undefined.UndefinedOr[guilds.RoleColors] = undefined.UNDEFINED, hoist: undefined.UndefinedOr[bool] = undefined.UNDEFINED, icon: undefined.UndefinedNoneOr[files.Resourceish] = undefined.UNDEFINED, unicode_emoji: undefined.UndefinedNoneOr[str] = undefined.UNDEFINED, @@ -6391,6 +6395,8 @@ async def edit_role( If provided, the new color for the role. colour An alias for `color`. + colors + TODO hoist If provided, whether to hoist the role. icon diff --git a/hikari/guilds.py b/hikari/guilds.py index 98a3cbf65a..b271408eb7 100644 --- a/hikari/guilds.py +++ b/hikari/guilds.py @@ -1215,15 +1215,17 @@ def __str__(self) -> str: class RoleColors: """Represents a role colors object.""" - primary_color: colors.Color = attrs.field(eq=False, hash=False, repr=True) + _primary_color: colors.Color = attrs.field(eq=False, hash=False, repr=True) """The primary color of the role.""" - - secondary_color: colors.Color | None = attrs.field(eq=False, hash=False, repr=True) + _secondary_color: colors.Color | None = attrs.field(eq=False, hash=False, repr=True) """The secondary color of the role.""" - - tertiary_color: colors.Color | None = attrs.field(eq=False, hash=False, repr=True) + _tertiary_color: colors.Color | None = attrs.field(eq=False, hash=False, repr=True) """The tertiary color of the role.""" + @property + def primary_color + + @attrs.define(unsafe_hash=True, kw_only=True, weakref_slot=False) class Role(PartialRole): diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py index ce2232f918..1067052b6e 100644 --- a/hikari/impl/rest.py +++ b/hikari/impl/rest.py @@ -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_ @@ -3766,16 +3766,17 @@ 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] = undefined.UNDEFINED, + colour: undefined.UndefinedOr[colors_.Colorish] = undefined.UNDEFINED, + colors: undefined.UndefinedOr[guilds.RoleColors] = undefined.UNDEFINED, hoist: undefined.UndefinedOr[bool] = undefined.UNDEFINED, icon: undefined.UndefinedOr[files.Resourceish] = undefined.UNDEFINED, unicode_emoji: undefined.UndefinedOr[str] = undefined.UNDEFINED, mentionable: undefined.UndefinedOr[bool] = undefined.UNDEFINED, reason: undefined.UndefinedOr[str] = undefined.UNDEFINED, ) -> guilds.Role: - if not undefined.any_undefined(color, colour): - msg = "Can not specify 'color' and 'colour' together." + if not undefined.any_undefined(color, colour, colors): + msg = "Can not specify 'color', 'colour' or 'colors' together." raise TypeError(msg) if not undefined.any_undefined(icon, unicode_emoji): @@ -3786,8 +3787,8 @@ async def create_role( 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) + body.put("color", color, conversion=colors_.Color.of) + body.put("color", colour, conversion=colors_.Color.of) body.put("hoist", hoist) body.put("unicode_emoji", unicode_emoji) body.put("mentionable", mentionable) @@ -3820,16 +3821,17 @@ 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] = undefined.UNDEFINED, + colour: undefined.UndefinedOr[colors_.Colorish] = undefined.UNDEFINED, + colors: undefined.UndefinedOr[guilds.RoleColors] = undefined.UNDEFINED, hoist: undefined.UndefinedOr[bool] = undefined.UNDEFINED, icon: undefined.UndefinedNoneOr[files.Resourceish] = undefined.UNDEFINED, unicode_emoji: undefined.UndefinedNoneOr[str] = undefined.UNDEFINED, mentionable: undefined.UndefinedOr[bool] = undefined.UNDEFINED, reason: undefined.UndefinedOr[str] = undefined.UNDEFINED, ) -> guilds.Role: - if not undefined.any_undefined(color, colour): - msg = "Can not specify 'color' and 'colour' together." + if not undefined.any_undefined(color, colour, colors): + msg = "Can not specify 'color', 'colour' or 'colors' together." raise TypeError(msg) if not undefined.any_undefined(icon, unicode_emoji): @@ -3841,8 +3843,8 @@ async def edit_role( 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) + body.put("color", color, conversion=colors_.Color.of) + body.put("color", colour, conversion=colors_.Color.of) body.put("hoist", hoist) body.put("unicode_emoji", unicode_emoji) body.put("mentionable", mentionable) From a1f5be0fb99a5aac00ed618a6f30a0115cd33b92 Mon Sep 17 00:00:00 2001 From: Rayakame Date: Sun, 11 May 2025 22:43:24 +0200 Subject: [PATCH 4/6] feat(guilds): refactor RoleColors class to use Colorish type (enhanced-guild-role-styles) --- hikari/guilds.py | 76 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 69 insertions(+), 7 deletions(-) diff --git a/hikari/guilds.py b/hikari/guilds.py index b271408eb7..a6a7ad9668 100644 --- a/hikari/guilds.py +++ b/hikari/guilds.py @@ -1215,15 +1215,77 @@ def __str__(self) -> str: class RoleColors: """Represents a role colors object.""" - _primary_color: colors.Color = attrs.field(eq=False, hash=False, repr=True) - """The primary color of the role.""" - _secondary_color: colors.Color | None = attrs.field(eq=False, hash=False, repr=True) - """The secondary color of the role.""" - _tertiary_color: colors.Color | None = attrs.field(eq=False, hash=False, repr=True) - """The tertiary color of the role.""" + _primary_color: colors.Colorish = attrs.field(hash=True, repr=True, alias="primary_color") + _secondary_color: colors.Colorish | None = attrs.field(hash=True, repr=True, alias="secondary_color", default=None) + _tertiary_color: colors.Colorish | None = attrs.field( hash=True, repr=True, alias="tertiary_color", default=None) @property - def primary_color + def primary_color(self) -> colors.Color: + """The primary color of the role.""" + return colors.Color.of(self._primary_color) + + def set_primary_color(self, primary_color: colors.Colorish) -> typing.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 + + @property + def secondary_color(self) -> colors.Color | None: + """The secondary color of the role.""" + if self._secondary_color is None: + return None + return colors.Color.of(self._secondary_color) + + def set_secondary_color(self, secondary_color: colors.Colorish | None) -> typing.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 + + @property + def tertiary_color(self) -> colors.Color | None: + """The tertiary color of the role.""" + if self._secondary_color is None: + return None + return colors.Color.of(self._secondary_color) + + def set_tertiary_color(self, tertiary_color: colors.Colorish | None) -> typing.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 + From 0a5d19880fe5aa20432665817373893013d9bbb6 Mon Sep 17 00:00:00 2001 From: Rayakame Date: Sun, 11 May 2025 22:54:37 +0200 Subject: [PATCH 5/6] pipeline fixes --- hikari/guilds.py | 20 ++++++++++---------- hikari/impl/rest.py | 8 ++++---- tests/hikari/test_guilds.py | 10 +++------- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/hikari/guilds.py b/hikari/guilds.py index a6a7ad9668..ba5e1678ba 100644 --- a/hikari/guilds.py +++ b/hikari/guilds.py @@ -60,6 +60,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 @@ -76,7 +77,8 @@ if typing.TYPE_CHECKING: import datetime - from hikari import colors + from typing_extensions import Self + from hikari import colours from hikari import emojis as emojis_ from hikari import files @@ -1217,21 +1219,21 @@ class RoleColors: _primary_color: colors.Colorish = attrs.field(hash=True, repr=True, alias="primary_color") _secondary_color: colors.Colorish | None = attrs.field(hash=True, repr=True, alias="secondary_color", default=None) - _tertiary_color: colors.Colorish | None = attrs.field( hash=True, repr=True, alias="tertiary_color", default=None) + _tertiary_color: colors.Colorish | None = attrs.field(hash=True, repr=True, alias="tertiary_color", default=None) @property def primary_color(self) -> colors.Color: """The primary color of the role.""" return colors.Color.of(self._primary_color) - def set_primary_color(self, primary_color: colors.Colorish) -> typing.Self: + def set_primary_color(self, primary_color: colors.Colorish) -> Self: """Set the primary color of the role. Parameters ---------- primary_color The new color to set - + Returns ------- RoleColors @@ -1247,14 +1249,14 @@ def secondary_color(self) -> colors.Color | None: return None return colors.Color.of(self._secondary_color) - def set_secondary_color(self, secondary_color: colors.Colorish | None) -> typing.Self: + def set_secondary_color(self, secondary_color: colors.Colorish | None) -> Self: """Set the secondary color of the role. Parameters ---------- secondary_color The new color to set - + Returns ------- RoleColors @@ -1270,14 +1272,14 @@ def tertiary_color(self) -> colors.Color | None: return None return colors.Color.of(self._secondary_color) - def set_tertiary_color(self, tertiary_color: colors.Colorish | None) -> typing.Self: + def set_tertiary_color(self, tertiary_color: colors.Colorish | None) -> Self: """Set the secondary color of the role. Parameters ---------- tertiary_color The new color to set - + Returns ------- RoleColors @@ -1287,8 +1289,6 @@ def set_tertiary_color(self, tertiary_color: colors.Colorish | None) -> typing.S return self - - @attrs.define(unsafe_hash=True, kw_only=True, weakref_slot=False) class Role(PartialRole): """Represents a guild bound role object.""" diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py index 1067052b6e..632b7fb9e6 100644 --- a/hikari/impl/rest.py +++ b/hikari/impl/rest.py @@ -3775,8 +3775,8 @@ async def create_role( mentionable: undefined.UndefinedOr[bool] = undefined.UNDEFINED, reason: undefined.UndefinedOr[str] = undefined.UNDEFINED, ) -> guilds.Role: - if not undefined.any_undefined(color, colour, colors): - msg = "Can not specify 'color', 'colour' or 'colors' together." + if not undefined.any_undefined(color, colour): + msg = "Can not specify 'color' and 'colour' together." raise TypeError(msg) if not undefined.any_undefined(icon, unicode_emoji): @@ -3830,8 +3830,8 @@ async def edit_role( mentionable: undefined.UndefinedOr[bool] = undefined.UNDEFINED, reason: undefined.UndefinedOr[str] = undefined.UNDEFINED, ) -> guilds.Role: - if not undefined.any_undefined(color, colour, colors): - msg = "Can not specify 'color', 'colour' or 'colors' together." + if not undefined.any_undefined(color, colour): + msg = "Can not specify 'color' and 'colour' together." raise TypeError(msg) if not undefined.any_undefined(icon, unicode_emoji): diff --git a/tests/hikari/test_guilds.py b/tests/hikari/test_guilds.py index a2bb1d1a56..83bb9ec8af 100644 --- a/tests/hikari/test_guilds.py +++ b/tests/hikari/test_guilds.py @@ -139,9 +139,7 @@ def model(self, mock_app): name="@everyone", color=colors.Color(0x1A2B3C), colors=guilds.RoleColors( - primary_color=colors.Color(0x1A2B3C), - secondary_color=colors.Color(0x1A2B3C), - tertiary_color=None + primary_color=colors.Color(0x1A2B3C), secondary_color=colors.Color(0x1A2B3C), tertiary_color=None ), guild_id=snowflakes.Snowflake(112233), is_hoisted=False, @@ -162,10 +160,8 @@ def model(self, mock_app): def test_colour_property(self, model): assert model.colour == colors.Color(0x1A2B3C) assert model.colors == guilds.RoleColors( - primary_color=colors.Color(0x1A2B3C), - secondary_color=colors.Color(0x1A2B3C), - tertiary_color=None - ) + 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( From 102d28c56ce45a91414f638a2431ac51262e49b4 Mon Sep 17 00:00:00 2001 From: Rayakame Date: Tue, 1 Jul 2025 12:43:42 +0200 Subject: [PATCH 6/6] refactor(colors): Replace RoleColors with ColorGradient (enhanced-guild-role-styles) --- hikari/api/entity_factory.py | 16 +++++ hikari/api/rest.py | 14 ++-- hikari/colors.py | 83 +++++++++++++++++++++++- hikari/colours.py | 5 +- hikari/guilds.py | 81 +---------------------- hikari/impl/entity_factory.py | 10 ++- hikari/impl/rest.py | 30 ++++++--- tests/hikari/impl/test_entity_factory.py | 2 +- tests/hikari/test_guilds.py | 4 +- 9 files changed, 139 insertions(+), 106 deletions(-) diff --git a/hikari/api/entity_factory.py b/hikari/api/entity_factory.py index 43521b531c..1536b8abef 100644 --- a/hikari/api/entity_factory.py +++ b/hikari/api/entity_factory.py @@ -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 @@ -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 # ###################### diff --git a/hikari/api/rest.py b/hikari/api/rest.py index 5a9d2afdc4..0b4bc108f7 100644 --- a/hikari/api/rest.py +++ b/hikari/api/rest.py @@ -6395,9 +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, - colors: undefined.UndefinedOr[guilds.RoleColors] = 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, @@ -6422,8 +6421,6 @@ async def create_role( If provided, the role's color. colour An alias for `color`. - colors - TODO hoist If provided, whether to hoist the role. icon @@ -6504,9 +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, - colors: undefined.UndefinedOr[guilds.RoleColors] = 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, @@ -6531,8 +6527,6 @@ async def edit_role( If provided, the new color for the role. colour An alias for `color`. - colors - TODO hoist If provided, whether to hoist the role. icon diff --git a/hikari/colors.py b/hikari/colors.py index 4311ed6052..fd26be8379 100644 --- a/hikari/colors.py +++ b/hikari/colors.py @@ -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. @@ -585,3 +590,79 @@ def to_bytes( 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) + + 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 + + @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) + + 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 + + @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) + + 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 diff --git a/hikari/colours.py b/hikari/colours.py index 9931e6c382..13c1031f05 100644 --- a/hikari/colours.py +++ b/hikari/colours.py @@ -22,7 +22,7 @@ from __future__ import annotations -__all__: typing.Sequence[str] = ("Colour", "Colourish") +__all__: typing.Sequence[str] = ("Colour", "ColourGradient", "Colourish") import typing @@ -33,3 +33,6 @@ Colourish = colors.Colorish """An alias for [`hikari.colors.Colorish`][].""" + +ColourGradient = colors.ColorGradient +"""An alias for [`hikari.colors.ColorGradient`][].""" diff --git a/hikari/guilds.py b/hikari/guilds.py index ba5e1678ba..45b736e938 100644 --- a/hikari/guilds.py +++ b/hikari/guilds.py @@ -50,7 +50,6 @@ "PartialRole", "RESTGuild", "Role", - "RoleColors", "WelcomeChannel", "WelcomeScreen", ) @@ -77,8 +76,6 @@ if typing.TYPE_CHECKING: import datetime - from typing_extensions import Self - from hikari import colours from hikari import emojis as emojis_ from hikari import files @@ -1213,82 +1210,6 @@ def __str__(self) -> str: return self.name -@attrs.define(unsafe_hash=True, kw_only=True, weakref_slot=False) -class RoleColors: - """Represents a role colors object.""" - - _primary_color: colors.Colorish = attrs.field(hash=True, repr=True, alias="primary_color") - _secondary_color: colors.Colorish | None = attrs.field(hash=True, repr=True, alias="secondary_color", default=None) - _tertiary_color: colors.Colorish | None = attrs.field(hash=True, repr=True, alias="tertiary_color", default=None) - - @property - def primary_color(self) -> colors.Color: - """The primary color of the role.""" - return colors.Color.of(self._primary_color) - - def set_primary_color(self, primary_color: colors.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 - - @property - def secondary_color(self) -> colors.Color | None: - """The secondary color of the role.""" - if self._secondary_color is None: - return None - return colors.Color.of(self._secondary_color) - - def set_secondary_color(self, secondary_color: colors.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 - - @property - def tertiary_color(self) -> colors.Color | None: - """The tertiary color of the role.""" - if self._secondary_color is None: - return None - return colors.Color.of(self._secondary_color) - - def set_tertiary_color(self, tertiary_color: colors.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 - - @attrs.define(unsafe_hash=True, kw_only=True, weakref_slot=False) class Role(PartialRole): """Represents a guild bound role object.""" @@ -1299,7 +1220,7 @@ class Role(PartialRole): This will be applied to a member's name in chat if it's their top coloured role. """ - colors: RoleColors = attrs.field(eq=False, hash=False, repr=False) + 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) diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py index 4e823bab6a..bd1ce9ec40 100644 --- a/hikari/impl/entity_factory.py +++ b/hikari/impl/entity_factory.py @@ -637,6 +637,14 @@ def app(self) -> traits.RESTAware: """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 { + "primary_color": gradient.primary_color, + "secondary_color": gradient.secondary_color, + "tertiary_color": gradient.tertiary_color, + } + ###################### # APPLICATION MODELS # ###################### @@ -2108,7 +2116,7 @@ def deserialize_role( 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) - colors = guild_models.RoleColors( + colors = color_models.ColorGradient( primary_color=primary_color, secondary_color=secondary_color, tertiary_color=tertiary_color ) diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py index 3d6bdabf14..74e74c28ff 100644 --- a/hikari/impl/rest.py +++ b/hikari/impl/rest.py @@ -3813,9 +3813,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, - colors: undefined.UndefinedOr[guilds.RoleColors] = 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, @@ -3834,8 +3833,14 @@ async def create_role( 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) + 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) + else: + body.put("color", colour, conversion=colors_.Color.of) body.put("hoist", hoist) body.put("unicode_emoji", unicode_emoji) body.put("mentionable", mentionable) @@ -3868,9 +3873,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, - colors: undefined.UndefinedOr[guilds.RoleColors] = 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, @@ -3890,8 +3894,14 @@ async def edit_role( 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) + 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) + else: + body.put("color", colour, conversion=colors_.Color.of) body.put("hoist", hoist) body.put("unicode_emoji", unicode_emoji) body.put("mentionable", mentionable) diff --git a/tests/hikari/impl/test_entity_factory.py b/tests/hikari/impl/test_entity_factory.py index a7535fb9ff..467c3a9925 100644 --- a/tests/hikari/impl/test_entity_factory.py +++ b/tests/hikari/impl/test_entity_factory.py @@ -3444,7 +3444,7 @@ 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 == guild_models.RoleColors( + 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, diff --git a/tests/hikari/test_guilds.py b/tests/hikari/test_guilds.py index 83bb9ec8af..c13a8861e3 100644 --- a/tests/hikari/test_guilds.py +++ b/tests/hikari/test_guilds.py @@ -138,7 +138,7 @@ def model(self, mock_app): id=snowflakes.Snowflake(979899100), name="@everyone", color=colors.Color(0x1A2B3C), - colors=guilds.RoleColors( + colors=colors.ColorGradient( primary_color=colors.Color(0x1A2B3C), secondary_color=colors.Color(0x1A2B3C), tertiary_color=None ), guild_id=snowflakes.Snowflake(112233), @@ -159,7 +159,7 @@ def model(self, mock_app): def test_colour_property(self, model): assert model.colour == colors.Color(0x1A2B3C) - assert model.colors == guilds.RoleColors( + assert model.colors == colors.ColorGradient( primary_color=colors.Color(0x1A2B3C), secondary_color=colors.Color(0x1A2B3C), tertiary_color=None )