rewrite: Initial rewrite skeleton.
Remove modules that will no longer be required. Move pending modules to pending-rewrite folders.
This commit is contained in:
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
|||||||
[submodule "bot/cmdClient"]
|
|
||||||
path = bot/cmdClient
|
|
||||||
url = https://github.com/Intery/dpy-cmdClient.git
|
|
||||||
|
|||||||
Submodule bot/cmdClient deleted from a6ece4cb02
@@ -1,5 +0,0 @@
|
|||||||
from . import enums
|
|
||||||
from .interactions import _component_interaction_factory, Interaction, ComponentInteraction, ModalResponse
|
|
||||||
from .components import *
|
|
||||||
from .modals import *
|
|
||||||
from .manager import InteractionManager
|
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
import logging
|
|
||||||
import traceback
|
|
||||||
import asyncio
|
|
||||||
import uuid
|
|
||||||
import json
|
|
||||||
|
|
||||||
from .enums import ButtonStyle, InteractionType
|
|
||||||
|
|
||||||
|
|
||||||
class MessageComponent:
|
|
||||||
_type = None
|
|
||||||
interaction_type = InteractionType.MESSAGE_COMPONENT
|
|
||||||
|
|
||||||
def __init_(self, *args, **kwargs):
|
|
||||||
self.message = None
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def to_json(self):
|
|
||||||
return json.dumps(self.to_dict())
|
|
||||||
|
|
||||||
|
|
||||||
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 AwaitableComponent:
|
|
||||||
interaction_type: InteractionType = None
|
|
||||||
|
|
||||||
async def wait_for(self, timeout=None, check=None):
|
|
||||||
from meta import client
|
|
||||||
|
|
||||||
def _check(interaction):
|
|
||||||
valid = True
|
|
||||||
valid = valid and interaction.interaction_type == self.interaction_type
|
|
||||||
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 add_callback(self, timeout=None, repeat=True, check=None, pass_args=(), pass_kwargs={}):
|
|
||||||
def wrapper(func):
|
|
||||||
async def _func(*args, **kwargs):
|
|
||||||
try:
|
|
||||||
return await func(*args, **kwargs)
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
pass
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
pass
|
|
||||||
except Exception:
|
|
||||||
from meta import client
|
|
||||||
full_traceback = traceback.format_exc()
|
|
||||||
|
|
||||||
client.log(
|
|
||||||
f"Caught an unhandled exception while executing interaction callback "
|
|
||||||
f"for interaction type '{self.interaction_type.name}' with id '{self.custom_id}'.\n"
|
|
||||||
f"{self!r}\n"
|
|
||||||
f"{func!r}\n"
|
|
||||||
f"{full_traceback}",
|
|
||||||
context=f"cid:{self.custom_id}",
|
|
||||||
level=logging.ERROR
|
|
||||||
)
|
|
||||||
|
|
||||||
async def wrapped():
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
button_press = await self.wait_for(timeout=timeout, check=check)
|
|
||||||
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 Button(MessageComponent, AwaitableComponent):
|
|
||||||
_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 = str(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()
|
|
||||||
if self.disabled:
|
|
||||||
data['disabled'] = self.disabled
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
class SelectOption:
|
|
||||||
def __init__(self, label, value, description, emoji=None, default=False):
|
|
||||||
self.label = label
|
|
||||||
self.value = value
|
|
||||||
self.description = description
|
|
||||||
self.emoji = emoji
|
|
||||||
self.default = default
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
data = {
|
|
||||||
"label": self.label,
|
|
||||||
"value": self.value,
|
|
||||||
"description": self.description,
|
|
||||||
}
|
|
||||||
if self.emoji:
|
|
||||||
data['emoji'] = self.emoji.to_dict()
|
|
||||||
if self.default:
|
|
||||||
data['default'] = self.default
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
class SelectMenu(MessageComponent, AwaitableComponent):
|
|
||||||
_type = 3
|
|
||||||
|
|
||||||
def __init__(self, *options, custom_id=None, placeholder=None, min_values=None, max_values=None, disabled=False):
|
|
||||||
self.options = options
|
|
||||||
self.custom_id = custom_id or str(uuid.uuid4())
|
|
||||||
self.placeholder = placeholder
|
|
||||||
self.min_values = min_values
|
|
||||||
self.max_values = max_values
|
|
||||||
self.disabled = disabled
|
|
||||||
|
|
||||||
def set_default(self, value=None, index=None):
|
|
||||||
"""
|
|
||||||
Convenience method to set the default option.
|
|
||||||
"""
|
|
||||||
if index is not None and value is not None:
|
|
||||||
raise ValueError("Both index and value were supplied for the default.")
|
|
||||||
if index is not None:
|
|
||||||
for i, option in enumerate(self.options):
|
|
||||||
option.default = (i == index)
|
|
||||||
elif value is not None:
|
|
||||||
for option in self.options:
|
|
||||||
option.default = (option.value == value)
|
|
||||||
else:
|
|
||||||
for option in self.options:
|
|
||||||
option.default = False
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
data = {
|
|
||||||
"type": self._type,
|
|
||||||
'custom_id': self.custom_id,
|
|
||||||
'options': [option.to_dict() for option in self.options],
|
|
||||||
}
|
|
||||||
if self.placeholder:
|
|
||||||
data['placeholder'] = self.placeholder
|
|
||||||
if self.min_values:
|
|
||||||
data['min_values'] = self.min_values
|
|
||||||
if self.max_values:
|
|
||||||
data['max_values'] = self.max_values
|
|
||||||
if self.disabled:
|
|
||||||
data['disabled'] = self.disabled
|
|
||||||
return data
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
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 TextInputStyle(IntEnum):
|
|
||||||
SHORT = 1
|
|
||||||
PARAGRAPH = 2
|
|
||||||
|
|
||||||
|
|
||||||
class InteractionCallback(IntEnum):
|
|
||||||
PONG = 1
|
|
||||||
CHANNEL_MESSAGE_WITH_SOURCE = 4
|
|
||||||
DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE = 5
|
|
||||||
DEFERRED_UPDATE_MESSAGE = 6
|
|
||||||
UPDATE_MESSAGE = 7
|
|
||||||
APPLICATION_COMMAND_AUTOCOMPLETE_RESULT = 8
|
|
||||||
MODAL = 9
|
|
||||||
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
from .enums import ComponentType, InteractionType, InteractionCallback
|
|
||||||
|
|
||||||
|
|
||||||
class Interaction:
|
|
||||||
__slots__ = (
|
|
||||||
'id',
|
|
||||||
'token',
|
|
||||||
'_state'
|
|
||||||
)
|
|
||||||
|
|
||||||
async def response(self, content=None, embed=None, embeds=None, components=None, ephemeral=None):
|
|
||||||
data = {}
|
|
||||||
if content is not None:
|
|
||||||
data['content'] = str(content)
|
|
||||||
|
|
||||||
if embed is not None:
|
|
||||||
embeds = embeds or []
|
|
||||||
embeds.append(embed)
|
|
||||||
|
|
||||||
if embeds is not None:
|
|
||||||
data['embeds'] = [embed.to_dict() for embed in embeds]
|
|
||||||
|
|
||||||
if components is not None:
|
|
||||||
data['components'] = [component.to_dict() for component in components]
|
|
||||||
|
|
||||||
if ephemeral is not None:
|
|
||||||
data['flags'] = 1 << 6
|
|
||||||
|
|
||||||
return await self._state.http.interaction_callback(
|
|
||||||
self.id,
|
|
||||||
self.token,
|
|
||||||
InteractionCallback.CHANNEL_MESSAGE_WITH_SOURCE,
|
|
||||||
data
|
|
||||||
)
|
|
||||||
|
|
||||||
async def response_update(self, content=None, embed=None, embeds=None, components=None):
|
|
||||||
data = {}
|
|
||||||
if content is not None:
|
|
||||||
data['content'] = str(content)
|
|
||||||
|
|
||||||
if embed is not None:
|
|
||||||
embeds = embeds or []
|
|
||||||
embeds.append(embed)
|
|
||||||
|
|
||||||
if embeds is not None:
|
|
||||||
data['embeds'] = [embed.to_dict() for embed in embeds]
|
|
||||||
|
|
||||||
if components is not None:
|
|
||||||
data['components'] = [component.to_dict() for component in components]
|
|
||||||
|
|
||||||
return await self._state.http.interaction_callback(
|
|
||||||
self.id,
|
|
||||||
self.token,
|
|
||||||
InteractionCallback.UPDATE_MESSAGE,
|
|
||||||
data
|
|
||||||
)
|
|
||||||
|
|
||||||
async def response_deferred(self):
|
|
||||||
return await self._state.http.interaction_callback(
|
|
||||||
self.id,
|
|
||||||
self.token,
|
|
||||||
InteractionCallback.DEFERRED_UPDATE_MESSAGE
|
|
||||||
)
|
|
||||||
|
|
||||||
async def response_modal(self, modal):
|
|
||||||
return await self._state.http.interaction_callback(
|
|
||||||
self.id,
|
|
||||||
self.token,
|
|
||||||
InteractionCallback.MODAL,
|
|
||||||
modal.to_dict()
|
|
||||||
)
|
|
||||||
|
|
||||||
def ack(self):
|
|
||||||
asyncio.create_task(self.response_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__ = ()
|
|
||||||
|
|
||||||
|
|
||||||
class Selection(ComponentInteraction):
|
|
||||||
__slots__ = ('values',)
|
|
||||||
|
|
||||||
def _from_data(self, data):
|
|
||||||
super()._from_data(data)
|
|
||||||
self.values = data['data']['values']
|
|
||||||
|
|
||||||
@property
|
|
||||||
def value(self):
|
|
||||||
if len(self.values) > 1:
|
|
||||||
raise ValueError("Cannot use 'value' property on multi-selection.")
|
|
||||||
elif len(self.values) == 1:
|
|
||||||
return self.values[0]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class ModalResponse(Interaction):
|
|
||||||
__slots__ = (
|
|
||||||
'message',
|
|
||||||
'user',
|
|
||||||
'_state'
|
|
||||||
'id',
|
|
||||||
'token',
|
|
||||||
'application_id',
|
|
||||||
'custom_id',
|
|
||||||
'values'
|
|
||||||
)
|
|
||||||
|
|
||||||
interaction_type = InteractionType.MODAL_SUBMIT
|
|
||||||
|
|
||||||
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.custom_id = component_data.get('custom_id', None)
|
|
||||||
|
|
||||||
values = {}
|
|
||||||
for row in component_data['components']:
|
|
||||||
for component in row['components']:
|
|
||||||
values[component['custom_id']] = component['value']
|
|
||||||
self.values = values
|
|
||||||
|
|
||||||
|
|
||||||
def _component_interaction_factory(data):
|
|
||||||
component_type = data['data']['component_type']
|
|
||||||
|
|
||||||
if component_type == ComponentType.BUTTON:
|
|
||||||
return ButtonPress
|
|
||||||
elif component_type == ComponentType.SELECTMENU:
|
|
||||||
return Selection
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
|
|
||||||
|
|
||||||
class InteractionManager:
|
|
||||||
def __init__(self, timeout=600, extend=None):
|
|
||||||
self.futures = []
|
|
||||||
self.self_futures = []
|
|
||||||
|
|
||||||
self.cleanup_function = self._cleanup
|
|
||||||
self.timeout_function = self._timeout
|
|
||||||
self.close_function = self._close
|
|
||||||
|
|
||||||
self.timeout = timeout
|
|
||||||
self.extend = extend or timeout
|
|
||||||
self.expires_at = None
|
|
||||||
|
|
||||||
self.cleaned_up = asyncio.Event()
|
|
||||||
|
|
||||||
async def _timeout_loop(self):
|
|
||||||
diff = (self.expires_at - datetime.now()).total_seconds()
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
await asyncio.sleep(diff)
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
break
|
|
||||||
diff = (self.expires_at - datetime.now()).total_seconds()
|
|
||||||
if diff <= 0:
|
|
||||||
asyncio.create_task(self.run_timeout())
|
|
||||||
break
|
|
||||||
|
|
||||||
def extend_timeout(self):
|
|
||||||
new_expiry = max(datetime.now() + timedelta(seconds=self.extend), self.expires_at)
|
|
||||||
self.expires_at = new_expiry
|
|
||||||
|
|
||||||
async def wait(self):
|
|
||||||
"""
|
|
||||||
Wait until the manager is "done".
|
|
||||||
That is, until all the futures are done, or `closed` is set.
|
|
||||||
"""
|
|
||||||
closed_task = asyncio.create_task(self.cleaned_up.wait())
|
|
||||||
futures_task = asyncio.create_task(asyncio.wait(self.futures))
|
|
||||||
await asyncio.wait((closed_task, futures_task), return_when=asyncio.FIRST_COMPLETED)
|
|
||||||
|
|
||||||
async def __aenter__(self):
|
|
||||||
if self.timeout is not None:
|
|
||||||
self.expires_at = datetime.now() + timedelta(seconds=self.timeout)
|
|
||||||
self.self_futures.append(
|
|
||||||
asyncio.create_task(self._timeout_loop())
|
|
||||||
)
|
|
||||||
return self
|
|
||||||
|
|
||||||
async def __aexit__(self, *args):
|
|
||||||
if not self.cleaned_up.is_set():
|
|
||||||
await self.cleanup(exiting=True)
|
|
||||||
|
|
||||||
async def _cleanup(self, manager, timeout=False, closing=False, exiting=False, **kwargs):
|
|
||||||
for future in self.futures:
|
|
||||||
future.cancel()
|
|
||||||
for future in self.self_futures:
|
|
||||||
future.cancel()
|
|
||||||
self.cleaned_up.set()
|
|
||||||
|
|
||||||
def on_cleanup(self, func):
|
|
||||||
self.cleanup_function = func
|
|
||||||
return func
|
|
||||||
|
|
||||||
async def cleanup(self, **kwargs):
|
|
||||||
try:
|
|
||||||
await self.cleanup_function(self, **kwargs)
|
|
||||||
except Exception:
|
|
||||||
logging.debug("An error occurred while cleaning up the InteractionManager", exc_info=True)
|
|
||||||
|
|
||||||
async def _timeout(self, manager, **kwargs):
|
|
||||||
await self.cleanup(timeout=True, **kwargs)
|
|
||||||
|
|
||||||
def on_timeout(self, func):
|
|
||||||
self.timeout_function = func
|
|
||||||
return func
|
|
||||||
|
|
||||||
async def run_timeout(self):
|
|
||||||
try:
|
|
||||||
await self.timeout_function(self)
|
|
||||||
except Exception:
|
|
||||||
logging.debug("An error occurred while timing out the InteractionManager", exc_info=True)
|
|
||||||
|
|
||||||
async def close(self, **kwargs):
|
|
||||||
"""
|
|
||||||
Request closure of the manager.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
await self.close_function(self, **kwargs)
|
|
||||||
except Exception:
|
|
||||||
logging.debug("An error occurred while closing the InteractionManager", exc_info=True)
|
|
||||||
|
|
||||||
def on_close(self, func):
|
|
||||||
self.close_function = func
|
|
||||||
return func
|
|
||||||
|
|
||||||
async def _close(self, manager, **kwargs):
|
|
||||||
await self.cleanup(closing=True, **kwargs)
|
|
||||||
|
|
||||||
def add_future(self, future):
|
|
||||||
self.futures.append(future)
|
|
||||||
return future
|
|
||||||
|
|
||||||
def register(self, component, callback, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Attaches the given awaitable interaction and adds the given callback.
|
|
||||||
"""
|
|
||||||
future = component.add_callback(*args, **kwargs)(callback)
|
|
||||||
self.add_future(future)
|
|
||||||
return component
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import uuid
|
|
||||||
|
|
||||||
from .enums import TextInputStyle, InteractionType
|
|
||||||
from .components import AwaitableComponent
|
|
||||||
|
|
||||||
|
|
||||||
class Modal(AwaitableComponent):
|
|
||||||
interaction_type = InteractionType.MODAL_SUBMIT
|
|
||||||
|
|
||||||
def __init__(self, title, *components, custom_id=None):
|
|
||||||
self.custom_id = custom_id or str(uuid.uuid4())
|
|
||||||
|
|
||||||
self.title = title
|
|
||||||
self.components = components
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
data = {
|
|
||||||
'title': self.title,
|
|
||||||
'custom_id': self.custom_id,
|
|
||||||
'components': [comp.to_dict() for comp in self.components]
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
class TextInput:
|
|
||||||
_type = 4
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
label, placeholder=None, value=None, required=False,
|
|
||||||
style=TextInputStyle.SHORT, min_length=None, max_length=None,
|
|
||||||
custom_id=None
|
|
||||||
):
|
|
||||||
self.custom_id = custom_id or str(uuid.uuid4())
|
|
||||||
|
|
||||||
self.label = label
|
|
||||||
self.placeholder = placeholder
|
|
||||||
self.value = value
|
|
||||||
self.required = required
|
|
||||||
self.style = style
|
|
||||||
self.min_length = min_length
|
|
||||||
self.max_length = max_length
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
data = {
|
|
||||||
'type': self._type,
|
|
||||||
'custom_id': self.custom_id,
|
|
||||||
'style': int(self.style),
|
|
||||||
'label': self.label,
|
|
||||||
}
|
|
||||||
for key in ('min_length', 'max_length', 'required', 'value', 'placeholder'):
|
|
||||||
if (value := getattr(self, key)) is not None:
|
|
||||||
data[key] = value
|
|
||||||
return data
|
|
||||||
@@ -1,289 +0,0 @@
|
|||||||
"""
|
|
||||||
Temporary patches for the discord.py library to support new features of the discord API.
|
|
||||||
"""
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from json import JSONEncoder
|
|
||||||
|
|
||||||
from discord.state import ConnectionState
|
|
||||||
from discord.http import Route, HTTPClient
|
|
||||||
from discord.abc import Messageable
|
|
||||||
from discord.utils import InvalidArgument, _get_as_snowflake, to_json
|
|
||||||
from discord import File, AllowedMentions, Member, User, Message
|
|
||||||
|
|
||||||
from .interactions import _component_interaction_factory, ModalResponse
|
|
||||||
from .interactions.enums import InteractionType
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def _default(self, obj):
|
|
||||||
return getattr(obj.__class__, "to_json", _default.default)(obj)
|
|
||||||
|
|
||||||
|
|
||||||
_default.default = JSONEncoder().default
|
|
||||||
JSONEncoder.default = _default
|
|
||||||
|
|
||||||
|
|
||||||
def send_message(self, channel_id, content, *, tts=False, embeds=None,
|
|
||||||
nonce=None, allowed_mentions=None, message_reference=None, components=None):
|
|
||||||
r = Route('POST', '/channels/{channel_id}/messages', channel_id=channel_id)
|
|
||||||
payload = {}
|
|
||||||
|
|
||||||
if content:
|
|
||||||
payload['content'] = content
|
|
||||||
|
|
||||||
if tts:
|
|
||||||
payload['tts'] = True
|
|
||||||
|
|
||||||
if embeds:
|
|
||||||
payload['embeds'] = embeds
|
|
||||||
|
|
||||||
if nonce:
|
|
||||||
payload['nonce'] = nonce
|
|
||||||
|
|
||||||
if allowed_mentions:
|
|
||||||
payload['allowed_mentions'] = allowed_mentions
|
|
||||||
|
|
||||||
if message_reference:
|
|
||||||
payload['message_reference'] = message_reference
|
|
||||||
|
|
||||||
if components:
|
|
||||||
payload['components'] = components
|
|
||||||
|
|
||||||
return self.request(r, json=payload)
|
|
||||||
|
|
||||||
|
|
||||||
def send_files(
|
|
||||||
self,
|
|
||||||
channel_id, *,
|
|
||||||
files,
|
|
||||||
content=None, tts=False, embed=None, embeds=None, nonce=None, allowed_mentions=None, message_reference=None,
|
|
||||||
components=None
|
|
||||||
):
|
|
||||||
r = Route('POST', '/channels/{channel_id}/messages', channel_id=channel_id)
|
|
||||||
form = []
|
|
||||||
|
|
||||||
payload = {'tts': tts}
|
|
||||||
if content:
|
|
||||||
payload['content'] = content
|
|
||||||
if embed:
|
|
||||||
payload['embed'] = embed
|
|
||||||
if embeds:
|
|
||||||
payload['embeds'] = embeds
|
|
||||||
if nonce:
|
|
||||||
payload['nonce'] = nonce
|
|
||||||
if allowed_mentions:
|
|
||||||
payload['allowed_mentions'] = allowed_mentions
|
|
||||||
if message_reference:
|
|
||||||
payload['message_reference'] = message_reference
|
|
||||||
if components:
|
|
||||||
payload['components'] = components
|
|
||||||
|
|
||||||
form.append({'name': 'payload_json', 'value': to_json(payload)})
|
|
||||||
if len(files) == 1:
|
|
||||||
file = files[0]
|
|
||||||
form.append({
|
|
||||||
'name': 'file',
|
|
||||||
'value': file.fp,
|
|
||||||
'filename': file.filename,
|
|
||||||
'content_type': 'application/octet-stream'
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
for index, file in enumerate(files):
|
|
||||||
form.append({
|
|
||||||
'name': 'file%s' % index,
|
|
||||||
'value': file.fp,
|
|
||||||
'filename': file.filename,
|
|
||||||
'content_type': 'application/octet-stream'
|
|
||||||
})
|
|
||||||
|
|
||||||
return self.request(r, form=form, files=files)
|
|
||||||
|
|
||||||
|
|
||||||
def interaction_callback(self, interaction_id, interaction_token, callback_type, callback_data=None):
|
|
||||||
r = Route(
|
|
||||||
'POST',
|
|
||||||
'/interactions/{interaction_id}/{interaction_token}/callback',
|
|
||||||
interaction_id=interaction_id,
|
|
||||||
interaction_token=interaction_token
|
|
||||||
)
|
|
||||||
|
|
||||||
payload = {}
|
|
||||||
|
|
||||||
payload['type'] = int(callback_type)
|
|
||||||
if callback_data:
|
|
||||||
payload['data'] = callback_data
|
|
||||||
|
|
||||||
return self.request(r, json=payload)
|
|
||||||
|
|
||||||
|
|
||||||
def interaction_edit(self, application_id, interaction_token, content=None, embed=None, embeds=None, components=None):
|
|
||||||
r = Route(
|
|
||||||
'PATCH',
|
|
||||||
'/webhooks/{application_id}/{interaction_token}/messages/@original',
|
|
||||||
application_id=application_id,
|
|
||||||
interaction_token=interaction_token
|
|
||||||
)
|
|
||||||
payload = {}
|
|
||||||
if content is not None:
|
|
||||||
payload['content'] = str(content)
|
|
||||||
|
|
||||||
if embed is not None:
|
|
||||||
embeds = embeds or []
|
|
||||||
embeds.append(embed)
|
|
||||||
|
|
||||||
if embeds is not None:
|
|
||||||
payload['embeds'] = [embed.to_dict() for embed in embeds]
|
|
||||||
|
|
||||||
if components is not None:
|
|
||||||
payload['components'] = [component.to_dict() for component in components]
|
|
||||||
|
|
||||||
return self.request(r, json=payload)
|
|
||||||
|
|
||||||
|
|
||||||
def edit_message(self, channel_id, message_id, components=None, **fields):
|
|
||||||
r = Route('PATCH', '/channels/{channel_id}/messages/{message_id}', channel_id=channel_id, message_id=message_id)
|
|
||||||
if components is not None:
|
|
||||||
fields['components'] = [comp.to_dict() for comp in components]
|
|
||||||
return self.request(r, json=fields)
|
|
||||||
|
|
||||||
|
|
||||||
HTTPClient.send_files = send_files
|
|
||||||
HTTPClient.send_message = send_message
|
|
||||||
HTTPClient.edit_message = edit_message
|
|
||||||
HTTPClient.interaction_callback = interaction_callback
|
|
||||||
HTTPClient.interaction_edit = interaction_edit
|
|
||||||
|
|
||||||
|
|
||||||
async def send(self, content=None, *, tts=False, embed=None, embeds=None, file=None,
|
|
||||||
files=None, delete_after=None, nonce=None,
|
|
||||||
allowed_mentions=None, reference=None,
|
|
||||||
mention_author=None, components=None):
|
|
||||||
|
|
||||||
channel = await self._get_channel()
|
|
||||||
state = self._state
|
|
||||||
content = str(content) if content is not None else None
|
|
||||||
if embed is not None:
|
|
||||||
if embeds is not None:
|
|
||||||
embeds.append(embed)
|
|
||||||
else:
|
|
||||||
embeds = [embed]
|
|
||||||
embed = embed.to_dict()
|
|
||||||
if embeds is not None:
|
|
||||||
embeds = [embed.to_dict() for embed in embeds]
|
|
||||||
|
|
||||||
if components is not None:
|
|
||||||
components = [comp.to_dict() for comp in components]
|
|
||||||
|
|
||||||
if allowed_mentions is not None:
|
|
||||||
if state.allowed_mentions is not None:
|
|
||||||
allowed_mentions = state.allowed_mentions.merge(allowed_mentions).to_dict()
|
|
||||||
else:
|
|
||||||
allowed_mentions = allowed_mentions.to_dict()
|
|
||||||
else:
|
|
||||||
allowed_mentions = state.allowed_mentions and state.allowed_mentions.to_dict()
|
|
||||||
|
|
||||||
if mention_author is not None:
|
|
||||||
allowed_mentions = allowed_mentions or AllowedMentions().to_dict()
|
|
||||||
allowed_mentions['replied_user'] = bool(mention_author)
|
|
||||||
|
|
||||||
if reference is not None:
|
|
||||||
try:
|
|
||||||
reference = reference.to_message_reference_dict()
|
|
||||||
except AttributeError:
|
|
||||||
raise InvalidArgument('reference parameter must be Message or MessageReference') from None
|
|
||||||
|
|
||||||
if file is not None and files is not None:
|
|
||||||
raise InvalidArgument('cannot pass both file and files parameter to send()')
|
|
||||||
|
|
||||||
if file is not None:
|
|
||||||
if not isinstance(file, File):
|
|
||||||
raise InvalidArgument('file parameter must be File')
|
|
||||||
|
|
||||||
try:
|
|
||||||
data = await state.http.send_files(channel.id, files=[file], allowed_mentions=allowed_mentions,
|
|
||||||
content=content, tts=tts, embed=embed, nonce=nonce,
|
|
||||||
message_reference=reference, components=components)
|
|
||||||
finally:
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
elif files is not None:
|
|
||||||
if len(files) > 10:
|
|
||||||
raise InvalidArgument('files parameter must be a list of up to 10 elements')
|
|
||||||
elif not all(isinstance(file, File) for file in files):
|
|
||||||
raise InvalidArgument('files parameter must be a list of File')
|
|
||||||
|
|
||||||
try:
|
|
||||||
data = await state.http.send_files(channel.id, files=files, content=content, tts=tts,
|
|
||||||
embeds=embeds, nonce=nonce, allowed_mentions=allowed_mentions,
|
|
||||||
message_reference=reference, components=components)
|
|
||||||
finally:
|
|
||||||
for f in files:
|
|
||||||
f.close()
|
|
||||||
else:
|
|
||||||
data = await state.http.send_message(channel.id, content, tts=tts, embeds=embeds,
|
|
||||||
nonce=nonce, allowed_mentions=allowed_mentions,
|
|
||||||
message_reference=reference, components=components)
|
|
||||||
|
|
||||||
ret = state.create_message(channel=channel, data=data)
|
|
||||||
if delete_after is not None:
|
|
||||||
await ret.delete(delay=delete_after)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
Messageable.send = send
|
|
||||||
|
|
||||||
|
|
||||||
def parse_interaction_create(self, data):
|
|
||||||
self.dispatch('raw_interaction_create', data)
|
|
||||||
|
|
||||||
if (guild_id := data.get('guild_id', None)):
|
|
||||||
guild = self._get_guild(int(guild_id))
|
|
||||||
if guild is None:
|
|
||||||
log.debug('INTERACTION_CREATE referencing an unknown guild ID: %s. Discarding.', guild_id)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
guild = None
|
|
||||||
|
|
||||||
if (member_data := data.get('member', None)) is not None:
|
|
||||||
# Construct member
|
|
||||||
# TODO: Theoretical reliance on cached guild
|
|
||||||
user = Member(data=member_data, guild=guild, state=self)
|
|
||||||
else:
|
|
||||||
# Assume user
|
|
||||||
user = self.get_user(_get_as_snowflake(data['user'], 'id')) or User(data=data['user'], state=self)
|
|
||||||
|
|
||||||
if 'message' in data:
|
|
||||||
message = self._get_message(_get_as_snowflake(data['message'], 'id'))
|
|
||||||
if not message:
|
|
||||||
message_data = data['message']
|
|
||||||
channel, _ = self._get_guild_channel(message_data)
|
|
||||||
message = Message(data=message_data, channel=channel, state=self)
|
|
||||||
if self._messages is not None:
|
|
||||||
self._messages.append(message)
|
|
||||||
else:
|
|
||||||
message = None
|
|
||||||
|
|
||||||
interaction = None
|
|
||||||
if data['type'] == InteractionType.MESSAGE_COMPONENT:
|
|
||||||
interaction_class = _component_interaction_factory(data)
|
|
||||||
if interaction_class:
|
|
||||||
interaction = interaction_class(message, user, data, self)
|
|
||||||
else:
|
|
||||||
log.debug(
|
|
||||||
'INTERACTION_CREATE recieved unhandled message component interaction type: %s',
|
|
||||||
data['data']['component_type']
|
|
||||||
)
|
|
||||||
elif data['type'] == InteractionType.MODAL_SUBMIT:
|
|
||||||
interaction = ModalResponse(message, user, data, self)
|
|
||||||
else:
|
|
||||||
log.debug('INTERACTION_CREATE recieved unhandled interaction type: %s', data['type'])
|
|
||||||
log.debug(data)
|
|
||||||
interaction = None
|
|
||||||
|
|
||||||
if interaction:
|
|
||||||
self.dispatch('interaction_create', interaction)
|
|
||||||
|
|
||||||
|
|
||||||
ConnectionState.parse_interaction_create = parse_interaction_create
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user