Skip to content

Commit 289c40b

Browse files
feat💥: add support for new features in modals (#1784)
* 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. * feat: update classes to support new select features * feat💥: make ModalContext able to handle new component types This requires breaking the assumption that responses always has string keys. That being said, since this is Discord breaking things, not us, this should be fine for a non-breaking release. * docs: update/fix docstrings of select menus Signed-off-by: Astrea <25420078+AstreaTSS@users.noreply.github.com> * ci: correct from checks. * feat: add file upload components for modals * feat: make label for InputText optional Totally forgot Discord did this, oops. * feat: allow TextDisplayComponent to be used in modals Apparently this has been possible for a while now. * fix: missed a spot * refactor: import ComponentType from enums * fix: min/max_value -> min/max_values * fix: make to_dict behavior correct for FileUploadComponent * feat: add radio and checkbox components --------- Signed-off-by: Astrea <25420078+AstreaTSS@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent b751d15 commit 289c40b

7 files changed

Lines changed: 562 additions & 28 deletions

File tree

interactions/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@
143143
Extension,
144144
File,
145145
FileComponent,
146+
FileUploadComponent,
146147
FlatUIColors,
147148
FlatUIColours,
148149
ForumLayoutType,
@@ -202,6 +203,7 @@
202203
Invite,
203204
InviteTargetType,
204205
is_owner,
206+
LabelComponent,
205207
LeakyBucketSystem,
206208
listen,
207209
Listener,
@@ -375,6 +377,11 @@
375377
WebhookMixin,
376378
WebhookTypes,
377379
WebSocketOPCode,
380+
RadioGroupOption,
381+
RadioGroupComponent,
382+
CheckboxGroupOption,
383+
CheckboxGroupComponent,
384+
CheckboxComponent,
378385
)
379386
from .api import events
380387
from . import ext
@@ -467,6 +474,9 @@
467474
"ChannelMention",
468475
"ChannelSelectMenu",
469476
"ChannelType",
477+
"CheckboxComponent",
478+
"CheckboxGroupComponent",
479+
"CheckboxGroupOption",
470480
"Client",
471481
"ClientT",
472482
"ClientUser",
@@ -507,6 +517,7 @@
507517
"Extension",
508518
"File",
509519
"FileComponent",
520+
"FileUploadComponent",
510521
"FlatUIColors",
511522
"FlatUIColours",
512523
"ForumLayoutType",
@@ -559,6 +570,7 @@
559570
"InvitableMixin",
560571
"Invite",
561572
"InviteTargetType",
573+
"LabelComponent",
562574
"LeakyBucketSystem",
563575
"Listener",
564576
"LocalisedDesc",
@@ -615,6 +627,8 @@
615627
"PollResults",
616628
"PremiumTier",
617629
"PremiumType",
630+
"RadioGroupComponent",
631+
"RadioGroupOption",
618632
"Reaction",
619633
"ReactionUsers",
620634
"Resolved",
@@ -715,6 +729,8 @@
715729
"has_role",
716730
"integration_types",
717731
"is_owner",
732+
"is_owner",
733+
"kwarg_spam",
718734
"kwarg_spam",
719735
"listen",
720736
"logger_name",

