-
-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathinteractions.py
251 lines (205 loc) · 11.5 KB
/
interactions.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
import typing
import logging
from . import utils
from .user import User
from .member import Member
from .http import HTTPClient
from .message import Message
from .errors import NotFound
from .channel import DMChannel
from typing_extensions import Literal
from typing import Union, List, Optional
from .components import Button, SelectMenu
from .enums import ComponentType, InteractionCallbackType
log = logging.getLogger(__name__)
__all__ = ('EphemeralMessage',
'Interaction',
'ButtonClick',
'SelectionSelect')
class EphemeralMessage:
"""
Since Discord doesn't return anything when we send a ephemeral message,
this class has no attributes and you can't do anything with it.
"""
class ButtonClick:
"""Represents a :class:`discord.Button` that was pressed in an ephemeral Message(contains its custom_id and its hash)."""
def __init__(self, data):
self.component_type: int = data.get('component_type')
custom_id = data.get('custom_id')
self.custom_id: typing.Union[str, int] = int(custom_id) if custom_id.isdigit() else custom_id
self.__hash__: str = data.get('hash', None)
def __hash__(self):
return self.__hash__
def __repr__(self):
return f"<ButtonClick custom_id={self.custom_id}{', hash='+self.__hash__ if self.__hash__ else ''}>"
class SelectionSelect:
"""Represents a :class:`discord.SelectMenu` in an ephemeral Message from which options have been selected (contains its custom_id and the selected options)."""
def __init__(self, data):
self.component_type: int = data.get('component_type')
custom_id = data.get('custom_id')
self.custom_id: typing.Union[str, int] = int(custom_id) if custom_id.isdigit() else custom_id
self.values: typing.List[typing.Union[str, int]] = [int(value) if value.isdigit() else value for value in data.get('values', [])]
def __repr__(self):
return f'<SelectionSelect custom_id={self.custom_id}, values={self.values}>'
class Interaction:
"""
The Class for an discord-interaction like klick an :class:`Button` or select an option of :class:`SelectMenu` in discord
For more general information's about Interactions visit the Documentation of the
`Discord-API <https://discord.com/developers/docs/interactions/slash-commands#interaction-object>`_
"""
def __init__(self, state, data):
self._state = state
self._http: HTTPClient = state.http
self._interaction_type = data.get('type', None)
self.__token = data.get('token', None)
self._message = data.get('message')
self.message_id = int(self._message.get('id'))
self.message_flags = self._message.get('flags', 0)
self._data = data.get('data', None)
self._member = data.get('member', None)
self._user = data.get('user', self._member.get('user', None) if self._member else None)
self.user_id = int(self._user['id'])
self.__interaction_id = int(data.get('id'))
self.guild_id = int(data.get('guild_id', 0))
self._guild = None
self._channel = None
self.channel_id = int(data.get('channel_id', 0))
self.__application_id = int(data.get('application_id'))
self.member: typing.Optional[Member] = None
self.user: typing.Optional[User] = None
self.deferred = False
self.deferred_hidden = False
self.callback_message = None
self._component = None
self.component_type: typing.Optional[int] = self._data.get('component_type', None)
self.message = EphemeralMessage() if self.message_is_hidden else None #Message(state=self._state, channel=self.channel, data=self._message)
# maybe ``later`` this library will also supports Slash-Commands
# self.command = None
def __repr__(self):
"""Represents a :class:`discord.Interaction`-object."""
return f'<Interaction {", ".join(["%s=%s" % (a, getattr(self, a)) for a in self.__slots__ if a[0] != "_"])}>'
async def defer(self, response_type: Literal[5, 6] = InteractionCallbackType.deferred_update_msg, hidden: bool = False) -> None:
"""
|coro|
'Defers' the response.
If :attr:`response_type` is `InteractionCallbackType.deferred_msg_with_source` it shows a loading state to the user.
:param response_type: Optional[Literal[5, 6]]
The type to response with, aiter :class:`InteractionCallbackType.deferred_msg_with_source` or :class:`InteractionCallbackType.deferred_update_msg` (e.g. 5 or 6)
:param hidden: Optional[bool]
Whether to defer ephemerally(only the :attr:`author` of the interaction can see the message)
.. note::
Only for :class:`InteractionCallbackType.deferred_msg_with_source`.
.. important::
If you doesn't respond with an message using :meth:`respond`
or edit the original message using :meth:`edit` within less than 3 seconds,
discord will indicates that the interaction failed and the interaction-token will be invalidated.
To provide this us this method
.. note::
A Token will be Valid for 15 Minutes so you could edit the original :attr:`message` with :meth:`edit`, :meth:`respond` or doing anything other with this interaction for 15 minutes.
after that time you have to edit the original message with the Methode :meth:`edit` of the :attr:`message` and sending new messages with the :meth:`send` Methode of :attr:`channel`
(you could not do this hidden as it isn't an respond anymore).
"""
if isinstance(response_type, int):
response_type = InteractionCallbackType.from_value(response_type)
if response_type not in (InteractionCallbackType.deferred_msg_with_source, InteractionCallbackType.deferred_update_msg):
raise ValueError('response_type has to bee discord.InteractionCallbackType.deferred_msg_with_source or discord.InteractionCallbackType.deferred_update_msg (e.g. 5 or 6), not %s.__class__.__name__' % response_type)
if self.deferred:
return log.warning("\033[91You have already responded to this Interaction!\033[0m")
base = {"type": response_type.value, "data": {'flags': 64 if hidden else None}}
try:
data = await self._http.post_initial_response(_resp=base, use_webhook=False, interaction_id=self.__interaction_id,
token=self.__token, application_id=self.__application_id)
except NotFound:
log.warning(f'Unknown Interaction {self.__interaction_id}')
else:
self.deferred = True
if hidden is True and response_type is InteractionCallbackType.deferred_msg_with_source:
self.deferred_hidden = True
return data
async def edit(self, **fields) -> Message:
"""|coro|
'Defers' if it isn't yet and edit the message
"""
if not self.channel:
self._channel = self._state.add_dm_channel(data=await self._http.get_channel(self.channel_id))
await self.message.edit(__is_interaction_response=True, __deferred=False if (not self.deferred or self.callback_message) else True, __use_webhook=False,
__interaction_id=self.__interaction_id, __interaction_token=self.__token,
__application_id=self.__application_id, **fields)
self.deferred = True
return self.message
async def respond(self, content=None, *, tts=False, embed=None, embeds=None, components=None, file=None,
files=None, delete_after=None, nonce=None,
allowed_mentions=None, reference=None,
mention_author=None, hidden=False) -> typing.Union[Message, EphemeralMessage]:
"""|coro|
Responds to an interaction by sending a message that can be made visible only to the person who performed the
interaction by setting the `hidden` parameter to :bool:`True`.
"""
if not self.channel:
self._channel = self._state.add_dm_channel(data=await self._http.get_channel(self.channel_id))
msg = await self.channel.send(content, tts=tts, embed=embed, embeds=embeds, components=components, file=file,
files=files, delete_after=delete_after, nonce=nonce,
allowed_mentions=allowed_mentions, reference=reference,
mention_author=mention_author, hidden=hidden, __is_interaction_response=True,
__deferred=self.deferred, __use_webhook=False, __interaction_id=self.__interaction_id,
__interaction_token=self.__token, __application_id=self.__application_id,
followup=True if self.callback_message else False)
if hidden is True:
self.deferred_hidden = True
if not self.callback_message and not self.deferred:
self.callback_message = msg if msg else EphemeralMessage()
self.deferred = True
return msg
async def get_original_callback(self):
"""|coro|
Fetch the Original Callback-Message of the Interaction
.. warning::
This is a API-Call and should use carefully"""
return await self._state.http.get_original_interaction_response(self.__token, self.__application_id)
@property
def created_at(self):
"""
Returns the Interaction’s creation time in UTC.
:return: datetime.datetime
"""
return utils.snowflake_time(self.__interaction_id)
@property
def author(self) -> typing.Union[Member, User]:
return self.member if self.member is not None else self.user
@property
def channel(self):
return self._channel if self._channel else self.message.channel
@property
def guild(self):
return self._guild
@property
def message_is_dm(self) -> bool:
return not self.guild_id
#@property
#def message(self) -> typing.Union[Message, EphemeralMessage]:
# message = self._state._get_message(self.message_id)
# if not message:
# message = EphemeralMessage() if self.message_is_hidden else Message(state=self._state, channel=self.channel, data=self._message)
# return message
@property
def message_is_hidden(self) -> bool:
return self.message_flags == 64
@property
def component(self) -> Union[Button, SelectMenu, ButtonClick, SelectionSelect, None]:
if self._component is None:
custom_id = self._data.get('custom_id')
if custom_id is not None:
if custom_id.isdigit():
custom_id = int(custom_id)
if self._data.get('component_type') == 2:
self._component = utils.get(self.message.all_buttons, custom_id=custom_id)
elif self._data.get('component_type') == 3:
select_menu = utils.get(self.message.all_select_menus, custom_id=custom_id)
if select_menu is not None:
setattr(select_menu, '_values', self._data['values'])
self._component = select_menu
return self._component
class InteractionType:
PingAck = 1
SlashCommand = 2
Component = 3