diff --git a/discord/asset.py b/discord/asset.py index 7d622984..c6f038cd 100644 --- a/discord/asset.py +++ b/discord/asset.py @@ -245,6 +245,15 @@ class Asset(AssetMixin): animated=animated, ) + @classmethod + def _from_role_icon(cls, state, role_id: int, role_hash: str) -> Asset: + return cls( + state, + url=f"{cls.BASE}/role-icons/{role_id}/{role_hash}.png", + key=role_hash, + animated=False, + ) + def __str__(self) -> str: return self._url diff --git a/discord/guild.py b/discord/guild.py index a5dfafaf..db778b73 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -2448,6 +2448,8 @@ class Guild(Hashable): colour: Union[Colour, int] = ..., hoist: bool = ..., mentionable: bool = ..., + icon: bytes = ..., + emoji: str = ..., ) -> Role: ... @@ -2461,6 +2463,8 @@ class Guild(Hashable): color: Union[Colour, int] = ..., hoist: bool = ..., mentionable: bool = ..., + icon: bytes = ..., + emoji: str = ..., ) -> Role: ... @@ -2473,6 +2477,8 @@ class Guild(Hashable): colour: Union[Colour, int] = MISSING, hoist: bool = MISSING, mentionable: bool = MISSING, + icon: bytes = MISSING, + emoji: str = MISSING, reason: Optional[str] = None, ) -> Role: """|coro| @@ -2502,6 +2508,10 @@ class Guild(Hashable): mentionable: :class:`bool` Indicates if the role should be mentionable by others. Defaults to ``False``. + emoji: :class:`str` + The unicode emoji that is shown next to users with the role. + icon: :class:`bytes` + A custom image that is shown next to users with the role. reason: Optional[:class:`str`] The reason for creating this role. Shows up on the audit log. @@ -2540,6 +2550,12 @@ class Guild(Hashable): if name is not MISSING: fields["name"] = name + if emoji is not MISSING: + fields["unicode_emoji"] = emoji + + if icon is not MISSING: + fields["icon"] = utils._bytes_to_base64_data(icon) + data = await self._state.http.create_role(self.id, reason=reason, **fields) role = Role(guild=self, data=data, state=self._state) diff --git a/discord/role.py b/discord/role.py index b408e206..56627a14 100644 --- a/discord/role.py +++ b/discord/role.py @@ -23,13 +23,14 @@ DEALINGS IN THE SOFTWARE. """ from __future__ import annotations -from typing import Any, Dict, List, Optional, TypeVar, Union, overload, TYPE_CHECKING +from typing import Any, Dict, List, Optional, TypeVar, Union, TYPE_CHECKING +from .asset import Asset from .permissions import Permissions from .errors import InvalidArgument from .colour import Colour from .mixins import Hashable -from .utils import snowflake_time, _get_as_snowflake, MISSING +from .utils import snowflake_time, _get_as_snowflake, MISSING, _bytes_to_base64_data __all__ = ( "RoleTags", @@ -158,7 +159,15 @@ class Role(Hashable): guild: :class:`Guild` The guild the role belongs to. hoist: :class:`bool` - Indicates if the role will be displayed separately from other members. + Indicates if the role will be displayed separately from other members. + icon: Optional[:class:`Asset`] + A custom image that is shown next to users with the role. + + .. versionadded:: 2.0 + emoji: Optional[:class:`str`] + The unicode emoji that is shown next to users with the role. + + .. versionadded:: 2.0 position: :class:`int` The position of the role. This number is usually positive. The bottom role has a position of 0. @@ -191,6 +200,8 @@ class Role(Hashable): "hoist", "guild", "tags", + "_icon", + "emoji", "_state", ) @@ -251,6 +262,8 @@ class Role(Hashable): self.position: int = data.get("position", 0) self._colour: int = data.get("color", 0) self.hoist: bool = data.get("hoist", False) + self.emoji: Optional[str] = data.get("unicode_emoji") + self._icon: Optional[str] = data.get("icon") self.managed: bool = data.get("managed", False) self.mentionable: bool = data.get("mentionable", False) self.tags: Optional[RoleTags] @@ -318,6 +331,13 @@ class Role(Hashable): """:class:`str`: Returns a string that allows you to mention a role.""" return f"<@&{self.id}>" + @property + def icon(self) -> Optional[Asset]: + """Optional[:class:`Asset`]: Returns the custom icon shown next to users with the role, if it exists.""" + if self._icon is None: + return + return Asset._from_role_icon(self._state, self.id, self._icon) + @property def members(self) -> List[Member]: """List[:class:`Member`]: Returns all the members with this role.""" @@ -361,6 +381,8 @@ class Role(Hashable): hoist: bool = MISSING, mentionable: bool = MISSING, position: int = MISSING, + icon: bytes = MISSING, + emoji: str = MISSING, reason: Optional[str] = MISSING, ) -> Optional[Role]: """|coro| @@ -393,6 +415,10 @@ class Role(Hashable): position: :class:`int` The new role's position. This must be below your top role's position or it will fail. + emoji: :class:`str` + The unicode emoji that is shown next to users with the role. + icon: :class:`bytes` + A custom image that is shown next to users with the role. reason: Optional[:class:`str`] The reason for editing this role. Shows up on the audit log. @@ -436,6 +462,12 @@ class Role(Hashable): if mentionable is not MISSING: payload["mentionable"] = mentionable + if emoji is not MISSING: + payload["unicode_emoji"] = emoji + + if icon is not MISSING: + payload["icon"] = _bytes_to_base64_data(icon) + data = await self._state.http.edit_role(self.guild.id, self.id, reason=reason, **payload) return Role(guild=self.guild, data=data, state=self._state) diff --git a/discord/types/role.py b/discord/types/role.py index aba7b1bd..3ad21dd8 100644 --- a/discord/types/role.py +++ b/discord/types/role.py @@ -29,7 +29,9 @@ from .snowflake import Snowflake class _RoleOptional(TypedDict, total=False): + icon: str tags: RoleTags + unicode_emoji: str class Role(_RoleOptional):