interactions/models/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
ExplicitContentFilterLevel,
6262
File,
6363
FileComponent,
64+
FileUploadComponent,
6465
FlatUIColors,
6566
FlatUIColours,
6667
ForumLayoutType,
@@ -97,6 +98,7 @@
9798
InvitableMixin,
9899
Invite,
99100
InviteTargetType,
101+
LabelComponent,
100102
MaterialColors,
101103
MaterialColours,
102104
MediaGalleryComponent,
@@ -217,6 +219,11 @@
217219
WebhookMixin,
218220
WebhookTypes,
219221
WebSocketOPCode,
222+
RadioGroupOption,
223+
RadioGroupComponent,
224+
CheckboxGroupOption,
225+
CheckboxGroupComponent,
226+
CheckboxComponent,
220227
)
221228
from .internal import (
222229
ActiveVoiceState,
@@ -399,6 +406,9 @@
399406
"ChannelMention",
400407
"ChannelSelectMenu",
401408
"ChannelType",
409+
"CheckboxComponent",
410+
"CheckboxGroupComponent",
411+
"CheckboxGroupOption",
402412
"ClientUser",
403413
"Color",
404414
"Colour",
@@ -437,6 +447,7 @@
437447
"Extension",
438448
"File",
439449
"FileComponent",
450+
"FileUploadComponent",
440451
"FlatUIColors",
441452
"FlatUIColours",
442453
"ForumLayoutType",
@@ -488,6 +499,7 @@
488499
"InvitableMixin",
489500
"Invite",
490501
"InviteTargetType",
502+
"LabelComponent",
491503
"LeakyBucketSystem",
492504
"Listener",
493505
"LocalisedDesc",
@@ -542,6 +554,8 @@
542554
"PollResults",
543555
"PremiumTier",
544556
"PremiumType",
557+
"RadioGroupComponent",
558+
"RadioGroupOption",
545559
"Reaction",
546560
"ReactionUsers",
547561
"Resolved",
@@ -629,6 +643,7 @@
629643
"has_role",
630644
"integration_types",
631645
"is_owner",
646+
"is_owner",
632647
"listen",
633648
"max_concurrency",
634649
"message_context_menu",

interactions/models/discord/__init__.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,20 @@
166166
process_message_payload,
167167
process_message_reference,
168168
)
169-
from .modal import InputText, Modal, ParagraphText, ShortText, TextStyles
169+
from .modal import (
170+
InputText,
171+
Modal,
172+
ParagraphText,
173+
ShortText,
174+
TextStyles,
175+
LabelComponent,
176+
FileUploadComponent,
177+
RadioGroupOption,
178+
RadioGroupComponent,
179+
CheckboxGroupOption,
180+
CheckboxGroupComponent,
181+
CheckboxComponent,
182+
)
170183
from .onboarding import Onboarding, OnboardingPrompt, OnboardingPromptOption
171184
from .poll import PollMedia, PollAnswer, PollAnswerCount, PollResults, Poll
172185
from .reaction import Reaction, ReactionUsers
@@ -240,6 +253,9 @@
240253
"ChannelMention",
241254
"ChannelSelectMenu",
242255
"ChannelType",
256+
"CheckboxComponent",
257+
"CheckboxGroupComponent",
258+
"CheckboxGroupOption",
243259
"ClientUser",
244260
"Color",
245261
"Colour",
@@ -262,6 +278,7 @@
262278
"ExplicitContentFilterLevel",
263279
"File",
264280
"FileComponent",
281+
"FileUploadComponent",
265282
"FlatUIColors",
266283
"FlatUIColours",
267284
"ForumLayoutType",
@@ -297,6 +314,7 @@
297314
"InvitableMixin",
298315
"Invite",
299316
"InviteTargetType",
317+
"LabelComponent",
300318
"MFALevel",
301319
"MaterialColors",
302320
"MaterialColours",
@@ -335,6 +353,8 @@
335353
"PollResults",
336354
"PremiumTier",
337355
"PremiumType",
356+
"RadioGroupComponent",
357+
"RadioGroupOption",
338358
"Reaction",
339359
"ReactionUsers",
340360
"Role",

interactions/models/discord/components.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,8 @@ class BaseSelectMenu(InteractiveComponent):
368368
min_values Optional[int]: The minimum number of items that must be chosen. (default 1, min 0, max 25)
369369
max_values Optional[int]: The maximum number of items that can be chosen. (default 1, max 25)
370370
disabled bool: Disable the select and make it not intractable, default false.
371-
type Union[ComponentType, int]: The action role type number defined by discord. This cannot be modified.
371+
type Union[ComponentType, int]: The type of component, as defined by discord. This cannot be modified.
372+
required bool: Whether this select menu is required to be filled out or not in modals.
372373
373374
"""
374375

@@ -380,12 +381,14 @@ def __init__(
380381
max_values: int = 1,
381382
custom_id: str | None = None,
382383
disabled: bool = False,
384+
required: bool = True,
383385
) -> None:
384386
self.custom_id: str = custom_id or str(uuid.uuid4())
385387
self.placeholder: str | None = placeholder
386388
self.min_values: int = min_values
387389
self.max_values: int = max_values
388390
self.disabled: bool = disabled
391+
self.required: bool = required
389392

390393
self.type: ComponentType = MISSING
391394

@@ -397,10 +400,11 @@ def from_dict(cls, data: discord_typings.SelectMenuComponentData) -> "BaseSelect
397400
max_values=data["max_values"],
398401
custom_id=data["custom_id"],
399402
disabled=data.get("disabled", False),
403+
required=data.get("required", True),
400404
)
401405

402406
def __repr__(self) -> str:
403-
return f"<{self.__class__.__name__} type={self.type} custom_id={self.custom_id} placeholder={self.placeholder} min_values={self.min_values} max_values={self.max_values} disabled={self.disabled}>"
407+
return f"<{self.__class__.__name__} type={self.type} custom_id={self.custom_id} placeholder={self.placeholder} min_values={self.min_values} max_values={self.max_values} disabled={self.disabled} required={self.required}>"
404408

405409
def to_dict(self) -> discord_typings.SelectMenuComponentData:
406410
return {
@@ -410,6 +414,7 @@ def to_dict(self) -> discord_typings.SelectMenuComponentData:
410414
"min_values": self.min_values,
411415
"max_values": self.max_values,
412416
"disabled": self.disabled,
417+
"required": self.required,
413418
}
414419

415420

@@ -589,6 +594,7 @@ class StringSelectMenu(BaseSelectMenu):
589594
max_values Optional[int]: The maximum number of items that can be chosen. (default 1, max 25)
590595
disabled bool: Disable the select and make it not intractable, default false.
591596
type Union[ComponentType, int]: The type of component, as defined by discord. This cannot be modified.
597+
required bool: Whether this select menu is required to be filled out or not in modals.
592598
593599
"""
594600

@@ -603,13 +609,15 @@ def __init__(
603609
max_values: int = 1,
604610
custom_id: str | None = None,
605611
disabled: bool = False,
612+
required: bool = True,
606613
) -> None:
607614
super().__init__(
608615
placeholder=placeholder,
609616
min_values=min_values,
610617
max_values=max_values,
611618
custom_id=custom_id,
612619
disabled=disabled,
620+
required=required,
613621
)
614622
if isinstance(options, (list, tuple)) and len(options) == 1 and isinstance(options[0], (list, tuple)):
615623
# user passed in a list of options, expand it out
@@ -627,10 +635,11 @@ def from_dict(cls, data: discord_typings.SelectMenuComponentData) -> "StringSele
627635
max_values=data["max_values"],
628636
custom_id=data["custom_id"],
629637
disabled=data.get("disabled", False),
638+
required=data.get("required", True),
630639
)
631640

632641
def __repr__(self) -> str:
633-
return f"<{self.__class__.__name__} type={self.type} custom_id={self.custom_id} placeholder={self.placeholder} min_values={self.min_values} max_values={self.max_values} disabled={self.disabled} options={self.options}>"
642+
return f"<{self.__class__.__name__} type={self.type} custom_id={self.custom_id} placeholder={self.placeholder} min_values={self.min_values} max_values={self.max_values} disabled={self.disabled} required={self.required} options={self.options}>"
634643

