Skip to content

Commit b69c15f

Browse files
committed
base merge
1 parent 75fe3e9 commit b69c15f

File tree

7 files changed

+137
-43
lines changed

7 files changed

+137
-43
lines changed

interactions/api/gateway.py

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
class Heartbeat(Thread):
3333
"""
3434
A class representing a consistent heartbeat connection with the gateway.
35-
3635
:ivar WebSocket ws: The WebSocket class to infer on.
3736
:ivar Union[int, float] interval: The heartbeat interval determined by the gateway.
3837
:ivar Event event: The multi-threading event.
@@ -79,7 +78,6 @@ def stop(self) -> None:
7978
class WebSocket:
8079
"""
8180
A class representing a websocket connection with the gateway.
82-
8381
:ivar Intents intents: An instance of :class:`interactions.api.models.Intents`.
8482
:ivar AbstractEventLoop loop: The coroutine event loop established on.
8583
:ivar Request req: An instance of :class:`interactions.api.http.Request`.
@@ -151,7 +149,6 @@ async def connect(
151149
) -> None:
152150
"""
153151
Establishes a connection to the gateway.
154-
155152
:param token: The token to use for identifying.
156153
:type token: str
157154
:param shard?: The shard ID to identify under.
@@ -181,7 +178,6 @@ async def handle_connection(
181178
) -> None:
182179
"""
183180
Handles the connection to the gateway.
184-
185181
:param stream: The data stream from the gateway.
186182
:type stream: dict
187183
:param shard?: The shard ID to identify under.
@@ -243,7 +239,6 @@ async def handle_connection(
243239
def handle_dispatch(self, event: str, data: dict) -> None:
244240
"""
245241
Handles the dispatched event data from a gateway event.
246-
247242
:param event: The name of the event.
248243
:type event: str
249244
:param data: The data of the event.
@@ -277,37 +272,25 @@ def handle_dispatch(self, event: str, data: dict) -> None:
277272
context = self.contextualize(data)
278273
_name: str
279274
_args: list = [context]
275+
_kwargs: dict = dict()
280276
if data["type"] == InteractionType.APPLICATION_COMMAND:
281277
_name = context.data.name
282278
if hasattr(context.data, "options"):
283279
if context.data.options:
284280
for option in context.data.options:
285-
if option["type"] in (
286-
OptionType.SUB_COMMAND,
287-
OptionType.SUB_COMMAND_GROUP,
288-
):
289-
if option.get("options"):
290-
for sub_option in option["options"]:
291-
_args.append(sub_option)
292-
else:
293-
pass
294-
else:
295-
_args.append(option["value"])
281+
_kwargs.update(self.check_sub_command(option))
296282
elif data["type"] == InteractionType.MESSAGE_COMPONENT:
297283
_name = context.data.custom_id
298284
elif data["type"] == InteractionType.APPLICATION_COMMAND_AUTOCOMPLETE:
299285
_name = "autocomplete_"
300286
if hasattr(context.data, "options"):
301287
if context.data.options:
302288
for option in context.data.options:
303-
if option["type"] in (
304-
OptionType.SUB_COMMAND,
305-
OptionType.SUB_COMMAND_GROUP,
306-
):
307-
if option.get("options"):
308-
for sub_option in option["options"]:
309-
if sub_option.get("focused"):
310-
_name += sub_option["name"]
289+
add_name, add_args = self.check_sub_auto(option)
290+
if add_name:
291+
_name += add_name
292+
if add_args:
293+
_args.append(add_args)
311294
elif data["type"] == InteractionType.MODAL_SUBMIT:
312295
_name = f"modal_{context.data.custom_id}"
313296
if hasattr(context.data, "components"):
@@ -316,15 +299,48 @@ def handle_dispatch(self, event: str, data: dict) -> None:
316299
for _value in component.components:
317300
_args.append(_value["value"])
318301

319-
self.dispatch.dispatch(_name, *_args)
302+
self.dispatch.dispatch(_name, *_args, **_kwargs)
320303

321304
self.dispatch.dispatch("raw_socket_create", data)
322305

306+
def check_sub_command(self, option) -> dict:
307+
_kwargs = dict()
308+
if "options" in option:
309+
if option["type"] == OptionType.SUB_COMMAND_GROUP:
310+
_kwargs["sub_command_group"] = option["name"]
311+
for group_option in option["options"]:
312+
_kwargs["sub_command"] = group_option["name"]
313+
if "options" in group_option:
314+
for sub_option in group_option["options"]:
315+
_kwargs[sub_option["name"]] = sub_option["value"]
316+
elif option["type"] == OptionType.SUB_COMMAND:
317+
_kwargs["sub_command"] = option["name"]
318+
for sub_option in option["options"]:
319+
_kwargs[sub_option["name"]] = sub_option["value"]
320+
else:
321+
_kwargs[option["name"]] = option["value"]
322+
323+
return _kwargs
324+
325+
def check_sub_auto(self, option) -> tuple:
326+
if "options" in option:
327+
if option["type"] == OptionType.SUB_COMMAND_GROUP:
328+
for group_option in option["options"]:
329+
if "options" in group_option:
330+
for sub_option in option["options"]:
331+
if sub_option.get("focused"):
332+
return sub_option["name"], sub_option["value"]
333+
elif option["type"] == OptionType.SUB_COMMAND:
334+
for sub_option in option["options"]:
335+
if sub_option.get("focused"):
336+
return sub_option["name"], sub_option["value"]
337+
elif option.get("focused"):
338+
return option["name"], option["value"]
339+
323340
def contextualize(self, data: dict) -> object:
324341
"""
325342
Takes raw data given back from the gateway
326343
and gives "context" based off of what it is.
327-
328344
:param data: The data from the gateway.
329345
:type data: dict
330346
:return: The context object.
@@ -358,7 +374,6 @@ async def identify(
358374
) -> None:
359375
"""
360376
Sends an ``IDENTIFY`` packet to the gateway.
361-
362377
:param shard?: The shard ID to identify under.
363378
:type shard: Optional[int]
364379
:param presence?: The presence to change the bot to on identify.

interactions/api/gateway.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,5 @@ class WebSocket:
4747
) -> None: ...
4848
async def resume(self) -> None: ...
4949
async def heartbeat(self) -> None: ...
50+
def check_sub_auto(self, option) -> tuple: ...
51+
def check_sub_command(self, option) -> dict: ...

interactions/client.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,8 +396,10 @@ async def button_response(ctx):
396396
"""
397397

398398
def decorator(coro: Coroutine) -> Any:
399-
payload: Component = _component(component)
400-
return self.event(coro, name=payload.custom_id)
399+
payload: str = (
400+
_component(component).custom_id if isinstance(component, Component) else component
401+
)
402+
return self.event(coro, name=payload)
401403

402404
return decorator
403405

interactions/context.py

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from .api.models.misc import DictSerializerMixin, Snowflake
99
from .api.models.user import User
1010
from .base import CustomFormatter, Data
11-
from .enums import ComponentType, InteractionCallbackType, InteractionType
11+
from .enums import InteractionCallbackType, InteractionType
1212
from .models.command import Choice
1313
from .models.component import ActionRow, Button, Component, Modal, SelectMenu
1414
from .models.misc import InteractionData
@@ -134,7 +134,7 @@ async def defer(self, ephemeral: Optional[bool] = False) -> None:
134134
:type ephemeral: Optional[bool]
135135
"""
136136
self.deferred = True
137-
_ephemeral: int = (1 << 6) if bool(ephemeral) else 0
137+
_ephemeral: int = (1 << 6) if ephemeral else 0
138138
# ephemeral doesn't change callback typings. just data json
139139
if self.type == InteractionType.MESSAGE_COMPONENT:
140140
self.callback = InteractionCallbackType.DEFERRED_UPDATE_MESSAGE
@@ -191,20 +191,26 @@ async def send(
191191

192192
if isinstance(components, ActionRow):
193193
_components[0]["components"] = [component._json for component in components.components]
194-
elif isinstance(components, (Button, SelectMenu)):
194+
elif isinstance(components, Button):
195+
_components[0]["components"] = [] if components is None else [components._json]
196+
elif isinstance(components, SelectMenu):
197+
components._json["options"] = [option._json for option in components.options]
195198
_components[0]["components"] = [] if components is None else [components._json]
196199
else:
197200
_components = [] if components is None else [components]
198201

199-
_ephemeral: int = (1 << 6) if bool(ephemeral) else 0
202+
_ephemeral: int = (1 << 6) if ephemeral else 0
200203

201-
if not self.deferred:
204+
if not self.deferred and self.type != InteractionType.MESSAGE_COMPONENT:
202205
self.callback = (
203206
InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE
204207
if self.type == InteractionType.APPLICATION_COMMAND
205208
else InteractionCallbackType.UPDATE_MESSAGE
206209
)
207210

211+
if not self.deferred and self.type == InteractionType.MESSAGE_COMPONENT:
212+
self.callback = InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE
213+
208214
# TODO: post-v4: Add attachments into Message obj.
209215
payload: Message = Message(
210216
content=_content,
@@ -220,8 +226,18 @@ async def send(
220226
_payload: dict = {"type": self.callback.value, "data": payload._json}
221227

222228
async def func():
223-
if self.responded or self.deferred:
224-
if self.type == InteractionType.APPLICATION_COMMAND and self.deferred:
229+
if (
230+
self.responded
231+
or self.deferred
232+
or self.type == InteractionType.MESSAGE_COMPONENT
233+
and self.callback == InteractionCallbackType.DEFERRED_UPDATE_MESSAGE
234+
):
235+
if (
236+
self.type == InteractionType.APPLICATION_COMMAND
237+
and self.deferred
238+
or self.type == InteractionType.MESSAGE_COMPONENT
239+
and self.deferred
240+
):
225241
res = await self.client.edit_interaction_response(
226242
data=payload._json,
227243
token=self.token,
@@ -280,8 +296,12 @@ async def edit(
280296

281297
if isinstance(components, ActionRow):
282298
_components[0]["components"] = [component._json for component in components.components]
283-
elif isinstance(components, (Button, SelectMenu)):
299+
elif isinstance(components, Button):
300+
_components[0]["components"] = [] if components is None else [components._json]
301+
elif isinstance(components, SelectMenu):
302+
components._json["options"] = [option._json for option in components.options]
284303
_components[0]["components"] = [] if components is None else [components._json]
304+
285305
else:
286306
_components = []
287307

@@ -297,14 +317,28 @@ async def edit(
297317

298318
async def func():
299319
if self.deferred:
300-
if self.type == InteractionType.MESSAGE_COMPONENT:
320+
if (
321+
self.type == InteractionType.MESSAGE_COMPONENT
322+
and self.callback != InteractionCallbackType.DEFERRED_UPDATE_MESSAGE
323+
):
301324
await self.client._post_followup(
302325
data=payload._json,
303326
token=self.token,
304327
application_id=str(self.application_id),
305328
)
306329
else:
307-
if hasattr(self.message, "id") and self.message.id is not None:
330+
if (
331+
self.callback == InteractionCallbackType.DEFERRED_UPDATE_MESSAGE
332+
and self.type == InteractionType.MESSAGE_COMPONENT
333+
):
334+
res = await self.client.edit_interaction_response(
335+
data=payload._json,
336+
token=self.token,
337+
application_id=str(self.application_id),
338+
)
339+
self.responded = True
340+
self.message = Message(**res)
341+
elif hasattr(self.message, "id") and self.message.id is not None:
308342
res = await self.client.edit_message(
309343
int(self.channel_id), int(self.message.id), payload=payload._json
310344
)
@@ -325,6 +359,11 @@ async def func():
325359
)
326360
self.message = Message(**res)
327361
else:
362+
self.callback = (
363+
InteractionCallbackType.UPDATE_MESSAGE
364+
if self.type == InteractionType.MESSAGE_COMPONENT
365+
else self.callback
366+
)
328367
res = await self.client.edit_interaction_response(
329368
token=self.token,
330369
application_id=str(self.application_id),
@@ -459,5 +498,32 @@ class ComponentContext(CommandContext):
459498

460499
def __init__(self, **kwargs) -> None:
461500
super().__init__(**kwargs)
462-
self.type = ComponentType(self.type)
501+
self.type = InteractionType(self.type)
463502
self.responded = False # remind components that it was not responded to.
503+
self.deferred = False # remind components they not have been deferred
504+
505+
async def defer(
506+
self, ephemeral: Optional[bool] = False, edit_origin: Optional[bool] = False
507+
) -> None:
508+
"""
509+
This "defers" an component response, allowing up
510+
to a 15-minute delay between invocation and responding.
511+
512+
:param ephemeral?: Whether the deferred state is hidden or not.
513+
:type ephemeral: Optional[bool]
514+
:param edit_origin?: Whether you want to edit the original message or send a followup message
515+
:type edit_origin: Optional[bool]
516+
"""
517+
self.deferred = True
518+
_ephemeral: int = (1 << 6) if bool(ephemeral) else 0
519+
# ephemeral doesn't change callback typings. just data json
520+
if self.type == InteractionType.MESSAGE_COMPONENT and edit_origin:
521+
self.callback = InteractionCallbackType.DEFERRED_UPDATE_MESSAGE
522+
elif self.type == InteractionType.MESSAGE_COMPONENT and not edit_origin:
523+
self.callback = InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE
524+
525+
await self.client.create_interaction_response(
526+
token=self.token,
527+
application_id=int(self.id),
528+
data={"type": self.callback.value, "data": {"flags": _ephemeral}},
529+
)

interactions/context.pyi

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,6 @@ class CommandContext(Context):
6666
async def populate(self, choices: Union[Choice, List[Choice]]) -> List[Choice]: ...
6767

6868
class ComponentContext(CommandContext):
69-
type: ComponentType
69+
type: InteractionType
7070
def __init__(self, **kwargs) -> None: ...
71+
def defer(self, ephemeral: Optional[bool] = False, edit_origin: Optional[bool] = False) -> None: ...

interactions/models/command.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,10 @@ def __init__(self, **kwargs) -> None:
110110
if self._json.get("options"):
111111
self._json["options"] = [option._json for option in self.options]
112112
if self._json.get("choices"):
113-
self._json["choices"] = [choice._json for choice in self.choices]
113+
if isinstance(self._json.get("choices"), dict):
114+
self._json["choices"] = [choice for choice in self.choices]
115+
else:
116+
self._json["choices"] = [choice._json for choice in self.choices]
114117

115118

116119
class Permission(DictSerializerMixin):

interactions/models/component.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ class SelectMenu(DictSerializerMixin):
7070
"min_values",
7171
"max_values",
7272
"disabled",
73+
"label",
74+
"value",
7375
)
7476
type: ComponentType
7577
custom_id: str
@@ -83,7 +85,7 @@ def __init__(self, **kwargs) -> None:
8385
super().__init__(**kwargs)
8486
self.type = ComponentType.SELECT
8587
self.options = (
86-
[SelectOption(**option) for option in self.options]
88+
[SelectOption(**option._json) for option in self.options]
8789
if self._json.get("options")
8890
else None
8991
)
@@ -323,6 +325,9 @@ class ActionRow(DictSerializerMixin):
323325
def __init__(self, **kwargs) -> None:
324326
super().__init__(**kwargs)
325327
self.type = ComponentType.ACTION_ROW
328+
for component in self.components:
329+
if isinstance(component, SelectMenu):
330+
component._json["options"] = [option._json for option in component.options]
326331
self.components = (
327332
[Component(**component._json) for component in self.components]
328333
if self._json.get("components")

0 commit comments

Comments
 (0)