Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: select menu default values #1115

Merged
merged 28 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e30c16a
fix(docs): move `SelectOption` to Discord Models section
shiftinv Oct 4, 2023
d71670b
feat(components): implement `default_values`
shiftinv Oct 4, 2023
922ff45
fix: move `ui` import in `abc` module into method to avoid cyclic imp…
shiftinv Oct 5, 2023
8f49e50
refactor: rename `select.base.Object` to `ItemShape` to avoid mixups
shiftinv Oct 5, 2023
e823f5d
feat(ui): implement `default_values` handling
shiftinv Oct 7, 2023
d9f0f79
refactor: swap keys/values of default type map
shiftinv Oct 7, 2023
d170cc9
docs(ui): add missing attribute documentation
shiftinv Oct 7, 2023
7e25f5a
fix: remove `default_values` from __repr__, way too verbose
shiftinv Oct 7, 2023
dc308c1
feat: add `BaseSelect.default_values` property
shiftinv Oct 7, 2023
2dd4475
chore: fix `ChannelSelect` parameter order
shiftinv Oct 7, 2023
c267fbe
feat(ui): add `default_values` parameter to decorators
shiftinv Oct 7, 2023
81bf599
feat: update `ActionRow` factory methods
shiftinv Oct 7, 2023
8d45ada
feat: support broader set of types in `BaseSelect.default_values` setter
shiftinv Oct 7, 2023
96a2a47
feat: add `SelectDefaultValue.__repr__`
shiftinv Oct 7, 2023
8551d66
docs: add SelectDefaultValueType
shiftinv Oct 7, 2023
79a75d8
fix(docs): fix `default_values` property type
shiftinv Oct 7, 2023
3f365d4
feat(types): add `Message.resolved` payloads
shiftinv Oct 7, 2023
6937285
docs: add changelog entry
shiftinv Oct 7, 2023
6ff7def
Merge remote-tracking branch 'upstream/master' into feature/select-de…
shiftinv Mar 26, 2024
3100aa2
chore: fix lint
shiftinv Mar 26, 2024
d18562b
Merge remote-tracking branch 'upstream/master' into feature/select-de…
shiftinv Apr 24, 2024
a078be1
Merge remote-tracking branch 'upstream/master' into feature/select-de…
shiftinv Sep 7, 2024
14b5a85
fix: use new channel union type
shiftinv Sep 7, 2024
de0ca0a
fix: allow dm/gdm instances in default_values
shiftinv Sep 7, 2024
117108f
chore: add some future fixmes re private channel types
shiftinv Sep 8, 2024
4fada30
fix: adjust `SelectDefaultValue` repr
shiftinv Sep 16, 2024
bd85a82
test: add a couple unittests
shiftinv Sep 16, 2024
33e7de8
Merge branch 'master' into feature/select-default-values
shiftinv Nov 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/1115.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add :class:`SelectDefaultValue`, and add :attr:`~UserSelectMenu.default_values` to all auto-populated select menu types.
8 changes: 3 additions & 5 deletions disnake/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
from .permissions import PermissionOverwrite, Permissions
from .role import Role
from .sticker import GuildSticker, StandardSticker, StickerItem
from .ui.action_row import components_to_dict
from .utils import _overload_with_permissions
from .voice_client import VoiceClient, VoiceProtocol

Expand Down Expand Up @@ -179,6 +178,7 @@ def avatar(self) -> Optional[Asset]:
raise NotImplementedError