635644
def to_dict(self) -> discord_typings.SelectMenuComponentData:
636645
return {
@@ -651,6 +660,7 @@ class UserSelectMenu(DefaultableSelectMenu):
651660
default_values list[BaseUser, Member, SelectDefaultValues]: A list of default values to pre-select in the select.
652661
disabled bool: Disable the select and make it not intractable, default false.
653662
type Union[ComponentType, int]: The type of component, as defined by discord. This cannot be modified.
663+
required bool: Whether this select menu is required to be filled out or not in modals.
654664
655665
"""
656666

@@ -672,13 +682,15 @@ def __init__(
672682
| None
673683
) = None,
674684
disabled: bool = False,
685+
required: bool = True,
675686
) -> None:
676687
super().__init__(
677688
placeholder=placeholder,
678689
min_values=min_values,
679690
max_values=max_values,
680691
custom_id=custom_id,
681692
disabled=disabled,
693+
required=required,
682694
defaults=default_values,
683695
)
684696

@@ -697,6 +709,7 @@ class RoleSelectMenu(DefaultableSelectMenu):
697709
default_values list[Role, SelectDefaultValues]: A list of default values to pre-select in the select.
698710
disabled bool: Disable the select and make it not intractable, default false.
699711
type Union[ComponentType, int]: The type of component, as defined by discord. This cannot be modified.
712+
required bool: Whether this select menu is required to be filled out or not in modals.
700713
701714
"""
702715

@@ -708,6 +721,7 @@ def __init__(
708721
max_values: int = 1,
709722
custom_id: str | None = None,
710723
disabled: bool = False,
724+
required: bool = True,
711725
default_values: (
712726
list[
713727
Union[
@@ -724,6 +738,7 @@ def __init__(
724738
max_values=max_values,
725739
custom_id=custom_id,
726740
disabled=disabled,
741+
required=required,
727742
defaults=default_values,
728743
)
729744

@@ -742,6 +757,7 @@ class MentionableSelectMenu(DefaultableSelectMenu):
742757
default_values list[BaseUser, Role, BaseChannel, Member, SelectDefaultValues]: A list of default values to pre-select in the select.
743758
disabled bool: Disable the select and make it not intractable, default false.
744759
type Union[ComponentType, int]: The type of component, as defined by discord. This cannot be modified.
760+
required bool: Whether this select menu is required to be filled out or not in modals.
745761
746762
"""
747763

@@ -753,6 +769,7 @@ def __init__(
753769
max_values: int = 1,
754770
custom_id: str | None = None,
755771
disabled: bool = False,
772+
required: bool = True,
756773
default_values: (
757774
list[
758775
Union[
@@ -772,6 +789,7 @@ def __init__(
772789
max_values=max_values,
773790
custom_id=custom_id,
774791
disabled=disabled,
792+
required=required,
775793
defaults=default_values,
776794
)
777795

@@ -790,6 +808,7 @@ class ChannelSelectMenu(DefaultableSelectMenu):
790808
default_values list[BaseChannel, SelectDefaultValues]: A list of default values to pre-select in the select.
791809
disabled bool: Disable the select and make it not intractable, default false.
792810
type Union[ComponentType, int]: The type of component, as defined by discord. This cannot be modified.
811+
required bool: Whether this select menu is required to be filled out or not in modals.
793812
794813
"""
795814

@@ -802,6 +821,7 @@ def __init__(
802821
max_values: int = 1,
803822
custom_id: str | None = None,
804823
disabled: bool = False,
824+
required: bool = True,
805825
default_values: (
806826
list[
807827
Union[
@@ -818,6 +838,7 @@ def __init__(
818838
max_values=max_values,
819839
custom_id=custom_id,
820840
disabled=disabled,
841+
required=required,
821842
defaults=default_values,
822843
)
823844

@@ -834,11 +855,12 @@ def from_dict(cls, data: discord_typings.SelectMenuComponentData) -> "ChannelSel
834855
max_values=data["max_values"],
835856
custom_id=data["custom_id"],
836857
disabled=data.get("disabled", False),
858+
required=data.get("required", True),
837859
channel_types=data.get("channel_types", []),
838860
)
839861

840862
def __repr__(self) -> str:
841-
return f"<{self.__class__.__name__} type={self.type} custom_id={self.custom_id} placeholder={self.placeholder} min_values={self.min_values} max_values={self.max_values} disabled={self.disabled} channel_types={self.channel_types}>"
863+
return f"<{self.__class__.__name__} type={self.type} custom_id={self.custom_id} placeholder={self.placeholder} min_values={self.min_values} max_values={self.max_values} disabled={self.disabled} required={self.required} channel_types={self.channel_types}>"
842864

843865
def to_dict(self) -> discord_typings.SelectMenuComponentData:
844866
return {

0 commit comments

Comments
 (0)