Skip to content

Commit 8f2561d

Browse files
committed
feat: add support for selects in modals (untested)
Many attempts have been made to make this non-breaking - this means the UX is a little bit off. Also, as title says, untested.
1 parent 0d11f77 commit 8f2561d

File tree

5 files changed

+71
-15
lines changed

5 files changed

+71
-15
lines changed

interactions/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@
202202
Invite,
203203
InviteTargetType,
204204
is_owner,
205+
LabelComponent,
205206
LeakyBucketSystem,
206207
listen,
207208
Listener,
@@ -562,6 +563,7 @@
562563
"InviteTargetType",
563564
"is_owner",
564565
"kwarg_spam",
566+
"LabelComponent",
565567
"LeakyBucketSystem",
566568
"listen",
567569
"Listener",

interactions/models/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
InvitableMixin,
9898
Invite,
9999
InviteTargetType,
100+
LabelComponent,
100101
MaterialColors,
101102
MaterialColours,
102103
MediaGalleryComponent,
@@ -492,6 +493,7 @@
492493
"Invite",
493494
"InviteTargetType",
494495
"is_owner",
496+
"LabelComponent",
495497
"LeakyBucketSystem",
496498
"listen",
497499
"Listener",

interactions/models/discord/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@
166166
process_message_payload,
167167
process_message_reference,
168168
)
169-
from .modal import InputText, Modal, ParagraphText, ShortText, TextStyles
169+
from .modal import InputText, Modal, ParagraphText, ShortText, TextStyles, LabelComponent
170170
from .onboarding import Onboarding, OnboardingPrompt, OnboardingPromptOption
171171
from .poll import PollMedia, PollAnswer, PollAnswerCount, PollResults, Poll
172172
from .reaction import Reaction, ReactionUsers
@@ -287,6 +287,7 @@
287287
"InvitableMixin",
288288
"Invite",
289289
"InviteTargetType",
290+
"LabelComponent",
290291
"MaterialColors",
291292
"MaterialColours",
292293
"MediaGalleryComponent",

interactions/models/discord/enums.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,8 @@ class ComponentType(CursedIntEnum):
701701
"""Separator component for visual separation"""
702702
CONTAINER = 17
703703
"""Container component for grouping together other components"""
704+
LABEL = 18
705+
"""Label component for modals"""
704706

705707
# TODO: this is hacky, is there a better way to do this?
706708
@staticmethod

interactions/models/discord/modal.py

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import uuid
22
from enum import IntEnum
33
from typing import Union, Optional, Any, TypeVar
4+
from typing_extensions import Self
45

56
import discord_typings
67

78
from interactions.client.const import MISSING
89
from interactions.client.mixins.serialization import DictSerializationMixin
9-
from interactions.client.utils import dict_filter
10-
from interactions.models.discord.components import ComponentType
10+
from interactions.client.utils import dict_filter, dict_filter_none
11+
from interactions.models.discord.components import ComponentType, BaseComponent, StringSelectMenu
1112
from interactions.models.internal.application_commands import CallbackType
1213

13-
__all__ = ("InputText", "Modal", "ParagraphText", "ShortText", "TextStyles")
14+
__all__ = ("InputText", "Modal", "ParagraphText", "ShortText", "TextStyles", "LabelComponent")
1415

1516
T = TypeVar("T", bound="InputText")
1617

@@ -62,7 +63,7 @@ def to_dict(
6263
)
6364

6465
@classmethod
65-
def from_dict(cls, data: dict[str, Any]) -> T:
66+
def from_dict(cls, data: dict[str, Any]) -> Self:
6667
if data["style"] == TextStyles.SHORT:
6768
cls = ShortText
6869
elif data["style"] == TextStyles.PARAGRAPH:
@@ -127,36 +128,84 @@ def __init__(
127128
)
128129

129130

131+
class LabelComponent(BaseComponent):
132+
def __init__(
133+
self,
134+
*,
135+
label: str,
136+
description: Optional[str] = None,
137+
component: StringSelectMenu | InputText,
138+
):
139+
self.label = label
140+
self.component = component
141+
self.description = description
142+
self.type = ComponentType.LABEL
143+
144+
def to_dict(self) -> dict:
145+
return dict_filter_none(
146+
{
147+
"type": self.type,
148+
"label": self.label,
149+
"description": self.description,
150+
"component": self.component.to_dict() if hasattr(self.component, "to_dict") else self.component,
151+
}
152+
)
153+
154+
@classmethod
155+
def from_dict(cls, data: dict) -> Self:
156+
return cls(
157+
label=data["label"],
158+
description=data.get("description"),
159+
component=BaseComponent.from_dict_factory(
160+
data["component"],
161+
alternate_mapping={
162+
ComponentType.INPUT_TEXT: InputText,
163+
ComponentType.STRING_SELECT: StringSelectMenu,
164+
},
165+
),
166+
)
167+
168+
130169
class Modal:
131170
def __init__(
132171
self,
133-
*components: InputText,
172+
*components: InputText | LabelComponent,
134173
title: str,
135174
custom_id: Optional[str] = None,
136175
) -> None:
137176
self.title: str = title
138-
self.components: list[InputText] = list(components)
177+
self.components: list[InputText | LabelComponent] = list(components)
139178
self.custom_id: str = custom_id or str(uuid.uuid4())
140179

141180
self.type = CallbackType.MODAL
142181

143182
def to_dict(self) -> discord_typings.ModalInteractionData:
183+
dict_components: list[dict] = []
184+
185+
for component in self.components:
186+
if isinstance(component, InputText):
187+
dict_components.append({"type": ComponentType.ACTION_ROW, "components": [component.to_dict()]})
188+
elif isinstance(component, LabelComponent):
189+
dict_components.append(component.to_dict())
190+
else:
191+
# backwards compatibility behavior, remove in v6
192+
dict_components.append(
193+
{
194+
"type": ComponentType.ACTION_ROW,
195+
"components": [component],
196+
}
197+
)
198+
144199
return {
145200
"type": self.type,
146201
"data": {
147202
"title": self.title,
148203
"custom_id": self.custom_id,
149-
"components": [
150-
{
151-
"type": ComponentType.ACTION_ROW,
152-
"components": [c.to_dict() if hasattr(c, "to_dict") else c],
153-
}
154-
for c in self.components
155-
],
204+
"components": dict_components,
156205
},
157206
}
158207

159-
def add_components(self, *components: InputText) -> None:
208+
def add_components(self, *components: InputText | LabelComponent) -> None:
160209
"""
161210
Add components to the modal.
162211

0 commit comments

Comments
 (0)