Browse Source

Handle resolved data and fix textdisplay in modals

pull/10302/head
Soheab_ 1 month ago
parent
commit
b955efd7a1
  1. 3
      discord/state.py
  2. 21
      discord/types/interactions.py
  3. 70
      discord/ui/modal.py
  4. 24
      discord/ui/select.py
  5. 8
      discord/ui/view.py

3
discord/state.py

@ -828,7 +828,8 @@ class ConnectionState(Generic[ClientT]):
inner_data = data['data']
custom_id = inner_data['custom_id']
components = inner_data['components']
self._view_store.dispatch_modal(custom_id, interaction, components)
resolved = inner_data.get('resolved')
self._view_store.dispatch_modal(custom_id, interaction, components, resolved)
self.dispatch('interaction', interaction)
def parse_presence_update(self, data: gw.PresenceUpdateEvent) -> None:

21
discord/types/interactions.py

@ -36,6 +36,7 @@ from .role import Role
from .snowflake import Snowflake
from .user import User
from .guild import GuildFeature
from .components import ComponentBase
if TYPE_CHECKING:
from .message import Message
@ -204,19 +205,19 @@ class SelectMessageComponentInteractionData(_BaseMessageComponentInteractionData
MessageComponentInteractionData = Union[ButtonMessageComponentInteractionData, SelectMessageComponentInteractionData]
class ModalSubmitTextInputInteractionData(TypedDict):
class ModalSubmitTextInputInteractionData(ComponentBase):
type: Literal[4]
custom_id: str
value: str
class ModalSubmitStringSelectInteractionData(TypedDict):
type: Literal[3]
class ModalSubmitSelectInteractionData(ComponentBase):
type: Literal[3, 5, 6, 7, 8]
custom_id: str
values: List[str]
ModalSubmitComponentItemInteractionData = Union[ModalSubmitTextInputInteractionData, ModalSubmitStringSelectInteractionData]
ModalSubmitComponentItemInteractionData = Union[ModalSubmitSelectInteractionData, ModalSubmitTextInputInteractionData]
class ModalSubmitActionRowInteractionData(TypedDict):
@ -224,19 +225,27 @@ class ModalSubmitActionRowInteractionData(TypedDict):
components: List[ModalSubmitComponentItemInteractionData]
class ModalSubmitLabelInteractionData(TypedDict):
class ModalSubmitTextDisplayInteractionData(ComponentBase):
type: Literal[10]
content: str
class ModalSubmitLabelInteractionData(ComponentBase):
type: Literal[18]
component: ModalSubmitComponentItemInteractionData
ModalSubmitComponentInteractionData = Union[
ModalSubmitLabelInteractionData, ModalSubmitActionRowInteractionData, ModalSubmitComponentItemInteractionData
ModalSubmitActionRowInteractionData,
ModalSubmitTextDisplayInteractionData,
ModalSubmitLabelInteractionData,
]
class ModalSubmitInteractionData(TypedDict):
custom_id: str
components: List[ModalSubmitComponentInteractionData]
resolved: NotRequired[ResolvedData]
InteractionData = Union[

70
discord/ui/modal.py

@ -41,7 +41,10 @@ if TYPE_CHECKING:
from typing_extensions import Self
from ..interactions import Interaction
from ..types.interactions import ModalSubmitComponentInteractionData as ModalSubmitComponentInteractionDataPayload
from ..types.interactions import (
ModalSubmitComponentInteractionData as ModalSubmitComponentInteractionDataPayload,
ResolvedData as ResolvedDataPayload,
)
# fmt: off
@ -168,23 +171,67 @@ class Modal(BaseView):
"""
_log.error('Ignoring exception in modal %r:', self, exc_info=error)
def _refresh(self, interaction: Interaction, components: Sequence[ModalSubmitComponentInteractionDataPayload]) -> None:
def _refresh(
self,
interaction: Interaction,
components: Sequence[ModalSubmitComponentInteractionDataPayload],
resolved: Optional[ResolvedDataPayload],
) -> None:
for component in components:
if component['type'] == 1:
self._refresh(interaction, component['components'])
self._refresh(interaction, component['components'], resolved) # type: ignore
elif component['type'] == 18:
self._refresh(interaction, [component['component']])
self._refresh(interaction, [component['component']], resolved) # type: ignore
else:
item = find(lambda i: getattr(i, 'custom_id', None) == component['custom_id'], self.walk_children()) # type: ignore
item = find(
lambda i: getattr(i, 'custom_id', getattr(i, 'id', None))
== component.get('custom_id', component.get('id', None)),
self.walk_children(),
)
if item is None:
_log.debug('Modal interaction referencing unknown item custom_id %s. Discarding', component['custom_id'])
_log.debug(
'Modal interaction referencing unknown item (custom_id, id): %s. Discarding',
(component.get('custom_id'), component.get('id')),
)
continue
# you may be wondering why we do this resolved stuff here
# well the resolved field is only sent once for all select menus,
# in the interaction payload, not per select menu like in messages
# so we have to manually add the correct resolved data to each
# select menu
component_type = component['type']
if component_type in (5, 6, 7, 8):
resolved = resolved or {}
selected_values = component.get('values', [])
if component_type in (5, 6, 7):
users = {k: v for k, v in resolved.get('users', {}).items() if k in selected_values}
members = {k: v for k, v in resolved.get('members', {}).items() if k in selected_values}
roles = {k: v for k, v in resolved.get('roles', {}).items() if k in selected_values}
if component_type == 5: # user select
component['resolved'] = {'users': users, 'members': members} # type: ignore
elif component_type == 6: # role select
component['resolved'] = {'roles': roles} # type: ignore
elif component_type == 7: # mentionable select
component['resolved'] = {'users': users, 'members': members, 'roles': roles} # type: ignore
else: # channel select
component['resolved'] = { # type: ignore
'channels': {k: v for k, v in resolved.get('channels', {}).items() if k in selected_values}
}
item._refresh_state(interaction, component) # type: ignore
async def _scheduled_task(self, interaction: Interaction, components: List[ModalSubmitComponentInteractionDataPayload]):
async def _scheduled_task(
self,
interaction: Interaction,
components: List[ModalSubmitComponentInteractionDataPayload],
resolved: Optional[ResolvedDataPayload],
):
try:
self._refresh_timeout()
self._refresh(interaction, components)
self._refresh(interaction, components, resolved)
allow = await self.interaction_check(interaction)
if not allow:
@ -221,10 +268,13 @@ class Modal(BaseView):
return components
def _dispatch_submit(
self, interaction: Interaction, components: List[ModalSubmitComponentInteractionDataPayload]
self,
interaction: Interaction,
components: List[ModalSubmitComponentInteractionDataPayload],
resolved: Optional[ResolvedDataPayload],
) -> asyncio.Task[None]:
return asyncio.create_task(
self._scheduled_task(interaction, components), name=f'discord-ui-modal-dispatch-{self.id}'
self._scheduled_task(interaction, components, resolved), name=f'discord-ui-modal-dispatch-{self.id}'
)
def to_dict(self) -> Dict[str, Any]:

24
discord/ui/select.py

@ -585,6 +585,10 @@ class UserSelect(BaseSelect[V]):
Number of items must be in range of ``min_values`` and ``max_values``.
.. versionadded:: 2.4
required: :class:`bool`
Whether the select is required. Only applicable within modals.
.. versionadded:: 2.7
row: Optional[:class:`int`]
The relative row this select menu belongs to. A Discord component can only have 5
rows. By default, items are arranged automatically into those 5 rows. If you'd
@ -612,6 +616,7 @@ class UserSelect(BaseSelect[V]):
max_values: int = 1,
disabled: bool = False,
row: Optional[int] = None,
required: bool = False,
default_values: Sequence[ValidDefaultValues] = MISSING,
id: Optional[int] = None,
) -> None:
@ -622,6 +627,7 @@ class UserSelect(BaseSelect[V]):
min_values=min_values,
max_values=max_values,
disabled=disabled,
required=required,
row=row,
default_values=_handle_select_defaults(default_values, self.type),
id=id,
@ -687,6 +693,10 @@ class RoleSelect(BaseSelect[V]):
Number of items must be in range of ``min_values`` and ``max_values``.
.. versionadded:: 2.4
required: :class:`bool`
Whether the select is required. Only applicable within modals.
.. versionadded:: 2.6
row: Optional[:class:`int`]
The relative row this select menu belongs to. A Discord component can only have 5
rows. By default, items are arranged automatically into those 5 rows. If you'd
@ -713,6 +723,7 @@ class RoleSelect(BaseSelect[V]):
min_values: int = 1,
max_values: int = 1,
disabled: bool = False,
required: bool = False,
row: Optional[int] = None,
default_values: Sequence[ValidDefaultValues] = MISSING,
id: Optional[int] = None,
@ -724,6 +735,7 @@ class RoleSelect(BaseSelect[V]):
min_values=min_values,
max_values=max_values,
disabled=disabled,
required=required,
row=row,
default_values=_handle_select_defaults(default_values, self.type),
id=id,
@ -785,6 +797,10 @@ class MentionableSelect(BaseSelect[V]):
Number of items must be in range of ``min_values`` and ``max_values``.
.. versionadded:: 2.4
required: :class:`bool`
Whether the select is required. Only applicable within modals.
.. versionadded:: 2.6
row: Optional[:class:`int`]
The relative row this select menu belongs to. A Discord component can only have 5
rows. By default, items are arranged automatically into those 5 rows. If you'd
@ -811,6 +827,7 @@ class MentionableSelect(BaseSelect[V]):
min_values: int = 1,
max_values: int = 1,
disabled: bool = False,
required: bool = False,
row: Optional[int] = None,
default_values: Sequence[ValidDefaultValues] = MISSING,
id: Optional[int] = None,
@ -822,6 +839,7 @@ class MentionableSelect(BaseSelect[V]):
min_values=min_values,
max_values=max_values,
disabled=disabled,
required=required,
row=row,
default_values=_handle_select_defaults(default_values, self.type),
id=id,
@ -889,6 +907,10 @@ class ChannelSelect(BaseSelect[V]):
Number of items must be in range of ``min_values`` and ``max_values``.
.. versionadded:: 2.4
required: :class:`bool`
Whether the select is required. Only applicable within modals.
.. versionadded:: 2.6
row: Optional[:class:`int`]
The relative row this select menu belongs to. A Discord component can only have 5
rows. By default, items are arranged automatically into those 5 rows. If you'd
@ -919,6 +941,7 @@ class ChannelSelect(BaseSelect[V]):
min_values: int = 1,
max_values: int = 1,
disabled: bool = False,
required: bool = False,
row: Optional[int] = None,
default_values: Sequence[ValidDefaultValues] = MISSING,
id: Optional[int] = None,
@ -930,6 +953,7 @@ class ChannelSelect(BaseSelect[V]):
min_values=min_values,
max_values=max_values,
disabled=disabled,
required=required,
row=row,
channel_types=channel_types,
default_values=_handle_select_defaults(default_values, self.type),

8
discord/ui/view.py

@ -85,7 +85,10 @@ if TYPE_CHECKING:
from ..interactions import Interaction
from ..message import Message
from ..types.components import ComponentBase as ComponentBasePayload
from ..types.interactions import ModalSubmitComponentInteractionData as ModalSubmitComponentInteractionDataPayload
from ..types.interactions import (
ModalSubmitComponentInteractionData as ModalSubmitComponentInteractionDataPayload,
ResolvedData as ResolvedDataPayload,
)
from ..state import ConnectionState
from .modal import Modal
@ -1041,13 +1044,14 @@ class ViewStore:
custom_id: str,
interaction: Interaction,
components: List[ModalSubmitComponentInteractionDataPayload],
resolved: Optional[ResolvedDataPayload],
) -> None:
modal = self._modals.get(custom_id)
if modal is None:
_log.debug('Modal interaction referencing unknown custom_id %s. Discarding', custom_id)
return
self.add_task(modal._dispatch_submit(interaction, components))
self.add_task(modal._dispatch_submit(interaction, components, resolved))
def remove_interaction_mapping(self, interaction_id: int) -> None:
# This is called before re-adding the view

Loading…
Cancel
Save