|
|
@ -23,24 +23,213 @@ DEALINGS IN THE SOFTWARE. |
|
|
|
""" |
|
|
|
|
|
|
|
from __future__ import annotations |
|
|
|
from typing import TYPE_CHECKING, List, Optional |
|
|
|
from typing import Literal, TYPE_CHECKING, List, Optional, Tuple, Type, Union |
|
|
|
import unicodedata |
|
|
|
|
|
|
|
from .mixins import Hashable |
|
|
|
from .asset import Asset |
|
|
|
from .utils import snowflake_time |
|
|
|
from .enums import StickerType, try_enum |
|
|
|
from .asset import Asset, AssetMixin |
|
|
|
from .utils import cached_slot_property, find, snowflake_time, get, MISSING |
|
|
|
from .errors import InvalidData |
|
|
|
from .enums import StickerType, StickerFormatType, try_enum |
|
|
|
|
|
|
|
__all__ = ( |
|
|
|
'StickerPack', |
|
|
|
'StickerItem', |
|
|
|
'Sticker', |
|
|
|
'StandardSticker', |
|
|
|
'GuildSticker', |
|
|
|
) |
|
|
|
|
|
|
|
if TYPE_CHECKING: |
|
|
|
import datetime |
|
|
|
from .state import ConnectionState |
|
|
|
from .types.message import Sticker as StickerPayload |
|
|
|
from .user import User |
|
|
|
from .guild import Guild |
|
|
|
from .types.sticker import ( |
|
|
|
StickerPack as StickerPackPayload, |
|
|
|
StickerItem as StickerItemPayload, |
|
|
|
Sticker as StickerPayload, |
|
|
|
StandardSticker as StandardStickerPayload, |
|
|
|
GuildSticker as GuildStickerPayload, |
|
|
|
ListNitroStickerPacks as ListNitroStickerPacksPayload |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
class Sticker(Hashable): |
|
|
|
class StickerPack(Hashable): |
|
|
|
"""Represents a sticker pack. |
|
|
|
|
|
|
|
.. versionadded:: 2.0 |
|
|
|
|
|
|
|
.. container:: operations |
|
|
|
|
|
|
|
.. describe:: str(x) |
|
|
|
|
|
|
|
Returns the name of the sticker pack. |
|
|
|
|
|
|
|
.. describe:: x == y |
|
|
|
|
|
|
|
Checks if the sticker pack is equal to another sticker pack. |
|
|
|
|
|
|
|
.. describe:: x != y |
|
|
|
|
|
|
|
Checks if the sticker pack is not equal to another sticker pack. |
|
|
|
|
|
|
|
Attributes |
|
|
|
----------- |
|
|
|
name: :class:`str` |
|
|
|
The name of the sticker pack. |
|
|
|
description: :class:`str` |
|
|
|
The description of the sticker pack. |
|
|
|
id: :class:`int` |
|
|
|
The id of the sticker pack. |
|
|
|
stickers: List[:class:`StandardSticker`] |
|
|
|
The stickers of this sticker pack. |
|
|
|
sku_id: :class:`int` |
|
|
|
The SKU ID of the sticker pack. |
|
|
|
cover_sticker_id: :class:`int` |
|
|
|
The ID of the sticker used for the cover of the sticker pack. |
|
|
|
cover_sticker: :class:`StandardSticker` |
|
|
|
The sticker used for the cover of the sticker pack. |
|
|
|
""" |
|
|
|
|
|
|
|
__slots__ = ( |
|
|
|
'_state', |
|
|
|
'id', |
|
|
|
'stickers', |
|
|
|
'name', |
|
|
|
'sku_id', |
|
|
|
'cover_sticker_id', |
|
|
|
'cover_sticker', |
|
|
|
'description', |
|
|
|
'_banner', |
|
|
|
) |
|
|
|
|
|
|
|
def __init__(self, *, state: ConnectionState, data: StickerPackPayload) -> None: |
|
|
|
self._state: ConnectionState = state |
|
|
|
self._from_data(data) |
|
|
|
|
|
|
|
def _from_data(self, data: StickerPackPayload) -> None: |
|
|
|
self.id: int = int(data['id']) |
|
|
|
stickers = data['stickers'] |
|
|
|
self.stickers: List[StandardSticker] = [StandardSticker(state=self._state, data=sticker) for sticker in stickers] |
|
|
|
self.name: str = data['name'] |
|
|
|
self.sku_id: int = int(data['sku_id']) |
|
|
|
self.cover_sticker_id: int = int(data['cover_sticker_id']) |
|
|
|
self.cover_sticker: StandardSticker = get(self.stickers, id=self.cover_sticker_id) # type: ignore |
|
|
|
self.description: str = data['description'] |
|
|
|
self._banner: int = int(data['banner_asset_id']) |
|
|
|
|
|
|
|
@property |
|
|
|
def banner(self) -> Asset: |
|
|
|
""":class:`Asset`: The banner asset of the sticker pack.""" |
|
|
|
return Asset._from_sticker_banner(self._state, self._banner) |
|
|
|
|
|
|
|
def __repr__(self) -> str: |
|
|
|
return f'<StickerPack id={self.id} name={self.name!r} description={self.description!r}>' |
|
|
|
|
|
|
|
def __str__(self) -> str: |
|
|
|
return self.name |
|
|
|
|
|
|
|
|
|
|
|
class _StickerTag(Hashable, AssetMixin): |
|
|
|
__slots__ = () |
|
|
|
|
|
|
|
id: int |
|
|
|
format: StickerFormatType |
|
|
|
|
|
|
|
async def read(self) -> bytes: |
|
|
|
"""|coro| |
|
|
|
|
|
|
|
Retrieves the content of this sticker as a :class:`bytes` object. |
|
|
|
|
|
|
|
.. note:: |
|
|
|
|
|
|
|
Stickers that use the :attr:`StickerFormatType.lottie` format cannot be read. |
|
|
|
|
|
|
|
Raises |
|
|
|
------ |
|
|
|
HTTPException |
|
|
|
Downloading the asset failed. |
|
|
|
NotFound |
|
|
|
The asset was deleted. |
|
|
|
|
|
|
|
Returns |
|
|
|
------- |
|
|
|
:class:`bytes` |
|
|
|
The content of the asset. |
|
|
|
""" |
|
|
|
if self.format is StickerFormatType.lottie: |
|
|
|
raise TypeError('Cannot read stickers of format "lottie".') |
|
|
|
return await super().read() |
|
|
|
|
|
|
|
|
|
|
|
class StickerItem(_StickerTag): |
|
|
|
"""Represents a sticker item. |
|
|
|
|
|
|
|
.. versionadded:: 2.0 |
|
|
|
|
|
|
|
.. container:: operations |
|
|
|
|
|
|
|
.. describe:: str(x) |
|
|
|
|
|
|
|
Returns the name of the sticker item. |
|
|
|
|
|
|
|
.. describe:: x == y |
|
|
|
|
|
|
|
Checks if the sticker item is equal to another sticker item. |
|
|
|
|
|
|
|
.. describe:: x != y |
|
|
|
|
|
|
|
Checks if the sticker item is not equal to another sticker item. |
|
|
|
|
|
|
|
Attributes |
|
|
|
----------- |
|
|
|
name: :class:`str` |
|
|
|
The sticker's name. |
|
|
|
id: :class:`int` |
|
|
|
The id of the sticker. |
|
|
|
format: :class:`StickerFormatType` |
|
|
|
The format for the sticker's image. |
|
|
|
url: :class:`str` |
|
|
|
The URL for the sticker's image. |
|
|
|
""" |
|
|
|
|
|
|
|
__slots__ = ('_state', 'name', 'id', 'format', 'url') |
|
|
|
|
|
|
|
def __init__(self, *, state: ConnectionState, data: StickerItemPayload): |
|
|
|
self._state: ConnectionState = state |
|
|
|
self.name: str = data['name'] |
|
|
|
self.id: int = int(data['id']) |
|
|
|
self.format: StickerFormatType = try_enum(StickerFormatType, data['format_type']) |
|
|
|
self.url: str = f'{Asset.BASE}/stickers/{self.id}.{self.format.file_extension}' |
|
|
|
|
|
|
|
def __repr__(self) -> str: |
|
|
|
return f'<StickerItem id={self.id} name={self.name!r} format={self.format}>' |
|
|
|
|
|
|
|
def __str__(self) -> str: |
|
|
|
return self.name |
|
|
|
|
|
|
|
async def fetch(self) -> Union[Sticker, StandardSticker, GuildSticker]: |
|
|
|
"""|coro| |
|
|
|
|
|
|
|
Attempts to retrieve the full sticker data of the sticker item. |
|
|
|
|
|
|
|
Raises |
|
|
|
-------- |
|
|
|
HTTPException |
|
|
|
Retrieving the sticker failed. |
|
|
|
|
|
|
|
Returns |
|
|
|
-------- |
|
|
|
Union[:class:`StandardSticker`, :class:`GuildSticker`] |
|
|
|
The retrieved sticker. |
|
|
|
""" |
|
|
|
data: StickerPayload = await self._state.http.get_sticker(self.id) |
|
|
|
cls, _ = _sticker_factory(data['type']) # type: ignore |
|
|
|
return cls(state=self._state, data=data) |
|
|
|
|
|
|
|
|
|
|
|
class Sticker(_StickerTag): |
|
|
|
"""Represents a sticker. |
|
|
|
|
|
|
|
.. versionadded:: 1.6 |
|
|
@ -69,30 +258,27 @@ class Sticker(Hashable): |
|
|
|
The description of the sticker. |
|
|
|
pack_id: :class:`int` |
|
|
|
The id of the sticker's pack. |
|
|
|
format: :class:`StickerType` |
|
|
|
format: :class:`StickerFormatType` |
|
|
|
The format for the sticker's image. |
|
|
|
tags: List[:class:`str`] |
|
|
|
A list of tags for the sticker. |
|
|
|
url: :class:`str` |
|
|
|
The URL for the sticker's image. |
|
|
|
""" |
|
|
|
|
|
|
|
__slots__ = ('_state', 'id', 'name', 'description', 'pack_id', 'format', '_image', 'tags') |
|
|
|
__slots__ = ('_state', 'id', 'name', 'description', 'format', 'url') |
|
|
|
|
|
|
|
def __init__(self, *, state: ConnectionState, data: StickerPayload): |
|
|
|
def __init__(self, *, state: ConnectionState, data: StickerPayload) -> None: |
|
|
|
self._state: ConnectionState = state |
|
|
|
self._from_data(data) |
|
|
|
|
|
|
|
def _from_data(self, data: StickerPayload) -> None: |
|
|
|
self.id: int = int(data['id']) |
|
|
|
self.name: str = data['name'] |
|
|
|
self.description: str = data['description'] |
|
|
|
self.pack_id: int = int(data.get('pack_id', 0)) |
|
|
|
self.format: StickerType = try_enum(StickerType, data['format_type']) |
|
|
|
self._image: str = data['asset'] |
|
|
|
|
|
|
|
try: |
|
|
|
self.tags: List[str] = [tag.strip() for tag in data['tags'].split(',')] |
|
|
|
except KeyError: |
|
|
|
self.tags = [] |
|
|
|
self.format: StickerFormatType = try_enum(StickerFormatType, data['format_type']) |
|
|
|
self.url: str = f'{Asset.BASE}/stickers/{self.id}.{self.format.file_extension}' |
|
|
|
|
|
|
|
def __repr__(self) -> str: |
|
|
|
return f'<{self.__class__.__name__} id={self.id} name={self.name!r}>' |
|
|
|
return f'<Sticker id={self.id} name={self.name!r}>' |
|
|
|
|
|
|
|
def __str__(self) -> str: |
|
|
|
return self.name |
|
|
@ -102,19 +288,229 @@ class Sticker(Hashable): |
|
|
|
""":class:`datetime.datetime`: Returns the sticker's creation time in UTC.""" |
|
|
|
return snowflake_time(self.id) |
|
|
|
|
|
|
|
@property |
|
|
|
def image(self) -> Optional[Asset]: |
|
|
|
"""Returns an :class:`Asset` for the sticker's image. |
|
|
|
|
|
|
|
.. note:: |
|
|
|
This will return ``None`` if the format is ``StickerType.lottie``. |
|
|
|
class StandardSticker(Sticker): |
|
|
|
"""Represents a sticker that is found in a standard sticker pack. |
|
|
|
|
|
|
|
.. versionadded:: 2.0 |
|
|
|
|
|
|
|
.. container:: operations |
|
|
|
|
|
|
|
.. describe:: str(x) |
|
|
|
|
|
|
|
Returns the name of the sticker. |
|
|
|
|
|
|
|
.. describe:: x == y |
|
|
|
|
|
|
|
Checks if the sticker is equal to another sticker. |
|
|
|
|
|
|
|
.. describe:: x != y |
|
|
|
|
|
|
|
Checks if the sticker is not equal to another sticker. |
|
|
|
|
|
|
|
Attributes |
|
|
|
---------- |
|
|
|
name: :class:`str` |
|
|
|
The sticker's name. |
|
|
|
id: :class:`int` |
|
|
|
The id of the sticker. |
|
|
|
description: :class:`str` |
|
|
|
The description of the sticker. |
|
|
|
pack_id: :class:`int` |
|
|
|
The id of the sticker's pack. |
|
|
|
format: :class:`StickerFormatType` |
|
|
|
The format for the sticker's image. |
|
|
|
tags: List[:class:`str`] |
|
|
|
A list of tags for the sticker. |
|
|
|
sort_value: :class:`int` |
|
|
|
The sticker's sort order within its pack. |
|
|
|
""" |
|
|
|
|
|
|
|
__slots__ = ('sort_value', 'pack_id', 'type', 'tags') |
|
|
|
|
|
|
|
def _from_data(self, data: StandardStickerPayload) -> None: |
|
|
|
super()._from_data(data) |
|
|
|
self.sort_value: int = data['sort_value'] |
|
|
|
self.pack_id: int = int(data['pack_id']) |
|
|
|
self.type: StickerType = StickerType.standard |
|
|
|
|
|
|
|
try: |
|
|
|
self.tags: List[str] = [tag.strip() for tag in data['tags'].split(',')] |
|
|
|
except KeyError: |
|
|
|
self.tags = [] |
|
|
|
|
|
|
|
def __repr__(self) -> str: |
|
|
|
return f'<StandardSticker id={self.id} name={self.name!r} pack_id={self.pack_id}>' |
|
|
|
|
|
|
|
async def pack(self) -> StickerPack: |
|
|
|
"""|coro| |
|
|
|
|
|
|
|
Retrieves the sticker pack that this sticker belongs to. |
|
|
|
|
|
|
|
Raises |
|
|
|
-------- |
|
|
|
InvalidData |
|
|
|
The corresponding sticker pack was not found. |
|
|
|
HTTPException |
|
|
|
Retrieving the sticker pack failed. |
|
|
|
|
|
|
|
Returns |
|
|
|
-------- |
|
|
|
:class:`StickerPack` |
|
|
|
The retrieved sticker pack. |
|
|
|
""" |
|
|
|
data: ListNitroStickerPacksPayload = await self._state.http.list_nitro_sticker_packs() |
|
|
|
packs = data['sticker_packs'] |
|
|
|
pack = find(lambda d: int(d['id']) == self.pack_id, packs) |
|
|
|
|
|
|
|
if pack: |
|
|
|
return StickerPack(state=self._state, data=pack) |
|
|
|
raise InvalidData(f'Could not find corresponding sticker pack for {self!r}') |
|
|
|
|
|
|
|
|
|
|
|
class GuildSticker(Sticker): |
|
|
|
"""Represents a sticker that belongs to a guild. |
|
|
|
|
|
|
|
.. versionadded:: 2.0 |
|
|
|
|
|
|
|
.. container:: operations |
|
|
|
|
|
|
|
.. describe:: str(x) |
|
|
|
|
|
|
|
Returns the name of the sticker. |
|
|
|
|
|
|
|
.. describe:: x == y |
|
|
|
|
|
|
|
Checks if the sticker is equal to another sticker. |
|
|
|
|
|
|
|
.. describe:: x != y |
|
|
|
|
|
|
|
Checks if the sticker is not equal to another sticker. |
|
|
|
|
|
|
|
Attributes |
|
|
|
---------- |
|
|
|
name: :class:`str` |
|
|
|
The sticker's name. |
|
|
|
id: :class:`int` |
|
|
|
The id of the sticker. |
|
|
|
description: :class:`str` |
|
|
|
The description of the sticker. |
|
|
|
format: :class:`StickerFormatType` |
|
|
|
The format for the sticker's image. |
|
|
|
available: :class:`bool` |
|
|
|
Whether this sticker is available for use. |
|
|
|
guild_id: :class:`int` |
|
|
|
The ID of the guild that this sticker is from. |
|
|
|
user: Optional[:class:`User`] |
|
|
|
The user that created this sticker. This can only be retrieved using :meth:`Guild.fetch_sticker` and |
|
|
|
having the :attr:`~Permissions.manage_emojis_and_stickers` permission. |
|
|
|
emoji: :class:`str` |
|
|
|
The name of a unicode emoji that represents this sticker. |
|
|
|
""" |
|
|
|
|
|
|
|
__slots__ = ('available', 'guild_id', 'user', 'emoji', 'type', '_cs_guild') |
|
|
|
|
|
|
|
def _from_data(self, data: GuildStickerPayload) -> None: |
|
|
|
super()._from_data(data) |
|
|
|
self.available: bool = data['available'] |
|
|
|
self.guild_id: int = int(data['guild_id']) |
|
|
|
user = data.get('user') |
|
|
|
self.user: Optional[User] = self._state.store_user(user) if user else None |
|
|
|
self.emoji: str = data['tags'] |
|
|
|
self.type: StickerType = StickerType.guild |
|
|
|
|
|
|
|
def __repr__(self) -> str: |
|
|
|
return f'<GuildSticker name={self.name!r} id={self.id} guild_id={self.guild_id} user={self.user!r}>' |
|
|
|
|
|
|
|
@cached_slot_property('_cs_guild') |
|
|
|
def guild(self) -> Optional[Guild]: |
|
|
|
"""Optional[:class:`Guild`]: The guild that this sticker is from. |
|
|
|
Could be ``None`` if the bot is not in the guild. |
|
|
|
|
|
|
|
.. versionadded:: 2.0 |
|
|
|
""" |
|
|
|
return self._state._get_guild(self.guild_id) |
|
|
|
|
|
|
|
async def edit( |
|
|
|
self, |
|
|
|
*, |
|
|
|
name: str = MISSING, |
|
|
|
description: str = MISSING, |
|
|
|
emoji: str = MISSING, |
|
|
|
reason: Optional[str] = None, |
|
|
|
) -> None: |
|
|
|
"""|coro| |
|
|
|
|
|
|
|
Edits a :class:`Sticker` for the guild. |
|
|
|
|
|
|
|
Parameters |
|
|
|
----------- |
|
|
|
name: :class:`str` |
|
|
|
The sticker's new name. Must be at least 2 characters. |
|
|
|
description: Optional[:class:`str`] |
|
|
|
The sticker's new description. Can be ``None``. |
|
|
|
emoji: :class:`str` |
|
|
|
The name of a unicode emoji that represents the sticker's expression. |
|
|
|
reason: :class:`str` |
|
|
|
The reason for editing this sticker. Shows up on the audit log. |
|
|
|
|
|
|
|
Raises |
|
|
|
------- |
|
|
|
Forbidden |
|
|
|
You are not allowed to edit stickers. |
|
|
|
HTTPException |
|
|
|
An error occurred editing the sticker. |
|
|
|
""" |
|
|
|
payload = {} |
|
|
|
|
|
|
|
if name is not MISSING: |
|
|
|
payload['name'] = name |
|
|
|
|
|
|
|
if description is not MISSING: |
|
|
|
payload['description'] = description |
|
|
|
|
|
|
|
if emoji is not MISSING: |
|
|
|
try: |
|
|
|
emoji = unicodedata.name(emoji) |
|
|
|
except TypeError: |
|
|
|
pass |
|
|
|
else: |
|
|
|
emoji = emoji.replace(' ', '_') |
|
|
|
|
|
|
|
payload['tags'] = emoji |
|
|
|
|
|
|
|
data: GuildStickerPayload = await self._state.http.modify_guild_sticker(self.guild_id, self.id, payload, reason) |
|
|
|
|
|
|
|
self._from_data(data) |
|
|
|
|
|
|
|
async def delete(self, *, reason: Optional[str] = None) -> None: |
|
|
|
"""|coro| |
|
|
|
|
|
|
|
Deletes the custom :class:`Sticker` from the guild. |
|
|
|
|
|
|
|
You must have :attr:`~Permissions.manage_emojis_and_stickers` permission to |
|
|
|
do this. |
|
|
|
|
|
|
|
Parameters |
|
|
|
----------- |
|
|
|
reason: Optional[:class:`str`] |
|
|
|
The reason for deleting this sticker. Shows up on the audit log. |
|
|
|
|
|
|
|
Raises |
|
|
|
------- |
|
|
|
Optional[:class:`Asset`] |
|
|
|
The resulting CDN asset. |
|
|
|
Forbidden |
|
|
|
You are not allowed to delete stickers. |
|
|
|
HTTPException |
|
|
|
An error occurred deleting the sticker. |
|
|
|
""" |
|
|
|
if self.format is StickerType.lottie: |
|
|
|
return None |
|
|
|
await self._state.http.delete_guild_sticker(self.guild_id, self.id, reason) |
|
|
|
|
|
|
|
|
|
|
|
return Asset._from_sticker(self._state, self.id, self._image) |
|
|
|
def _sticker_factory(sticker_type: Literal[1, 2]) -> Tuple[Type[Union[StandardSticker, GuildSticker, Sticker]], StickerType]: |
|
|
|
value = try_enum(StickerType, sticker_type) |
|
|
|
if value == StickerType.standard: |
|
|
|
return StandardSticker, value |
|
|
|
elif value == StickerType.guild: |
|
|
|
return GuildSticker, value |
|
|
|
else: |
|
|
|
return Sticker, value |
|
|
|