From f3cfce0e7f7efdb6c6e51ae2d4c5d77c30550017 Mon Sep 17 00:00:00 2001 From: Damego Date: Sat, 8 Oct 2022 09:09:42 +0500 Subject: [PATCH 01/12] feat: Implement helper methods for invites (#1098) --- interactions/api/http/invite.py | 4 +-- interactions/api/models/guild.py | 60 ++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/interactions/api/http/invite.py b/interactions/api/http/invite.py index e7706e479..f2800f64d 100644 --- a/interactions/api/http/invite.py +++ b/interactions/api/http/invite.py @@ -24,11 +24,9 @@ async def get_invite( """ Gets a Discord invite using its code. - .. note:: with_expiration is currently broken, the API will always return expiration_date. - :param invite_code: A string representing the invite code. :param with_counts: Whether approximate_member_count and approximate_presence_count are returned. - :param with_expiration: Whether the invite's expiration is returned. + :param with_expiration: Whether the invite's expiration date is returned. :param guild_scheduled_event_id: A guild scheduled event's ID. """ params_set = { diff --git a/interactions/api/models/guild.py b/interactions/api/models/guild.py index 5bd09847e..183a09217 100644 --- a/interactions/api/models/guild.py +++ b/interactions/api/models/guild.py @@ -2830,6 +2830,66 @@ async def get_full_audit_logs( return AuditLogs(**_audit_log_dict) + async def get_invite( + self, + invite_code: str, + with_counts: Optional[bool] = MISSING, + with_expiration: Optional[bool] = MISSING, + guild_scheduled_event_id: Optional[int] = MISSING, + ) -> "Invite": + """ + Gets the invite using its code. + + :param str invite_code: A string representing the invite code. + :param Optional[bool] with_counts: Whether approximate_member_count and approximate_presence_count are returned. + :param Optional[bool] with_expiration: Whether the invite's expiration date is returned. + :param Optional[int] guild_scheduled_event_id: A guild scheduled event's ID. + :return: An invite + :rtype: Invite + """ + if not self._client: + raise LibraryException(code=13) + + _with_counts = with_counts if with_counts is not MISSING else None + _with_expiration = with_expiration if with_expiration is not MISSING else None + _guild_scheduled_event_id = ( + guild_scheduled_event_id if guild_scheduled_event_id is not MISSING else None + ) + + res = await self._client.get_invite( + invite_code=invite_code, + with_counts=_with_counts, + with_expiration=_with_expiration, + guild_scheduled_event_id=_guild_scheduled_event_id, + ) + + return Invite(**res, _client=self._client) + + async def delete_invite(self, invite_code: str, reason: Optional[str] = None) -> None: + """ + Deletes the invite using its code. + + :param str invite_code: A string representing the invite code. + :param Optional[str] reason: The reason of the deletion + """ + if not self._client: + raise LibraryException(code=13) + + await self._client.delete_invite(invite_code=invite_code, reason=reason) + + async def get_invites(self) -> List["Invite"]: + """ + Gets invites of the guild. + + :return: A list of guild invites + :rtype: List[Invite] + """ + if not self._client: + raise LibraryException(code=13) + + res = await self._client.get_guild_invites(guild_id=int(self.id)) + return [Invite(**_, _client=self._client) for _ in res] + @property def icon_url(self) -> Optional[str]: """ From 527f320ba6f506f2c2622d813c73e8f6a11ec4e8 Mon Sep 17 00:00:00 2001 From: i0 <41456914+i0bs@users.noreply.github.com> Date: Sat, 8 Oct 2022 15:35:57 -0400 Subject: [PATCH 02/12] feat: mention spam trigger type --- interactions/api/models/misc.py | 1 + 1 file changed, 1 insertion(+) diff --git a/interactions/api/models/misc.py b/interactions/api/models/misc.py index 34e15e208..2da558529 100644 --- a/interactions/api/models/misc.py +++ b/interactions/api/models/misc.py @@ -208,6 +208,7 @@ class AutoModTriggerType(IntEnum): HARMFUL_LINK = 2 SPAM = 3 KEYWORD_PRESET = 4 + MENTION_SPAM = 5 class AutoModKeywordPresetTypes(IntEnum): From 7394cd6afebbff0bb4aea6d95fcd20f30a66b053 Mon Sep 17 00:00:00 2001 From: DeltaX <33706469+DeltaXWizard@users.noreply.github.com> Date: Sun, 9 Oct 2022 10:53:54 -0400 Subject: [PATCH 03/12] fix: Reimplement manual sharding/presence, fix forum tag implementation (#1115) * fix: Reimplement manual sharding/presence instantiation. (This was accidentally removed per gateway rework) * refactor: Reorganise tag creation/updating/deletion to non-deprecated endpoints and make it cache-reflective. --- interactions/api/gateway/client.py | 10 ++++- interactions/api/http/channel.py | 67 ++++++++++++++++++++++++++---- interactions/api/http/thread.py | 4 +- interactions/client/bot.py | 4 +- 4 files changed, 73 insertions(+), 12 deletions(-) diff --git a/interactions/api/gateway/client.py b/interactions/api/gateway/client.py index e9fd15579..3de335c6f 100644 --- a/interactions/api/gateway/client.py +++ b/interactions/api/gateway/client.py @@ -122,6 +122,8 @@ def __init__( intents: Intents, session_id: Optional[str] = MISSING, sequence: Optional[int] = MISSING, + shards: Optional[List[Tuple[int]]] = MISSING, + presence: Optional[ClientPresence] = MISSING, ) -> None: """ :param token: The token of the application for connecting to the Gateway. @@ -132,6 +134,10 @@ def __init__( :type session_id?: Optional[str] :param sequence?: The identifier sequence if trying to reconnect. Defaults to ``None``. :type sequence?: Optional[int] + :param shards?: The list of shards for the application's initial connection, if provided. Defaults to ``None``. + :type shards?: Optional[List[Tuple[int]]] + :param presence?: The presence shown on an application once first connected. Defaults to ``None``. + :type presence?: Optional[ClientPresence] """ try: self._loop = get_event_loop() if version_info < (3, 10) else get_running_loop() @@ -161,8 +167,8 @@ def __init__( } self._intents: Intents = intents - self.__shard: Optional[List[Tuple[int]]] = None - self.__presence: Optional[ClientPresence] = None + self.__shard: Optional[List[Tuple[int]]] = None if shards is MISSING else shards + self.__presence: Optional[ClientPresence] = None if presence is MISSING else presence self._task: Optional[Task] = None self.__heartbeat_event = Event(loop=self._loop) if version_info < (3, 10) else Event() diff --git a/interactions/api/http/channel.py b/interactions/api/http/channel.py index dd0a40899..7335be2f9 100644 --- a/interactions/api/http/channel.py +++ b/interactions/api/http/channel.py @@ -4,6 +4,7 @@ from ..error import LibraryException from ..models.channel import Channel from ..models.message import Message +from ..models.misc import Snowflake from .request import _Request from .route import Route @@ -312,8 +313,10 @@ async def create_tag( self, channel_id: int, name: str, + moderated: bool = False, emoji_id: Optional[int] = None, emoji_name: Optional[str] = None, + reason: Optional[str] = None, ) -> dict: """ Create a new tag. @@ -324,25 +327,41 @@ async def create_tag( :param channel_id: Channel ID snowflake. :param name: The name of the tag + :param moderated: Whether the tag can only be assigned to moderators or not. Defaults to ``False`` :param emoji_id: The ID of the emoji to use for the tag :param emoji_name: The name of the emoji to use for the tag + :param reason: The reason for the creating the tag, if any. + :return: A Forum tag. """ - _dct = {"name": name} + # This *assumes* cache is up-to-date. + + _channel = self.cache[Channel].get(Snowflake(channel_id)) + _tags = [_._json for _ in _channel.available_tags] # list of tags in dict form + + _dct = {"name": name, "moderated": moderated} if emoji_id: _dct["emoji_id"] = emoji_id if emoji_name: _dct["emoji_name"] = emoji_name - return await self._req.request(Route("POST", f"/channels/{channel_id}/tags"), json=_dct) + _tags.append(_dct) + + updated_channel = await self.modify_channel( + channel_id, {"available_tags": _tags}, reason=reason + ) + _channel_obj = Channel(**updated_channel, _client=self) + return _channel_obj.available_tags[-1]._json async def edit_tag( self, channel_id: int, tag_id: int, name: str, + moderated: Optional[bool] = None, emoji_id: Optional[int] = None, emoji_name: Optional[str] = None, + reason: Optional[str] = None, ) -> dict: """ Update a tag. @@ -351,28 +370,62 @@ async def edit_tag( Can either have an emoji_id or an emoji_name, but not both. emoji_id is meant for custom emojis, emoji_name is meant for unicode emojis. + The object returns *will* have a different tag ID. + :param channel_id: Channel ID snowflake. :param tag_id: The ID of the tag to update. + :param moderated: Whether the tag can only be assigned to moderators or not. Defaults to ``False`` :param name: The new name of the tag :param emoji_id: The ID of the emoji to use for the tag :param emoji_name: The name of the emoji to use for the tag + :param reason: The reason for deleting the tag, if any. + + :return The updated tag object. """ - _dct = {"name": name} + # This *assumes* cache is up-to-date. + + _channel = self.cache[Channel].get(Snowflake(channel_id)) + _tags = [_._json for _ in _channel.available_tags] # list of tags in dict form + + _old_tag = [tag for tag in _tags if tag["id"] == tag_id][0] + + _tags.remove(_old_tag) + + _dct = {"name": name, "tag_id": tag_id} + if moderated: + _dct["moderated"] = moderated if emoji_id: _dct["emoji_id"] = emoji_id if emoji_name: _dct["emoji_name"] = emoji_name - return await self._req.request( - Route("PUT", f"/channels/{channel_id}/tags/{tag_id}"), json=_dct + _tags.append(_dct) + + updated_channel = await self.modify_channel( + channel_id, {"available_tags": _tags}, reason=reason ) + _channel_obj = Channel(**updated_channel, _client=self) + + self.cache[Channel].merge(_channel_obj) + + return [tag for tag in _channel_obj.available_tags if tag.name == name][0] - async def delete_tag(self, channel_id: int, tag_id: int) -> None: # wha? + async def delete_tag(self, channel_id: int, tag_id: int, reason: Optional[str] = None) -> None: """ Delete a forum tag. :param channel_id: Channel ID snowflake. :param tag_id: The ID of the tag to delete + :param reason: The reason for deleting the tag, if any. """ - return await self._req.request(Route("DELETE", f"/channels/{channel_id}/tags/{tag_id}")) + _channel = self.cache[Channel].get(Snowflake(channel_id)) + _tags = [_._json for _ in _channel.available_tags] + + _old_tag = [tag for tag in _tags if tag["id"] == Snowflake(tag_id)][0] + + _tags.remove(_old_tag) + + request = await self.modify_channel(channel_id, {"available_tags": _tags}, reason=reason) + + self.cache[Channel].merge(Channel(**request, _client=self)) diff --git a/interactions/api/http/thread.py b/interactions/api/http/thread.py index a56dcf1d2..42cbadfbf 100644 --- a/interactions/api/http/thread.py +++ b/interactions/api/http/thread.py @@ -159,7 +159,7 @@ async def create_thread( reason: Optional[str] = None, ) -> dict: """ - From a given channel, create a Thread with an optional message to start with.. + From a given channel, create a Thread with an optional message to start with. :param channel_id: The ID of the channel to create this thread in :param name: The name of the thread @@ -212,7 +212,7 @@ async def create_thread_in_forum( :param name: The name of the thread :param auto_archive_duration: duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080 - :param message_payload: The payload/dictionary contents of the first message in the forum thread. + :param message: The payload/dictionary contents of the first message in the forum thread. :param applied_tags: List of tag ids that can be applied to the forum, if any. :param files: An optional list of files to send attached to the message. :param rate_limit_per_user: Seconds a user has to wait before sending another message (0 to 21600), if given. diff --git a/interactions/client/bot.py b/interactions/client/bot.py index fa74e4bd8..a5090b248 100644 --- a/interactions/client/bot.py +++ b/interactions/client/bot.py @@ -80,11 +80,13 @@ def __init__( self._loop: AbstractEventLoop = get_event_loop() self._http: HTTPClient = token self._intents: Intents = kwargs.get("intents", Intents.DEFAULT) - self._websocket: WSClient = WSClient(token=token, intents=self._intents) self._shards: List[Tuple[int]] = kwargs.get("shards", []) self._commands: List[Command] = [] self._default_scope = kwargs.get("default_scope") self._presence = kwargs.get("presence") + self._websocket: WSClient = WSClient( + token=token, intents=self._intents, shards=self._shards, presence=self._presence + ) self._token = token self._extensions = {} self._scopes = set([]) From 03dfc205cabd69ecb8a4a262af56ceac68914af7 Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Sun, 9 Oct 2022 16:56:51 +0200 Subject: [PATCH 04/12] chore: bump version (#1116) --- interactions/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interactions/base.py b/interactions/base.py index 6f96725a7..ae82a9787 100644 --- a/interactions/base.py +++ b/interactions/base.py @@ -6,7 +6,7 @@ "__authors__", ) -__version__ = "4.3.2" +__version__ = "4.3.3" __authors__ = { "current": [ From 39102fcf183c9f083fc78ba51355d16a73e7f1d1 Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Sun, 9 Oct 2022 20:12:18 +0200 Subject: [PATCH 05/12] fix: properly initialise private attributes in iterators (#1114) --- interactions/api/models/channel.py | 2 ++ interactions/api/models/guild.py | 3 +++ interactions/utils/abc/base_iterators.py | 1 - 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/interactions/api/models/channel.py b/interactions/api/models/channel.py index 4c5144efa..ca9bfd12d 100644 --- a/interactions/api/models/channel.py +++ b/interactions/api/models/channel.py @@ -138,6 +138,8 @@ def __init__( ): super().__init__(obj, _client, maximum=maximum, start_at=start_at, check=check) + self.__stop: bool = False + from .message import Message if reverse and start_at is MISSING: diff --git a/interactions/api/models/guild.py b/interactions/api/models/guild.py index 183a09217..fa5ed1bc4 100644 --- a/interactions/api/models/guild.py +++ b/interactions/api/models/guild.py @@ -241,6 +241,9 @@ def __init__( start_at: Optional[Union[int, str, Snowflake, Member]] = MISSING, check: Optional[Callable[[Member], bool]] = None, ): + + self.__stop: bool = False + super().__init__(obj, _client, maximum=maximum, start_at=start_at, check=check) self.after = self.start_at diff --git a/interactions/utils/abc/base_iterators.py b/interactions/utils/abc/base_iterators.py index 67443c8f6..c17d65846 100644 --- a/interactions/utils/abc/base_iterators.py +++ b/interactions/utils/abc/base_iterators.py @@ -56,7 +56,6 @@ def __init__( if not hasattr(start_at, "id") else int(start_at.id) ) - self.__stop: bool = False self.objects: Optional[List[_O]] = None From 51c73dedb58241c1f2673c4b63fab0ce06b44d8d Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Sun, 9 Oct 2022 23:34:03 +0200 Subject: [PATCH 06/12] fix: set `message.member.user` as `message.author` again (#1118) --- interactions/api/models/message.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interactions/api/models/message.py b/interactions/api/models/message.py index aa7611022..318383faa 100644 --- a/interactions/api/models/message.py +++ b/interactions/api/models/message.py @@ -831,6 +831,9 @@ def __attrs_post_init__(self): if self.guild_id: self.member._extras["guild_id"] = self.guild_id + if self.author and self.member: + self.member.user = self.author + async def get_channel(self) -> Channel: """ Gets the channel where the message was sent. From c75bda4a43c4622c74d0b086ada81c8a2062baf4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 22:42:16 +0200 Subject: [PATCH 07/12] ci: weekly check. (#1119) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 22.8.0 → 22.10.0](https://github.com/psf/black/compare/22.8.0...22.10.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bde5b0c27..48487486e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,7 +29,7 @@ repos: - id: check-merge-conflict name: Merge Conflicts - repo: https://github.com/psf/black - rev: 22.8.0 + rev: 22.10.0 hooks: - id: black name: Black Formatting From d048a8cbec2a51f18ac7382278d765d86280f199 Mon Sep 17 00:00:00 2001 From: DeltaX <33706469+DeltaXWizard@users.noreply.github.com> Date: Thu, 13 Oct 2022 18:38:09 -0400 Subject: [PATCH 08/12] feat: Implement barebones Select Menus for other supporting types (#836) * feat: Implement barebones Select Menus for other supporting types with type-conversion support. * fix: Tweak SelectMenu option generation for other type of menus * docs: update FAQ and quickstart documentation links (#1038) * Docs: add back previous changes * docs: fix outdated links in the quickstart (#1037) Co-authored-by: Damego * chore: Resolve merge conflicts. Co-authored-by: EdVraz <88881326+EdVraz@users.noreply.github.com> Co-authored-by: Damego --- interactions/api/gateway/client.py | 45 +++++++++++++++++- interactions/client/bot.py | 4 +- interactions/client/enums.py | 11 ++++- interactions/client/models/component.py | 61 +++++++++++++++++-------- 4 files changed, 96 insertions(+), 25 deletions(-) diff --git a/interactions/api/gateway/client.py b/interactions/api/gateway/client.py index 3de335c6f..af4b7ee67 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 @@ -379,7 +379,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: @@ -842,6 +849,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 a5090b248..480ba068e 100644 --- a/interactions/client/bot.py +++ b/interactions/client/bot.py @@ -727,8 +727,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] From 1d55e8327f99adda16a4692b1cd20a0c54e57987 Mon Sep 17 00:00:00 2001 From: DeltaX <33706469+DeltaXWizard@users.noreply.github.com> Date: Sat, 15 Oct 2022 09:28:39 -0400 Subject: [PATCH 09/12] chore: bump version (#1120) --- interactions/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interactions/base.py b/interactions/base.py index ae82a9787..30a5683ef 100644 --- a/interactions/base.py +++ b/interactions/base.py @@ -6,7 +6,7 @@ "__authors__", ) -__version__ = "4.3.3" +__version__ = "4.3.4" __authors__ = { "current": [ From 29b342efa857123331e5540a238f7569a95b6e62 Mon Sep 17 00:00:00 2001 From: Damego Date: Sat, 15 Oct 2022 18:52:23 +0500 Subject: [PATCH 10/12] fix: Mentionable select type bug (#1122) * fix: Mentionable select type bug * ci: correct from checks. Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- interactions/api/gateway/client.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/interactions/api/gateway/client.py b/interactions/api/gateway/client.py index af4b7ee67..050c90f67 100644 --- a/interactions/api/gateway/client.py +++ b/interactions/api/gateway/client.py @@ -873,14 +873,15 @@ def __select_option_type_context(self, context: "_Context", type: int) -> dict: 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, - } + if ( + users := context.data.resolved.members + if context.guild_id + else context.data.resolved.users + ): + _resolved.update(**users) + if roles := context.data.resolved.roles: + _resolved.update(**roles) + return _resolved async def _reconnect(self, to_resume: bool, code: Optional[int] = 1012) -> None: From 5c3c5d7514478086587b478cada0a8be38124d56 Mon Sep 17 00:00:00 2001 From: Damego Date: Sun, 16 Oct 2022 22:10:45 +0500 Subject: [PATCH 11/12] fix: add missed httpclient in guild role events (#1123) * fix: add missed httpclient in guild role events * ci: correct from checks. Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- interactions/api/gateway/client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/interactions/api/gateway/client.py b/interactions/api/gateway/client.py index 050c90f67..38c7e782b 100644 --- a/interactions/api/gateway/client.py +++ b/interactions/api/gateway/client.py @@ -467,7 +467,11 @@ def _dispatch_event(self, event: str, data: dict) -> None: guild_obj = guild_model = None if model is GuildRole: - guild_obj = Role(**role_data) if (role_data := data.get("role")) else None + guild_obj = ( + Role(**role_data, _client=self._http) + if (role_data := data.get("role")) + else None + ) guild_model = Role elif model is GuildMember: guild_obj = Member(**data) From 21ff691b3ce313c095ceeaaa5bd166cf11834dfc Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Sun, 16 Oct 2022 19:12:32 +0200 Subject: [PATCH 12/12] fix: location of select values when dispatching for new select types (#1124) --- interactions/api/gateway/client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/interactions/api/gateway/client.py b/interactions/api/gateway/client.py index 38c7e782b..e4a6eb84f 100644 --- a/interactions/api/gateway/client.py +++ b/interactions/api/gateway/client.py @@ -382,11 +382,13 @@ def _dispatch_event(self, event: str, data: dict) -> None: if _context.data.component_type.value not in {5, 6, 7, 8}: __args.append(_context.data.values) else: + _list = [] # temp storage for items 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]) + _list.append(_data[value]) + __args.append(_list) self._dispatch.dispatch("on_component", _context) elif data["type"] == InteractionType.APPLICATION_COMMAND_AUTOCOMPLETE: