Browse Source

Implement role icons

pull/10109/head
dolfies 4 years ago
parent
commit
93b4d06f52
  1. 9
      discord/asset.py
  2. 29
      discord/guild.py
  3. 52
      discord/http.py
  4. 4
      discord/member.py
  5. 50
      discord/role.py
  6. 4
      discord/types/role.py

9
discord/asset.py

@ -246,6 +246,15 @@ class Asset(AssetMixin):
animated=animated
)
@classmethod
def _from_role_icon(cls, state, role_id: int, icon_hash: str) -> Asset:
return cls(
state,
url=f'{cls.BASE}/role-icons/{role_id}/{icon_hash}.png',
key=icon_hash,
animated=False,
)
def __str__(self) -> str:
return self._url

29
discord/guild.py

@ -79,6 +79,7 @@ from .sticker import GuildSticker
from .file import File
from .settings import GuildSettings
from .profile import MemberProfile
from .partial_emoji import PartialEmoji
__all__ = (
@ -1437,8 +1438,6 @@ class Guild(Hashable):
community: :class:`bool`
Whether the guild should be a Community guild. If set to ``True``\, both ``rules_channel``
and ``public_updates_channel`` parameters are required.
region: Union[:class:`str`, :class:`VoiceRegion`]
The new region for the guild's voice communication.
afk_channel: Optional[:class:`VoiceChannel`]
The new channel that is the AFK channel. Could be ``None`` for no AFK channel.
afk_timeout: :class:`int`
@ -1569,9 +1568,6 @@ class Guild(Hashable):
fields['owner_id'] = owner.id
if region is not MISSING:
fields['region'] = str(region)
if verification_level is not MISSING:
if not isinstance(verification_level, VerificationLevel):
raise InvalidArgument('verification_level field must be of type VerificationLevel')
@ -2408,6 +2404,8 @@ class Guild(Hashable):
colour: Union[Colour, int] = ...,
hoist: bool = ...,
mentionable: bool = ...,
icon: Optional[bytes] = ...,
emoji: Optional[PartialEmoji] = ...,
) -> Role:
...
@ -2433,6 +2431,8 @@ class Guild(Hashable):
colour: Union[Colour, int] = MISSING,
hoist: bool = MISSING,
mentionable: bool = MISSING,
icon: Optional[bytes] = MISSING,
emoji: Optional[PartialEmoji] = MISSING,
reason: Optional[str] = None,
) -> Role:
"""|coro|
@ -2462,6 +2462,11 @@ class Guild(Hashable):
mentionable: :class:`bool`
Indicates if the role should be mentionable by others.
Defaults to ``False``.
icon: Optional[:class:`bytes`]
A :term:`py:bytes-like object` representing the icon. Only PNG/JPEG is supported.
Could be ``None`` to denote removal of the icon.
emoji: Optional[:class:`PartialEmoji`]
An emoji to show next to the role. Only unicode emojis are supported.
reason: Optional[:class:`str`]
The reason for creating this role. Shows up on the audit log.
@ -2500,6 +2505,20 @@ class Guild(Hashable):
if name is not MISSING:
fields['name'] = name
if icon is not MISSING:
if icon is None:
fields['icon'] = icon
else:
fields['icon'] = utils._bytes_to_base64_data(icon)
if emoji is not MISSING:
if emoji is None:
fields['unicode_emoji'] = None
elif emoji.id is not None:
raise InvalidArgument('emoji only supports unicode emojis')
else:
fields['unicode_emoji'] = emoji.name
data = await self._state.http.create_role(self.id, reason=reason, **fields)
role = Role(guild=self, data=data, state=self._state)

52
discord/http.py

@ -1120,22 +1120,10 @@ class HTTPClient:
# Guild management
def get_guilds(
self,
limit: int,
before: Optional[Snowflake] = None,
after: Optional[Snowflake] = None,
with_counts: bool = True
) -> Response[List[guild.Guild]]:
params: Dict[str, Snowflake] = {
def get_guilds(self, with_counts: bool = True) -> Response[List[guild.Guild]]:
params = {
'with_counts': str(with_counts).lower()
}
if limit and limit != 200:
params['limit'] = limit
if before:
params['before'] = before
if after:
params['after'] = after
return self.request(Route('GET', '/users/@me/guilds'), params=params, super_properties_to_track=True)
@ -1147,8 +1135,12 @@ class HTTPClient:
return self.request(r, json=payload)
def get_guild(self, guild_id: Snowflake) -> Response[guild.Guild]:
return self.request(Route('GET', '/guilds/{guild_id}', guild_id=guild_id))
def get_guild(self, guild_id: Snowflake, with_counts: bool = True) -> Response[guild.Guild]:
params = {
'with_counts': str(with_counts).lower()
}
return self.request(Route('GET', '/guilds/{guild_id}', guild_id=guild_id), params=params)
def delete_guild(self, guild_id: Snowflake) -> Response[None]:
return self.request(Route('DELETE', '/guilds/{guild_id}', guild_id=guild_id))
@ -1564,7 +1556,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', 'mentionable', 'icon', 'unicode_emoji')
payload = {k: v for k, v in fields.items() if k in valid_keys}
return self.request(r, json=payload, reason=reason)
@ -1784,7 +1776,7 @@ class HTTPClient:
# Misc
async def get_gateway(self, *, encoding: str = 'json', zlib: bool = True) -> str:
# The gateway URL hasn't changed for over 5 years
# The gateway URL hasn't changed for over 5 years
# And, the official clients aren't GETting it anymore, sooooo...
self.zlib = zlib
if zlib:
@ -1847,7 +1839,7 @@ class HTTPClient:
def delete_connection(self, type, id):
return self.request(Route('DELETE', '/users/@me/connections/{type}/{id}', type=type, id=id))
def get_applications(self, *, with_team_applications: bool = True) -> Response[List[appinfo.AppInfo]]:
def get_my_applications(self, *, with_team_applications: bool = True) -> Response[List[appinfo.AppInfo]]:
params = {
'with_team_applications': str(with_team_applications).lower()
}
@ -1857,9 +1849,22 @@ class HTTPClient:
def get_my_application(self, app_id: Snowflake) -> Response[appinfo.AppInfo]:
return self.request(Route('GET', '/applications/{app_id}', app_id=app_id), super_properties_to_track=True)
def edit_application(self, app_id: Snowflake, payload) -> Response[appinfo.AppInfo]:
return self.request(Route('PATCH', '/applications/{app_id}', app_id=app_id), super_properties_to_track=True, json=payload)
def delete_application(self, app_id: Snowflake) -> Response[appinfo.AppInfo]:
return self.request(Route('POST', '/applications/{app_id}/delete', app_id=app_id), super_properties_to_track=True)
def get_partial_application(self, app_id: Snowflake):
return self.request(Route('GET', '/applications/{app_id}/rpc', app_id=app_id), auth=False)
def create_app(self, name: str):
payload = {
'name': name
}
return self.request(Route('POST', '/applications'), json=payload)
def get_app_entitlements(self, app_id: Snowflake): # TODO: return type
r = Route('GET', '/users/@me/applications/{app_id}/entitlements', app_id=app_id)
return self.request(r, super_properties_to_track=True)
@ -1884,6 +1889,15 @@ class HTTPClient:
def get_team(self, team_id: Snowflake): # TODO: return type
return self.request(Route('GET', '/teams/{team_id}', team_id=team_id), super_properties_to_track=True)
def botify_app(self, app_id: Snowflake):
return self.request(Route('POST', '/applications/{app_id}/bot', app_id=app_id), super_properties_to_track=True)
def reset_secret(self, app_id: Snowflake) -> Response[appinfo.AppInfo]:
return self.request(Route('POST', '/applications/{app_id}/reset', app_id=app_id), super_properties_to_track=True)
def reset_token(self, app_id: Snowflake):
return self.request(Route('POST', '/applications/{app_id}/bot/reset', app_id=app_id), super_properties_to_track=True)
def mobile_report( # Report v1
self, guild_id: Snowflake, channel_id: Snowflake, message_id: Snowflake, reason: str
): # TODO: return type

4
discord/member.py

@ -401,7 +401,7 @@ class Member(discord.abc.Messageable, discord.abc.Connectable, _UserTag):
if original != modified:
to_return = User._copy(self._user)
u.name, u._avatar, u.discriminator, u._public_flags = modified
# Signal to dispatch on_user_update
# Signal to dispatch user_update
return to_return, u
@property
@ -419,7 +419,7 @@ class Member(discord.abc.Messageable, discord.abc.Connectable, _UserTag):
@status.setter
def status(self, value: Status) -> None:
# internal use only
# Internal use only
self._client_status[None] = str(value)
@property

50
discord/role.py

@ -25,11 +25,13 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations
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
from .partial_emoji import PartialEmoji
__all__ = (
'RoleTags',
@ -184,6 +186,8 @@ class Role(Hashable):
'guild',
'tags',
'_state',
'_icon',
'_emoji',
)
def __init__(self, *, guild: Guild, state: ConnectionState, data: RolePayload):
@ -242,6 +246,8 @@ class Role(Hashable):
self.hoist: bool = data.get('hoist', False)
self.managed: bool = data.get('managed', False)
self.mentionable: bool = data.get('mentionable', False)
self._icon: Optional[str] = data.get('icon')
self._emoji: Optional[str] = data.get('unicode_emoji')
self.tags: Optional[RoleTags]
try:
@ -317,6 +323,26 @@ class Role(Hashable):
role_id = self.id
return [member for member in all_members if member._roles.has(role_id)]
@property
def icon(self) -> Optional[Asset]:
"""Optional[:class:`Asset`]: Returns the role's icon asset, if available.
.. versionadded:: 2.0
"""
if (icon := self._icon) is None:
return
return Asset._from_role_icon(self._state, self.id, icon)
@property
def emoji(self) -> Optional[PartialEmoji]:
"""Optional[:class:`PartialEmoji`] Returns the role's unicode emoji, if available.
.. versionadded:: 2.0
"""
if (emoji := self._emoji) is None:
return
return PartialEmoji.from_str(emoji)
async def _move(self, position: int, reason: Optional[str]) -> None:
if position <= 0:
raise InvalidArgument("Cannot move role to position 0 or below")
@ -350,6 +376,8 @@ class Role(Hashable):
hoist: bool = MISSING,
mentionable: bool = MISSING,
position: int = MISSING,
icon: Optional[bytes] = MISSING,
emoji: Optional[PartialEmoji] = MISSING,
reason: Optional[str] = MISSING,
) -> Optional[Role]:
"""|coro|
@ -382,6 +410,11 @@ class Role(Hashable):
position: :class:`int`
The new role's position. This must be below your top role's
position or it will fail.
icon: Optional[:class:`bytes`]
A :term:`py:bytes-like object` representing the icon. Only PNG/JPEG is supported.
Could be ``None`` to denote removal of the icon.
emoji: Optional[:class:`PartialEmoji`]
An emoji to show next to the role. Only unicode emojis are supported.
reason: Optional[:class:`str`]
The reason for editing this role. Shows up on the audit log.
@ -394,6 +427,7 @@ class Role(Hashable):
InvalidArgument
An invalid position was given or the default
role was asked to be moved.
A custom emoji was passed to ``emoji``.
Returns
--------
@ -425,6 +459,20 @@ class Role(Hashable):
if mentionable is not MISSING:
payload['mentionable'] = mentionable
if icon is not MISSING:
if icon is None:
payload['icon'] = icon
else:
payload['icon'] = _bytes_to_base64_data(icon)
if emoji is not MISSING:
if emoji is None:
payload['unicode_emoji'] = None
elif emoji.id is not None:
raise InvalidArgument('emoji only supports unicode emojis')
else:
payload['unicode_emoji'] = emoji.name
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)

4
discord/types/role.py

@ -24,12 +24,14 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations
from typing import TypedDict
from typing import Optional, TypedDict
from .snowflake import Snowflake
class _RoleOptional(TypedDict, total=False):
tags: RoleTags
icon: Optional[str]
unicode_emoji: Optional[str]
class Role(_RoleOptional):

Loading…
Cancel
Save