From 236e8ecc9aacd7d277f1a1cab94b732c9d01b3e0 Mon Sep 17 00:00:00 2001 From: ffouqueray Date: Tue, 21 Apr 2026 00:30:13 +0200 Subject: [PATCH 01/26] feat: Add SharedClientTheme support for customizable message appearance --- discord/__init__.py | 1 + discord/abc.py | 40 +++++++- discord/enums.py | 18 ++++ discord/http.py | 13 +++ discord/message.py | 18 ++++ discord/shared_client_theme.py | 146 +++++++++++++++++++++++++++ discord/types/message.py | 2 + discord/types/shared_client_theme.py | 38 +++++++ docs/api/data_classes.rst | 8 ++ docs/api/enums.rst | 27 +++++ 10 files changed, 310 insertions(+), 1 deletion(-) create mode 100644 discord/shared_client_theme.py create mode 100644 discord/types/shared_client_theme.py diff --git a/discord/__init__.py b/discord/__init__.py index 120069b750..76a80a18a7 100644 --- a/discord/__init__.py +++ b/discord/__init__.py @@ -68,6 +68,7 @@ from .role import * from .scheduled_events import * from .shard import * +from .shared_client_theme import * from .soundboard import * from .stage_instance import * from .sticker import * diff --git a/discord/abc.py b/discord/abc.py index 4b6ca924aa..59beaf367f 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -58,6 +58,7 @@ from .scheduled_events import ScheduledEvent from .sticker import GuildSticker, StickerItem from .utils import warn_deprecated +from .shared_client_theme import SharedClientThemeBaseType __all__ = ( "Snowflake", @@ -89,6 +90,7 @@ from .member import Member from .message import Message, MessageReference, PartialMessage from .poll import Poll + from .shared_client_theme import SharedClientTheme from .state import ConnectionState from .threads import Thread from .types.channel import Channel as ChannelPayload @@ -1388,6 +1390,7 @@ async def send( suppress: bool = ..., suppress_embeds: bool = ..., silent: bool = ..., + shared_client_theme: SharedClientTheme = ..., ) -> Message: ... @overload @@ -1410,6 +1413,7 @@ async def send( suppress: bool = ..., suppress_embeds: bool = ..., silent: bool = ..., + shared_client_theme: SharedClientTheme = ..., ) -> Message: ... @overload @@ -1432,6 +1436,7 @@ async def send( suppress: bool = ..., suppress_embeds: bool = ..., silent: bool = ..., + shared_client_theme: SharedClientTheme = ..., ) -> Message: ... @overload @@ -1454,6 +1459,7 @@ async def send( suppress: bool = ..., suppress_embeds: bool = ..., silent: bool = ..., + shared_client_theme: SharedClientTheme = ..., ) -> Message: ... async def send( @@ -1477,6 +1483,7 @@ async def send( suppress=None, suppress_embeds=None, silent=None, + shared_client_theme=None, ): """|coro| @@ -1496,6 +1503,9 @@ async def send( parameter should be used with a :class:`list` of :class:`~discord.Embed` objects. **Specifying both parameters will lead to an exception**. + To upload a shared client theme, the ``shared_client_theme`` parameter should be used + with a :class:`~discord.SharedClientTheme` object. + Parameters ---------- content: Optional[:class:`str`] @@ -1568,6 +1578,10 @@ async def send( The poll to send. .. versionadded:: 2.6 + shared_client_theme: :class:`SharedClientTheme` + The shared client theme to send. + + .. versionadded:: 2.8 Returns ------- @@ -1585,13 +1599,35 @@ async def send( you specified both ``file`` and ``files``, or you specified both ``embed`` and ``embeds``, or the ``reference`` object is not a :class:`~discord.Message`, - :class:`~discord.MessageReference` or :class:`~discord.PartialMessage`. + :class:`~discord.MessageReference` or :class:`~discord.PartialMessage`, + or the ``shared_client_theme`` object does not meet the required criteria. """ channel = await self._get_channel() state = self._state content = str(content) if content is not None else None + if shared_client_theme is not None: + if shared_client_theme.colors is None or len(shared_client_theme.colors) == 0: + raise InvalidArgument("shared_client_theme must have at least one color") + if len(shared_client_theme.colors) > 5: + raise InvalidArgument("shared_client_theme cannot have more than 5 colors") + + if shared_client_theme.gradient_angle is None : + raise InvalidArgument("shared_client_theme must have a gradient angle") + if shared_client_theme.gradient_angle < 0 or shared_client_theme.gradient_angle > 360: + raise InvalidArgument("shared_client_theme gradient angle must be between 0 and 360 degrees") + + if shared_client_theme.base_mix is None : + raise InvalidArgument("shared_client_theme must have a base mix value") + if shared_client_theme.base_mix < 0 or shared_client_theme.base_mix > 100 : + raise InvalidArgument("shared_client_theme base mix must be between 0 and 100") + + if shared_client_theme.base_theme is not None and not isinstance(shared_client_theme.base_theme, SharedClientThemeBaseType): + raise InvalidArgument("shared_client_theme base theme must be a SharedClientThemeBaseType enum value") + + shared_client_theme = shared_client_theme.to_dict() + if embed is not None and embeds is not None: raise InvalidArgument( "cannot pass both embed and embeds parameter to send()" @@ -1706,6 +1742,7 @@ async def send( components=components, flags=flags.value, poll=poll, + shared_client_theme=shared_client_theme, ) finally: for f in files: @@ -1725,6 +1762,7 @@ async def send( components=components, flags=flags.value, poll=poll, + shared_client_theme=shared_client_theme, ) ret = state.create_message(channel=channel, data=data) diff --git a/discord/enums.py b/discord/enums.py index 802bb41535..501b01c193 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -88,6 +88,7 @@ "SelectDefaultValueType", "ApplicationEventWebhookStatus", "InviteTargetUsersJobStatusCode", + "SharedClientThemeBaseType", ) @@ -1212,6 +1213,23 @@ class InviteTargetUsersJobStatusCode(Enum): failed = 3 +class SharedClientThemeBaseType(Enum): + """The base theme mode of a :class:`SharedClientTheme`. + + .. versionadded:: 2.8 + + .. note:: + + ``unset`` is treated as equivalent to ``dark`` by Discord. + """ + + unset = 0 + dark = 1 + light = 2 + darker = 3 + midnight = 4 + + T = TypeVar("T") diff --git a/discord/http.py b/discord/http.py index fc6ffb22c3..d9218e094f 100644 --- a/discord/http.py +++ b/discord/http.py @@ -84,6 +84,9 @@ poll, role, scheduled_events, + ) + from .types import shared_client_theme as shared_client_theme_type + from .types import ( sticker, template, threads, @@ -507,6 +510,7 @@ def send_message( components: list[components.Component] | None = None, flags: int | None = None, poll: poll.Poll | None = None, + shared_client_theme: shared_client_theme_type.SharedClientTheme | None = None, ) -> Response[message.Message]: r = Route("POST", "/channels/{channel_id}/messages", channel_id=channel_id) payload = {} @@ -547,6 +551,9 @@ def send_message( if poll: payload["poll"] = poll + if shared_client_theme: + payload["shared_client_theme"] = shared_client_theme + return self.request(r, json=payload) def send_typing(self, channel_id: Snowflake) -> Response[None]: @@ -571,6 +578,7 @@ def send_multipart_helper( components: list[components.Component] | None = None, flags: int | None = None, poll: poll.Poll | None = None, + shared_client_theme: shared_client_theme_type.SharedClientTheme | None = None, ) -> Response[message.Message]: form = [] @@ -598,6 +606,9 @@ def send_multipart_helper( if poll: payload["poll"] = poll + if shared_client_theme: + payload["shared_client_theme"] = shared_client_theme + attachments = [] form.append({"name": "payload_json"}) for index, file in enumerate(files): @@ -641,6 +652,7 @@ def send_files( components: list[components.Component] | None = None, flags: int | None = None, poll: poll.Poll | None = None, + shared_client_theme: shared_client_theme_type.SharedClientTheme | None = None, ) -> Response[message.Message]: r = Route("POST", "/channels/{channel_id}/messages", channel_id=channel_id) return self.send_multipart_helper( @@ -658,6 +670,7 @@ def send_files( components=components, flags=flags, poll=poll, + shared_client_theme=shared_client_theme, ) def edit_multipart_helper( diff --git a/discord/message.py b/discord/message.py index 1781bf45f1..ef7ad3f2da 100644 --- a/discord/message.py +++ b/discord/message.py @@ -58,6 +58,7 @@ from .partial_emoji import PartialEmoji from .poll import Poll from .reaction import Reaction +from .shared_client_theme import SharedClientTheme from .sticker import StickerItem from .threads import Thread from .utils import MISSING, escape_mentions, find, warn_deprecated @@ -90,6 +91,7 @@ from .types.message import MessageSnapshot as MessageSnapshotPayload from .types.message import Reaction as ReactionPayload from .types.poll import Poll as PollPayload + from .types.shared_client_theme import SharedClientTheme as SharedClientThemePayload from .types.snowflake import SnowflakeList from .types.threads import ThreadArchiveDuration from .types.user import User as UserPayload @@ -1039,6 +1041,10 @@ class Message(Hashable): The poll associated with this message, if applicable. .. versionadded:: 2.6 + shared_client_theme: Optional[:class:`SharedClientTheme`] + The shared client theme transmitted via this message, if applicable. + + .. versionadded:: 2.8 call: Optional[:class:`MessageCall`] The call information associated with this message, if applicable. @@ -1087,6 +1093,7 @@ class Message(Hashable): "_poll", "call", "snapshots", + "shared_client_theme", ) if TYPE_CHECKING: @@ -1217,6 +1224,14 @@ def __init__( except KeyError: self.call = None + self.shared_client_theme: SharedClientTheme | None + try: + self.shared_client_theme = SharedClientTheme.from_dict( + data["shared_client_theme"] + ) + except KeyError: + self.shared_client_theme = None + for handler in ("author", "member", "mentions", "mention_roles"): try: getattr(self, f"_handle_{handler}")(data[handler]) @@ -1328,6 +1343,9 @@ def _handle_poll(self, value: PollPayload) -> None: self._poll = Poll.from_dict(value, self) self._state.store_poll(self._poll, self.id) + def _handle_shared_client_theme(self, value: SharedClientThemePayload) -> None: + self.shared_client_theme = SharedClientTheme.from_dict(value) + def _handle_author(self, author: UserPayload) -> None: self.author = self._state.store_user(author) if isinstance(self.guild, Guild): diff --git a/discord/shared_client_theme.py b/discord/shared_client_theme.py new file mode 100644 index 0000000000..f741c93e8b --- /dev/null +++ b/discord/shared_client_theme.py @@ -0,0 +1,146 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Iterable, Union + +from .colour import Colour +from .enums import SharedClientThemeBaseType, try_enum + +__all__ = ("SharedClientTheme",) + + +if TYPE_CHECKING: + from .types.shared_client_theme import SharedClientTheme as SharedClientThemePayload + + ColourLike = Union[Colour, str, int] + + +def _coerce_colour(value: "ColourLike") -> str: + if isinstance(value, Colour): + return f"{value.value:0>6x}" + if isinstance(value, int): + return f"{value:0>6x}" + if isinstance(value, str): + stripped = value.lstrip("#") + if len(stripped) != 6 or any(c not in "0123456789abcdefABCDEF" for c in stripped): + raise ValueError( + f"{value!r} is not a valid hexadecimal color (expected format: 'rrggbb')" + ) + return stripped.lower() + raise TypeError( + f"colors must be Colour, str, or int, not {type(value).__name__}" + ) + + +class SharedClientTheme: + """Represents a shared client theme that can be sent in a message. + + A shared client theme lets users transmit a customized Discord client + appearance (colors, gradient, and base mode) through a message. + + .. versionadded:: 2.8 + + Attributes + ---------- + colors: List[:class:`str`] + The hexadecimal-encoded colors of the theme. A maximum of 5 colors. + gradient_angle: :class:`int` + The direction of the theme's colors, in degrees. Must be between ``0`` and ``360``. + base_mix: :class:`int` + The intensity of the theme's colors. Must be between ``0`` and ``100``. + base_theme: Optional[:class:`SharedClientThemeBaseType`] + The mode of the theme. Defaults to :attr:`SharedClientThemeBaseType.unset`, + which Discord treats as :attr:`SharedClientThemeBaseType.dark`. + """ + + __slots__ = ("colors", "gradient_angle", "base_mix", "base_theme") + + def __init__( + self, + colors: Iterable["ColourLike"], + *, + gradient_angle: int, + base_mix: int, + base_theme: SharedClientThemeBaseType | None = None, + ): + normalized = [_coerce_colour(c) for c in colors] + if not normalized: + raise ValueError("colors must contain at least one color") + if len(normalized) > 5: + raise ValueError("colors must contain at most 5 colors") + + if not isinstance(gradient_angle, int) or isinstance(gradient_angle, bool): + raise TypeError("gradient_angle must be an int") + if not 0 <= gradient_angle <= 360: + raise ValueError("gradient_angle must be between 0 and 360") + + if not isinstance(base_mix, int) or isinstance(base_mix, bool): + raise TypeError("base_mix must be an int") + if not 0 <= base_mix <= 100: + raise ValueError("base_mix must be between 0 and 100") + + if base_theme is not None and not isinstance( + base_theme, SharedClientThemeBaseType + ): + raise TypeError( + "base_theme must be a SharedClientThemeBaseType or None" + ) + + self.colors: list[str] = normalized + self.gradient_angle: int = gradient_angle + self.base_mix: int = base_mix + self.base_theme: SharedClientThemeBaseType | None = base_theme + + def __repr__(self) -> str: + return ( + f"" + ) + + def to_dict(self) -> SharedClientThemePayload: + payload: SharedClientThemePayload = { + "colors": list(self.colors), + "gradient_angle": self.gradient_angle, + "base_mix": self.base_mix, + } + if self.base_theme is not None: + payload["base_theme"] = self.base_theme.value + return payload + + @classmethod + def from_dict(cls, data: SharedClientThemePayload) -> SharedClientTheme: + base_theme_value = data.get("base_theme") + return cls( + colors=data.get("colors", []), + gradient_angle=data["gradient_angle"], + base_mix=data["base_mix"], + base_theme=( + try_enum(SharedClientThemeBaseType, base_theme_value) + if base_theme_value is not None + else None + ), + ) diff --git a/discord/types/message.py b/discord/types/message.py index c6a48881c7..48f35e881a 100644 --- a/discord/types/message.py +++ b/discord/types/message.py @@ -33,6 +33,7 @@ from .emoji import PartialEmoji from .member import Member, UserWithMember from .poll import Poll +from .shared_client_theme import SharedClientTheme from .snowflake import Snowflake, SnowflakeList from .sticker import StickerItem from .threads import Thread @@ -174,6 +175,7 @@ class Message(TypedDict): poll: Poll call: MessageCall message_snapshots: NotRequired[list[MessageSnapshot]] + shared_client_theme: NotRequired[SharedClientTheme] class MessagePin(TypedDict): diff --git a/discord/types/shared_client_theme.py b/discord/types/shared_client_theme.py new file mode 100644 index 0000000000..aa43c0a927 --- /dev/null +++ b/discord/types/shared_client_theme.py @@ -0,0 +1,38 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + +from __future__ import annotations + +from typing import Literal, TypedDict + +from typing_extensions import NotRequired + +SharedClientThemeBaseType = Literal[0, 1, 2, 3, 4] + + +class SharedClientTheme(TypedDict): + colors: list[str] + gradient_angle: int + base_mix: int + base_theme: NotRequired[SharedClientThemeBaseType] diff --git a/docs/api/data_classes.rst b/docs/api/data_classes.rst index a28cb2a726..08b83505d8 100644 --- a/docs/api/data_classes.rst +++ b/docs/api/data_classes.rst @@ -151,6 +151,14 @@ Poll .. autoclass:: PollResults :members: +Shared Client Theme +~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: SharedClientTheme + +.. autoclass:: SharedClientTheme + :members: + Flags diff --git a/docs/api/enums.rst b/docs/api/enums.rst index efa16a4a5e..4ab2dedd8b 100644 --- a/docs/api/enums.rst +++ b/docs/api/enums.rst @@ -2529,6 +2529,33 @@ of :class:`enum.Enum`. Represents the default layout. +.. class:: SharedClientThemeBaseType + + The base theme mode of a :class:`SharedClientTheme`. + + .. versionadded:: 2.8 + + .. attribute:: unset + + No explicit base theme. Treated as :attr:`dark` by Discord. + + .. attribute:: dark + + The dark base theme. + + .. attribute:: light + + The light base theme. + + .. attribute:: darker + + The darker base theme. + + .. attribute:: midnight + + The midnight base theme. + + .. class:: IntegrationType The integration type for an application. From 9116f68012512b1c36b3c779a1dba8c2fe060c32 Mon Sep 17 00:00:00 2001 From: ffouqueray Date: Tue, 21 Apr 2026 01:09:44 +0200 Subject: [PATCH 02/26] fix: Remove redundant validation checks for shared_client_theme --- discord/abc.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/discord/abc.py b/discord/abc.py index 59beaf367f..1486fc7a68 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -1608,24 +1608,6 @@ async def send( content = str(content) if content is not None else None if shared_client_theme is not None: - if shared_client_theme.colors is None or len(shared_client_theme.colors) == 0: - raise InvalidArgument("shared_client_theme must have at least one color") - if len(shared_client_theme.colors) > 5: - raise InvalidArgument("shared_client_theme cannot have more than 5 colors") - - if shared_client_theme.gradient_angle is None : - raise InvalidArgument("shared_client_theme must have a gradient angle") - if shared_client_theme.gradient_angle < 0 or shared_client_theme.gradient_angle > 360: - raise InvalidArgument("shared_client_theme gradient angle must be between 0 and 360 degrees") - - if shared_client_theme.base_mix is None : - raise InvalidArgument("shared_client_theme must have a base mix value") - if shared_client_theme.base_mix < 0 or shared_client_theme.base_mix > 100 : - raise InvalidArgument("shared_client_theme base mix must be between 0 and 100") - - if shared_client_theme.base_theme is not None and not isinstance(shared_client_theme.base_theme, SharedClientThemeBaseType): - raise InvalidArgument("shared_client_theme base theme must be a SharedClientThemeBaseType enum value") - shared_client_theme = shared_client_theme.to_dict() if embed is not None and embeds is not None: From 7c2ae1e2452df48077caa2ad59b54e8daad3c0cc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 23:17:44 +0000 Subject: [PATCH 03/26] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/abc.py | 2 +- discord/shared_client_theme.py | 16 +++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/discord/abc.py b/discord/abc.py index 1486fc7a68..eff53d135f 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -56,9 +56,9 @@ from .permissions import PermissionOverwrite, Permissions from .role import Role from .scheduled_events import ScheduledEvent +from .shared_client_theme import SharedClientThemeBaseType from .sticker import GuildSticker, StickerItem from .utils import warn_deprecated -from .shared_client_theme import SharedClientThemeBaseType __all__ = ( "Snowflake", diff --git a/discord/shared_client_theme.py b/discord/shared_client_theme.py index f741c93e8b..f688c1f245 100644 --- a/discord/shared_client_theme.py +++ b/discord/shared_client_theme.py @@ -38,21 +38,21 @@ ColourLike = Union[Colour, str, int] -def _coerce_colour(value: "ColourLike") -> str: +def _coerce_colour(value: ColourLike) -> str: if isinstance(value, Colour): return f"{value.value:0>6x}" if isinstance(value, int): return f"{value:0>6x}" if isinstance(value, str): stripped = value.lstrip("#") - if len(stripped) != 6 or any(c not in "0123456789abcdefABCDEF" for c in stripped): + if len(stripped) != 6 or any( + c not in "0123456789abcdefABCDEF" for c in stripped + ): raise ValueError( f"{value!r} is not a valid hexadecimal color (expected format: 'rrggbb')" ) return stripped.lower() - raise TypeError( - f"colors must be Colour, str, or int, not {type(value).__name__}" - ) + raise TypeError(f"colors must be Colour, str, or int, not {type(value).__name__}") class SharedClientTheme: @@ -80,7 +80,7 @@ class SharedClientTheme: def __init__( self, - colors: Iterable["ColourLike"], + colors: Iterable[ColourLike], *, gradient_angle: int, base_mix: int, @@ -105,9 +105,7 @@ def __init__( if base_theme is not None and not isinstance( base_theme, SharedClientThemeBaseType ): - raise TypeError( - "base_theme must be a SharedClientThemeBaseType or None" - ) + raise TypeError("base_theme must be a SharedClientThemeBaseType or None") self.colors: list[str] = normalized self.gradient_angle: int = gradient_angle From 03e795bffaee0abc0aa691afc5b01e7e0ad56a8c Mon Sep 17 00:00:00 2001 From: ffouqueray Date: Tue, 21 Apr 2026 01:24:30 +0200 Subject: [PATCH 04/26] fix: Update versionadded for SharedClientTheme to 2.9 across multiple files --- discord/abc.py | 2 +- discord/enums.py | 2 +- discord/message.py | 2 +- discord/shared_client_theme.py | 2 +- docs/api/enums.rst | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/discord/abc.py b/discord/abc.py index eff53d135f..08effd1e04 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -1581,7 +1581,7 @@ async def send( shared_client_theme: :class:`SharedClientTheme` The shared client theme to send. - .. versionadded:: 2.8 + .. versionadded:: 2.9 Returns ------- diff --git a/discord/enums.py b/discord/enums.py index 501b01c193..bb44fae289 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -1216,7 +1216,7 @@ class InviteTargetUsersJobStatusCode(Enum): class SharedClientThemeBaseType(Enum): """The base theme mode of a :class:`SharedClientTheme`. - .. versionadded:: 2.8 + .. versionadded:: 2.9 .. note:: diff --git a/discord/message.py b/discord/message.py index ef7ad3f2da..efea651e69 100644 --- a/discord/message.py +++ b/discord/message.py @@ -1044,7 +1044,7 @@ class Message(Hashable): shared_client_theme: Optional[:class:`SharedClientTheme`] The shared client theme transmitted via this message, if applicable. - .. versionadded:: 2.8 + .. versionadded:: 2.9 call: Optional[:class:`MessageCall`] The call information associated with this message, if applicable. diff --git a/discord/shared_client_theme.py b/discord/shared_client_theme.py index f688c1f245..2c74d6558c 100644 --- a/discord/shared_client_theme.py +++ b/discord/shared_client_theme.py @@ -61,7 +61,7 @@ class SharedClientTheme: A shared client theme lets users transmit a customized Discord client appearance (colors, gradient, and base mode) through a message. - .. versionadded:: 2.8 + .. versionadded:: 2.9 Attributes ---------- diff --git a/docs/api/enums.rst b/docs/api/enums.rst index 4ab2dedd8b..6a526594b2 100644 --- a/docs/api/enums.rst +++ b/docs/api/enums.rst @@ -2533,7 +2533,7 @@ of :class:`enum.Enum`. The base theme mode of a :class:`SharedClientTheme`. - .. versionadded:: 2.8 + .. versionadded:: 2.9 .. attribute:: unset From 2ac124bcbf22a4e82faf9fd541c906aefdadabaa Mon Sep 17 00:00:00 2001 From: ffouqueray Date: Tue, 21 Apr 2026 01:34:59 +0200 Subject: [PATCH 05/26] fix: Add SharedClientTheme in changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79b8942837..bf12262e5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ These changes are available on the `master` branch, but have not yet been releas - Support for **Python 3.14**. ([#2948](https://github.com/Pycord-Development/pycord/pull/2948)) +- Added `SharedClientTheme` support for sending client themes. + ([#3209](https://github.com/Pycord-Development/pycord/pull/3209)) ### Changed From e940162cd219f42270afcccf1497ebf2d51986ee Mon Sep 17 00:00:00 2001 From: ffouqueray Date: Tue, 21 Apr 2026 09:48:20 +0200 Subject: [PATCH 06/26] fix: Update the SharedClientTheme constructor to mark the required arguments. --- discord/shared_client_theme.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/shared_client_theme.py b/discord/shared_client_theme.py index 2c74d6558c..76db5a0d99 100644 --- a/discord/shared_client_theme.py +++ b/discord/shared_client_theme.py @@ -81,9 +81,9 @@ class SharedClientTheme: def __init__( self, colors: Iterable[ColourLike], - *, gradient_angle: int, base_mix: int, + *, base_theme: SharedClientThemeBaseType | None = None, ): normalized = [_coerce_colour(c) for c in colors] From d41f8f6e202e04a25611abdfb3c62c46d09fcd45 Mon Sep 17 00:00:00 2001 From: Flavien F <65244389+UnBonWhisky@users.noreply.github.com> Date: Sun, 10 May 2026 10:44:05 +0200 Subject: [PATCH 07/26] Update discord/shared_client_theme.py Co-authored-by: Dorukyum <53639936+Dorukyum@users.noreply.github.com> Signed-off-by: Flavien F <65244389+UnBonWhisky@users.noreply.github.com> --- discord/shared_client_theme.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/shared_client_theme.py b/discord/shared_client_theme.py index 76db5a0d99..ce2c7ec6df 100644 --- a/discord/shared_client_theme.py +++ b/discord/shared_client_theme.py @@ -35,7 +35,7 @@ if TYPE_CHECKING: from .types.shared_client_theme import SharedClientTheme as SharedClientThemePayload - ColourLike = Union[Colour, str, int] + ColourLike = Colour | str | int def _coerce_colour(value: ColourLike) -> str: From d4c1a55ed92c6857d56f38a37fdd98e89cfbf9f3 Mon Sep 17 00:00:00 2001 From: Flavien F <65244389+UnBonWhisky@users.noreply.github.com> Date: Sun, 10 May 2026 10:44:20 +0200 Subject: [PATCH 08/26] Update CHANGELOG.md Co-authored-by: plun1331 Signed-off-by: Flavien F <65244389+UnBonWhisky@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf12262e5d..da6a6e2d51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ These changes are available on the `master` branch, but have not yet been releas - Support for **Python 3.14**. ([#2948](https://github.com/Pycord-Development/pycord/pull/2948)) -- Added `SharedClientTheme` support for sending client themes. +- Added `SharedClientTheme` object for sending and receiving client themes. ([#3209](https://github.com/Pycord-Development/pycord/pull/3209)) ### Changed From 73c3ce128e150c400427b432f3f40374f8a717d3 Mon Sep 17 00:00:00 2001 From: Flavien F <65244389+UnBonWhisky@users.noreply.github.com> Date: Sun, 10 May 2026 10:44:32 +0200 Subject: [PATCH 09/26] Update discord/abc.py Co-authored-by: Dorukyum <53639936+Dorukyum@users.noreply.github.com> Signed-off-by: Flavien F <65244389+UnBonWhisky@users.noreply.github.com> --- discord/abc.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/discord/abc.py b/discord/abc.py index 08effd1e04..6efb2ef198 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -1502,10 +1502,6 @@ async def send( single :class:`~discord.Embed` object. To upload multiple embeds, the ``embeds`` parameter should be used with a :class:`list` of :class:`~discord.Embed` objects. **Specifying both parameters will lead to an exception**. - - To upload a shared client theme, the ``shared_client_theme`` parameter should be used - with a :class:`~discord.SharedClientTheme` object. - Parameters ---------- content: Optional[:class:`str`] From 6234b7741f1fec9450eb51e3d052e053c7790375 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 10 May 2026 08:45:02 +0000 Subject: [PATCH 10/26] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/abc.py | 1 + discord/shared_client_theme.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/discord/abc.py b/discord/abc.py index 6efb2ef198..207e4002b4 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -1502,6 +1502,7 @@ async def send( single :class:`~discord.Embed` object. To upload multiple embeds, the ``embeds`` parameter should be used with a :class:`list` of :class:`~discord.Embed` objects. **Specifying both parameters will lead to an exception**. + Parameters ---------- content: Optional[:class:`str`] diff --git a/discord/shared_client_theme.py b/discord/shared_client_theme.py index ce2c7ec6df..2765693921 100644 --- a/discord/shared_client_theme.py +++ b/discord/shared_client_theme.py @@ -24,7 +24,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, Union +from typing import TYPE_CHECKING, Iterable from .colour import Colour from .enums import SharedClientThemeBaseType, try_enum From d96ea81c114151939c4289c1d0f3fa222da9cdcd Mon Sep 17 00:00:00 2001 From: UnBonWhisky Date: Sun, 17 May 2026 18:53:36 +0200 Subject: [PATCH 11/26] fix: applied the comments and used colour as the main and color as an alias --- discord/abc.py | 3 +- discord/enums.py | 9 ----- discord/http.py | 8 ++--- discord/shared_client_theme.py | 50 ++++++++++++++++------------ discord/types/shared_client_theme.py | 2 -- 5 files changed, 33 insertions(+), 39 deletions(-) diff --git a/discord/abc.py b/discord/abc.py index 207e4002b4..5d13c6d4db 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -1596,8 +1596,7 @@ async def send( you specified both ``file`` and ``files``, or you specified both ``embed`` and ``embeds``, or the ``reference`` object is not a :class:`~discord.Message`, - :class:`~discord.MessageReference` or :class:`~discord.PartialMessage`, - or the ``shared_client_theme`` object does not meet the required criteria. + :class:`~discord.MessageReference` or :class:`~discord.PartialMessage`. """ channel = await self._get_channel() diff --git a/discord/enums.py b/discord/enums.py index bb44fae289..4cc00da6e3 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -1214,15 +1214,6 @@ class InviteTargetUsersJobStatusCode(Enum): class SharedClientThemeBaseType(Enum): - """The base theme mode of a :class:`SharedClientTheme`. - - .. versionadded:: 2.9 - - .. note:: - - ``unset`` is treated as equivalent to ``dark`` by Discord. - """ - unset = 0 dark = 1 light = 2 diff --git a/discord/http.py b/discord/http.py index d9218e094f..d835e4b5a6 100644 --- a/discord/http.py +++ b/discord/http.py @@ -85,7 +85,6 @@ role, scheduled_events, ) - from .types import shared_client_theme as shared_client_theme_type from .types import ( sticker, template, @@ -94,6 +93,7 @@ webhook, welcome_screen, widget, + shared_client_theme, ) from .types.invite import ( InviteTargetUsersJobStatus as InviteTargetUsersJobStatusPayload, @@ -510,7 +510,7 @@ def send_message( components: list[components.Component] | None = None, flags: int | None = None, poll: poll.Poll | None = None, - shared_client_theme: shared_client_theme_type.SharedClientTheme | None = None, + shared_client_theme: shared_client_theme.SharedClientTheme | None = None, ) -> Response[message.Message]: r = Route("POST", "/channels/{channel_id}/messages", channel_id=channel_id) payload = {} @@ -578,7 +578,7 @@ def send_multipart_helper( components: list[components.Component] | None = None, flags: int | None = None, poll: poll.Poll | None = None, - shared_client_theme: shared_client_theme_type.SharedClientTheme | None = None, + shared_client_theme: shared_client_theme.SharedClientTheme | None = None, ) -> Response[message.Message]: form = [] @@ -652,7 +652,7 @@ def send_files( components: list[components.Component] | None = None, flags: int | None = None, poll: poll.Poll | None = None, - shared_client_theme: shared_client_theme_type.SharedClientTheme | None = None, + shared_client_theme: shared_client_theme.SharedClientTheme | None = None, ) -> Response[message.Message]: r = Route("POST", "/channels/{channel_id}/messages", channel_id=channel_id) return self.send_multipart_helper( diff --git a/discord/shared_client_theme.py b/discord/shared_client_theme.py index 2765693921..906bc8c8f4 100644 --- a/discord/shared_client_theme.py +++ b/discord/shared_client_theme.py @@ -38,21 +38,21 @@ ColourLike = Colour | str | int -def _coerce_colour(value: ColourLike) -> str: +def _coerce_colour(value: ColourLike) -> Colour: if isinstance(value, Colour): - return f"{value.value:0>6x}" + return value if isinstance(value, int): - return f"{value:0>6x}" + return Colour(value) if isinstance(value, str): stripped = value.lstrip("#") if len(stripped) != 6 or any( c not in "0123456789abcdefABCDEF" for c in stripped ): raise ValueError( - f"{value!r} is not a valid hexadecimal color (expected format: 'rrggbb')" + f"{value!r} is not a valid hexadecimal colour (expected format: 'rrggbb')" ) - return stripped.lower() - raise TypeError(f"colors must be Colour, str, or int, not {type(value).__name__}") + return Colour(int(stripped, 16)) + raise TypeError(f"colours must be Colour, str, or int, not {type(value).__name__}") class SharedClientTheme: @@ -65,8 +65,10 @@ class SharedClientTheme: Attributes ---------- - colors: List[:class:`str`] - The hexadecimal-encoded colors of the theme. A maximum of 5 colors. + colours: List[:class:`Colour`] + The colours of the theme. A maximum of 5 colours. + colors: List[:class:`Colour`] + Alias for :attr:`colours`. gradient_angle: :class:`int` The direction of the theme's colors, in degrees. Must be between ``0`` and ``360``. base_mix: :class:`int` @@ -76,29 +78,29 @@ class SharedClientTheme: which Discord treats as :attr:`SharedClientThemeBaseType.dark`. """ - __slots__ = ("colors", "gradient_angle", "base_mix", "base_theme") + __slots__ = ("colours", "gradient_angle", "base_mix", "base_theme") def __init__( self, - colors: Iterable[ColourLike], gradient_angle: int, base_mix: int, + colors: Iterable[ColourLike] | None = None, + colours: Iterable[ColourLike] | None = None, *, base_theme: SharedClientThemeBaseType | None = None, ): - normalized = [_coerce_colour(c) for c in colors] - if not normalized: - raise ValueError("colors must contain at least one color") + if colors is None and colours is None: + raise ValueError("colors or colours must be provided") + if colours is None: + colours = colors + + normalized = [_coerce_colour(c) for c in colours] if len(normalized) > 5: - raise ValueError("colors must contain at most 5 colors") + raise ValueError("colours must contain at most 5 colours") - if not isinstance(gradient_angle, int) or isinstance(gradient_angle, bool): - raise TypeError("gradient_angle must be an int") if not 0 <= gradient_angle <= 360: raise ValueError("gradient_angle must be between 0 and 360") - if not isinstance(base_mix, int) or isinstance(base_mix, bool): - raise TypeError("base_mix must be an int") if not 0 <= base_mix <= 100: raise ValueError("base_mix must be between 0 and 100") @@ -107,21 +109,25 @@ def __init__( ): raise TypeError("base_theme must be a SharedClientThemeBaseType or None") - self.colors: list[str] = normalized + self.colours: list[Colour] = normalized self.gradient_angle: int = gradient_angle self.base_mix: int = base_mix self.base_theme: SharedClientThemeBaseType | None = base_theme + @property + def colors(self) -> list[Colour]: + return self.colours + def __repr__(self) -> str: return ( - f"" ) def to_dict(self) -> SharedClientThemePayload: payload: SharedClientThemePayload = { - "colors": list(self.colors), + "colors": [f"{c.value:0>6x}" for c in self.colours], "gradient_angle": self.gradient_angle, "base_mix": self.base_mix, } @@ -133,7 +139,7 @@ def to_dict(self) -> SharedClientThemePayload: def from_dict(cls, data: SharedClientThemePayload) -> SharedClientTheme: base_theme_value = data.get("base_theme") return cls( - colors=data.get("colors", []), + colours=data.get("colors", []), gradient_angle=data["gradient_angle"], base_mix=data["base_mix"], base_theme=( diff --git a/discord/types/shared_client_theme.py b/discord/types/shared_client_theme.py index aa43c0a927..eebbcd0864 100644 --- a/discord/types/shared_client_theme.py +++ b/discord/types/shared_client_theme.py @@ -22,8 +22,6 @@ DEALINGS IN THE SOFTWARE. """ -from __future__ import annotations - from typing import Literal, TypedDict from typing_extensions import NotRequired From 2ad94478cc010906e0692f69f87d06bf0aae16ed Mon Sep 17 00:00:00 2001 From: UnBonWhisky Date: Sun, 17 May 2026 18:55:19 +0200 Subject: [PATCH 12/26] docs: moved SharedClientTheme from data_class to models --- docs/api/data_classes.rst | 8 -------- docs/api/models.rst | 5 +++++ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/docs/api/data_classes.rst b/docs/api/data_classes.rst index 08b83505d8..a28cb2a726 100644 --- a/docs/api/data_classes.rst +++ b/docs/api/data_classes.rst @@ -151,14 +151,6 @@ Poll .. autoclass:: PollResults :members: -Shared Client Theme -~~~~~~~~~~~~~~~~~~~ - -.. attributetable:: SharedClientTheme - -.. autoclass:: SharedClientTheme - :members: - Flags diff --git a/docs/api/models.rst b/docs/api/models.rst index 12094bc698..b2fcbcd1d1 100644 --- a/docs/api/models.rst +++ b/docs/api/models.rst @@ -121,6 +121,11 @@ Messages .. autoclass:: ReactionCountDetails() :members: +.. attributetable:: SharedClientTheme + +.. autoclass:: SharedClientTheme + :members: + Monetization ------------ From b8a19944a229c74e9449deaa984f958832664416 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 17 May 2026 16:55:50 +0000 Subject: [PATCH 13/26] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/http.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/discord/http.py b/discord/http.py index d835e4b5a6..05aa10bf35 100644 --- a/discord/http.py +++ b/discord/http.py @@ -84,8 +84,7 @@ poll, role, scheduled_events, - ) - from .types import ( + shared_client_theme, sticker, template, threads, @@ -93,7 +92,6 @@ webhook, welcome_screen, widget, - shared_client_theme, ) from .types.invite import ( InviteTargetUsersJobStatus as InviteTargetUsersJobStatusPayload, From 4401b08d33c822726866bd21640b3856ac8ffca7 Mon Sep 17 00:00:00 2001 From: UnBonWhisky Date: Sat, 23 May 2026 13:15:02 +0200 Subject: [PATCH 14/26] fix: refactor SharedClientTheme initialization and color handling --- discord/shared_client_theme.py | 47 ++++++++++------------------------ 1 file changed, 14 insertions(+), 33 deletions(-) diff --git a/discord/shared_client_theme.py b/discord/shared_client_theme.py index 906bc8c8f4..ecc06ab981 100644 --- a/discord/shared_client_theme.py +++ b/discord/shared_client_theme.py @@ -28,6 +28,7 @@ from .colour import Colour from .enums import SharedClientThemeBaseType, try_enum +from .utils import MISSING __all__ = ("SharedClientTheme",) @@ -35,25 +36,6 @@ if TYPE_CHECKING: from .types.shared_client_theme import SharedClientTheme as SharedClientThemePayload - ColourLike = Colour | str | int - - -def _coerce_colour(value: ColourLike) -> Colour: - if isinstance(value, Colour): - return value - if isinstance(value, int): - return Colour(value) - if isinstance(value, str): - stripped = value.lstrip("#") - if len(stripped) != 6 or any( - c not in "0123456789abcdefABCDEF" for c in stripped - ): - raise ValueError( - f"{value!r} is not a valid hexadecimal colour (expected format: 'rrggbb')" - ) - return Colour(int(stripped, 16)) - raise TypeError(f"colours must be Colour, str, or int, not {type(value).__name__}") - class SharedClientTheme: """Represents a shared client theme that can be sent in a message. @@ -82,21 +64,19 @@ class SharedClientTheme: def __init__( self, - gradient_angle: int, - base_mix: int, - colors: Iterable[ColourLike] | None = None, - colours: Iterable[ColourLike] | None = None, + gradient_angle: int = 0, + base_mix: int = 0, + colors: Iterable[Colour] = MISSING, + colours: Iterable[Colour] = MISSING, *, - base_theme: SharedClientThemeBaseType | None = None, + base_theme: SharedClientThemeBaseType = SharedClientThemeBaseType.unset, ): - if colors is None and colours is None: - raise ValueError("colors or colours must be provided") - if colours is None: - colours = colors + colours = colours if colours is not MISSING else colors - normalized = [_coerce_colour(c) for c in colours] - if len(normalized) > 5: - raise ValueError("colours must contain at most 5 colours") + if len(colours or []) > 5: + raise ValueError("colors or colours must contain at most 5 colours") + if colours is MISSING: + raise TypeError("colors or colours must be provided") if not 0 <= gradient_angle <= 360: raise ValueError("gradient_angle must be between 0 and 360") @@ -109,7 +89,7 @@ def __init__( ): raise TypeError("base_theme must be a SharedClientThemeBaseType or None") - self.colours: list[Colour] = normalized + self.colours: list[Colour] = colours self.gradient_angle: int = gradient_angle self.base_mix: int = base_mix self.base_theme: SharedClientThemeBaseType | None = base_theme @@ -138,8 +118,9 @@ def to_dict(self) -> SharedClientThemePayload: @classmethod def from_dict(cls, data: SharedClientThemePayload) -> SharedClientTheme: base_theme_value = data.get("base_theme") + colours = [Colour(int(c, 16)) for c in data.get("colors", [])] return cls( - colours=data.get("colors", []), + colours=colours, gradient_angle=data["gradient_angle"], base_mix=data["base_mix"], base_theme=( From c99567d14852b2be89652bf4d3d66c65d30bc67d Mon Sep 17 00:00:00 2001 From: UnBonWhisky Date: Sat, 23 May 2026 13:20:42 +0200 Subject: [PATCH 15/26] fix: update from_dict method to use Self type hint --- discord/shared_client_theme.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/discord/shared_client_theme.py b/discord/shared_client_theme.py index ecc06ab981..d370dd502a 100644 --- a/discord/shared_client_theme.py +++ b/discord/shared_client_theme.py @@ -29,6 +29,7 @@ from .colour import Colour from .enums import SharedClientThemeBaseType, try_enum from .utils import MISSING +from typing_extensions import Self __all__ = ("SharedClientTheme",) @@ -116,7 +117,7 @@ def to_dict(self) -> SharedClientThemePayload: return payload @classmethod - def from_dict(cls, data: SharedClientThemePayload) -> SharedClientTheme: + def from_dict(cls, data: SharedClientThemePayload) -> Self: base_theme_value = data.get("base_theme") colours = [Colour(int(c, 16)) for c in data.get("colors", [])] return cls( From 23bd2cf945d00e24c47df07f2dfb2383211993ca Mon Sep 17 00:00:00 2001 From: UnBonWhisky Date: Sat, 23 May 2026 13:22:56 +0200 Subject: [PATCH 16/26] fix: add parentheses to SharedClientTheme autoclass declaration --- docs/api/models.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/models.rst b/docs/api/models.rst index b2fcbcd1d1..27bbb42bc5 100644 --- a/docs/api/models.rst +++ b/docs/api/models.rst @@ -123,7 +123,7 @@ Messages .. attributetable:: SharedClientTheme -.. autoclass:: SharedClientTheme +.. autoclass:: SharedClientTheme() :members: Monetization From 7d4d65d6ee1a6b6add147bac86efbc72301762c4 Mon Sep 17 00:00:00 2001 From: UnBonWhisky Date: Sat, 23 May 2026 13:47:19 +0200 Subject: [PATCH 17/26] fix: refactor SharedClientTheme to use dataclass and update type hints --- discord/shared_client_theme.py | 28 +++++++++++++++++----------- docs/api/data_classes.rst | 5 +++++ docs/api/models.rst | 5 ----- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/discord/shared_client_theme.py b/discord/shared_client_theme.py index d370dd502a..532631f1bb 100644 --- a/discord/shared_client_theme.py +++ b/discord/shared_client_theme.py @@ -24,7 +24,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable +from typing import TYPE_CHECKING, Sequence +from dataclasses import dataclass from .colour import Colour from .enums import SharedClientThemeBaseType, try_enum @@ -37,7 +38,7 @@ if TYPE_CHECKING: from .types.shared_client_theme import SharedClientTheme as SharedClientThemePayload - +@dataclass class SharedClientTheme: """Represents a shared client theme that can be sent in a message. @@ -60,15 +61,18 @@ class SharedClientTheme: The mode of the theme. Defaults to :attr:`SharedClientThemeBaseType.unset`, which Discord treats as :attr:`SharedClientThemeBaseType.dark`. """ - - __slots__ = ("colours", "gradient_angle", "base_mix", "base_theme") + + gradient_angle: int = 0 + base_mix: int = 0 + colours: list[Colour] = MISSING + base_theme: SharedClientThemeBaseType | None = SharedClientThemeBaseType.unset def __init__( self, gradient_angle: int = 0, base_mix: int = 0, - colors: Iterable[Colour] = MISSING, - colours: Iterable[Colour] = MISSING, + colors: Sequence[Colour] = MISSING, + colours: Sequence[Colour] = MISSING, *, base_theme: SharedClientThemeBaseType = SharedClientThemeBaseType.unset, ): @@ -89,11 +93,13 @@ def __init__( base_theme, SharedClientThemeBaseType ): raise TypeError("base_theme must be a SharedClientThemeBaseType or None") - - self.colours: list[Colour] = colours - self.gradient_angle: int = gradient_angle - self.base_mix: int = base_mix - self.base_theme: SharedClientThemeBaseType | None = base_theme + + super().__init__( + gradient_angle=gradient_angle, + base_mix=base_mix, + colours=list(colours), + base_theme=base_theme, + ) @property def colors(self) -> list[Colour]: diff --git a/docs/api/data_classes.rst b/docs/api/data_classes.rst index a28cb2a726..7fbb16b0f1 100644 --- a/docs/api/data_classes.rst +++ b/docs/api/data_classes.rst @@ -89,6 +89,11 @@ Message .. autoclass:: File :members: +.. attributetable:: SharedClientTheme + +.. autoclass:: SharedClientTheme + :members: + Embed ~~~~~ diff --git a/docs/api/models.rst b/docs/api/models.rst index 27bbb42bc5..12094bc698 100644 --- a/docs/api/models.rst +++ b/docs/api/models.rst @@ -121,11 +121,6 @@ Messages .. autoclass:: ReactionCountDetails() :members: -.. attributetable:: SharedClientTheme - -.. autoclass:: SharedClientTheme() - :members: - Monetization ------------ From 5a1f0c11d3fda1d20faace3ef41adb1bad28cc95 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 23 May 2026 11:47:49 +0000 Subject: [PATCH 18/26] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/shared_client_theme.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/discord/shared_client_theme.py b/discord/shared_client_theme.py index 532631f1bb..5becc2b701 100644 --- a/discord/shared_client_theme.py +++ b/discord/shared_client_theme.py @@ -24,13 +24,14 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Sequence from dataclasses import dataclass +from typing import TYPE_CHECKING, Sequence + +from typing_extensions import Self from .colour import Colour from .enums import SharedClientThemeBaseType, try_enum from .utils import MISSING -from typing_extensions import Self __all__ = ("SharedClientTheme",) @@ -38,6 +39,7 @@ if TYPE_CHECKING: from .types.shared_client_theme import SharedClientTheme as SharedClientThemePayload + @dataclass class SharedClientTheme: """Represents a shared client theme that can be sent in a message. @@ -61,7 +63,7 @@ class SharedClientTheme: The mode of the theme. Defaults to :attr:`SharedClientThemeBaseType.unset`, which Discord treats as :attr:`SharedClientThemeBaseType.dark`. """ - + gradient_angle: int = 0 base_mix: int = 0 colours: list[Colour] = MISSING @@ -93,7 +95,7 @@ def __init__( base_theme, SharedClientThemeBaseType ): raise TypeError("base_theme must be a SharedClientThemeBaseType or None") - + super().__init__( gradient_angle=gradient_angle, base_mix=base_mix, From aa00312e6880e4fc3e13d69c1576b6a31c5871eb Mon Sep 17 00:00:00 2001 From: UnBonWhisky Date: Sat, 23 May 2026 13:56:21 +0200 Subject: [PATCH 19/26] fix: update SharedClientTheme to use field for colours with default factory --- discord/shared_client_theme.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/discord/shared_client_theme.py b/discord/shared_client_theme.py index 5becc2b701..7014871d9b 100644 --- a/discord/shared_client_theme.py +++ b/discord/shared_client_theme.py @@ -24,10 +24,9 @@ from __future__ import annotations -from dataclasses import dataclass from typing import TYPE_CHECKING, Sequence - from typing_extensions import Self +from dataclasses import dataclass, field from .colour import Colour from .enums import SharedClientThemeBaseType, try_enum @@ -66,7 +65,7 @@ class SharedClientTheme: gradient_angle: int = 0 base_mix: int = 0 - colours: list[Colour] = MISSING + colours: list[Colour] = field(default_factory=list) base_theme: SharedClientThemeBaseType | None = SharedClientThemeBaseType.unset def __init__( @@ -95,13 +94,11 @@ def __init__( base_theme, SharedClientThemeBaseType ): raise TypeError("base_theme must be a SharedClientThemeBaseType or None") - - super().__init__( - gradient_angle=gradient_angle, - base_mix=base_mix, - colours=list(colours), - base_theme=base_theme, - ) + + self.gradient_angle = gradient_angle + self.base_mix = base_mix + self.colours = list(colours) + self.base_theme = base_theme @property def colors(self) -> list[Colour]: From 12adfcb9d3dbe3649fe3a2e63de70464871c47a5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 23 May 2026 11:59:08 +0000 Subject: [PATCH 20/26] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/shared_client_theme.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/discord/shared_client_theme.py b/discord/shared_client_theme.py index 7014871d9b..2b54b6b909 100644 --- a/discord/shared_client_theme.py +++ b/discord/shared_client_theme.py @@ -24,9 +24,10 @@ from __future__ import annotations +from dataclasses import dataclass, field from typing import TYPE_CHECKING, Sequence + from typing_extensions import Self -from dataclasses import dataclass, field from .colour import Colour from .enums import SharedClientThemeBaseType, try_enum @@ -94,7 +95,7 @@ def __init__( base_theme, SharedClientThemeBaseType ): raise TypeError("base_theme must be a SharedClientThemeBaseType or None") - + self.gradient_angle = gradient_angle self.base_mix = base_mix self.colours = list(colours) From b827ed1a104d7506bef41a2b3c61505854986f2f Mon Sep 17 00:00:00 2001 From: UnBonWhisky Date: Sat, 23 May 2026 14:11:55 +0200 Subject: [PATCH 21/26] fix: update SharedClientTheme to use field for colours and adjust dataclass initialization --- discord/shared_client_theme.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/discord/shared_client_theme.py b/discord/shared_client_theme.py index 2b54b6b909..835d9868c9 100644 --- a/discord/shared_client_theme.py +++ b/discord/shared_client_theme.py @@ -24,7 +24,7 @@ from __future__ import annotations -from dataclasses import dataclass, field +from dataclasses import dataclass from typing import TYPE_CHECKING, Sequence from typing_extensions import Self @@ -40,7 +40,7 @@ from .types.shared_client_theme import SharedClientTheme as SharedClientThemePayload -@dataclass +@dataclass(init=False) class SharedClientTheme: """Represents a shared client theme that can be sent in a message. @@ -66,7 +66,7 @@ class SharedClientTheme: gradient_angle: int = 0 base_mix: int = 0 - colours: list[Colour] = field(default_factory=list) + colours: list[Colour] base_theme: SharedClientThemeBaseType | None = SharedClientThemeBaseType.unset def __init__( @@ -77,7 +77,7 @@ def __init__( colours: Sequence[Colour] = MISSING, *, base_theme: SharedClientThemeBaseType = SharedClientThemeBaseType.unset, - ): + ) -> None : colours = colours if colours is not MISSING else colors if len(colours or []) > 5: From a53d6010189a54d6623eb19e5431c3930d233f45 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 23 May 2026 12:12:25 +0000 Subject: [PATCH 22/26] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/shared_client_theme.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/shared_client_theme.py b/discord/shared_client_theme.py index 835d9868c9..755f94e817 100644 --- a/discord/shared_client_theme.py +++ b/discord/shared_client_theme.py @@ -77,7 +77,7 @@ def __init__( colours: Sequence[Colour] = MISSING, *, base_theme: SharedClientThemeBaseType = SharedClientThemeBaseType.unset, - ) -> None : + ) -> None: colours = colours if colours is not MISSING else colors if len(colours or []) > 5: From 4e8926ceb87aace23bb70d5a18e5f855df0a8f1b Mon Sep 17 00:00:00 2001 From: UnBonWhisky Date: Sat, 23 May 2026 14:15:08 +0200 Subject: [PATCH 23/26] fix: remove __repr__ method from SharedClientTheme class --- discord/shared_client_theme.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/discord/shared_client_theme.py b/discord/shared_client_theme.py index 755f94e817..63f4f90328 100644 --- a/discord/shared_client_theme.py +++ b/discord/shared_client_theme.py @@ -105,13 +105,6 @@ def __init__( def colors(self) -> list[Colour]: return self.colours - def __repr__(self) -> str: - return ( - f"" - ) - def to_dict(self) -> SharedClientThemePayload: payload: SharedClientThemePayload = { "colors": [f"{c.value:0>6x}" for c in self.colours], From 28c746f9ded1e0065331747c4441034bec035e40 Mon Sep 17 00:00:00 2001 From: UnBonWhisky <65244389+UnBonWhisky@users.noreply.github.com> Date: Sat, 23 May 2026 14:31:13 +0200 Subject: [PATCH 24/26] Update discord/message.py Co-authored-by: Paillat Signed-off-by: UnBonWhisky <65244389+UnBonWhisky@users.noreply.github.com> --- discord/message.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/discord/message.py b/discord/message.py index efea651e69..04eb6fd45a 100644 --- a/discord/message.py +++ b/discord/message.py @@ -1224,14 +1224,13 @@ def __init__( except KeyError: self.call = None - self.shared_client_theme: SharedClientTheme | None - try: - self.shared_client_theme = SharedClientTheme.from_dict( - data["shared_client_theme"] + if shared_client_theme := data.get("shared_client_theme"): + self.shared_client_theme: SharedClientTheme | None = SharedClientTheme.from_dict( + shared_client_theme ) - except KeyError: - self.shared_client_theme = None - + else: + self.shared_client_theme = None + for handler in ("author", "member", "mentions", "mention_roles"): try: getattr(self, f"_handle_{handler}")(data[handler]) From cfdcb80d807e3c1db075ee6774e8590ec8800205 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 23 May 2026 12:32:00 +0000 Subject: [PATCH 25/26] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/message.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/discord/message.py b/discord/message.py index 04eb6fd45a..6986e98a88 100644 --- a/discord/message.py +++ b/discord/message.py @@ -1225,12 +1225,12 @@ def __init__( self.call = None if shared_client_theme := data.get("shared_client_theme"): - self.shared_client_theme: SharedClientTheme | None = SharedClientTheme.from_dict( - shared_client_theme + self.shared_client_theme: SharedClientTheme | None = ( + SharedClientTheme.from_dict(shared_client_theme) ) else: - self.shared_client_theme = None - + self.shared_client_theme = None + for handler in ("author", "member", "mentions", "mention_roles"): try: getattr(self, f"_handle_{handler}")(data[handler]) From 383b28e95efe63b9f31236713535c99c5a41721c Mon Sep 17 00:00:00 2001 From: UnBonWhisky <65244389+UnBonWhisky@users.noreply.github.com> Date: Tue, 9 Jun 2026 12:49:37 +0200 Subject: [PATCH 26/26] Update CHANGELOG.md Co-authored-by: Paillat Signed-off-by: UnBonWhisky <65244389+UnBonWhisky@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 242c017e86..5d7aec805c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ released. ### Added -- Added `SharedClientTheme` object for sending and receiving client themes. +- Added support for sending and receiving client themes. ([#3209](https://github.com/Pycord-Development/pycord/pull/3209)) ### Changed