# FIXME: this shouldn't be a protocol. isinstance(thread, PrivateChannel) returns true, and issubclass doesn't work.
@runtime_checkable
class PrivateChannel(Snowflake, Protocol):
"""An ABC that details the common operations on a private Discord channel.
Expand Down Expand Up @@ -1719,16 +1719,14 @@ async def send(

if view is not None and components is not None:
raise TypeError("cannot pass both view and components parameter to send()")

elif view:
if not hasattr(view, "__discord_ui_view__"):
raise TypeError(f"view parameter must be View not {view.__class__!r}")

components_payload = view.to_components()

elif components:
components_payload = components_to_dict(components)
from .ui.action_row import components_to_dict

components_payload = components_to_dict(components)
else:
components_payload = None

Expand Down
1 change: 1 addition & 0 deletions disnake/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -5092,6 +5092,7 @@ def _channel_type_factory(
cls: Union[Type[disnake.abc.GuildChannel], Type[Thread]]
) -> List[ChannelType]:
return {
# FIXME: this includes private channels; improve this once there's a common base type for all channels
disnake.abc.GuildChannel: list(ChannelType.__members__.values()),
VocalGuildChannel: [ChannelType.voice, ChannelType.stage_voice],
disnake.abc.PrivateChannel: [ChannelType.private, ChannelType.group],
Expand Down
83 changes: 81 additions & 2 deletions disnake/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@
cast,
)

from .enums import ButtonStyle, ChannelType, ComponentType, TextInputStyle, try_enum
from .enums import (
ButtonStyle,
ChannelType,
ComponentType,
SelectDefaultValueType,
TextInputStyle,
try_enum,
)
from .partial_emoji import PartialEmoji, _EmojiTag
from .utils import MISSING, assert_never, get_slots

Expand All @@ -35,6 +42,7 @@
Component as ComponentPayload,
MentionableSelectMenu as MentionableSelectMenuPayload,
RoleSelectMenu as RoleSelectMenuPayload,
SelectDefaultValue as SelectDefaultValuePayload,
SelectOption as SelectOptionPayload,
StringSelectMenu as StringSelectMenuPayload,
TextInput as TextInputPayload,
Expand All @@ -53,6 +61,7 @@
"MentionableSelectMenu",
"ChannelSelectMenu",
"SelectOption",
"SelectDefaultValue",
"TextInput",
)

Expand Down Expand Up @@ -264,6 +273,12 @@ class BaseSelectMenu(Component):
A list of options that can be selected in this select menu.
disabled: :class:`bool`
Whether the select menu is disabled or not.
default_values: List[:class:`SelectDefaultValue`]
The list of values (users/roles/channels) that are selected by default.
If set, the number of items must be within the bounds set by ``min_values`` and ``max_values``.
Only available for auto-populated select menus.

.. versionadded:: 2.10
"""

__slots__: Tuple[str, ...] = (
Expand All @@ -272,9 +287,11 @@ class BaseSelectMenu(Component):
"min_values",
"max_values",
"disabled",
"default_values",
)

__repr_info__: ClassVar[Tuple[str, ...]] = __slots__
# FIXME: this isn't pretty; we should decouple __repr__ from slots
__repr_info__: ClassVar[Tuple[str, ...]] = tuple(s for s in __slots__ if s != "default_values")

# n.b: ideally this would be `BaseSelectMenuPayload`,
# but pyright made TypedDict keys invariant and doesn't
Expand All @@ -288,6 +305,9 @@ def __init__(self, data: AnySelectMenuPayload) -> None:
self.min_values: int = data.get("min_values", 1)
self.max_values: int = data.get("max_values", 1)
self.disabled: bool = data.get("disabled", False)
self.default_values: List[SelectDefaultValue] = [
SelectDefaultValue._from_dict(d) for d in (data.get("default_values") or [])
]

