Skip to content

Commit 6cf80ab

Browse files
committed
Merge branch 'master' of https://github.com/Middledot/pycord
2 parents 511f273 + 9a830be commit 6cf80ab

File tree

15 files changed

+577
-29
lines changed

15 files changed

+577
-29
lines changed

discord/bot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -730,8 +730,8 @@ async def process_application_commands(self, interaction: Interaction, auto_sync
730730
if auto_sync is None:
731731
auto_sync = self.auto_sync_commands
732732
if interaction.type not in (
733-
InteractionType.application_command,
734-
InteractionType.auto_complete
733+
InteractionType.application_command,
734+
InteractionType.auto_complete,
735735
):
736736
return
737737

discord/commands/core.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
from ..enums import SlashCommandOptionType, ChannelType
5757
from ..errors import ValidationError, ClientException
5858
from ..member import Member
59-
from ..message import Message
59+
from ..message import Attachment, Message
6060
from ..user import User
6161
from ..utils import find, get_or_fetch, async_all, utcnow
6262

@@ -755,6 +755,11 @@ async def _invoke(self, ctx: ApplicationContext) -> None:
755755
elif op.input_type == SlashCommandOptionType.string and (converter := op.converter) is not None:
756756
arg = await converter.convert(converter, ctx, arg)
757757

758+
elif op.input_type == SlashCommandOptionType.attachment:
759+
_data = ctx.interaction.data["resolved"]["attachments"][arg]
760+
_data["id"] = int(arg)
761+
arg = Attachment(state=ctx.interaction._state, data=_data)
762+
758763
kwargs[op._parameter_name] = arg
759764

760765
for o in self.options:

discord/commands/options.py

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
from ..enums import ChannelType, SlashCommandOptionType
2828

29+
2930
__all__ = (
3031
"ThreadOption",
3132
"Option",
@@ -70,20 +71,30 @@ def __init__(self, input_type: Any, /, description: str = None, **kwargs) -> Non
7071
self.converter = input_type
7172
input_type = SlashCommandOptionType.string
7273
else:
73-
_type = SlashCommandOptionType.from_datatype(input_type)
74-
if _type == SlashCommandOptionType.channel:
75-
if not isinstance(input_type, tuple):
76-
input_type = (input_type,)
77-
for i in input_type:
78-
if i.__name__ == "GuildChannel":
79-
continue
80-
if isinstance(i, ThreadOption):
81-
self.channel_types.append(i._type)
82-
continue
83-
84-
channel_type = channel_type_map[i.__name__]
85-
self.channel_types.append(channel_type)
86-
input_type = _type
74+
try:
75+
_type = SlashCommandOptionType.from_datatype(input_type)
76+
except TypeError as exc:
77+
from ..ext.commands.converter import CONVERTER_MAPPING
78+
79+
if input_type in CONVERTER_MAPPING:
80+
self.converter = CONVERTER_MAPPING[input_type]
81+
input_type = SlashCommandOptionType.string
82+
else:
83+
raise exc
84+
else:
85+
if _type == SlashCommandOptionType.channel:
86+
if not isinstance(input_type, tuple):
87+
input_type = (input_type,)
88+
for i in input_type:
89+
if i.__name__ == "GuildChannel":
90+
continue
91+
if isinstance(i, ThreadOption):
92+
self.channel_types.append(i._type)
93+
continue
94+
95+
channel_type = channel_type_map[i.__name__]
96+
self.channel_types.append(channel_type)
97+
input_type = _type
8798
self.input_type = input_type
8899
self.required: bool = (
89100
kwargs.pop("required", True) if "default" not in kwargs else False

discord/components.py

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,14 @@
2626
from __future__ import annotations
2727

2828
from typing import Any, ClassVar, Dict, List, Optional, TYPE_CHECKING, Tuple, Type, TypeVar, Union
29-
from .enums import try_enum, ComponentType, ButtonStyle
29+
from .enums import try_enum, ComponentType, ButtonStyle, InputTextStyle
3030
from .utils import get_slots, MISSING
3131
from .partial_emoji import PartialEmoji, _EmojiTag
3232

3333
if TYPE_CHECKING:
3434
from .types.components import (
3535
Component as ComponentPayload,
36+
InputText as InputTextComponentPayload,
3637
ButtonComponent as ButtonComponentPayload,
3738
SelectMenu as SelectMenuPayload,
3839
SelectOption as SelectOptionPayload,
@@ -128,6 +129,82 @@ def to_dict(self) -> ActionRowPayload:
128129
} # type: ignore
129130

130131

132+
class InputText(Component):
133+
"""Represents an Input Text field from the Discord Bot UI Kit.
134+
This inherits from :class:`Component`.
135+
Attributes
136+
----------
137+
style: :class:`.InputTextStyle`
138+
The style of the input text field.
139+
custom_id: Optional[:class:`str`]
140+
The ID of the input text field that gets received during an interaction.
141+
label: Optional[:class:`str`]
142+
The label for the input text field, if any.
143+
placeholder: Optional[:class:`str`]
144+
The placeholder text that is shown if nothing is selected, if any.
145+
min_length: Optional[:class:`int`]
146+
The minimum number of characters that must be entered
147+
Defaults to 0
148+
max_length: Optional[:class:`int`]
149+
The maximum number of characters that can be entered
150+
required: Optional[:class:`bool`]
151+
Whether the input text field is required or not. Defaults to `True`.
152+
value: Optional[:class:`str`]
153+
The value that has been entered in the input text field.
154+
"""
155+
156+
__slots__: Tuple[str, ...] = (
157+
"type",
158+
"style",
159+
"custom_id",
160+
"label",
161+
"placeholder",
162+
"min_length",
163+
"max_length",
164+
"required",
165+
"value",
166+
)
167+
168+
__repr_info__: ClassVar[Tuple[str, ...]] = __slots__
169+
170+
def __init__(self, data: InputTextComponentPayload):
171+
self.type = ComponentType.input_text
172+
self.style: InputTextStyle = try_enum(InputTextStyle, data["style"])
173+
self.custom_id = data["custom_id"]
174+
self.label: Optional[str] = data.get("label", None)
175+
self.placeholder: Optional[str] = data.get("placeholder", None)
176+
self.min_length: Optional[int] = data.get("min_length", None)
177+
self.max_length: Optional[int] = data.get("max_length", None)
178+
self.required: bool = data.get("required", True)
179+
self.value: Optional[str] = data.get("value", None)
180+
181+
def to_dict(self) -> InputTextComponentPayload:
182+
payload = {
183+
"type": 4,
184+
"style": self.style.value,
185+
"label": self.label,
186+
}
187+
if self.custom_id:
188+
payload["custom_id"] = self.custom_id
189+
190+
if self.placeholder:
191+
payload["placeholder"] = self.placeholder
192+
193+
if self.min_length:
194+
payload["min_length"] = self.min_length
195+
196+
if self.max_length:
197+
payload["max_length"] = self.max_length
198+
199+
if not self.required:
200+
payload["required"] = self.required
201+
202+
if self.value:
203+
payload["value"] = self.value
204+
205+
return payload # type: ignore
206+
207+
131208
class Button(Component):
132209
"""Represents a button from the Discord Bot UI Kit.
133210

discord/enums.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
'ScheduledEventStatus',
6161
'ScheduledEventPrivacyLevel',
6262
'ScheduledEventLocationType',
63+
'InputTextStyle',
6364
)
6465

6566

@@ -550,6 +551,7 @@ class InteractionType(Enum):
550551
application_command = 2
551552
component = 3
552553
auto_complete = 4
554+
modal_submit = 5
553555

554556

555557
class InteractionResponseType(Enum):
@@ -561,7 +563,7 @@ class InteractionResponseType(Enum):
561563
deferred_message_update = 6 # for components
562564
message_update = 7 # for components
563565
auto_complete_result = 8 # for autocomplete interactions
564-
566+
modal = 9 # for modal dialogs
565567

566568
class VideoQualityMode(Enum):
567569
auto = 1
@@ -575,6 +577,7 @@ class ComponentType(Enum):
575577
action_row = 1
576578
button = 2
577579
select = 3
580+
input_text = 4
578581

579582
def __int__(self):
580583
return self.value
@@ -599,6 +602,14 @@ def __int__(self):
599602
return self.value
600603

601604

605+
class InputTextStyle(Enum):
606+
short = 1
607+
singleline = 1
608+
paragraph = 2
609+
multiline = 2
610+
long = 2
611+
612+
602613
class ApplicationType(Enum):
603614
game = 1
604615
music = 2
@@ -654,6 +665,8 @@ def from_datatype(cls, datatype):
654665
return cls.channel
655666
if datatype.__name__ == "Role":
656667
return cls.role
668+
if datatype.__name__ == "Attachment":
669+
return cls.attachment
657670
if datatype.__name__ == "Mentionable":
658671
return cls.mentionable
659672

discord/interactions.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
from aiohttp import ClientSession
6060
from .embeds import Embed
6161
from .ui.view import View
62+
from .ui.modal import Modal
6263
from .channel import VoiceChannel, StageChannel, TextChannel, CategoryChannel, StoreChannel, PartialMessageable
6364
from .threads import Thread
6465
from .commands import OptionChoice
@@ -775,7 +776,24 @@ async def send_autocomplete_result(
775776
)
776777

777778
self._responded = True
778-
779+
780+
async def send_modal(self, modal: Modal):
781+
if self._responded:
782+
raise InteractionResponded(self._parent)
783+
784+
payload = modal.to_dict()
785+
adapter = async_context.get()
786+
await adapter.create_interaction_response(
787+
self._parent.id,
788+
self._parent.token,
789+
session=self._parent._session,
790+
type=InteractionResponseType.modal.value,
791+
data=payload,
792+
)
793+
self._responded = True
794+
self._parent._state.store_modal(modal, self._parent.user.id)
795+
796+
779797
class _InteractionMessageState:
780798
__slots__ = ('_parent', '_interaction')
781799

discord/state.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,15 @@
4747
from .raw_models import *
4848
from .member import Member
4949
from .role import Role
50-
from .enums import ChannelType, try_enum, Status, ScheduledEventStatus
50+
from .enums import ChannelType, try_enum, Status, ScheduledEventStatus, InteractionType
5151
from . import utils
5252
from .flags import ApplicationFlags, Intents, MemberCacheFlags
5353
from .object import Object
5454
from .invite import Invite
5555
from .integrations import _integration_factory
5656
from .interactions import Interaction
5757
from .ui.view import ViewStore, View
58+
from .ui.modal import Modal, ModalStore
5859
from .stage_instance import StageInstance
5960
from .threads import Thread, ThreadMember
6061
from .sticker import GuildSticker
@@ -256,7 +257,7 @@ def clear(self, *, views: bool = True) -> None:
256257
self._guilds: Dict[int, Guild] = {}
257258
if views:
258259
self._view_store: ViewStore = ViewStore(self)
259-
260+
self._modal_store: ModalStore = ModalStore(self)
260261
self._voice_clients: Dict[int, VoiceProtocol] = {}
261262

262263
# LRU of max size 128
@@ -363,6 +364,9 @@ def store_sticker(self, guild: Guild, data: GuildStickerPayload) -> GuildSticker
363364
def store_view(self, view: View, message_id: Optional[int] = None) -> None:
364365
self._view_store.add_view(view, message_id)
365366

367+
def store_modal(self, modal: Modal, message_id: int) -> None:
368+
self._modal_store.add_modal(modal, message_id)
369+
366370
def prevent_view_updates_for(self, message_id: int) -> Optional[View]:
367371
return self._view_store.remove_message_tracking(message_id)
368372

@@ -705,6 +709,12 @@ def parse_interaction_create(self, data) -> None:
705709
custom_id = interaction.data['custom_id'] # type: ignore
706710
component_type = interaction.data['component_type'] # type: ignore
707711
self._view_store.dispatch(component_type, custom_id, interaction)
712+
if interaction.type == InteractionType.modal_submit:
713+
user_id, custom_id = (
714+
interaction.user.id,
715+
interaction.data["custom_id"],
716+
)
717+
asyncio.create_task(self._modal_store.dispatch(user_id, custom_id, interaction))
708718

709719
self.dispatch('interaction', interaction)
710720

discord/types/components.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@
2828
from typing import List, Literal, TypedDict, Union
2929
from .emoji import PartialEmoji
3030

31-
ComponentType = Literal[1, 2, 3]
31+
ComponentType = Literal[1, 2, 3, 4]
3232
ButtonStyle = Literal[1, 2, 3, 4, 5]
33+
InputTextStyle = Literal[1, 2]
3334

3435

3536
class ActionRow(TypedDict):
@@ -50,6 +51,21 @@ class ButtonComponent(_ButtonComponentOptional):
5051
style: ButtonStyle
5152

5253

54+
class _InputTextComponentOptional(TypedDict, total=False):
55+
min_length: int
56+
max_length: int
57+
required: bool
58+
placeholder: str
59+
value: str
60+
61+
62+
class InputText(_InputTextComponentOptional):
63+
type: Literal[4]
64+
style: InputTextStyle
65+
custom_id: str
66+
label: str
67+
68+
5369
class _SelectMenuOptional(TypedDict, total=False):
5470
placeholder: str
5571
min_values: int
@@ -74,4 +90,4 @@ class SelectMenu(_SelectMenuOptional):
7490
options: List[SelectOption]
7591

7692

77-
Component = Union[ActionRow, ButtonComponent, SelectMenu]
93+
Component = Union[ActionRow, ButtonComponent, SelectMenu, InputText]

discord/types/interactions.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
from __future__ import annotations
2727

2828
from typing import Optional, TYPE_CHECKING, Dict, TypedDict, Union, List, Literal
29+
30+
from .message import Attachment
2931
from .snowflake import Snowflake
3032
from .components import Component, ComponentType
3133
from .embed import Embed
@@ -57,7 +59,7 @@ class _ApplicationCommandOptionOptional(TypedDict, total=False):
5759
options: List[ApplicationCommandOption]
5860

5961

60-
ApplicationCommandOptionType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
62+
ApplicationCommandOptionType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
6163

6264

6365
class ApplicationCommandOption(_ApplicationCommandOptionOptional):
@@ -122,7 +124,7 @@ class _ApplicationCommandInteractionDataOptionBoolean(_ApplicationCommandInterac
122124

123125

124126
class _ApplicationCommandInteractionDataOptionSnowflake(_ApplicationCommandInteractionDataOption):
125-
type: Literal[6, 7, 8, 9]
127+
type: Literal[6, 7, 8, 9, 11]
126128
value: Snowflake
127129

128130

@@ -153,6 +155,7 @@ class ApplicationCommandInteractionDataResolved(TypedDict, total=False):
153155
members: Dict[Snowflake, Member]
154156
roles: Dict[Snowflake, Role]
155157
channels: Dict[Snowflake, ApplicationCommandResolvedPartialChannel]
158+
attachments: Dict[Snowflake, Attachment]
156159

157160

158161
class _ApplicationCommandInteractionDataOptional(TypedDict, total=False):

0 commit comments

Comments
 (0)