On branch DiscordProfile
Initial commit
This commit is contained in:
@@ -0,0 +1,554 @@
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2021 Rapptz
|
||||
Copyright (c) 2021-present Pycord Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import unicodedata
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
|
||||
from .asset import Asset, AssetMixin
|
||||
from .enums import StickerFormatType, StickerType, try_enum
|
||||
from .errors import InvalidData
|
||||
from .mixins import Hashable
|
||||
from .utils import MISSING, cached_slot_property, find, get, snowflake_time
|
||||
|
||||
__all__ = (
|
||||
"StickerPack",
|
||||
"StickerItem",
|
||||
"Sticker",
|
||||
"StandardSticker",
|
||||
"GuildSticker",
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import datetime
|
||||
|
||||
from .guild import Guild
|
||||
from .state import ConnectionState
|
||||
from .types.sticker import EditGuildSticker
|
||||
from .types.sticker import GuildSticker as GuildStickerPayload
|
||||
from .types.sticker import ListPremiumStickerPacks as ListPremiumStickerPacksPayload
|
||||
from .types.sticker import StandardSticker as StandardStickerPayload
|
||||
from .types.sticker import Sticker as StickerPayload
|
||||
from .types.sticker import StickerItem as StickerItemPayload
|
||||
from .types.sticker import StickerPack as StickerPackPayload
|
||||
from .user import User
|
||||
|
||||
|
||||
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:
|
||||
"""The banner asset of the sticker pack."""
|
||||
return Asset._from_sticker_banner(self._state, self._banner)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
"<StickerPack"
|
||||
f" 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.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:class:`bytes`
|
||||
The content of the asset.
|
||||
|
||||
Raises
|
||||
------
|
||||
HTTPException
|
||||
Downloading the asset failed.
|
||||
NotFound
|
||||
The asset was deleted.
|
||||
TypeError
|
||||
The sticker is a lottie type.
|
||||
"""
|
||||
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"]
|
||||
)
|
||||
base = (
|
||||
"https://media.discordapp.net"
|
||||
if self.format is StickerFormatType.gif
|
||||
else Asset.BASE
|
||||
)
|
||||
self.url: str = f"{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) -> Sticker | StandardSticker | GuildSticker:
|
||||
"""|coro|
|
||||
|
||||
Attempts to retrieve the full sticker data of the sticker item.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Union[:class:`StandardSticker`, :class:`GuildSticker`]
|
||||
The retrieved sticker.
|
||||
|
||||
Raises
|
||||
------
|
||||
HTTPException
|
||||
Retrieving the sticker failed.
|
||||
"""
|
||||
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
|
||||
|
||||
.. 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.
|
||||
url: :class:`str`
|
||||
The URL for the sticker's image.
|
||||
"""
|
||||
|
||||
__slots__ = ("_state", "id", "name", "description", "format", "url")
|
||||
|
||||
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.format: StickerFormatType = try_enum(
|
||||
StickerFormatType, data["format_type"]
|
||||
)
|
||||
base = (
|
||||
"https://media.discordapp.net"
|
||||
if self.format is StickerFormatType.gif
|
||||
else Asset.BASE
|
||||
)
|
||||
self.url: str = f"{base}/stickers/{self.id}.{self.format.file_extension}"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Sticker id={self.id} name={self.name!r}>"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def created_at(self) -> datetime.datetime:
|
||||
"""Returns the sticker's creation time in UTC."""
|
||||
return snowflake_time(self.id)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:class:`StickerPack`
|
||||
The retrieved sticker pack.
|
||||
|
||||
Raises
|
||||
------
|
||||
InvalidData
|
||||
The corresponding sticker pack was not found.
|
||||
HTTPException
|
||||
Retrieving the sticker pack failed.
|
||||
"""
|
||||
data: ListPremiumStickerPacksPayload = (
|
||||
await self._state.http.list_premium_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: User | None = self._state.store_user(user) if user else None
|
||||
self.emoji: str = data["tags"]
|
||||
self.type: StickerType = StickerType.guild
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
"<GuildSticker"
|
||||
f" name={self.name!r} id={self.id} guild_id={self.guild_id} user={self.user!r}>"
|
||||
)
|
||||
|
||||
@cached_slot_property("_cs_guild")
|
||||
def guild(self) -> Guild | None:
|
||||
"""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: str | None = None,
|
||||
) -> GuildSticker:
|
||||
"""|coro|
|
||||
|
||||
Edits a :class:`GuildSticker` 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.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:class:`GuildSticker`
|
||||
The newly modified sticker.
|
||||
|
||||
Raises
|
||||
------
|
||||
Forbidden
|
||||
You are not allowed to edit stickers.
|
||||
HTTPException
|
||||
An error occurred editing the sticker.
|
||||
"""
|
||||
payload: EditGuildSticker = {}
|
||||
|
||||
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
|
||||
)
|
||||
return GuildSticker(state=self._state, data=data)
|
||||
|
||||
async def delete(self, *, reason: str | None = 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
|
||||
------
|
||||
Forbidden
|
||||
You are not allowed to delete stickers.
|
||||
HTTPException
|
||||
An error occurred deleting the sticker.
|
||||
"""
|
||||
await self._state.http.delete_guild_sticker(self.guild_id, self.id, reason)
|
||||
|
||||
|
||||
def _sticker_factory(
|
||||
sticker_type: Literal[1, 2],
|
||||
) -> tuple[type[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
|
||||
Reference in New Issue
Block a user