diff --git a/discord/components.py b/discord/components.py index b30c3ec3..125beb79 100644 --- a/discord/components.py +++ b/discord/components.py @@ -37,6 +37,7 @@ if TYPE_CHECKING: SelectOption as SelectOptionPayload, ActionRow as ActionRowPayload, ) + from .emoji import Emoji __all__ = ( @@ -269,7 +270,7 @@ class SelectOption: description: Optional[:class:`str`] An additional description of the option, if any. Can only be up to 50 characters. - emoji: Optional[:class:`PartialEmoji`] + emoji: Optional[Union[:class:`str`, :class:`Emoji`, :class:`PartialEmoji`]] The emoji of the option, if available. default: :class:`bool` Whether this option is selected by default. @@ -289,15 +290,20 @@ class SelectOption: label: str, value: str = MISSING, description: Optional[str] = None, - emoji: Optional[Union[str, PartialEmoji]] = None, + emoji: Optional[Union[str, Emoji, PartialEmoji]] = None, default: bool = False, ) -> None: self.label = label self.value = label if value is MISSING else value self.description = description - if isinstance(emoji, str): - emoji = PartialEmoji.from_str(emoji) + if emoji is not None: + if isinstance(emoji, str): + emoji = PartialEmoji.from_str(emoji) + elif isinstance(emoji, _EmojiTag): + emoji = emoji._to_partial() + else: + raise TypeError(f'expected emoji to be str, Emoji, or PartialEmoji not {emoji.__class__}') self.emoji = emoji self.default = default diff --git a/discord/emoji.py b/discord/emoji.py index f661ecd3..0630bdcd 100644 --- a/discord/emoji.py +++ b/discord/emoji.py @@ -27,7 +27,7 @@ from typing import Any, Iterator, List, Optional, TYPE_CHECKING, Tuple from .asset import Asset, AssetMixin from .utils import SnowflakeList, snowflake_time, MISSING -from .partial_emoji import _EmojiTag +from .partial_emoji import _EmojiTag, PartialEmoji from .user import User __all__ = ( @@ -122,6 +122,9 @@ class Emoji(_EmojiTag, AssetMixin): user = emoji.get('user') self.user: Optional[User] = User(state=self._state, data=user) if user else None + def _to_partial(self) -> PartialEmoji: + return PartialEmoji(name=self.name, animated=self.animated, id=self.id) + def __iter__(self) -> Iterator[Tuple[str, Any]]: for attr in self.__slots__: if attr[0] != '_': diff --git a/discord/partial_emoji.py b/discord/partial_emoji.py index fc40ff55..52fca121 100644 --- a/discord/partial_emoji.py +++ b/discord/partial_emoji.py @@ -45,6 +45,9 @@ class _EmojiTag: id: int + def _to_partial(self) -> PartialEmoji: + raise NotImplementedError + PE = TypeVar('PE', bound='PartialEmoji') @@ -151,6 +154,9 @@ class PartialEmoji(_EmojiTag, AssetMixin): o['animated'] = self.animated return o + def _to_partial(self) -> PartialEmoji: + return self + @classmethod def with_state( cls: Type[PE], state: ConnectionState, *, name: str, animated: bool = False, id: Optional[int] = None diff --git a/discord/ui/button.py b/discord/ui/button.py index 7ac2a8af..7dccfd15 100644 --- a/discord/ui/button.py +++ b/discord/ui/button.py @@ -31,7 +31,7 @@ import os from .item import Item, ItemCallbackType from ..enums import ButtonStyle, ComponentType -from ..partial_emoji import PartialEmoji +from ..partial_emoji import PartialEmoji, _EmojiTag from ..components import Button as ButtonComponent __all__ = ( @@ -41,6 +41,7 @@ __all__ = ( if TYPE_CHECKING: from .view import View + from ..emoji import Emoji B = TypeVar('B', bound='Button') V = TypeVar('V', bound='View', covariant=True) @@ -64,7 +65,7 @@ class Button(Item[V]): Whether the button is disabled or not. label: Optional[:class:`str`] The label of the button, if any. - emoji: Optional[:class:`PartialEmoji`] + emoji: Optional[Union[:class:`PartialEmoji`, :class:`Emoji`, :class:`str`]] The emoji of the button, if available. row: Optional[:class:`int`] The relative row this button belongs to. A Discord component can only have 5 @@ -91,7 +92,7 @@ class Button(Item[V]): disabled: bool = False, custom_id: Optional[str] = None, url: Optional[str] = None, - emoji: Optional[Union[str, PartialEmoji]] = None, + emoji: Optional[Union[str, Emoji, PartialEmoji]] = None, row: Optional[int] = None, ): super().__init__() @@ -105,8 +106,13 @@ class Button(Item[V]): if url is not None: style = ButtonStyle.link - if isinstance(emoji, str): - emoji = PartialEmoji.from_str(emoji) + if emoji is not None: + if isinstance(emoji, str): + emoji = PartialEmoji.from_str(emoji) + elif isinstance(emoji, _EmojiTag): + emoji = emoji._to_partial() + else: + raise TypeError(f'expected emoji to be str, Emoji, or PartialEmoji not {emoji.__class__}') self._underlying = ButtonComponent._raw_construct( type=ComponentType.button, @@ -178,12 +184,14 @@ class Button(Item[V]): return self._underlying.emoji @emoji.setter - def emoji(self, value: Optional[Union[str, PartialEmoji]]): # type: ignore + def emoji(self, value: Optional[Union[str, Emoji, PartialEmoji]]): # type: ignore if value is not None: if isinstance(value, str): self._underlying.emoji = PartialEmoji.from_str(value) + elif isinstance(value, _EmojiTag): + self._underlying.emoji = value._to_partial() else: - self._underlying.emoji = value + raise TypeError(f'expected str, Emoji, or PartialEmoji, received {value.__class__} instead') else: self._underlying.emoji = None @@ -219,7 +227,7 @@ def button( custom_id: Optional[str] = None, disabled: bool = False, style: ButtonStyle = ButtonStyle.secondary, - emoji: Optional[Union[str, PartialEmoji]] = None, + emoji: Optional[Union[str, Emoji, PartialEmoji]] = None, row: Optional[int] = None, ) -> Callable[[ItemCallbackType], ItemCallbackType]: """A decorator that attaches a button to a component. @@ -247,8 +255,9 @@ def button( The style of the button. Defaults to :attr:`ButtonStyle.grey`. disabled: :class:`bool` Whether the button is disabled or not. Defaults to ``False``. - emoji: Optional[Union[:class:`str`, :class:`PartialEmoji`]] - The emoji of the button. This can be in string form or a :class:`PartialEmoji`. + emoji: Optional[Union[:class:`str`, :class:`Emoji`, :class:`PartialEmoji`]] + The emoji of the button. This can be in string form or a :class:`PartialEmoji` + or a full :class:`Emoji`. row: Optional[:class:`int`] The relative row this button belongs to. A Discord component can only have 5 rows. By default, items are arranged automatically into those 5 rows. If you'd diff --git a/discord/ui/select.py b/discord/ui/select.py index 22143aa7..a563e64b 100644 --- a/discord/ui/select.py +++ b/discord/ui/select.py @@ -30,6 +30,7 @@ import os from .item import Item, ItemCallbackType from ..enums import ComponentType from ..partial_emoji import PartialEmoji +from ..emoji import Emoji from ..interactions import Interaction from ..utils import MISSING from ..components import ( @@ -176,7 +177,7 @@ class Select(Item[V]): label: str, value: str = MISSING, description: Optional[str] = None, - emoji: Optional[Union[str, PartialEmoji]] = None, + emoji: Optional[Union[str, Emoji, PartialEmoji]] = None, default: bool = False, ): """Adds an option to the select menu. @@ -195,9 +196,9 @@ class Select(Item[V]): description: Optional[:class:`str`] An additional description of the option, if any. Can only be up to 50 characters. - emoji: Optional[Union[:class:`str`, :class:`PartialEmoji`]] + emoji: Optional[Union[:class:`str`, :class:`Emoji`, :class:`PartialEmoji`]] The emoji of the option, if available. This can either be a string representing - the custom or unicode emoji or an instance of :class:`PartialEmoji`. + the custom or unicode emoji or an instance of :class:`PartialEmoji` or :class:`Emoji`. default: :class:`bool` Whether this option is selected by default. @@ -207,9 +208,6 @@ class Select(Item[V]): The number of options exceeds 25. """ - if isinstance(emoji, str): - emoji = PartialEmoji.from_str(emoji) - option = SelectOption( label=label, value=value,