Skip to content

Commit 8c2ab12

Browse files
authored
Merge pull request #382 from EdVraz/stable
fix!: Select menus/options and component response. feat(context)!: correct deferring for components.
2 parents 6ef5e66 + 87f8db6 commit 8c2ab12

File tree

5 files changed

+89
-21
lines changed

5 files changed

+89
-21
lines changed

interactions/api/gateway.py

Lines changed: 0 additions & 7 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.
@@ -346,7 +341,6 @@ def contextualize(self, data: dict) -> object:
346341
"""
347342
Takes raw data given back from the gateway
348343
and gives "context" based off of what it is.
349-
350344
:param data: The data from the gateway.
351345
:type data: dict
352346
:return: The context object.
@@ -380,7 +374,6 @@ async def identify(
380374
) -> None:
381375
"""
382376
Sends an ``IDENTIFY`` packet to the gateway.
383-
384377
:param shard?: The shard ID to identify under.
385378
:type shard: Optional[int]
386379
:param presence?: The presence to change the bot to on identify.

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)