Browse Source

Implement button clicking

pull/10109/head
dolfies 4 years ago
parent
commit
e168fd38a0
  1. 59
      discord/components.py
  2. 3
      discord/http.py
  3. 25
      discord/message.py

59
discord/components.py

@ -24,9 +24,11 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations from __future__ import annotations
from datetime import datetime
from typing import Any, ClassVar, Dict, List, Optional, TYPE_CHECKING, Tuple, Type, TypeVar, Union from typing import Any, ClassVar, Dict, List, Optional, TYPE_CHECKING, Tuple, Type, TypeVar, Union
from .enums import try_enum, ComponentType, ButtonStyle from .enums import try_enum, ComponentType, ButtonStyle
from .utils import get_slots, MISSING from .utils import get_slots, MISSING, time_snowflake
from .partial_emoji import PartialEmoji, _EmojiTag from .partial_emoji import PartialEmoji, _EmojiTag
if TYPE_CHECKING: if TYPE_CHECKING:
@ -38,6 +40,7 @@ if TYPE_CHECKING:
ActionRow as ActionRowPayload, ActionRow as ActionRowPayload,
) )
from .emoji import Emoji from .emoji import Emoji
from .message import Message
__all__ = ( __all__ = (
@ -70,10 +73,11 @@ class Component:
The type of component. The type of component.
""" """
__slots__: Tuple[str, ...] = ('type',) __slots__: Tuple[str, ...] = ('type', 'message')
__repr_info__: ClassVar[Tuple[str, ...]] __repr_info__: ClassVar[Tuple[str, ...]]
type: ComponentType type: ComponentType
message: Message
def __repr__(self) -> str: def __repr__(self) -> str:
attrs = ' '.join(f'{key}={getattr(self, key)!r}' for key in self.__repr_info__) attrs = ' '.join(f'{key}={getattr(self, key)!r}' for key in self.__repr_info__)
@ -110,15 +114,18 @@ class ActionRow(Component):
The type of component. The type of component.
children: List[:class:`Component`] children: List[:class:`Component`]
The children components that this holds, if any. The children components that this holds, if any.
message: :class:`Message`
The originating message.
""" """
__slots__: Tuple[str, ...] = ('children',) __slots__: Tuple[str, ...] = ('children',)
__repr_info__: ClassVar[Tuple[str, ...]] = __slots__ __repr_info__: ClassVar[Tuple[str, ...]] = __slots__
def __init__(self, data: ComponentPayload): def __init__(self, data: ComponentPayload, message: Message):
self.message = message
self.type: ComponentType = try_enum(ComponentType, data['type']) self.type: ComponentType = try_enum(ComponentType, data['type'])
self.children: List[Component] = [_component_factory(d) for d in data.get('components', [])] self.children: List[Component] = [_component_factory(d, message) for d in data.get('components', [])]
def to_dict(self) -> ActionRowPayload: def to_dict(self) -> ActionRowPayload:
return { return {
@ -149,6 +156,8 @@ class Button(Component):
The label of the button, if any. The label of the button, if any.
emoji: Optional[:class:`PartialEmoji`] emoji: Optional[:class:`PartialEmoji`]
The emoji of the button, if available. The emoji of the button, if available.
message: :class:`Message`
The originating message, if any.
""" """
__slots__: Tuple[str, ...] = ( __slots__: Tuple[str, ...] = (
@ -162,7 +171,8 @@ class Button(Component):
__repr_info__: ClassVar[Tuple[str, ...]] = __slots__ __repr_info__: ClassVar[Tuple[str, ...]] = __slots__
def __init__(self, data: ButtonComponentPayload): def __init__(self, data: ButtonComponentPayload, message: Message):
self.message = message
self.type: ComponentType = try_enum(ComponentType, data['type']) self.type: ComponentType = try_enum(ComponentType, data['type'])
self.style: ButtonStyle = try_enum(ButtonStyle, data['style']) self.style: ButtonStyle = try_enum(ButtonStyle, data['style'])
self.custom_id: Optional[str] = data.get('custom_id') self.custom_id: Optional[str] = data.get('custom_id')
@ -193,6 +203,32 @@ class Button(Component):
return payload # type: ignore return payload # type: ignore
async def click(self):
"""|coro|
Clicks the button.
Raises
-------
HTTPException
Clicking the button failed.
"""
message = self.message
payload = {
'application_id': str(message.application_id),
'channel_id': str(message.channel.id),
'data': {
'component_type': 2,
'custom_id': self.custom_id,
},
'guild_id': message.guild and str(message.guild.id),
'message_flags': message.flags.value,
'message_id': str(message.id),
'nonce': str(time_snowflake(datetime.utcnow())),
'type': 3, # Should be an enum but eh
}
await message._state.http.interact(payload) # type: ignore
class SelectMenu(Component): class SelectMenu(Component):
"""Represents a select menu from the Discord Bot UI Kit. """Represents a select menu from the Discord Bot UI Kit.
@ -218,6 +254,8 @@ class SelectMenu(Component):
A list of options that can be selected in this menu. A list of options that can be selected in this menu.
disabled: :class:`bool` disabled: :class:`bool`
Whether the select is disabled or not. Whether the select is disabled or not.
message: :class:`Message`
The originating message, if any.
""" """
__slots__: Tuple[str, ...] = ( __slots__: Tuple[str, ...] = (
@ -231,7 +269,8 @@ class SelectMenu(Component):
__repr_info__: ClassVar[Tuple[str, ...]] = __slots__ __repr_info__: ClassVar[Tuple[str, ...]] = __slots__
def __init__(self, data: SelectMenuPayload): def __init__(self, data: SelectMenuPayload, message: Message):
self.message = message
self.type = ComponentType.select self.type = ComponentType.select
self.custom_id: str = data['custom_id'] self.custom_id: str = data['custom_id']
self.placeholder: Optional[str] = data.get('placeholder') self.placeholder: Optional[str] = data.get('placeholder')
@ -358,14 +397,14 @@ class SelectOption:
return payload return payload
def _component_factory(data: ComponentPayload) -> Component: def _component_factory(data: ComponentPayload, message: Message) -> Component:
component_type = data['type'] component_type = data['type']
if component_type == 1: if component_type == 1:
return ActionRow(data) return ActionRow(data, message)
elif component_type == 2: elif component_type == 2:
return Button(data) # type: ignore return Button(data, message) # type: ignore
elif component_type == 3: elif component_type == 3:
return SelectMenu(data) # type: ignore return SelectMenu(data, message) # type: ignore
else: else:
as_enum = try_enum(ComponentType, component_type) as_enum = try_enum(ComponentType, component_type)
return Component._raw_construct(type=as_enum) return Component._raw_construct(type=as_enum)

3
discord/http.py

@ -1880,3 +1880,6 @@ class HTTPClient:
} }
return self.request(Route('POST', '/report'), json=payload) return self.request(Route('POST', '/report'), json=payload)
def interact(self, data) -> Response[None]:
return self.request(Route('POST', '/interactions'), json=data)

25
discord/message.py

@ -75,7 +75,6 @@ if TYPE_CHECKING:
from .mentions import AllowedMentions from .mentions import AllowedMentions
from .user import User from .user import User
from .role import Role from .role import Role
from .ui.view import View
MR = TypeVar('MR', bound='MessageReference') MR = TypeVar('MR', bound='MessageReference')
EmojiInputType = Union[Emoji, PartialEmoji, str] EmojiInputType = Union[Emoji, PartialEmoji, str]
@ -604,6 +603,8 @@ class Message(Hashable):
.. versionadded:: 2.0 .. versionadded:: 2.0
guild: Optional[:class:`Guild`] guild: Optional[:class:`Guild`]
The guild that the message belongs to, if applicable. The guild that the message belongs to, if applicable.
application_id: Optional[:class:`int`]
The application that sent this message, if applicable.
""" """
__slots__ = ( __slots__ = (
@ -619,6 +620,7 @@ class Message(Hashable):
'content', 'content',
'channel', 'channel',
'webhook_id', 'webhook_id',
'application_id',
'mention_everyone', 'mention_everyone',
'embeds', 'embeds',
'id', 'id',
@ -659,6 +661,7 @@ class Message(Hashable):
self._state: ConnectionState = state self._state: ConnectionState = state
self.id: int = int(data['id']) self.id: int = int(data['id'])
self.webhook_id: Optional[int] = utils._get_as_snowflake(data, 'webhook_id') self.webhook_id: Optional[int] = utils._get_as_snowflake(data, 'webhook_id')
self.application_id: Optional[int] = utils._get_as_snowflake(data, 'application_id')
self.reactions: List[Reaction] = [Reaction(message=self, data=d) for d in data.get('reactions', [])] self.reactions: List[Reaction] = [Reaction(message=self, data=d) for d in data.get('reactions', [])]
self.attachments: List[Attachment] = [Attachment(data=a, state=self._state) for a in data['attachments']] self.attachments: List[Attachment] = [Attachment(data=a, state=self._state) for a in data['attachments']]
self.embeds: List[Embed] = [Embed.from_dict(a) for a in data['embeds']] self.embeds: List[Embed] = [Embed.from_dict(a) for a in data['embeds']]
@ -674,7 +677,7 @@ class Message(Hashable):
self.content: str = data['content'] self.content: str = data['content']
self.nonce: Optional[Union[int, str]] = data.get('nonce') self.nonce: Optional[Union[int, str]] = data.get('nonce')
self.stickers: List[StickerItem] = [StickerItem(data=d, state=state) for d in data.get('sticker_items', [])] self.stickers: List[StickerItem] = [StickerItem(data=d, state=state) for d in data.get('sticker_items', [])]
self.components: List[Component] = [_component_factory(d) for d in data.get('components', [])] self.components: List[Component] = [_component_factory(d, self) for d in data.get('components', [])]
self.call: Optional[CallMessage] = None self.call: Optional[CallMessage] = None
try: try:
@ -1757,11 +1760,6 @@ class PartialMessage(Hashable):
to the object, otherwise it uses the attributes set in :attr:`~discord.Client.allowed_mentions`. to the object, otherwise it uses the attributes set in :attr:`~discord.Client.allowed_mentions`.
If no object is passed at all then the defaults given by :attr:`~discord.Client.allowed_mentions` If no object is passed at all then the defaults given by :attr:`~discord.Client.allowed_mentions`
are used instead. are used instead.
view: Optional[:class:`~discord.ui.View`]
The updated view to update this message with. If ``None`` is passed then
the view is removed.
.. versionadded:: 2.0
Raises Raises
------- -------
@ -1818,17 +1816,6 @@ class PartialMessage(Hashable):
allowed_mentions = allowed_mentions.to_dict() allowed_mentions = allowed_mentions.to_dict()
fields['allowed_mentions'] = allowed_mentions fields['allowed_mentions'] = allowed_mentions
try:
view = fields.pop('view')
except KeyError:
# To check for the view afterwards
view = None
else:
self._state.prevent_view_updates_for(self.id)
if view:
fields['components'] = view.to_components()
else:
fields['components'] = []
if fields: if fields:
data = await self._state.http.edit_message(self.channel.id, self.id, **fields) data = await self._state.http.edit_message(self.channel.id, self.id, **fields)
@ -1839,6 +1826,4 @@ class PartialMessage(Hashable):
if fields: if fields:
# data isn't unbound # data isn't unbound
msg = self._state.create_message(channel=self.channel, data=data) # type: ignore msg = self._state.create_message(channel=self.channel, data=data) # type: ignore
if view and not view.is_finished():
self._state.store_view(view, self.id)
return msg return msg

Loading…
Cancel
Save