feature (interactions): Basic button support.
This commit is contained in:
3
bot/meta/interactions/__init__.py
Normal file
3
bot/meta/interactions/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from . import enums
|
||||
from .interactions import _component_interaction_factory, Interaction, ComponentInteraction
|
||||
from .components import *
|
||||
125
bot/meta/interactions/components.py
Normal file
125
bot/meta/interactions/components.py
Normal 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 = {}
|
||||
28
bot/meta/interactions/enums.py
Normal file
28
bot/meta/interactions/enums.py
Normal 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
|
||||
52
bot/meta/interactions/interactions.py
Normal file
52
bot/meta/interactions/interactions.py
Normal 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
|
||||
Reference in New Issue
Block a user