feature (interactions): Basic button support.

This commit is contained in:
2022-04-18 12:53:17 +03:00
parent f753271403
commit cc7c988007
8 changed files with 299 additions and 7 deletions

View File

@@ -0,0 +1,3 @@
from . import enums
from .interactions import _component_interaction_factory, Interaction, ComponentInteraction
from .components import *

View File

@@ -0,0 +1,125 @@
import asyncio
import uuid
from .enums import ButtonStyle, InteractionType
"""
Notes:
When interaction is sent, add message info
Add wait_for to Button and SelectMenu
wait_for_interaction for generic
listen=True for the listenables, register with a listener
Need a deregister then as well
send(..., components=[ActionRow(Button(...))])
Automatically ack interaction? DEFERRED_UPDATE_MESSAGE
async def Button.wait_for(timeout=None, ack=False)
Blocks until the button is pressed. Returns a ButtonPress (Interaction).
def MessageComponent.add_callback(timeout)
Adds an async callback function to the Component.
Construct the response independent of the original component.
Original component has a convenience wait_for that runs wait_for_interaction(custom_id=self.custom_id)...
The callback? Just add a wait_for
"""
class MessageComponent:
_type = None
def __init_(self, *args, **kwargs):
self.message = None
def listen(self):
...
def close(self):
...
class ActionRow(MessageComponent):
_type = 1
def __init__(self, *components):
self.components = components
def to_dict(self):
data = {
"type": self._type,
"components": [comp.to_dict() for comp in self.components]
}
return data
class Button(MessageComponent):
_type = 2
def __init__(self, label, style=ButtonStyle.PRIMARY, custom_id=None, url=None, emoji=None, disabled=False):
if style == ButtonStyle.LINK:
if url is None:
raise ValueError("Link buttons must have a url")
custom_id = None
elif custom_id is None:
custom_id = uuid.uuid4()
self.label = label
self.style = style
self.custom_id = custom_id
self.url = url
self.emoji = emoji
self.disabled = disabled
def to_dict(self):
data = {
"type": self._type,
"label": self.label,
"style": int(self.style)
}
if self.style == ButtonStyle.LINK:
data['url'] = self.url
else:
data['custom_id'] = self.custom_id
if self.emoji is not None:
# TODO: This only supports PartialEmoji, not Emoji
data['emoji'] = self.emoji.to_dict()
return data
async def wait_for_press(self, timeout=None, check=None):
from meta import client
def _check(interaction):
valid = True
print(interaction.custom_id)
valid = valid and interaction.interaction_type == InteractionType.MESSAGE_COMPONENT
valid = valid and interaction.custom_id == self.custom_id
valid = valid and (check is None or check(interaction))
return valid
return await client.wait_for('interaction_create', timeout=timeout, check=_check)
def on_press(self, timeout=None, repeat=True, pass_args=(), pass_kwargs={}):
def wrapper(func):
async def wrapped():
while True:
try:
button_press = await self.wait_for_press(timeout=timeout)
except asyncio.TimeoutError:
break
asyncio.create_task(func(button_press, *pass_args, **pass_kwargs))
if not repeat:
break
future = asyncio.create_task(wrapped())
return future
return wrapper
class SelectMenu(MessageComponent):
_type = 3
# MessageComponent listener
live_components = {}

View File

@@ -0,0 +1,28 @@
from enum import IntEnum
class InteractionType(IntEnum):
PING = 1
APPLICATION_COMMAND = 2
MESSAGE_COMPONENT = 3
APPLICATION_COMMAND_AUTOCOMPLETE = 4
MODAL_SUBMIT = 5
class ComponentType(IntEnum):
ACTIONROW = 1
BUTTON = 2
SELECTMENU = 3
TEXTINPUT = 4
class ButtonStyle(IntEnum):
PRIMARY = 1
SECONDARY = 2
SUCCESS = 3
DANGER = 4
LINK = 5
class InteractionCallback(IntEnum):
DEFERRED_UPDATE_MESSAGE = 6

View File

@@ -0,0 +1,52 @@
import asyncio
from .enums import ComponentType, InteractionType, InteractionCallback
class Interaction:
__slots__ = (
'id',
'token',
'_state'
)
async def callback_deferred(self):
return await self._state.http.interaction_callback(self.id, self.token, InteractionCallback.DEFERRED_UPDATE_MESSAGE)
def ack(self):
asyncio.create_task(self.callback_deferred())
class ComponentInteraction(Interaction):
interaction_type = InteractionType.MESSAGE_COMPONENT
# TODO: Slots
def __init__(self, message, user, data, state):
self.message = message
self.user = user
self._state = state
self._from_data(data)
def _from_data(self, data):
self.id = data['id']
self.token = data['token']
self.application_id = data['application_id']
component_data = data['data']
self.component_type = ComponentType(component_data['component_type'])
self.custom_id = component_data.get('custom_id', None)
class ButtonPress(ComponentInteraction):
__slots__ = ()
def _component_interaction_factory(data):
component_type = data['data']['component_type']
if component_type == ComponentType.BUTTON:
return ButtonPress
else:
return None