diff --git a/discord/audit_logs.py b/discord/audit_logs.py index 1870620f0..2d6fa3c5c 100644 --- a/discord/audit_logs.py +++ b/discord/audit_logs.py @@ -119,7 +119,10 @@ def _transform_overwrites( def _transform_icon(entry: AuditLogEntry, data: Optional[str]) -> Optional[Asset]: if data is None: return None - return Asset._from_guild_icon(entry._state, entry.guild.id, data) + if entry.action is enums.AuditLogAction.guild_update: + return Asset._from_guild_icon(entry._state, entry.guild.id, data) + else: + return Asset._from_icon(entry._state, entry._target_id, data, path='role') def _transform_avatar(entry: AuditLogEntry, data: Optional[str]) -> Optional[Asset]: diff --git a/discord/guild.py b/discord/guild.py index 41545f773..01993a4d7 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -212,6 +212,7 @@ class Guild(Hashable): - ``PARTNERED``: Guild is a partnered server. - ``PREVIEW_ENABLED``: Guild can be viewed before being accepted via Membership Screening. - ``PRIVATE_THREADS``: Guild has access to create private threads. + - ``ROLE_ICONS``: Guild is able to set role icons. - ``SEVEN_DAY_THREAD_ARCHIVE``: Guild has access to the seven day archive time for threads. - ``THREE_DAY_THREAD_ARCHIVE``: Guild has access to the three day archive time for threads. - ``TICKETED_EVENTS_ENABLED``: Guild has enabled ticketed events. @@ -2394,6 +2395,7 @@ class Guild(Hashable): permissions: Permissions = ..., colour: Union[Colour, int] = ..., hoist: bool = ..., + display_icon: Union[bytes, str] = MISSING, mentionable: bool = ..., ) -> Role: ... @@ -2407,6 +2409,7 @@ class Guild(Hashable): permissions: Permissions = ..., color: Union[Colour, int] = ..., hoist: bool = ..., + display_icon: Union[bytes, str] = MISSING, mentionable: bool = ..., ) -> Role: ... @@ -2419,6 +2422,7 @@ class Guild(Hashable): color: Union[Colour, int] = MISSING, colour: Union[Colour, int] = MISSING, hoist: bool = MISSING, + display_icon: Union[bytes, str] = MISSING, mentionable: bool = MISSING, reason: Optional[str] = None, ) -> Role: @@ -2434,6 +2438,9 @@ class Guild(Hashable): .. versionchanged:: 1.6 Can now pass ``int`` to ``colour`` keyword-only parameter. + .. versionadded:: 2.0 + The ``display_icon`` keyword-only parameter was added. + Parameters ----------- name: :class:`str` @@ -2446,6 +2453,11 @@ class Guild(Hashable): hoist: :class:`bool` Indicates if the role should be shown separately in the member list. Defaults to ``False``. + display_icon: Union[:class:`bytes`, :class:`str`] + A :term:`py:bytes-like object` representing the icon + or :class:`str` representing unicode emoji that should be used as a role icon. + Only PNG/JPEG is supported. + This is only available to guilds that contain ``ROLE_ICONS`` in :attr:`features`. mentionable: :class:`bool` Indicates if the role should be mentionable by others. Defaults to ``False``. @@ -2481,6 +2493,12 @@ class Guild(Hashable): if hoist is not MISSING: fields['hoist'] = hoist + if display_icon is not MISSING: + if isinstance(display_icon, bytes): + fields['icon'] = utils._bytes_to_base64_data(display_icon) + else: + fields['unicode_emoji'] = display_icon + if mentionable is not MISSING: fields['mentionable'] = mentionable diff --git a/discord/http.py b/discord/http.py index 2524844de..372e7a882 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1422,7 +1422,7 @@ class HTTPClient: self, guild_id: Snowflake, role_id: Snowflake, *, reason: Optional[str] = None, **fields: Any ) -> Response[role.Role]: r = Route('PATCH', '/guilds/{guild_id}/roles/{role_id}', guild_id=guild_id, role_id=role_id) - valid_keys = ('name', 'permissions', 'color', 'hoist', 'mentionable') + valid_keys = ('name', 'permissions', 'color', 'hoist', 'icon', 'unicode_emoji', 'mentionable') payload = {k: v for k, v in fields.items() if k in valid_keys} return self.request(r, json=payload, reason=reason) diff --git a/discord/role.py b/discord/role.py index 0427ddc3a..8caea16bd 100644 --- a/discord/role.py +++ b/discord/role.py @@ -25,11 +25,12 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations from typing import Any, Dict, List, Optional, TypeVar, Union, overload, 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, _bytes_to_base64_data, _get_as_snowflake, MISSING __all__ = ( 'RoleTags', @@ -163,6 +164,18 @@ class Role(Hashable): compare for roles in the hierarchy is using the comparison operators on the role objects themselves. + unicode_emoji: Optional[:class:`str`] + The role's unicode emoji, if available. + + .. note:: + + If :attr:`icon` is not ``None``, it is displayed as role icon + instead of the unicode emoji under this attribute. + + If you want the icon that a role has displayed, consider using :attr:`display_icon`. + + .. versionadded:: 2.0 + managed: :class:`bool` Indicates if the role is managed by the guild through some form of integrations such as Twitch. @@ -178,6 +191,8 @@ class Role(Hashable): '_permissions', '_colour', 'position', + '_icon', + 'unicode_emoji', 'managed', 'mentionable', 'hoist', @@ -240,6 +255,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._icon: Optional[str] = data.get('icon') + self.unicode_emoji: Optional[str] = data.get('unicode_emoji') self.managed: bool = data.get('managed', False) self.mentionable: bool = data.get('mentionable', False) self.tags: Optional[RoleTags] @@ -297,6 +314,30 @@ class Role(Hashable): """:class:`Colour`: Returns the role color. An alias exists under ``colour``.""" return self.colour + @property + def icon(self) -> Optional[Asset]: + """Optional[:class:`.Asset`]: Returns the role's icon asset, if available. + + .. note:: + If this is ``None``, the role might instead have unicode emoji as its icon + if :attr:`unicode_emoji` is not ``None``. + + If you want the icon that a role has displayed, consider using :attr:`display_icon`. + + .. versionadded:: 2.0 + """ + if self._icon is None: + return None + return Asset._from_icon(self._state, self.id, self._icon, path='role') + + @property + def display_icon(self) -> Optional[Union[Asset, str]]: + """Optional[Union[:class:`.Asset`, :class:`str`]]: Returns the role's display icon, if available. + + .. versionadded:: 2.0 + """ + return self.icon or self.unicode_emoji + @property def created_at(self) -> datetime.datetime: """:class:`datetime.datetime`: Returns the role's creation time in UTC.""" @@ -348,6 +389,7 @@ class Role(Hashable): colour: Union[Colour, int] = MISSING, color: Union[Colour, int] = MISSING, hoist: bool = MISSING, + display_icon: Optional[Union[bytes, str]] = MISSING, mentionable: bool = MISSING, position: int = MISSING, reason: Optional[str] = MISSING, @@ -367,6 +409,9 @@ class Role(Hashable): .. versionchanged:: 2.0 Edits are no longer in-place, the newly edited role is returned instead. + .. versionadded:: 2.0 + The ``display_icon`` keyword-only parameter was added. + Parameters ----------- name: :class:`str` @@ -377,6 +422,12 @@ class Role(Hashable): The new colour to change to. (aliased to color as well) hoist: :class:`bool` Indicates if the role should be shown separately in the member list. + display_icon: Optional[Union[:class:`bytes`, :class:`str`]] + A :term:`py:bytes-like object` representing the icon + or :class:`str` representing unicode emoji that should be used as a role icon. + Could be ``None`` to denote removal of the icon. + Only PNG/JPEG is supported. + This is only available to guilds that contain ``ROLE_ICONS`` in :attr:`features`. mentionable: :class:`bool` Indicates if the role should be mentionable by others. position: :class:`int` @@ -422,6 +473,14 @@ class Role(Hashable): if hoist is not MISSING: payload['hoist'] = hoist + if display_icon is not MISSING: + payload['icon'] = None + payload['unicode_emoji'] = None + if isinstance(display_icon, bytes): + payload['icon'] = _bytes_to_base64_data(display_icon) + else: + payload['unicode_emoji'] = display_icon + if mentionable is not MISSING: payload['mentionable'] = mentionable diff --git a/discord/types/audit_log.py b/discord/types/audit_log.py index 563819fd3..53b57d79e 100644 --- a/discord/types/audit_log.py +++ b/discord/types/audit_log.py @@ -84,7 +84,17 @@ AuditLogEvent = Literal[ class _AuditLogChange_Str(TypedDict): key: Literal[ - 'name', 'description', 'preferred_locale', 'vanity_url_code', 'topic', 'code', 'allow', 'deny', 'permissions', 'tags' + 'name', + 'description', + 'preferred_locale', + 'vanity_url_code', + 'topic', + 'code', + 'allow', + 'deny', + 'permissions', + 'tags', + 'unicode_emoji', ] new_value: str old_value: str diff --git a/discord/types/guild.py b/discord/types/guild.py index 5025f1c72..29946053d 100644 --- a/discord/types/guild.py +++ b/discord/types/guild.py @@ -90,6 +90,7 @@ GuildFeature = Literal[ 'PARTNERED', 'PREVIEW_ENABLED', 'PRIVATE_THREADS', + 'ROLE_ICONS', 'SEVEN_DAY_THREAD_ARCHIVE', 'THREE_DAY_THREAD_ARCHIVE', 'TICKETED_EVENTS_ENABLED', diff --git a/discord/types/role.py b/discord/types/role.py index aba7b1bda..877d31774 100644 --- a/discord/types/role.py +++ b/discord/types/role.py @@ -29,6 +29,8 @@ from .snowflake import Snowflake class _RoleOptional(TypedDict, total=False): + icon: Optional[str] + unicode_emoji: Optional[str] tags: RoleTags diff --git a/docs/api.rst b/docs/api.rst index ab80ebd78..dfefb1f75 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1994,6 +1994,8 @@ of :class:`enum.Enum`. - :attr:`~AuditLogDiff.colour` - :attr:`~AuditLogDiff.mentionable` - :attr:`~AuditLogDiff.hoist` + - :attr:`~AuditLogDiff.icon` + - :attr:`~AuditLogDiff.unicode_emoji` - :attr:`~AuditLogDiff.name` - :attr:`~AuditLogDiff.permissions` @@ -2004,6 +2006,7 @@ of :class:`enum.Enum`. - The name has changed - The permissions have changed - The colour has changed + - The role icon (or unicode emoji) has changed - Its hoist/mentionable state has changed When this is the action, the type of :attr:`~AuditLogEntry.target` is @@ -2014,6 +2017,8 @@ of :class:`enum.Enum`. - :attr:`~AuditLogDiff.colour` - :attr:`~AuditLogDiff.mentionable` - :attr:`~AuditLogDiff.hoist` + - :attr:`~AuditLogDiff.icon` + - :attr:`~AuditLogDiff.unicode_emoji` - :attr:`~AuditLogDiff.name` - :attr:`~AuditLogDiff.permissions` @@ -2828,7 +2833,7 @@ AuditLogDiff .. attribute:: icon - A guild's icon. See also :attr:`Guild.icon`. + A guild's or role's icon. See also :attr:`Guild.icon` or :attr:`Role.icon`. :type: :class:`Asset` @@ -3202,7 +3207,15 @@ AuditLogDiff The name of the emoji that represents a sticker being changed. - See also :attr:`GuildSticker.emoji` + See also :attr:`GuildSticker.emoji`. + + :type: :class:`str` + + .. attribute:: unicode_emoji + + The unicode emoji that is used as an icon for the role being changed. + + See also :attr:`Role.unicode_emoji`. :type: :class:`str`