Rewrite Asset design
This is a breaking change. This does the following transformations, assuming `asset` represents an asset type. Object.is_asset_animated() => Object.asset.is_animated() Object.asset => Object.asset.key Object.asset_url => Object.asset_url Object.asset_url_as => Object.asset.replace(...) Since the asset type now requires a key (or hash, if you will), Emoji had to be flattened similar to how Attachment was done since these assets are keyed solely ID. Emoji.url (Asset) => Emoji.url (str) Emoji.url_as => removed Emoji.url.read => Emoji.read Emoji.url.save => Emoji.save This transformation was also done to PartialEmoji.
This commit is contained in:
parent
57dbb37a52
commit
9eaf1e85e4
@ -49,8 +49,6 @@ class AppInfo:
|
||||
|
||||
.. versionadded:: 1.3
|
||||
|
||||
icon: Optional[:class:`str`]
|
||||
The icon hash, if it exists.
|
||||
description: Optional[:class:`str`]
|
||||
The application description.
|
||||
bot_public: :class:`bool`
|
||||
@ -88,12 +86,6 @@ class AppInfo:
|
||||
If this application is a game sold on Discord,
|
||||
this field will be the URL slug that links to the store page
|
||||
|
||||
.. versionadded:: 1.3
|
||||
|
||||
cover_image: Optional[:class:`str`]
|
||||
If this application is a game sold on Discord,
|
||||
this field will be the hash of the image on store embeds
|
||||
|
||||
.. versionadded:: 1.3
|
||||
"""
|
||||
|
||||
@ -106,14 +98,14 @@ class AppInfo:
|
||||
'bot_public',
|
||||
'bot_require_code_grant',
|
||||
'owner',
|
||||
'icon',
|
||||
'_icon',
|
||||
'summary',
|
||||
'verify_key',
|
||||
'team',
|
||||
'guild_id',
|
||||
'primary_sku_id',
|
||||
'slug',
|
||||
'cover_image',
|
||||
'_cover_image',
|
||||
)
|
||||
|
||||
def __init__(self, state, data):
|
||||
@ -122,7 +114,7 @@ class AppInfo:
|
||||
self.id = int(data['id'])
|
||||
self.name = data['name']
|
||||
self.description = data['description']
|
||||
self.icon = data['icon']
|
||||
self._icon = data['icon']
|
||||
self.rpc_origins = data['rpc_origins']
|
||||
self.bot_public = data['bot_public']
|
||||
self.bot_require_code_grant = data['bot_require_code_grant']
|
||||
@ -138,7 +130,7 @@ class AppInfo:
|
||||
|
||||
self.primary_sku_id = utils._get_as_snowflake(data, 'primary_sku_id')
|
||||
self.slug = data.get('slug')
|
||||
self.cover_image = data.get('cover_image')
|
||||
self._cover_image = data.get('cover_image')
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
@ -148,81 +140,21 @@ class AppInfo:
|
||||
)
|
||||
|
||||
@property
|
||||
def icon_url(self):
|
||||
""":class:`.Asset`: Retrieves the application's icon asset.
|
||||
|
||||
This is equivalent to calling :meth:`icon_url_as` with
|
||||
the default parameters ('webp' format and a size of 1024).
|
||||
|
||||
.. versionadded:: 1.3
|
||||
"""
|
||||
return self.icon_url_as()
|
||||
|
||||
def icon_url_as(self, *, format='webp', size=1024):
|
||||
"""Returns an :class:`Asset` for the icon the application has.
|
||||
|
||||
The format must be one of 'webp', 'jpeg', 'jpg' or 'png'.
|
||||
The size must be a power of 2 between 16 and 4096.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
format: :class:`str`
|
||||
The format to attempt to convert the icon to. Defaults to 'webp'.
|
||||
size: :class:`int`
|
||||
The size of the image to display.
|
||||
|
||||
Raises
|
||||
------
|
||||
InvalidArgument
|
||||
Bad image format passed to ``format`` or invalid ``size``.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The resulting CDN asset.
|
||||
"""
|
||||
return Asset._from_icon(self._state, self, 'app', format=format, size=size)
|
||||
def icon(self):
|
||||
"""Optional[:class:`.Asset`]: Retrieves the application's icon asset, if any."""
|
||||
if self._icon is None:
|
||||
return None
|
||||
return Asset._from_icon(self._state, self.id, self._icon, path='app')
|
||||
|
||||
@property
|
||||
def cover_image_url(self):
|
||||
""":class:`.Asset`: Retrieves the cover image on a store embed.
|
||||
def cover_image(self):
|
||||
"""Optional[:class:`.Asset`]: Retrieves the cover image on a store embed, if any.
|
||||
|
||||
This is equivalent to calling :meth:`cover_image_url_as` with
|
||||
the default parameters ('webp' format and a size of 1024).
|
||||
|
||||
.. versionadded:: 1.3
|
||||
This is only available if the application is a game sold on Discord.
|
||||
"""
|
||||
return self.cover_image_url_as()
|
||||
|
||||
def cover_image_url_as(self, *, format='webp', size=1024):
|
||||
"""Returns an :class:`Asset` for the image on store embeds
|
||||
if this application is a game sold on Discord.
|
||||
|
||||
The format must be one of 'webp', 'jpeg', 'jpg' or 'png'.
|
||||
The size must be a power of 2 between 16 and 4096.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
format: :class:`str`
|
||||
The format to attempt to convert the image to. Defaults to 'webp'.
|
||||
size: :class:`int`
|
||||
The size of the image to display.
|
||||
|
||||
Raises
|
||||
------
|
||||
InvalidArgument
|
||||
Bad image format passed to ``format`` or invalid ``size``.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The resulting CDN asset.
|
||||
"""
|
||||
return Asset._from_cover_image(self._state, self, format=format, size=size)
|
||||
if self._cover_image is None:
|
||||
return None
|
||||
return Asset._from_cover_image(self._state, self.id, self._cover_image)
|
||||
|
||||
@property
|
||||
def guild(self):
|
||||
|
339
discord/asset.py
339
discord/asset.py
@ -22,22 +22,28 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
from typing import Literal, TYPE_CHECKING
|
||||
import os
|
||||
from typing import BinaryIO, Literal, TYPE_CHECKING, Tuple, Union
|
||||
from .errors import DiscordException
|
||||
from .errors import InvalidArgument
|
||||
from . import utils
|
||||
|
||||
import yarl
|
||||
|
||||
__all__ = (
|
||||
'Asset',
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
ValidStaticFormatTypes = Literal['webp', 'jpeg', 'jpg', 'png']
|
||||
ValidAvatarFormatTypes = Literal['webp', 'jpeg', 'jpg', 'png', 'gif']
|
||||
ValidAssetFormatTypes = Literal['webp', 'jpeg', 'jpg', 'png', 'gif']
|
||||
|
||||
VALID_STATIC_FORMATS = frozenset({"jpeg", "jpg", "webp", "png"})
|
||||
VALID_AVATAR_FORMATS = VALID_STATIC_FORMATS | {"gif"}
|
||||
VALID_ASSET_FORMATS = VALID_STATIC_FORMATS | {"gif"}
|
||||
|
||||
|
||||
class Asset:
|
||||
"""Represents a CDN asset on Discord.
|
||||
@ -52,10 +58,6 @@ class Asset:
|
||||
|
||||
Returns the length of the CDN asset's URL.
|
||||
|
||||
.. describe:: bool(x)
|
||||
|
||||
Checks if the Asset has a URL.
|
||||
|
||||
.. describe:: x == y
|
||||
|
||||
Checks if the asset is equal to another asset.
|
||||
@ -68,96 +70,88 @@ class Asset:
|
||||
|
||||
Returns the hash of the asset.
|
||||
"""
|
||||
__slots__ = ('_state', '_url')
|
||||
|
||||
__slots__: Tuple[str, ...] = (
|
||||
'_state',
|
||||
'_url',
|
||||
'_animated',
|
||||
'_key',
|
||||
)
|
||||
|
||||
BASE = 'https://cdn.discordapp.com'
|
||||
|
||||
def __init__(self, state, url=None):
|
||||
def __init__(self, state, *, url: str, key: str, animated: bool = False):
|
||||
self._state = state
|
||||
self._url = url
|
||||
self._animated = animated
|
||||
self._key = key
|
||||
|
||||
@classmethod
|
||||
def _from_avatar(cls, state, user, *, format=None, static_format='webp', size=1024):
|
||||
if not utils.valid_icon_size(size):
|
||||
raise InvalidArgument("size must be a power of 2 between 16 and 4096")
|
||||
if format is not None and format not in VALID_AVATAR_FORMATS:
|
||||
raise InvalidArgument(f"format must be None or one of {VALID_AVATAR_FORMATS}")
|
||||
if format == "gif" and not user.is_avatar_animated():
|
||||
raise InvalidArgument("non animated avatars do not support gif format")
|
||||
if static_format not in VALID_STATIC_FORMATS:
|
||||
raise InvalidArgument(f"static_format must be one of {VALID_STATIC_FORMATS}")
|
||||
|
||||
if user.avatar is None:
|
||||
return user.default_avatar_url
|
||||
|
||||
if format is None:
|
||||
format = 'gif' if user.is_avatar_animated() else static_format
|
||||
|
||||
return cls(state, f'/avatars/{user.id}/{user.avatar}.{format}?size={size}')
|
||||
def _from_default_avatar(cls, state, index: int) -> Asset:
|
||||
return cls(
|
||||
state,
|
||||
url=f'{cls.BASE}/embed/avatars/{index}.png',
|
||||
key=str(index),
|
||||
animated=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _from_icon(cls, state, object, path, *, format='webp', size=1024):
|
||||
if object.icon is None:
|
||||
return cls(state)
|
||||
|
||||
if not utils.valid_icon_size(size):
|
||||
raise InvalidArgument("size must be a power of 2 between 16 and 4096")
|
||||
if format not in VALID_STATIC_FORMATS:
|
||||
raise InvalidArgument(f"format must be None or one of {VALID_STATIC_FORMATS}")
|
||||
|
||||
url = f'/{path}-icons/{object.id}/{object.icon}.{format}?size={size}'
|
||||
return cls(state, url)
|
||||
def _from_avatar(cls, state, user_id: int, avatar: str) -> Asset:
|
||||
animated = avatar.startswith('a_')
|
||||
format = 'gif' if animated else 'png'
|
||||
return cls(
|
||||
state,
|
||||
url=f'{cls.BASE}/avatars/{user_id}/{avatar}.{format}?size=1024',
|
||||
key=avatar,
|
||||
animated=animated,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _from_cover_image(cls, state, obj, *, format='webp', size=1024):
|
||||
if obj.cover_image is None:
|
||||
return cls(state)
|
||||
|
||||
if not utils.valid_icon_size(size):
|
||||
raise InvalidArgument("size must be a power of 2 between 16 and 4096")
|
||||
if format not in VALID_STATIC_FORMATS:
|
||||
raise InvalidArgument(f"format must be None or one of {VALID_STATIC_FORMATS}")
|
||||
|
||||
url = f'/app-assets/{obj.id}/store/{obj.cover_image}.{format}?size={size}'
|
||||
return cls(state, url)
|
||||
def _from_icon(cls, state, object_id: int, icon_hash: str, path: str) -> Asset:
|
||||
return cls(
|
||||
state,
|
||||
url=f'{cls.BASE}/{path}-icons/{object_id}/{icon_hash}.png?size=1024',
|
||||
key=icon_hash,
|
||||
animated=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _from_guild_image(cls, state, id, hash, key, *, format='webp', size=1024):
|
||||
if not utils.valid_icon_size(size):
|
||||
raise InvalidArgument("size must be a power of 2 between 16 and 4096")
|
||||
if format not in VALID_STATIC_FORMATS:
|
||||
raise InvalidArgument(f"format must be one of {VALID_STATIC_FORMATS}")
|
||||
|
||||
if hash is None:
|
||||
return cls(state)
|
||||
|
||||
return cls(state, f'/{key}/{id}/{hash}.{format}?size={size}')
|
||||
def _from_cover_image(cls, state, object_id: int, cover_image_hash: str) -> Asset:
|
||||
return cls(
|
||||
state,
|
||||
url=f'{cls.BASE}/app-assets/{object_id}/store/{cover_image_hash}.png?size=1024',
|
||||
key=cover_image_hash,
|
||||
animated=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _from_guild_icon(cls, state, guild, *, format=None, static_format='webp', size=1024):
|
||||
if not utils.valid_icon_size(size):
|
||||
raise InvalidArgument("size must be a power of 2 between 16 and 4096")
|
||||
if format is not None and format not in VALID_AVATAR_FORMATS:
|
||||
raise InvalidArgument(f"format must be one of {VALID_AVATAR_FORMATS}")
|
||||
if format == "gif" and not guild.is_icon_animated():
|
||||
raise InvalidArgument("non animated guild icons do not support gif format")
|
||||
if static_format not in VALID_STATIC_FORMATS:
|
||||
raise InvalidArgument(f"static_format must be one of {VALID_STATIC_FORMATS}")
|
||||
|
||||
if guild.icon is None:
|
||||
return cls(state)
|
||||
|
||||
if format is None:
|
||||
format = 'gif' if guild.is_icon_animated() else static_format
|
||||
|
||||
return cls(state, f'/icons/{guild.id}/{guild.icon}.{format}?size={size}')
|
||||
def _from_guild_image(cls, state, guild_id: int, image: str, path: str) -> Asset:
|
||||
return cls(
|
||||
state,
|
||||
url=f'{cls.BASE}/{path}/{guild_id}/{image}.png?size=1024',
|
||||
key=image,
|
||||
animated=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _from_sticker_url(cls, state, sticker, *, size=1024):
|
||||
if not utils.valid_icon_size(size):
|
||||
raise InvalidArgument("size must be a power of 2 between 16 and 4096")
|
||||
def _from_guild_icon(cls, state, guild_id: int, icon_hash: str) -> Asset:
|
||||
animated = icon_hash.startswith('a_')
|
||||
format = 'gif' if animated else 'png'
|
||||
return cls(
|
||||
state,
|
||||
url=f'{cls.BASE}/icons/{guild_id}/{icon_hash}.{format}?size=1024',
|
||||
key=icon_hash,
|
||||
animated=animated,
|
||||
)
|
||||
|
||||
return cls(state, f'/stickers/{sticker.id}/{sticker.image}.png?size={size}')
|
||||
@classmethod
|
||||
def _from_sticker(cls, state, sticker_id: int, sticker_hash: str) -> Asset:
|
||||
return cls(
|
||||
state,
|
||||
url=f'{cls.BASE}/stickers/{sticker_id}/{sticker_hash}.png?size=1024',
|
||||
key=sticker_hash,
|
||||
animated=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _from_emoji(cls, state, emoji, *, format=None, static_format='png'):
|
||||
@ -172,46 +166,184 @@ class Asset:
|
||||
|
||||
return cls(state, f'/emojis/{emoji.id}.{format}')
|
||||
|
||||
def __str__(self):
|
||||
return self.BASE + self._url if self._url is not None else ''
|
||||
def __str__(self) -> str:
|
||||
return self._url
|
||||
|
||||
def __len__(self):
|
||||
if self._url:
|
||||
return len(self.BASE + self._url)
|
||||
return 0
|
||||
|
||||
def __bool__(self):
|
||||
return self._url is not None
|
||||
def __len__(self) -> int:
|
||||
return len(self._url)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Asset url={self._url!r}>'
|
||||
shorten = self._url.replace(self.BASE, '')
|
||||
return f'<Asset url={shorten!r}>'
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, Asset) and self._url == other._url
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._url)
|
||||
|
||||
async def read(self):
|
||||
@property
|
||||
def url(self) -> str:
|
||||
""":class:`str`: Returns the underlying URL of the asset."""
|
||||
return self._url
|
||||
|
||||
@property
|
||||
def key(self) -> str:
|
||||
""":class:`str`: Returns the identifying key of the asset."""
|
||||
return self._key
|
||||
|
||||
def is_animated(self) -> bool:
|
||||
""":class:`bool`: Returns whether the asset is animated."""
|
||||
return self._animated
|
||||
|
||||
def replace(
|
||||
self,
|
||||
size: int = ...,
|
||||
format: ValidAssetFormatTypes = ...,
|
||||
static_format: ValidStaticFormatTypes = ...,
|
||||
) -> Asset:
|
||||
"""Returns a new asset with the passed components replaced.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
size: :class:`int`
|
||||
The new size of the asset.
|
||||
format: :class:`str`
|
||||
The new format to change it to. Must be either
|
||||
'webp', 'jpeg', 'jpg', 'png', or 'gif' if it's animated.
|
||||
static_format: :class:`str`
|
||||
The new format to change it to if the asset isn't animated.
|
||||
Must be either 'webp', 'jpeg', 'jpg', or 'png'.
|
||||
|
||||
Raises
|
||||
-------
|
||||
InvalidArgument
|
||||
An invalid size or format was passed.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The newly updated asset.
|
||||
"""
|
||||
url = yarl.URL(self._url)
|
||||
path, _ = os.path.splitext(url.path)
|
||||
|
||||
if format is not ...:
|
||||
if self._animated:
|
||||
if format not in VALID_ASSET_FORMATS:
|
||||
raise InvalidArgument(f'format must be one of {VALID_ASSET_FORMATS}')
|
||||
else:
|
||||
if format not in VALID_STATIC_FORMATS:
|
||||
raise InvalidArgument(f'format must be one of {VALID_STATIC_FORMATS}')
|
||||
url = url.with_path(f'{path}.{format}')
|
||||
|
||||
if static_format is not ... and not self._animated:
|
||||
if static_format not in VALID_STATIC_FORMATS:
|
||||
raise InvalidArgument(f'static_format must be one of {VALID_STATIC_FORMATS}')
|
||||
url = url.with_path(f'{path}.{static_format}')
|
||||
|
||||
if size is not ...:
|
||||
if not utils.valid_icon_size(size):
|
||||
raise InvalidArgument('size must be a power of 2 between 16 and 4096')
|
||||
url = url.with_query(size=size)
|
||||
else:
|
||||
url = url.with_query(url.raw_query_string)
|
||||
|
||||
url = str(url)
|
||||
return Asset(state=self._state, url=url, key=self._key, animated=self._animated)
|
||||
|
||||
def with_size(self, size: int) -> Asset:
|
||||
"""Returns a new asset with the specified size.
|
||||
|
||||
Parameters
|
||||
------------
|
||||
size: :class:`int`
|
||||
The new size of the asset.
|
||||
|
||||
Raises
|
||||
-------
|
||||
InvalidArgument
|
||||
The asset had an invalid size.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The new updated asset.
|
||||
"""
|
||||
if not utils.valid_icon_size(size):
|
||||
raise InvalidArgument('size must be a power of 2 between 16 and 4096')
|
||||
|
||||
url = str(yarl.URL(self._url).with_query(size=size))
|
||||
return Asset(state=self._state, url=url, key=self._key, animated=self._animated)
|
||||
|
||||
def with_format(self, format: ValidAssetFormatTypes) -> Asset:
|
||||
"""Returns a new asset with the specified format.
|
||||
|
||||
Parameters
|
||||
------------
|
||||
format: :class:`str`
|
||||
The new format of the asset.
|
||||
|
||||
Raises
|
||||
-------
|
||||
InvalidArgument
|
||||
The asset had an invalid format.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The new updated asset.
|
||||
"""
|
||||
|
||||
if self._animated:
|
||||
if format not in VALID_ASSET_FORMATS:
|
||||
raise InvalidArgument(f'format must be one of {VALID_ASSET_FORMATS}')
|
||||
else:
|
||||
if format not in VALID_STATIC_FORMATS:
|
||||
raise InvalidArgument(f'format must be one of {VALID_STATIC_FORMATS}')
|
||||
|
||||
url = yarl.URL(self._url)
|
||||
path, _ = os.path.splitext(url.path)
|
||||
url = str(url.with_path(f'{path}.{format}').with_query(url.raw_query_string))
|
||||
return Asset(state=self._state, url=url, key=self._key, animated=self._animated)
|
||||
|
||||
def with_static_format(self, format: ValidStaticFormatTypes) -> Asset:
|
||||
"""Returns a new asset with the specified static format.
|
||||
|
||||
This only changes the format if the underlying asset is
|
||||
not animated. Otherwise, the asset is not changed.
|
||||
|
||||
Parameters
|
||||
------------
|
||||
format: :class:`str`
|
||||
The new static format of the asset.
|
||||
|
||||
Raises
|
||||
-------
|
||||
InvalidArgument
|
||||
The asset had an invalid format.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The new updated asset.
|
||||
"""
|
||||
|
||||
if self._animated:
|
||||
return self
|
||||
return self.with_format(format)
|
||||
|
||||
async def read(self) -> bytes:
|
||||
"""|coro|
|
||||
|
||||
Retrieves the content of this asset as a :class:`bytes` object.
|
||||
|
||||
.. warning::
|
||||
|
||||
:class:`PartialEmoji` won't have a connection state if user created,
|
||||
and a URL won't be present if a custom image isn't associated with
|
||||
the asset, e.g. a guild with no custom icon.
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
Raises
|
||||
------
|
||||
DiscordException
|
||||
There was no valid URL or internal connection state.
|
||||
There was no internal connection state.
|
||||
HTTPException
|
||||
Downloading the asset failed.
|
||||
NotFound
|
||||
@ -222,15 +354,12 @@ class Asset:
|
||||
:class:`bytes`
|
||||
The content of the asset.
|
||||
"""
|
||||
if not self._url:
|
||||
raise DiscordException('Invalid asset (no URL provided)')
|
||||
|
||||
if self._state is None:
|
||||
raise DiscordException('Invalid state (no ConnectionState provided)')
|
||||
|
||||
return await self._state.http.get_from_cdn(self.BASE + self._url)
|
||||
|
||||
async def save(self, fp, *, seek_begin=True):
|
||||
async def save(self, fp: Union[str, bytes, os.PathLike, BinaryIO], *, seek_begin: bool = True) -> int:
|
||||
"""|coro|
|
||||
|
||||
Saves this asset into a file-like object.
|
||||
@ -245,7 +374,7 @@ class Asset:
|
||||
Raises
|
||||
------
|
||||
DiscordException
|
||||
There was no valid URL or internal connection state.
|
||||
There was no internal connection state.
|
||||
HTTPException
|
||||
Downloading the asset failed.
|
||||
NotFound
|
||||
|
@ -94,9 +94,9 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
||||
:attr:`~Permissions.manage_messages` bypass slowmode.
|
||||
nsfw: :class:`bool`
|
||||
If the channel is marked as "not safe for work".
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
|
||||
To check if the channel or the guild of that channel are marked as NSFW, consider :meth:`is_nsfw` instead.
|
||||
"""
|
||||
|
||||
@ -894,9 +894,9 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
|
||||
top category is position 0.
|
||||
nsfw: :class:`bool`
|
||||
If the channel is marked as "not safe for work".
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
|
||||
To check if the channel or the guild of that channel are marked as NSFW, consider :meth:`is_nsfw` instead.
|
||||
"""
|
||||
|
||||
@ -1096,9 +1096,9 @@ class StoreChannel(discord.abc.GuildChannel, Hashable):
|
||||
top channel is position 0.
|
||||
nsfw: :class:`bool`
|
||||
If the channel is marked as "not safe for work".
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
|
||||
To check if the channel or the guild of that channel are marked as NSFW, consider :meth:`is_nsfw` instead.
|
||||
"""
|
||||
__slots__ = ('name', 'id', 'guild', '_state', 'nsfw',
|
||||
@ -1343,13 +1343,11 @@ class GroupChannel(discord.abc.Messageable, Hashable):
|
||||
The group channel ID.
|
||||
owner: :class:`User`
|
||||
The user that owns the group channel.
|
||||
icon: Optional[:class:`str`]
|
||||
The group channel's icon hash if provided.
|
||||
name: Optional[:class:`str`]
|
||||
The group channel's name if provided.
|
||||
"""
|
||||
|
||||
__slots__ = ('id', 'recipients', 'owner', 'icon', 'name', 'me', '_state')
|
||||
__slots__ = ('id', 'recipients', 'owner', '_icon', 'name', 'me', '_state')
|
||||
|
||||
def __init__(self, *, me, state, data):
|
||||
self._state = state
|
||||
@ -1359,7 +1357,7 @@ class GroupChannel(discord.abc.Messageable, Hashable):
|
||||
|
||||
def _update_group(self, data):
|
||||
owner_id = utils._get_as_snowflake(data, 'owner_id')
|
||||
self.icon = data.get('icon')
|
||||
self._icon = data.get('icon')
|
||||
self.name = data.get('name')
|
||||
|
||||
try:
|
||||
@ -1393,40 +1391,11 @@ class GroupChannel(discord.abc.Messageable, Hashable):
|
||||
return ChannelType.group
|
||||
|
||||
@property
|
||||
def icon_url(self):
|
||||
""":class:`Asset`: Returns the channel's icon asset if available.
|
||||
|
||||
This is equivalent to calling :meth:`icon_url_as` with
|
||||
the default parameters ('webp' format and a size of 1024).
|
||||
"""
|
||||
return self.icon_url_as()
|
||||
|
||||
def icon_url_as(self, *, format='webp', size=1024):
|
||||
"""Returns an :class:`Asset` for the icon the channel has.
|
||||
|
||||
The format must be one of 'webp', 'jpeg', 'jpg' or 'png'.
|
||||
The size must be a power of 2 between 16 and 4096.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
format: :class:`str`
|
||||
The format to attempt to convert the icon to. Defaults to 'webp'.
|
||||
size: :class:`int`
|
||||
The size of the image to display.
|
||||
|
||||
Raises
|
||||
------
|
||||
InvalidArgument
|
||||
Bad image format passed to ``format`` or invalid ``size``.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The resulting CDN asset.
|
||||
"""
|
||||
return Asset._from_icon(self._state, self, 'channel', format=format, size=size)
|
||||
def icon(self):
|
||||
"""Optional[:class:`Asset`]: Returns the channel's icon asset if available."""
|
||||
if self._icon is None:
|
||||
return None
|
||||
return Asset._from_icon(self._state, self.id, self._icon, path='channel')
|
||||
|
||||
@property
|
||||
def created_at(self):
|
||||
|
104
discord/emoji.py
104
discord/emoji.py
@ -22,6 +22,8 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
import io
|
||||
|
||||
from .asset import Asset
|
||||
from . import utils
|
||||
from .partial_emoji import _EmojiTag
|
||||
@ -132,13 +134,10 @@ class Emoji(_EmojiTag):
|
||||
return utils.snowflake_time(self.id)
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
""":class:`Asset`: Returns the asset of the emoji.
|
||||
|
||||
This is equivalent to calling :meth:`url_as` with
|
||||
the default parameters (i.e. png/gif detection).
|
||||
"""
|
||||
return self.url_as(format=None)
|
||||
def url(self) -> str:
|
||||
""":class:`str`: Returns the URL of the emoji."""
|
||||
fmt = 'gif' if self.animated else 'png'
|
||||
return f'{Asset.BASE}/emojis/{self.id}.{fmt}'
|
||||
|
||||
@property
|
||||
def roles(self):
|
||||
@ -157,39 +156,6 @@ class Emoji(_EmojiTag):
|
||||
""":class:`Guild`: The guild this emoji belongs to."""
|
||||
return self._state._get_guild(self.guild_id)
|
||||
|
||||
|
||||
def url_as(self, *, format=None, static_format="png"):
|
||||
"""Returns an :class:`Asset` for the emoji's url.
|
||||
|
||||
The format must be one of 'webp', 'jpeg', 'jpg', 'png' or 'gif'.
|
||||
'gif' is only valid for animated emojis.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
format: Optional[:class:`str`]
|
||||
The format to attempt to convert the emojis to.
|
||||
If the format is ``None``, then it is automatically
|
||||
detected as either 'gif' or static_format, depending on whether the
|
||||
emoji is animated or not.
|
||||
static_format: Optional[:class:`str`]
|
||||
Format to attempt to convert only non-animated emoji's to.
|
||||
Defaults to 'png'
|
||||
|
||||
Raises
|
||||
-------
|
||||
InvalidArgument
|
||||
Bad image format passed to ``format`` or ``static_format``.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The resulting CDN asset.
|
||||
"""
|
||||
return Asset._from_emoji(self._state, self, format=format, static_format=static_format)
|
||||
|
||||
|
||||
def is_usable(self):
|
||||
""":class:`bool`: Whether the bot can use this emoji.
|
||||
|
||||
@ -254,3 +220,61 @@ class Emoji(_EmojiTag):
|
||||
if roles:
|
||||
roles = [role.id for role in roles]
|
||||
await self._state.http.edit_custom_emoji(self.guild.id, self.id, name=name, roles=roles, reason=reason)
|
||||
|
||||
async def read(self):
|
||||
"""|coro|
|
||||
|
||||
Retrieves the content of this emoji as a :class:`bytes` object.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Raises
|
||||
------
|
||||
HTTPException
|
||||
Downloading the emoji failed.
|
||||
NotFound
|
||||
The emoji was deleted.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:class:`bytes`
|
||||
The content of the emoji.
|
||||
"""
|
||||
return await self._state.http.get_from_cdn(self.url)
|
||||
|
||||
async def save(self, fp, *, seek_begin=True):
|
||||
"""|coro|
|
||||
|
||||
Saves this emoji into a file-like object.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fp: Union[BinaryIO, :class:`os.PathLike`]
|
||||
Same as in :meth:`Attachment.save`.
|
||||
seek_begin: :class:`bool`
|
||||
Same as in :meth:`Attachment.save`.
|
||||
|
||||
Raises
|
||||
------
|
||||
HTTPException
|
||||
Downloading the emoji failed.
|
||||
NotFound
|
||||
The emoji was deleted.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`int`
|
||||
The number of bytes written.
|
||||
"""
|
||||
|
||||
data = await self.read()
|
||||
if isinstance(fp, io.IOBase) and fp.writable():
|
||||
written = fp.write(data)
|
||||
if seek_begin:
|
||||
fp.seek(0)
|
||||
return written
|
||||
else:
|
||||
with open(fp, 'wb') as f:
|
||||
return f.write(data)
|
||||
|
@ -504,7 +504,7 @@ class Intents(BaseFlags):
|
||||
- :attr:`Member.nick`
|
||||
- :attr:`Member.premium_since`
|
||||
- :attr:`User.name`
|
||||
- :attr:`User.avatar` (:attr:`User.avatar_url` and :meth:`User.avatar_url_as`)
|
||||
- :attr:`User.avatar`
|
||||
- :attr:`User.discriminator`
|
||||
|
||||
For more information go to the :ref:`member intent documentation <need_members_intent>`.
|
||||
|
179
discord/guild.py
179
discord/guild.py
@ -94,8 +94,6 @@ class Guild(Hashable):
|
||||
The timeout to get sent to the AFK channel.
|
||||
afk_channel: Optional[:class:`VoiceChannel`]
|
||||
The channel that denotes the AFK channel. ``None`` if it doesn't exist.
|
||||
icon: Optional[:class:`str`]
|
||||
The guild's icon.
|
||||
id: :class:`int`
|
||||
The guild's ID.
|
||||
owner_id: :class:`int`
|
||||
@ -118,8 +116,6 @@ class Guild(Hashable):
|
||||
The maximum amount of users in a video channel.
|
||||
|
||||
.. versionadded:: 1.4
|
||||
banner: Optional[:class:`str`]
|
||||
The guild's banner.
|
||||
description: Optional[:class:`str`]
|
||||
The guild's description.
|
||||
mfa_level: :class:`int`
|
||||
@ -154,8 +150,6 @@ class Guild(Hashable):
|
||||
- ``MEMBER_VERIFICATION_GATE_ENABLED``: Guild has Membership Screening enabled.
|
||||
- ``PREVIEW_ENABLED``: Guild can be viewed before being accepted via Membership Screening.
|
||||
|
||||
splash: Optional[:class:`str`]
|
||||
The guild's invite splash.
|
||||
premium_tier: :class:`int`
|
||||
The premium tier for this guild. Corresponds to "Nitro Server" in the official UI.
|
||||
The number goes from 0 to 3 inclusive.
|
||||
@ -164,10 +158,6 @@ class Guild(Hashable):
|
||||
preferred_locale: Optional[:class:`str`]
|
||||
The preferred locale for the guild. Used when filtering Server Discovery
|
||||
results to a specific language.
|
||||
discovery_splash: :class:`str`
|
||||
The guild's discovery splash.
|
||||
|
||||
.. versionadded:: 1.3
|
||||
|
||||
nsfw: :class:`bool`
|
||||
If the guild is marked as "not safe for work".
|
||||
@ -175,15 +165,15 @@ class Guild(Hashable):
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
|
||||
__slots__ = ('afk_timeout', 'afk_channel', '_members', '_channels', 'icon',
|
||||
'name', 'id', 'unavailable', 'banner', 'region', '_state',
|
||||
__slots__ = ('afk_timeout', 'afk_channel', '_members', '_channels', '_icon',
|
||||
'name', 'id', 'unavailable', '_banner', 'region', '_state',
|
||||
'_roles', '_member_count', '_large',
|
||||
'owner_id', 'mfa_level', 'emojis', 'features',
|
||||
'verification_level', 'explicit_content_filter', 'splash',
|
||||
'verification_level', 'explicit_content_filter', '_splash',
|
||||
'_voice_states', '_system_channel_id', 'default_notifications',
|
||||
'description', 'max_presences', 'max_members', 'max_video_channel_users',
|
||||
'premium_tier', 'premium_subscription_count', '_system_channel_flags',
|
||||
'preferred_locale', 'discovery_splash', '_rules_channel_id',
|
||||
'preferred_locale', '_discovery_splash', '_rules_channel_id',
|
||||
'_public_updates_channel_id', 'nsfw')
|
||||
|
||||
_PREMIUM_GUILD_LIMITS = {
|
||||
@ -293,8 +283,8 @@ class Guild(Hashable):
|
||||
self.default_notifications = try_enum(NotificationLevel, guild.get('default_message_notifications'))
|
||||
self.explicit_content_filter = try_enum(ContentFilter, guild.get('explicit_content_filter', 0))
|
||||
self.afk_timeout = guild.get('afk_timeout')
|
||||
self.icon = guild.get('icon')
|
||||
self.banner = guild.get('banner')
|
||||
self._icon = guild.get('icon')
|
||||
self._banner = guild.get('banner')
|
||||
self.unavailable = guild.get('unavailable', False)
|
||||
self.id = int(guild['id'])
|
||||
self._roles = {}
|
||||
@ -306,7 +296,7 @@ class Guild(Hashable):
|
||||
self.mfa_level = guild.get('mfa_level')
|
||||
self.emojis = tuple(map(lambda d: state.store_emoji(self, d), guild.get('emojis', [])))
|
||||
self.features = guild.get('features', [])
|
||||
self.splash = guild.get('splash')
|
||||
self._splash = guild.get('splash')
|
||||
self._system_channel_id = utils._get_as_snowflake(guild, 'system_channel_id')
|
||||
self.description = guild.get('description')
|
||||
self.max_presences = guild.get('max_presences')
|
||||
@ -316,7 +306,7 @@ class Guild(Hashable):
|
||||
self.premium_subscription_count = guild.get('premium_subscription_count') or 0
|
||||
self._system_channel_flags = guild.get('system_channel_flags', 0)
|
||||
self.preferred_locale = guild.get('preferred_locale')
|
||||
self.discovery_splash = guild.get('discovery_splash')
|
||||
self._discovery_splash = guild.get('discovery_splash')
|
||||
self._rules_channel_id = utils._get_as_snowflake(guild, 'rules_channel_id')
|
||||
self._public_updates_channel_id = utils._get_as_snowflake(guild, 'public_updates_channel_id')
|
||||
self.nsfw = guild.get('nsfw', False)
|
||||
@ -621,139 +611,32 @@ class Guild(Hashable):
|
||||
return self.get_member(self.owner_id)
|
||||
|
||||
@property
|
||||
def icon_url(self):
|
||||
""":class:`Asset`: Returns the guild's icon asset."""
|
||||
return self.icon_url_as()
|
||||
|
||||
def is_icon_animated(self):
|
||||
""":class:`bool`: Returns True if the guild has an animated icon."""
|
||||
return bool(self.icon and self.icon.startswith('a_'))
|
||||
|
||||
def icon_url_as(self, *, format=None, static_format='webp', size=1024):
|
||||
"""Returns an :class:`Asset` for the guild's icon.
|
||||
|
||||
The format must be one of 'webp', 'jpeg', 'jpg', 'png' or 'gif', and
|
||||
'gif' is only valid for animated avatars. The size must be a power of 2
|
||||
between 16 and 4096.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
format: Optional[:class:`str`]
|
||||
The format to attempt to convert the icon to.
|
||||
If the format is ``None``, then it is automatically
|
||||
detected into either 'gif' or static_format depending on the
|
||||
icon being animated or not.
|
||||
static_format: Optional[:class:`str`]
|
||||
Format to attempt to convert only non-animated icons to.
|
||||
size: :class:`int`
|
||||
The size of the image to display.
|
||||
|
||||
Raises
|
||||
------
|
||||
InvalidArgument
|
||||
Bad image format passed to ``format`` or invalid ``size``.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The resulting CDN asset.
|
||||
"""
|
||||
return Asset._from_guild_icon(self._state, self, format=format, static_format=static_format, size=size)
|
||||
def icon(self):
|
||||
"""Optional[:class:`Asset`]: Returns the guild's icon asset, if available."""
|
||||
if self._icon is None:
|
||||
return None
|
||||
return Asset._from_guild_icon(self._state, self.id, self._icon)
|
||||
|
||||
@property
|
||||
def banner_url(self):
|
||||
""":class:`Asset`: Returns the guild's banner asset."""
|
||||
return self.banner_url_as()
|
||||
|
||||
def banner_url_as(self, *, format='webp', size=2048):
|
||||
"""Returns an :class:`Asset` for the guild's banner.
|
||||
|
||||
The format must be one of 'webp', 'jpeg', or 'png'. The
|
||||
size must be a power of 2 between 16 and 4096.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
format: :class:`str`
|
||||
The format to attempt to convert the banner to.
|
||||
size: :class:`int`
|
||||
The size of the image to display.
|
||||
|
||||
Raises
|
||||
------
|
||||
InvalidArgument
|
||||
Bad image format passed to ``format`` or invalid ``size``.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The resulting CDN asset.
|
||||
"""
|
||||
return Asset._from_guild_image(self._state, self.id, self.banner, 'banners', format=format, size=size)
|
||||
def banner(self):
|
||||
"""Optional[:class:`Asset`]: Returns the guild's banner asset, if available."""
|
||||
if self._banner is None:
|
||||
return None
|
||||
return Asset._from_guild_image(self._state, self.id, self._banner, path='banners')
|
||||
|
||||
@property
|
||||
def splash_url(self):
|
||||
""":class:`Asset`: Returns the guild's invite splash asset."""
|
||||
return self.splash_url_as()
|
||||
|
||||
def splash_url_as(self, *, format='webp', size=2048):
|
||||
"""Returns an :class:`Asset` for the guild's invite splash.
|
||||
|
||||
The format must be one of 'webp', 'jpeg', 'jpg', or 'png'. The
|
||||
size must be a power of 2 between 16 and 4096.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
format: :class:`str`
|
||||
The format to attempt to convert the splash to.
|
||||
size: :class:`int`
|
||||
The size of the image to display.
|
||||
|
||||
Raises
|
||||
------
|
||||
InvalidArgument
|
||||
Bad image format passed to ``format`` or invalid ``size``.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The resulting CDN asset.
|
||||
"""
|
||||
return Asset._from_guild_image(self._state, self.id, self.splash, 'splashes', format=format, size=size)
|
||||
def splash(self):
|
||||
"""Optional[:class:`Asset`]: Returns the guild's invite splash asset, if available."""
|
||||
if self._splash is None:
|
||||
return None
|
||||
return Asset._from_guild_image(self._state, self.id, self._splash, path='splashes')
|
||||
|
||||
@property
|
||||
def discovery_splash_url(self):
|
||||
""":class:`Asset`: Returns the guild's discovery splash asset.
|
||||
|
||||
.. versionadded:: 1.3
|
||||
"""
|
||||
return self.discovery_splash_url_as()
|
||||
|
||||
def discovery_splash_url_as(self, *, format='webp', size=2048):
|
||||
"""Returns an :class:`Asset` for the guild's discovery splash.
|
||||
|
||||
The format must be one of 'webp', 'jpeg', 'jpg', or 'png'. The
|
||||
size must be a power of 2 between 16 and 4096.
|
||||
|
||||
.. versionadded:: 1.3
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
format: :class:`str`
|
||||
The format to attempt to convert the splash to.
|
||||
size: :class:`int`
|
||||
The size of the image to display.
|
||||
|
||||
Raises
|
||||
------
|
||||
InvalidArgument
|
||||
Bad image format passed to ``format`` or invalid ``size``.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The resulting CDN asset.
|
||||
"""
|
||||
return Asset._from_guild_image(self._state, self.id, self.discovery_splash, 'discovery-splashes', format=format, size=size)
|
||||
def discovery_splash(self):
|
||||
"""Optional[:class:`Asset`]: Returns the guild's discovery splash asset, if available."""
|
||||
if self._discovery_splash is None:
|
||||
return None
|
||||
return Asset._from_guild_image(self._state, self.id, self._discovery_splash, path='discovery-splashes')
|
||||
|
||||
@property
|
||||
def member_count(self):
|
||||
@ -1185,7 +1068,7 @@ class Guild(Hashable):
|
||||
try:
|
||||
icon_bytes = fields['icon']
|
||||
except KeyError:
|
||||
icon = self.icon
|
||||
icon = self._icon
|
||||
else:
|
||||
if icon_bytes is not None:
|
||||
icon = utils._bytes_to_base64_data(icon_bytes)
|
||||
@ -1195,7 +1078,7 @@ class Guild(Hashable):
|
||||
try:
|
||||
banner_bytes = fields['banner']
|
||||
except KeyError:
|
||||
banner = self.banner
|
||||
banner = self._banner
|
||||
else:
|
||||
if banner_bytes is not None:
|
||||
banner = utils._bytes_to_base64_data(banner_bytes)
|
||||
@ -1212,7 +1095,7 @@ class Guild(Hashable):
|
||||
try:
|
||||
splash_bytes = fields['splash']
|
||||
except KeyError:
|
||||
splash = self.splash
|
||||
splash = self._splash
|
||||
else:
|
||||
if splash_bytes is not None:
|
||||
splash = utils._bytes_to_base64_data(splash_bytes)
|
||||
|
@ -140,26 +140,20 @@ class PartialInviteGuild:
|
||||
The partial guild's verification level.
|
||||
features: List[:class:`str`]
|
||||
A list of features the guild has. See :attr:`Guild.features` for more information.
|
||||
icon: Optional[:class:`str`]
|
||||
The partial guild's icon.
|
||||
banner: Optional[:class:`str`]
|
||||
The partial guild's banner.
|
||||
splash: Optional[:class:`str`]
|
||||
The partial guild's invite splash.
|
||||
description: Optional[:class:`str`]
|
||||
The partial guild's description.
|
||||
"""
|
||||
|
||||
__slots__ = ('_state', 'features', 'icon', 'banner', 'id', 'name', 'splash', 'verification_level', 'description')
|
||||
__slots__ = ('_state', 'features', '_icon', '_banner', 'id', 'name', '_splash', 'verification_level', 'description')
|
||||
|
||||
def __init__(self, state, data: InviteGuildPayload, id: int):
|
||||
self._state = state
|
||||
self.id = id
|
||||
self.name = data['name']
|
||||
self.features = data.get('features', [])
|
||||
self.icon = data.get('icon')
|
||||
self.banner = data.get('banner')
|
||||
self.splash = data.get('splash')
|
||||
self._icon = data.get('icon')
|
||||
self._banner = data.get('banner')
|
||||
self._splash = data.get('splash')
|
||||
self.verification_level = try_enum(VerificationLevel, data.get('verification_level'))
|
||||
self.description = data.get('description')
|
||||
|
||||
@ -178,56 +172,25 @@ class PartialInviteGuild:
|
||||
return snowflake_time(self.id)
|
||||
|
||||
@property
|
||||
def icon_url(self) -> Asset:
|
||||
""":class:`Asset`: Returns the guild's icon asset."""
|
||||
return self.icon_url_as()
|
||||
|
||||
def is_icon_animated(self) -> bool:
|
||||
""":class:`bool`: Returns ``True`` if the guild has an animated icon.
|
||||
|
||||
.. versionadded:: 1.4
|
||||
"""
|
||||
return bool(self.icon and self.icon.startswith('a_'))
|
||||
|
||||
def icon_url_as(self, *, format=None, static_format='webp', size=1024) -> Asset:
|
||||
"""The same operation as :meth:`Guild.icon_url_as`.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The resulting CDN asset.
|
||||
"""
|
||||
return Asset._from_guild_icon(self._state, self, format=format, static_format=static_format, size=size)
|
||||
def icon_url(self) -> Optional[Asset]:
|
||||
"""Optional[:class:`Asset`]: Returns the guild's icon asset, if available."""
|
||||
if self._icon is None:
|
||||
return None
|
||||
return Asset._from_guild_icon(self._state, self.id, self._icon)
|
||||
|
||||
@property
|
||||
def banner_url(self) -> Asset:
|
||||
""":class:`Asset`: Returns the guild's banner asset."""
|
||||
return self.banner_url_as()
|
||||
|
||||
def banner_url_as(self, *, format='webp', size=2048) -> Asset:
|
||||
"""The same operation as :meth:`Guild.banner_url_as`.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The resulting CDN asset.
|
||||
"""
|
||||
return Asset._from_guild_image(self._state, self.id, self.banner, 'banners', format=format, size=size)
|
||||
def banner(self) -> Optional[Asset]:
|
||||
"""Optional[:class:`Asset`]: Returns the guild's banner asset, if available."""
|
||||
if self._banner is None:
|
||||
return None
|
||||
return Asset._from_guild_image(self._state, self.id, self._banner, path='banners')
|
||||
|
||||
@property
|
||||
def splash_url(self) -> Asset:
|
||||
""":class:`Asset`: Returns the guild's invite splash asset."""
|
||||
return self.splash_url_as()
|
||||
|
||||
def splash_url_as(self, *, format='webp', size=2048) -> Asset:
|
||||
"""The same operation as :meth:`Guild.splash_url_as`.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The resulting CDN asset.
|
||||
"""
|
||||
return Asset._from_guild_image(self._state, self.id, self.splash, 'splashes', format=format, size=size)
|
||||
def splash(self) -> Optional[Asset]:
|
||||
"""Optional[:class:`Asset`]: Returns the guild's invite splash asset, if available."""
|
||||
if self._splash is None:
|
||||
return None
|
||||
return Asset._from_guild_image(self._state, self.id, self._splash, path='splashes')
|
||||
|
||||
|
||||
class Invite(Hashable):
|
||||
|
@ -325,12 +325,12 @@ class Member(discord.abc.Messageable, _BaseUser):
|
||||
|
||||
def _update_inner_user(self, user):
|
||||
u = self._user
|
||||
original = (u.name, u.avatar, u.discriminator, u._public_flags)
|
||||
original = (u.name, u._avatar, u.discriminator, u._public_flags)
|
||||
# These keys seem to always be available
|
||||
modified = (user['username'], user['avatar'], user['discriminator'], user.get('public_flags', 0))
|
||||
if original != modified:
|
||||
to_return = User._copy(self._user)
|
||||
u.name, u.avatar, u.discriminator, u._public_flags = modified
|
||||
u.name, u._avatar, u.discriminator, u._public_flags = modified
|
||||
# Signal to dispatch on_user_update
|
||||
return to_return, u
|
||||
|
||||
@ -442,7 +442,7 @@ class Member(discord.abc.Messageable, _BaseUser):
|
||||
|
||||
.. note::
|
||||
|
||||
Due to a Discord API limitation, this may be ``None`` if
|
||||
Due to a Discord API limitation, this may be ``None`` if
|
||||
the user is listening to a song on Spotify with a title longer
|
||||
than 128 characters. See :issue:`1738` for more information.
|
||||
|
||||
|
@ -172,11 +172,11 @@ class Attachment(Hashable):
|
||||
The number of bytes written.
|
||||
"""
|
||||
data = await self.read(use_cached=use_cached)
|
||||
if isinstance(fp, io.IOBase) and fp.writable():
|
||||
if isinstance(fp, io.RawIOBase):
|
||||
written = fp.write(data)
|
||||
if seek_begin:
|
||||
fp.seek(0)
|
||||
return written
|
||||
return written or 0
|
||||
else:
|
||||
with open(fp, 'wb') as f:
|
||||
return f.write(data)
|
||||
|
@ -22,7 +22,10 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
import io
|
||||
|
||||
from .asset import Asset
|
||||
from .errors import DiscordException, InvalidArgument
|
||||
from . import utils
|
||||
|
||||
__all__ = (
|
||||
@ -149,44 +152,83 @@ class PartialEmoji(_EmojiTag):
|
||||
return utils.snowflake_time(self.id)
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
""":class:`Asset`: Returns the asset of the emoji, if it is custom.
|
||||
def url(self) -> str:
|
||||
""":class:`str`: Returns the URL of the emoji, if it is custom.
|
||||
|
||||
This is equivalent to calling :meth:`url_as` with
|
||||
the default parameters (i.e. png/gif detection).
|
||||
If this isn't a custom emoji then an empty string is returned
|
||||
"""
|
||||
return self.url_as(format=None)
|
||||
if self.is_unicode_emoji():
|
||||
return ''
|
||||
|
||||
def url_as(self, *, format=None, static_format="png"):
|
||||
"""Returns an :class:`Asset` for the emoji's url, if it is custom.
|
||||
fmt = 'gif' if self.animated else 'png'
|
||||
return f'{Asset.BASE}/emojis/{self.id}.{fmt}'
|
||||
|
||||
The format must be one of 'webp', 'jpeg', 'jpg', 'png' or 'gif'.
|
||||
'gif' is only valid for animated emojis.
|
||||
async def read(self):
|
||||
"""|coro|
|
||||
|
||||
.. versionadded:: 1.7
|
||||
Retrieves the content of this emoji as a :class:`bytes` object.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
format: Optional[:class:`str`]
|
||||
The format to attempt to convert the emojis to.
|
||||
If the format is ``None``, then it is automatically
|
||||
detected as either 'gif' or static_format, depending on whether the
|
||||
emoji is animated or not.
|
||||
static_format: Optional[:class:`str`]
|
||||
Format to attempt to convert only non-animated emoji's to.
|
||||
Defaults to 'png'
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Raises
|
||||
-------
|
||||
------
|
||||
DiscordException
|
||||
There was no internal connection state.
|
||||
InvalidArgument
|
||||
Bad image format passed to ``format`` or ``static_format``.
|
||||
The emoji isn't custom.
|
||||
HTTPException
|
||||
Downloading the emoji failed.
|
||||
NotFound
|
||||
The emoji was deleted.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:class:`bytes`
|
||||
The content of the emoji.
|
||||
"""
|
||||
if self._state is None:
|
||||
raise DiscordException('Invalid state (no ConnectionState provided)')
|
||||
|
||||
if self.is_unicode_emoji():
|
||||
raise InvalidArgument('PartialEmoji is not a custom emoji')
|
||||
|
||||
return await self._state.http.get_from_cdn(self.url)
|
||||
|
||||
async def save(self, fp, *, seek_begin=True):
|
||||
"""|coro|
|
||||
|
||||
Saves this emoji into a file-like object.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fp: Union[BinaryIO, :class:`os.PathLike`]
|
||||
Same as in :meth:`Attachment.save`.
|
||||
seek_begin: :class:`bool`
|
||||
Same as in :meth:`Attachment.save`.
|
||||
|
||||
Raises
|
||||
------
|
||||
DiscordException
|
||||
There was no internal connection state.
|
||||
HTTPException
|
||||
Downloading the emoji failed.
|
||||
NotFound
|
||||
The emoji was deleted.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The resulting CDN asset.
|
||||
:class:`int`
|
||||
The number of bytes written.
|
||||
"""
|
||||
if self.is_unicode_emoji():
|
||||
return Asset(self._state)
|
||||
|
||||
return Asset._from_emoji(self._state, self, format=format, static_format=static_format)
|
||||
data = await self.read()
|
||||
if isinstance(fp, io.IOBase) and fp.writable():
|
||||
written = fp.write(data)
|
||||
if seek_begin:
|
||||
fp.seek(0)
|
||||
return written
|
||||
else:
|
||||
with open(fp, 'wb') as f:
|
||||
return f.write(data)
|
||||
|
@ -62,12 +62,10 @@ class Sticker(Hashable):
|
||||
The id of the sticker's pack.
|
||||
format: :class:`StickerType`
|
||||
The format for the sticker's image.
|
||||
image: :class:`str`
|
||||
The sticker's image.
|
||||
tags: List[:class:`str`]
|
||||
A list of tags for the sticker.
|
||||
"""
|
||||
__slots__ = ('_state', 'id', 'name', 'description', 'pack_id', 'format', 'image', 'tags')
|
||||
__slots__ = ('_state', 'id', 'name', 'description', 'pack_id', 'format', '_image', 'tags')
|
||||
|
||||
def __init__(self, *, state, data):
|
||||
self._state = state
|
||||
@ -76,7 +74,7 @@ class Sticker(Hashable):
|
||||
self.description = data['description']
|
||||
self.pack_id = int(data['pack_id'])
|
||||
self.format = try_enum(StickerType, data['format_type'])
|
||||
self.image = data['asset']
|
||||
self._image = data['asset']
|
||||
|
||||
try:
|
||||
self.tags = [tag.strip() for tag in data['tags'].split(',')]
|
||||
@ -95,7 +93,7 @@ class Sticker(Hashable):
|
||||
return snowflake_time(self.id)
|
||||
|
||||
@property
|
||||
def image_url(self):
|
||||
def image(self):
|
||||
"""Returns an :class:`Asset` for the sticker's image.
|
||||
|
||||
.. note::
|
||||
@ -106,32 +104,7 @@ class Sticker(Hashable):
|
||||
Optional[:class:`Asset`]
|
||||
The resulting CDN asset.
|
||||
"""
|
||||
return self.image_url_as()
|
||||
|
||||
def image_url_as(self, *, size=1024):
|
||||
"""Optionally returns an :class:`Asset` for the sticker's image.
|
||||
|
||||
The size must be a power of 2 between 16 and 4096.
|
||||
|
||||
.. note::
|
||||
This will return ``None`` if the format is ``StickerType.lottie``.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
size: :class:`int`
|
||||
The size of the image to display.
|
||||
|
||||
Raises
|
||||
------
|
||||
InvalidArgument
|
||||
Invalid ``size``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Optional[:class:`Asset`]
|
||||
The resulting CDN asset or ``None``.
|
||||
"""
|
||||
if self.format is StickerType.lottie:
|
||||
return None
|
||||
|
||||
return Asset._from_sticker_url(self._state, self, size=size)
|
||||
return Asset._from_sticker_url(self._state, self.id, self._image)
|
||||
|
@ -42,8 +42,6 @@ class Team:
|
||||
The team ID.
|
||||
name: :class:`str`
|
||||
The team name
|
||||
icon: Optional[:class:`str`]
|
||||
The icon hash, if it exists.
|
||||
owner_id: :class:`int`
|
||||
The team's owner ID.
|
||||
members: List[:class:`TeamMember`]
|
||||
@ -52,14 +50,14 @@ class Team:
|
||||
.. versionadded:: 1.3
|
||||
"""
|
||||
|
||||
__slots__ = ('_state', 'id', 'name', 'icon', 'owner_id', 'members')
|
||||
__slots__ = ('_state', 'id', 'name', '_icon', 'owner_id', 'members')
|
||||
|
||||
def __init__(self, state, data):
|
||||
self._state = state
|
||||
|
||||
self.id = utils._get_as_snowflake(data, 'id')
|
||||
self.id = int(data['id'])
|
||||
self.name = data['name']
|
||||
self.icon = data['icon']
|
||||
self._icon = data['icon']
|
||||
self.owner_id = utils._get_as_snowflake(data, 'owner_user_id')
|
||||
self.members = [TeamMember(self, self._state, member) for member in data['members']]
|
||||
|
||||
@ -67,40 +65,11 @@ class Team:
|
||||
return f'<{self.__class__.__name__} id={self.id} name={self.name}>'
|
||||
|
||||
@property
|
||||
def icon_url(self):
|
||||
""":class:`.Asset`: Retrieves the team's icon asset.
|
||||
|
||||
This is equivalent to calling :meth:`icon_url_as` with
|
||||
the default parameters ('webp' format and a size of 1024).
|
||||
"""
|
||||
return self.icon_url_as()
|
||||
|
||||
def icon_url_as(self, *, format='webp', size=1024):
|
||||
"""Returns an :class:`Asset` for the icon the team has.
|
||||
|
||||
The format must be one of 'webp', 'jpeg', 'jpg' or 'png'.
|
||||
The size must be a power of 2 between 16 and 4096.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
format: :class:`str`
|
||||
The format to attempt to convert the icon to. Defaults to 'webp'.
|
||||
size: :class:`int`
|
||||
The size of the image to display.
|
||||
|
||||
Raises
|
||||
------
|
||||
InvalidArgument
|
||||
Bad image format passed to ``format`` or invalid ``size``.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The resulting CDN asset.
|
||||
"""
|
||||
return Asset._from_icon(self._state, self, 'team', format=format, size=size)
|
||||
def icon(self):
|
||||
"""Optional[:class:`.Asset`]: Retrieves the team's icon asset, if any."""
|
||||
if self._icon is None:
|
||||
return None
|
||||
return Asset._from_icon(self._state, self.id, self._icon, path='team')
|
||||
|
||||
@property
|
||||
def owner(self):
|
||||
|
@ -38,7 +38,7 @@ _BaseUser = discord.abc.User
|
||||
|
||||
|
||||
class BaseUser(_BaseUser):
|
||||
__slots__ = ('name', 'id', 'discriminator', 'avatar', 'bot', 'system', '_public_flags', '_state')
|
||||
__slots__ = ('name', 'id', 'discriminator', '_avatar', 'bot', 'system', '_public_flags', '_state')
|
||||
|
||||
def __init__(self, *, state, data):
|
||||
self._state = state
|
||||
@ -60,7 +60,7 @@ class BaseUser(_BaseUser):
|
||||
self.name = data['username']
|
||||
self.id = int(data['id'])
|
||||
self.discriminator = data['discriminator']
|
||||
self.avatar = data['avatar']
|
||||
self._avatar = data['avatar']
|
||||
self._public_flags = data.get('public_flags', 0)
|
||||
self.bot = data.get('bot', False)
|
||||
self.system = data.get('system', False)
|
||||
@ -72,7 +72,7 @@ class BaseUser(_BaseUser):
|
||||
self.name = user.name
|
||||
self.id = user.id
|
||||
self.discriminator = user.discriminator
|
||||
self.avatar = user.avatar
|
||||
self._avatar = user._avatar
|
||||
self.bot = user.bot
|
||||
self._state = user._state
|
||||
self._public_flags = user._public_flags
|
||||
@ -83,7 +83,7 @@ class BaseUser(_BaseUser):
|
||||
return {
|
||||
'username': self.name,
|
||||
'id': self.id,
|
||||
'avatar': self.avatar,
|
||||
'avatar': self._avatar,
|
||||
'discriminator': self.discriminator,
|
||||
'bot': self.bot,
|
||||
}
|
||||
@ -94,66 +94,20 @@ class BaseUser(_BaseUser):
|
||||
return PublicUserFlags._from_value(self._public_flags)
|
||||
|
||||
@property
|
||||
def avatar_url(self):
|
||||
def avatar(self):
|
||||
""":class:`Asset`: Returns an :class:`Asset` for the avatar the user has.
|
||||
|
||||
If the user does not have a traditional avatar, an asset for
|
||||
the default avatar is returned instead.
|
||||
|
||||
This is equivalent to calling :meth:`avatar_url_as` with
|
||||
the default parameters (i.e. webp/gif detection and a size of 1024).
|
||||
"""
|
||||
return self.avatar_url_as(format=None, size=1024)
|
||||
|
||||
def is_avatar_animated(self):
|
||||
""":class:`bool`: Indicates if the user has an animated avatar."""
|
||||
return bool(self.avatar and self.avatar.startswith('a_'))
|
||||
|
||||
def avatar_url_as(self, *, format=None, static_format='webp', size=1024):
|
||||
"""Returns an :class:`Asset` for the avatar the user has.
|
||||
|
||||
If the user does not have a traditional avatar, an asset for
|
||||
the default avatar is returned instead.
|
||||
|
||||
The format must be one of 'webp', 'jpeg', 'jpg', 'png' or 'gif', and
|
||||
'gif' is only valid for animated avatars. The size must be a power of 2
|
||||
between 16 and 4096.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
format: Optional[:class:`str`]
|
||||
The format to attempt to convert the avatar to.
|
||||
If the format is ``None``, then it is automatically
|
||||
detected into either 'gif' or static_format depending on the
|
||||
avatar being animated or not.
|
||||
static_format: Optional[:class:`str`]
|
||||
Format to attempt to convert only non-animated avatars to.
|
||||
Defaults to 'webp'
|
||||
size: :class:`int`
|
||||
The size of the image to display.
|
||||
|
||||
Raises
|
||||
------
|
||||
InvalidArgument
|
||||
Bad image format passed to ``format`` or ``static_format``, or
|
||||
invalid ``size``.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The resulting CDN asset.
|
||||
"""
|
||||
return Asset._from_avatar(self._state, self, format=format, static_format=static_format, size=size)
|
||||
|
||||
if self._avatar is None:
|
||||
return Asset._from_default_avatar(self._state, int(self.discriminator) % len(DefaultAvatar))
|
||||
else:
|
||||
return Asset._from_avatar(self._state, self.id, self._avatar)
|
||||
@property
|
||||
def default_avatar(self):
|
||||
""":class:`DefaultAvatar`: Returns the default avatar for a given user. This is calculated by the user's discriminator."""
|
||||
return try_enum(DefaultAvatar, int(self.discriminator) % len(DefaultAvatar))
|
||||
|
||||
@property
|
||||
def default_avatar_url(self):
|
||||
""":class:`Asset`: Returns a URL for a user's default avatar."""
|
||||
return Asset(self._state, f'/embed/avatars/{self.default_avatar.value}.png')
|
||||
""":class:`Asset`: Returns the default avatar for a given user. This is calculated by the user's discriminator."""
|
||||
return Asset._from_default_avatar(self._state, int(self.discriminator) % len(DefaultAvatar))
|
||||
|
||||
@property
|
||||
def colour(self):
|
||||
|
@ -475,56 +475,23 @@ class PartialWebhookGuild(Hashable):
|
||||
The partial guild's icon
|
||||
"""
|
||||
|
||||
__slots__ = ('id', 'name', 'icon', '_state')
|
||||
__slots__ = ('id', 'name', '_icon', '_state')
|
||||
|
||||
def __init__(self, *, data, state):
|
||||
self._state = state
|
||||
self.id = int(data['id'])
|
||||
self.name = data['name']
|
||||
self.icon = data['icon']
|
||||
self._icon = data['icon']
|
||||
|
||||
def __repr__(self):
|
||||
return f'<PartialWebhookGuild name={self.name!r} id={self.id}>'
|
||||
|
||||
@property
|
||||
def icon_url(self) -> Asset:
|
||||
""":class:`Asset`: Returns the guild's icon asset."""
|
||||
return self.icon_url_as()
|
||||
|
||||
def is_icon_animated(self) -> bool:
|
||||
""":class:`bool`: Returns True if the guild has an animated icon."""
|
||||
return bool(self.icon and self.icon.startswith('a_'))
|
||||
|
||||
def icon_url_as(self, *, format=None, static_format='webp', size=1024):
|
||||
"""Returns an :class:`Asset` for the guild's icon.
|
||||
|
||||
The format must be one of 'webp', 'jpeg', 'jpg', 'png' or 'gif', and
|
||||
'gif' is only valid for animated avatars. The size must be a power of 2
|
||||
between 16 and 4096.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
format: Optional[:class:`str`]
|
||||
The format to attempt to convert the icon to.
|
||||
If the format is ``None``, then it is automatically
|
||||
detected into either 'gif' or static_format depending on the
|
||||
icon being animated or not.
|
||||
static_format: Optional[:class:`str`]
|
||||
Format to attempt to convert only non-animated icons to.
|
||||
size: :class:`int`
|
||||
The size of the image to display.
|
||||
|
||||
Raises
|
||||
------
|
||||
InvalidArgument
|
||||
Bad image format passed to ``format`` or invalid ``size``.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The resulting CDN asset.
|
||||
"""
|
||||
return Asset._from_guild_icon(self._state, self, format=format, static_format=static_format, size=size)
|
||||
def icon_url(self) -> Optional[Asset]:
|
||||
"""Optional[:class:`Asset`]: Returns the guild's icon asset, if available."""
|
||||
if self._icon is None:
|
||||
return None
|
||||
return Asset._from_guild_icon(self._state, self.id, self._icon)
|
||||
|
||||
|
||||
class _FriendlyHttpAttributeErrorHelper:
|
||||
@ -684,7 +651,7 @@ class BaseWebhook(Hashable):
|
||||
'auth_token',
|
||||
'user',
|
||||
'name',
|
||||
'avatar',
|
||||
'_avatar',
|
||||
'source_channel',
|
||||
'source_guild',
|
||||
'_state',
|
||||
@ -701,7 +668,7 @@ class BaseWebhook(Hashable):
|
||||
self.channel_id = utils._get_as_snowflake(data, 'channel_id')
|
||||
self.guild_id = utils._get_as_snowflake(data, 'guild_id')
|
||||
self.name = data.get('name')
|
||||
self.avatar = data.get('avatar')
|
||||
self._avatar = data.get('avatar')
|
||||
self.token = data.get('token')
|
||||
|
||||
user = data.get('user')
|
||||
@ -755,59 +722,16 @@ class BaseWebhook(Hashable):
|
||||
return utils.snowflake_time(self.id)
|
||||
|
||||
@property
|
||||
def avatar_url(self) -> Asset:
|
||||
def avatar(self) -> Asset:
|
||||
""":class:`Asset`: Returns an :class:`Asset` for the avatar the webhook has.
|
||||
|
||||
If the webhook does not have a traditional avatar, an asset for
|
||||
the default avatar is returned instead.
|
||||
|
||||
This is equivalent to calling :meth:`avatar_url_as` with the
|
||||
default parameters.
|
||||
"""
|
||||
return self.avatar_url_as()
|
||||
|
||||
def avatar_url_as(self, *, format: Optional[Literal['png', 'jpg', 'jpeg']] = None, size: int = 1024) -> Asset:
|
||||
"""Returns an :class:`Asset` for the avatar the webhook has.
|
||||
|
||||
If the webhook does not have a traditional avatar, an asset for
|
||||
the default avatar is returned instead.
|
||||
|
||||
The format must be one of 'jpeg', 'jpg', or 'png'.
|
||||
The size must be a power of 2 between 16 and 1024.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
format: Optional[:class:`str`]
|
||||
The format to attempt to convert the avatar to.
|
||||
If the format is ``None``, then it is equivalent to png.
|
||||
size: :class:`int`
|
||||
The size of the image to display.
|
||||
|
||||
Raises
|
||||
------
|
||||
InvalidArgument
|
||||
Bad image format passed to ``format`` or invalid ``size``.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The resulting CDN asset.
|
||||
"""
|
||||
if self.avatar is None:
|
||||
if self._avatar is None:
|
||||
# Default is always blurple apparently
|
||||
return Asset(self._state, '/embed/avatars/0.png')
|
||||
|
||||
if not utils.valid_icon_size(size):
|
||||
raise InvalidArgument("size must be a power of 2 between 16 and 1024")
|
||||
|
||||
format = format or 'png'
|
||||
|
||||
if format not in ('png', 'jpg', 'jpeg'):
|
||||
raise InvalidArgument("format must be one of 'png', 'jpg', or 'jpeg'.")
|
||||
|
||||
url = f'/avatars/{self.id}/{self.avatar}.{format}?size={size}'
|
||||
return Asset(self._state, url)
|
||||
|
||||
return Asset._from_default_avatar(self._state, 0)
|
||||
return Asset._from_avatar(self._state, self.id, self._avatar)
|
||||
|
||||
class Webhook(BaseWebhook):
|
||||
"""Represents an asynchronous Discord webhook.
|
||||
@ -980,7 +904,7 @@ class Webhook(BaseWebhook):
|
||||
'name': name,
|
||||
'channel_id': channel.id,
|
||||
'guild_id': channel.guild.id,
|
||||
'user': {'username': user.name, 'discriminator': user.discriminator, 'id': user.id, 'avatar': user.avatar},
|
||||
'user': {'username': user.name, 'discriminator': user.discriminator, 'id': user.id, 'avatar': user._avatar},
|
||||
}
|
||||
|
||||
state = channel._state
|
||||
|
Loading…
x
Reference in New Issue
Block a user