def to_dict(self) -> BaseSelectMenuPayload:
payload: BaseSelectMenuPayload = {
Expand All @@ -301,6 +321,9 @@ def to_dict(self) -> BaseSelectMenuPayload:
if self.placeholder:
payload["placeholder"] = self.placeholder

if self.default_values:
payload["default_values"] = [v.to_dict() for v in self.default_values]

return payload


Expand Down Expand Up @@ -377,6 +400,11 @@ class UserSelectMenu(BaseSelectMenu):
Defaults to 1 and must be between 1 and 25.
disabled: :class:`bool`
Whether the select menu is disabled or not.
default_values: List[:class:`SelectDefaultValue`]
The list of values (users/members) that are selected by default.
If set, the number of items must be within the bounds set by ``min_values`` and ``max_values``.

.. versionadded:: 2.10
"""

__slots__: Tuple[str, ...] = ()
Expand Down Expand Up @@ -412,6 +440,11 @@ class RoleSelectMenu(BaseSelectMenu):
Defaults to 1 and must be between 1 and 25.
disabled: :class:`bool`
Whether the select menu is disabled or not.
default_values: List[:class:`SelectDefaultValue`]
The list of values (roles) that are selected by default.
If set, the number of items must be within the bounds set by ``min_values`` and ``max_values``.

.. versionadded:: 2.10
"""

__slots__: Tuple[str, ...] = ()
Expand Down Expand Up @@ -447,6 +480,11 @@ class MentionableSelectMenu(BaseSelectMenu):
Defaults to 1 and must be between 1 and 25.
disabled: :class:`bool`
Whether the select menu is disabled or not.
default_values: List[:class:`SelectDefaultValue`]
The list of values (users/roles) that are selected by default.
If set, the number of items must be within the bounds set by ``min_values`` and ``max_values``.

.. versionadded:: 2.10
"""

__slots__: Tuple[str, ...] = ()
Expand Down Expand Up @@ -485,6 +523,11 @@ class ChannelSelectMenu(BaseSelectMenu):
channel_types: Optional[List[:class:`ChannelType`]]
A list of channel types that can be selected in this select menu.
If ``None``, channels of all types may be selected.
default_values: List[:class:`SelectDefaultValue`]
The list of values (channels) that are selected by default.
If set, the number of items must be within the bounds set by ``min_values`` and ``max_values``.

.. versionadded:: 2.10
"""

__slots__: Tuple[str, ...] = ("channel_types",)
Expand Down Expand Up @@ -613,6 +656,42 @@ def to_dict(self) -> SelectOptionPayload:
return payload


class SelectDefaultValue:
"""Represents a default value of an auto-populated select menu (currently all
select menu types except :class:`StringSelectMenu`).

Depending on the :attr:`type` attribute, this can represent different types of objects.

.. versionadded:: 2.10

Attributes
----------
id: :class:`int`
The ID of the target object.
type: :class:`SelectDefaultValueType`
The type of the target object.
"""

__slots__: Tuple[str, ...] = ("id", "type")

def __init__(self, id: int, type: SelectDefaultValueType) -> None:
self.id: int = id
self.type: SelectDefaultValueType = type

@classmethod
def _from_dict(cls, data: SelectDefaultValuePayload) -> Self:
return cls(int(data["id"]), try_enum(SelectDefaultValueType, data["type"]))

def to_dict(self) -> SelectDefaultValuePayload:
return {
"id": self.id,
"type": self.type.value,
}

def __repr__(self) -> str:
return f"<SelectDefaultValue id={self.id!r} type={self.type.value!r}>"


class TextInput(Component):
"""Represents a text input from the Discord Bot UI Kit.

Expand Down
10 changes: 10 additions & 0 deletions disnake/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"ComponentType",
"ButtonStyle",
"TextInputStyle",
"SelectDefaultValueType",
"StagePrivacyLevel",
"InteractionType",
"InteractionResponseType",
Expand Down Expand Up @@ -694,6 +695,15 @@ def __int__(self) -> int:
return self.value


class SelectDefaultValueType(Enum):
user = "user"
role = "role"
channel = "channel"

def __str__(self) -> str:
return self.value


class ApplicationCommandType(Enum):
chat_input = 1
user = 2
Expand Down
9 changes: 9 additions & 0 deletions disnake/types/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@

from .channel import ChannelType
from .emoji import PartialEmoji
from .snowflake import Snowflake

ComponentType = Literal[1, 2, 3, 4, 5, 6, 7, 8]
ButtonStyle = Literal[1, 2, 3, 4, 5]
TextInputStyle = Literal[1, 2]

SelectDefaultValueType = Literal["user", "role", "channel"]

Component = Union["ActionRow", "ButtonComponent", "AnySelectMenu", "TextInput"]

Expand Down Expand Up @@ -40,12 +42,19 @@ class SelectOption(TypedDict):
default: NotRequired[bool]


class SelectDefaultValue(TypedDict):
id: Snowflake
type: SelectDefaultValueType


class _SelectMenu(TypedDict):
custom_id: str
placeholder: NotRequired[str]
min_values: NotRequired[int]
max_values: NotRequired[int]
disabled: NotRequired[bool]
# This is technically not applicable to string selects, but for simplicity we'll just have it here
default_values: NotRequired[List[SelectDefaultValue]]


class BaseSelectMenu(_SelectMenu):
Expand Down
6 changes: 4 additions & 2 deletions disnake/types/interactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ class InteractionDataResolved(TypedDict, total=False):
members: Dict[Snowflake, Member]
roles: Dict[Snowflake, Role]
channels: Dict[Snowflake, InteractionChannel]
# only in application commands


class ApplicationCommandInteractionDataResolved(InteractionDataResolved, total=False):
messages: Dict[Snowflake, Message]
attachments: Dict[Snowflake, Attachment]

Expand Down Expand Up @@ -158,7 +160,7 @@ class ApplicationCommandInteractionData(TypedDict):
id: Snowflake
name: str
type: ApplicationCommandType
resolved: NotRequired[InteractionDataResolved]
resolved: NotRequired[ApplicationCommandInteractionDataResolved]
options: NotRequired[List[ApplicationCommandInteractionDataOption]]
# this is the guild the command is registered to, not the guild the command was invoked in (see interaction.guild_id)
guild_id: NotRequired[Snowflake]
Expand Down
4 changes: 3 additions & 1 deletion disnake/types/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from .components import Component
from .embed import Embed
from .emoji import PartialEmoji
from .interactions import InteractionMessageReference
from .interactions import InteractionDataResolved, InteractionMessageReference
from .member import Member, UserWithMember
from .poll import Poll
from .snowflake import Snowflake, SnowflakeList
Expand Down Expand Up @@ -117,6 +117,8 @@ class Message(TypedDict):
position: NotRequired[int]
role_subscription_data: NotRequired[RoleSubscriptionData]
poll: NotRequired[Poll]
# contains resolved objects for `default_values` of select menus in this message; we currently don't have a use for this
resolved: NotRequired[InteractionDataResolved]

# specific to MESSAGE_CREATE/MESSAGE_UPDATE events
guild_id: NotRequired[Snowflake]
Expand Down
Loading
Loading