diff --git a/interactions/api/gateway/client.py b/interactions/api/gateway/client.py index e9fd15579..80f255da4 100644 --- a/interactions/api/gateway/client.py +++ b/interactions/api/gateway/client.py @@ -24,7 +24,7 @@ from aiohttp import ClientWebSocketResponse, WSMessage, WSMsgType from ...base import __version__, get_logger -from ...client.enums import InteractionType, OptionType +from ...client.enums import ComponentType, InteractionType, OptionType from ...client.models import Option from ...utils.missing import MISSING from ..dispatch import Listener @@ -373,7 +373,14 @@ def _dispatch_event(self, event: str, data: dict) -> None: _name = f"component_{_context.data.custom_id}" if _context.data._json.get("values"): - __args.append(_context.data.values) + if _context.data.component_type.value not in {5, 6, 7, 8}: + __args.append(_context.data.values) + else: + for value in _context.data._json.get("values"): + _data = self.__select_option_type_context( + _context, _context.data.component_type.value + ) # resolved. + __args.append(_data[value]) self._dispatch.dispatch("on_component", _context) elif data["type"] == InteractionType.APPLICATION_COMMAND_AUTOCOMPLETE: @@ -836,6 +843,40 @@ def __option_type_context(self, context: "_Context", type: int) -> dict: } return _resolved + def __select_option_type_context(self, context: "_Context", type: int) -> dict: + """ + Looks up the type of select menu respective to the existing option types. This is applicable for non-string + select menus. + + :param context: The context to refer types from. + :type context: object + :param type: The option type. + :type type: int + :return: The select menu type context. + :rtype: dict + """ + + _resolved = {} + + if type == ComponentType.USER_SELECT.value: + _resolved = ( + context.data.resolved.members if context.guild_id else context.data.resolved.users + ) + elif type == ComponentType.CHANNEL_SELECT.value: + _resolved = context.data.resolved.channels + elif type == ComponentType.ROLE_SELECT.value: + _resolved = context.data.resolved.roles + elif type == ComponentType.MENTIONABLE_SELECT.value: + _resolved = { + **( + context.data.resolved.members + if context.guild_id + else context.data.resolved.users + ), + **context.data.resolved.roles, + } + return _resolved + async def _reconnect(self, to_resume: bool, code: Optional[int] = 1012) -> None: """ Restarts the client's connection and heartbeat with the Gateway. diff --git a/interactions/client/bot.py b/interactions/client/bot.py index fa74e4bd8..9c1c85935 100644 --- a/interactions/client/bot.py +++ b/interactions/client/bot.py @@ -725,8 +725,8 @@ def event( :param coro: The coroutine of the event. :type coro: Optional[Callable[..., Coroutine]] - :param name(?): The name of the event. If not given, this defaults to the coroutine's name. - :type name: Optional[str] + :param name?: The name of the event. If not given, this defaults to the coroutine's name. + :type name?: Optional[str] :return: A callable response. :rtype: Callable[..., Any] """ diff --git a/interactions/client/enums.py b/interactions/client/enums.py index cf0af6d07..a1e57ce96 100644 --- a/interactions/client/enums.py +++ b/interactions/client/enums.py @@ -120,13 +120,22 @@ class ComponentType(IntEnum): :ivar ACTION_ROW: 1 :ivar BUTTON: 2 :ivar SELECT: 3 + :ivar STRING_SELECT: 3 :ivar INPUT_TEXT: 4 + :ivar USER_SELECT: 5 + :ivar ROLE_SELECT: 6 + :ivar MENTIONABLE_SELECT: 7 + :ivar CHANNEL_SELECT: 8 """ ACTION_ROW = 1 BUTTON = 2 - SELECT = 3 + SELECT = STRING_SELECT = 3 INPUT_TEXT = 4 + USER_SELECT = 5 + ROLE_SELECT = 6 + MENTIONABLE_SELECT = 7 + CHANNEL_SELECT = 8 class ButtonStyle(IntEnum): diff --git a/interactions/client/models/component.py b/interactions/client/models/component.py index c7ff13809..b8a3f0d60 100644 --- a/interactions/client/models/component.py +++ b/interactions/client/models/component.py @@ -77,26 +77,31 @@ class SelectMenu(ComponentMixin): placeholder="Check out my options. :)", custom_id="menu_component", ) - :ivar ComponentType type: The type of select menu. Always defaults to ``3``. + :ivar ComponentType type: The type of select menu. If not given, it defaults to ``ComponentType.SELECT`` (``STRING_SELECT``) :ivar str custom_id: The customized "ID" of the select menu. - :ivar List[SelectOption] options: The list of select options in the select menu. + :ivar Optional[List[SelectOption]] options: The list of select options in the select menu. This only applies to String-based selects. :ivar Optional[str] placeholder?: The placeholder of the select menu. :ivar Optional[int] min_values?: The minimum "options"/values to choose from the component. :ivar Optional[int] max_values?: The maximum "options"/values to choose from the component. :ivar Optional[bool] disabled?: Whether the select menu is unable to be used. + :ivar Optional[List[int]] channel_types: Optional channel types to filter/whitelist. Only works with the CHANNEL_SELECT type. """ type: ComponentType = field(converter=ComponentType, default=ComponentType.SELECT) custom_id: str = field() - options: List[SelectOption] = field(converter=convert_list(SelectOption)) + options: Optional[List[SelectOption]] = field( + converter=convert_list(SelectOption), default=None + ) placeholder: Optional[str] = field(default=None) min_values: Optional[int] = field(default=None) max_values: Optional[int] = field(default=None) disabled: Optional[bool] = field(default=None) + channel_types: Optional[List[int]] = field(default=None) def __attrs_post_init__(self) -> None: self._json.update({"type": self.type.value}) - self._json.update({"options": [option._json for option in self.options]}) + if self.options: + self._json.update({"options": [option._json for option in self.options]}) @define() @@ -284,10 +289,14 @@ class ActionRow(ComponentMixin): def __attrs_post_init__(self) -> None: for component in self.components: if isinstance(component, SelectMenu): - component._json["options"] = [ - option._json if isinstance(option, SelectOption) else option - for option in component._json["options"] - ] + component._json["options"] = ( + [ + option._json if isinstance(option, SelectOption) else option + for option in component._json["options"] + ] + if component._json.get("options") + else [] + ) self.components = ( [Component(**component._json) for component in self.components] if self._json.get("components") @@ -323,10 +332,14 @@ def __check_action_row(): action_row if isinstance(action_row, list) else action_row.components ): if isinstance(component, SelectMenu): - component._json["options"] = [ - option if isinstance(option, dict) else option._json - for option in component.options - ] + component._json["options"] = ( + [ + option if isinstance(option, dict) else option._json + for option in component.options + ] + if component._json.get("options") + else [] + ) _components.append( { @@ -367,10 +380,14 @@ def __check_components(): ): for component in components: if isinstance(component, SelectMenu): - component._json["options"] = [ - options if isinstance(options, dict) else options._json - for options in component._json["options"] - ] + component._json["options"] = ( + [ + options if isinstance(options, dict) else options._json + for options in component._json["options"] + ] + if component._json.get("options") + else [] + ) _components = [ { @@ -397,10 +414,14 @@ def __check_components(): return _components elif isinstance(components, SelectMenu): _components: List[dict] = [{"type": 1, "components": []}] - components._json["options"] = [ - options if isinstance(options, dict) else options._json - for options in components._json["options"] - ] + components._json["options"] = ( + [ + options if isinstance(options, dict) else options._json + for options in components._json["options"] + ] + if components._json.get("options") + else [] + ) _components[0]["components"] = ( [components._json]