From 6fbb08e42856c2f693172cc9636ef08563028ef7 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Thu, 21 Mar 2024 04:15:54 +0000 Subject: [PATCH 01/53] add enums, do command stuff, add context to interaction --- discord/commands/core.py | 118 ++++++++++++++++++++++++++++++++++----- discord/enums.py | 16 ++++++ discord/interactions.py | 9 ++- discord/message.py | 1 + 4 files changed, 128 insertions(+), 16 deletions(-) diff --git a/discord/commands/core.py b/discord/commands/core.py index 6f34c0c9d9..89fda9efb3 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -47,12 +47,13 @@ from ..channel import _threaded_guild_channel_factory from ..enums import Enum as DiscordEnum -from ..enums import MessageType, SlashCommandOptionType, try_enum +from ..enums import IntegrationType, InteractionContextType, MessageType, SlashCommandOptionType, try_enum from ..errors import ( ApplicationCommandError, ApplicationCommandInvokeError, CheckFailure, ClientException, + InvalidArgument, ValidationError, ) from ..member import Member @@ -61,7 +62,7 @@ from ..role import Role from ..threads import Thread from ..user import User -from ..utils import MISSING, async_all, find, maybe_coroutine, utcnow +from ..utils import MISSING, async_all, find, maybe_coroutine, utcnow, warn_deprecated from .context import ApplicationContext, AutocompleteContext from .options import Option, OptionChoice @@ -226,11 +227,33 @@ def __init__(self, func: Callable, **kwargs) -> None: "__default_member_permissions__", kwargs.get("default_member_permissions", None), ) - self.guild_only: bool | None = getattr( - func, "__guild_only__", kwargs.get("guild_only", None) - ) self.nsfw: bool | None = getattr(func, "__nsfw__", kwargs.get("nsfw", None)) + integration_types = getattr( + func, "__integration_types__", kwargs.get("integration_types", None) + ) + contexts = getattr( + func, "__contexts__", kwargs.get("contexts", None) + ) + guild_only = getattr( + func, "__guild_only__", kwargs.get("guild_only", MISSING) + ) + if guild_only is not MISSING: + warn_deprecated("guild_only", "contexts", "2.6") + if contexts and guild_only: + raise InvalidArgument("cannot pass both 'contexts' and 'guild_only' to ApplicationCommand") + if self.guild_ids and ((contexts is not None) or guild_only or integration_types): + raise InvalidArgument("the 'contexts' and 'integration_types' parameters are not available for guild commands") + + self.contexts: set[InteractionContextType] = contexts or { + InteractionContextType.guild, + InteractionContextType.bot_dm, + InteractionContextType.private_channel, + } + if guild_only: + self.guild_only: bool | None = guild_only + self.integration_types: set[IntegrationType] = integration_types or {IntegrationType.guild_install} + def __repr__(self) -> str: return f"" @@ -274,6 +297,19 @@ def callback( unwrap = unwrap_function(function) self.module = unwrap.__module__ + @property + def guild_only(self) -> bool: + warn_deprecated("guild_only", "contexts", "2.6") + return InteractionContextType.guild in self.contexts and len(self.contexts) == 1 + + @guild_only.setter + def guild_only(self, value: bool) -> None: + warn_deprecated("guild_only", "contexts", "2.6") + if value: + self.contexts = {InteractionContextType.guild} + else: + self.contexts = {InteractionContextType.guild, InteractionContextType.bot_dm, InteractionContextType.private_channel} + def _prepare_cooldowns(self, ctx: ApplicationContext): if self._buckets.valid: current = datetime.datetime.now().timestamp() @@ -631,6 +667,9 @@ class SlashCommand(ApplicationCommand): Returns a string that allows you to mention the slash command. guild_only: :class:`bool` Whether the command should only be usable inside a guild. + + .. deprecated:: 2.6 + Use the ``contexts`` parameter instead. nsfw: :class:`bool` Whether the command should be restricted to 18+ channels and users. Apps intending to be listed in the App Directory cannot have NSFW commands. @@ -654,6 +693,10 @@ class SlashCommand(ApplicationCommand): description_localizations: Dict[:class:`str`, :class:`str`] The description localizations for this command. The values of this should be ``"locale": "description"``. See `here `_ for a list of valid locales. + integration_types: set[:class:`IntegrationType`] + The installation contexts where this command is available. Cannot be set if this is a guild command. + contexts: set[:class:`InteractionContextType`] + The interaction contexts where this command is available. Cannot be set if this is a guild command. """ type = 1 @@ -881,9 +924,6 @@ def to_dict(self) -> dict: if self.is_subcommand: as_dict["type"] = SlashCommandOptionType.sub_command.value - if self.guild_only is not None: - as_dict["dm_permission"] = not self.guild_only - if self.nsfw is not None: as_dict["nsfw"] = self.nsfw @@ -892,6 +932,10 @@ def to_dict(self) -> dict: self.default_member_permissions.value ) + if not self.guild_ids: + as_dict["integration_types"] = [it.value for it in self.integration_types] + as_dict["contexts"] = [ctx.value for ctx in self.contexts] + return as_dict async def _invoke(self, ctx: ApplicationContext) -> None: @@ -1100,6 +1144,9 @@ class SlashCommandGroup(ApplicationCommand): isn't one. guild_only: :class:`bool` Whether the command should only be usable inside a guild. + + .. deprecated:: 2.6 + Use the ``contexts`` parameter instead. nsfw: :class:`bool` Whether the command should be restricted to 18+ channels and users. Apps intending to be listed in the App Directory cannot have NSFW commands. @@ -1118,6 +1165,10 @@ class SlashCommandGroup(ApplicationCommand): description_localizations: Dict[:class:`str`, :class:`str`] The description localizations for this command. The values of this should be ``"locale": "description"``. See `here `_ for a list of valid locales. + integration_types: set[:class:`IntegrationType`] + The installation contexts where this command is available. Cannot be set if this is a guild command. + contexts: set[:class:`InteractionContextType`] + The interaction contexts where this command is available. Cannot be set if this is a guild command. """ __initial_commands__: list[SlashCommand | SlashCommandGroup] @@ -1174,12 +1225,28 @@ def __init__( self.id = None # Permissions - self.default_member_permissions: Permissions | None = kwargs.get( - "default_member_permissions", None - ) - self.guild_only: bool | None = kwargs.get("guild_only", None) + self.default_member_permissions: Permissions | None = kwargs.get("default_member_permissions", None) self.nsfw: bool | None = kwargs.get("nsfw", None) + integration_types = kwargs.get("integration_types", None) + contexts = kwargs.get("contexts", None) + guild_only = kwargs.get("guild_only", MISSING) + if guild_only is not MISSING: + warn_deprecated("guild_only", "contexts", "2.6") + if contexts and guild_only: + raise InvalidArgument("cannot pass both 'contexts' and 'guild_only' to ApplicationCommand") + if self.guild_ids and ((contexts is not None) or guild_only or integration_types): + raise InvalidArgument("the 'contexts' and 'integration_types' parameters are not available for guild commands") + + self.contexts: set[InteractionContextType] = contexts or { + InteractionContextType.guild, + InteractionContextType.bot_dm, + InteractionContextType.private_channel, + } + if guild_only: + self.guild_only: bool | None = guild_only + self.integration_types: set[IntegrationType] = integration_types or {IntegrationType.guild_install} + self.name_localizations: dict[str, str] = kwargs.get( "name_localizations", MISSING ) @@ -1218,6 +1285,19 @@ def __init__( def module(self) -> str | None: return self.__module__ + @property + def guild_only(self) -> bool: + warn_deprecated("guild_only", "contexts", "2.6") + return InteractionContextType.guild in self.contexts and len(self.contexts) == 1 + + @guild_only.setter + def guild_only(self, value: bool) -> None: + warn_deprecated("guild_only", "contexts", "2.6") + if value: + self.contexts = {InteractionContextType.guild} + else: + self.contexts = {InteractionContextType.guild, InteractionContextType.bot_dm, InteractionContextType.private_channel} + def to_dict(self) -> dict: as_dict = { "name": self.name, @@ -1232,9 +1312,6 @@ def to_dict(self) -> dict: if self.parent is not None: as_dict["type"] = self.input_type.value - if self.guild_only is not None: - as_dict["dm_permission"] = not self.guild_only - if self.nsfw is not None: as_dict["nsfw"] = self.nsfw @@ -1243,6 +1320,10 @@ def to_dict(self) -> dict: self.default_member_permissions.value ) + if not self.guild_ids: + as_dict["integration_types"] = [it.value for it in self.integration_types] + as_dict["contexts"] = [ctx.value for ctx in self.contexts] + return as_dict def add_command(self, command: SlashCommand) -> None: @@ -1476,6 +1557,9 @@ class ContextMenuCommand(ApplicationCommand): The ids of the guilds where this command will be registered. guild_only: :class:`bool` Whether the command should only be usable inside a guild. + + .. deprecated:: 2.6 + Use the ``contexts`` parameter instead. nsfw: :class:`bool` Whether the command should be restricted to 18+ channels and users. Apps intending to be listed in the App Directory cannot have NSFW commands. @@ -1496,6 +1580,10 @@ class ContextMenuCommand(ApplicationCommand): name_localizations: Dict[:class:`str`, :class:`str`] The name localizations for this command. The values of this should be ``"locale": "name"``. See `here `_ for a list of valid locales. + integration_types: set[:class:`IntegrationType`] + The installation contexts where this command is available. Cannot be set if this is a guild command. + contexts: set[:class:`InteractionContextType`] + The interaction contexts where this command is available. Cannot be set if this is a guild command. """ def __new__(cls, *args, **kwargs) -> ContextMenuCommand: diff --git a/discord/enums.py b/discord/enums.py index 98e46e646d..cf5dc73b41 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -1020,6 +1020,22 @@ class EntitlementOwnerType(Enum): user = 2 +class IntegrationType(Enum): + """The application's integration type""" + + guild_install = 1 + user_install = 2 + + +class InteractionContextType(Enum): + """The interaction's context type""" + + guild = 0 + bot_dm = 1 + private_channel = 2 + + + T = TypeVar("T") diff --git a/discord/interactions.py b/discord/interactions.py index 0e254d514a..a254e893d1 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -30,7 +30,7 @@ from . import utils from .channel import ChannelType, PartialMessageable, _threaded_channel_factory -from .enums import InteractionResponseType, InteractionType, try_enum +from .enums import InteractionResponseType, InteractionContextType, InteractionType, try_enum from .errors import ClientException, InteractionResponded, InvalidArgument from .file import File from .flags import MessageFlags @@ -131,7 +131,12 @@ class Interaction: The guilds preferred locale, if invoked in a guild. custom_id: Optional[:class:`str`] The custom ID for the interaction. + authorizing_integration_owners: Any + TODO + context: Optional[:class:`InteractionContextType`] + The context in which this command was executed. """ + # TODO: authorizing_integration_owners __slots__: tuple[str, ...] = ( "id", @@ -149,6 +154,7 @@ class Interaction: "version", "custom_id", "entitlements", + "context", "_channel_data", "_message_data", "_guild_data", @@ -188,6 +194,7 @@ def _from_data(self, data: InteractionPayload): self.entitlements: list[Entitlement] = [ Entitlement(data=e, state=self._state) for e in data.get("entitlements", []) ] + self.context: InteractionContextType | None = try_enum(InteractionContextType, data["context"]) if "context" in data else None self.message: Message | None = None self.channel = None diff --git a/discord/message.py b/discord/message.py index 5f4d3bc994..30c1db9d39 100644 --- a/discord/message.py +++ b/discord/message.py @@ -847,6 +847,7 @@ def __init__( self.interaction = MessageInteraction(data=data["interaction"], state=state) except KeyError: self.interaction = None + # TODO: deprecate and replace with interaction_metadata self.thread: Thread | None try: From 0017c3322898b7c42c369097b024f8d52ca64ec0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 23 Mar 2024 14:09:09 +0000 Subject: [PATCH 02/53] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/commands/core.py | 76 +++++++++++++++++++++++++++------------- discord/enums.py | 1 - discord/interactions.py | 14 ++++++-- 3 files changed, 64 insertions(+), 27 deletions(-) diff --git a/discord/commands/core.py b/discord/commands/core.py index 89fda9efb3..bbafcf5c1a 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -47,7 +47,13 @@ from ..channel import _threaded_guild_channel_factory from ..enums import Enum as DiscordEnum -from ..enums import IntegrationType, InteractionContextType, MessageType, SlashCommandOptionType, try_enum +from ..enums import ( + IntegrationType, + InteractionContextType, + MessageType, + SlashCommandOptionType, + try_enum, +) from ..errors import ( ApplicationCommandError, ApplicationCommandInvokeError, @@ -232,27 +238,31 @@ def __init__(self, func: Callable, **kwargs) -> None: integration_types = getattr( func, "__integration_types__", kwargs.get("integration_types", None) ) - contexts = getattr( - func, "__contexts__", kwargs.get("contexts", None) - ) - guild_only = getattr( - func, "__guild_only__", kwargs.get("guild_only", MISSING) - ) + contexts = getattr(func, "__contexts__", kwargs.get("contexts", None)) + guild_only = getattr(func, "__guild_only__", kwargs.get("guild_only", MISSING)) if guild_only is not MISSING: warn_deprecated("guild_only", "contexts", "2.6") if contexts and guild_only: - raise InvalidArgument("cannot pass both 'contexts' and 'guild_only' to ApplicationCommand") - if self.guild_ids and ((contexts is not None) or guild_only or integration_types): - raise InvalidArgument("the 'contexts' and 'integration_types' parameters are not available for guild commands") + raise InvalidArgument( + "cannot pass both 'contexts' and 'guild_only' to ApplicationCommand" + ) + if self.guild_ids and ( + (contexts is not None) or guild_only or integration_types + ): + raise InvalidArgument( + "the 'contexts' and 'integration_types' parameters are not available for guild commands" + ) self.contexts: set[InteractionContextType] = contexts or { - InteractionContextType.guild, - InteractionContextType.bot_dm, + InteractionContextType.guild, + InteractionContextType.bot_dm, InteractionContextType.private_channel, } if guild_only: self.guild_only: bool | None = guild_only - self.integration_types: set[IntegrationType] = integration_types or {IntegrationType.guild_install} + self.integration_types: set[IntegrationType] = integration_types or { + IntegrationType.guild_install + } def __repr__(self) -> str: return f"" @@ -308,7 +318,11 @@ def guild_only(self, value: bool) -> None: if value: self.contexts = {InteractionContextType.guild} else: - self.contexts = {InteractionContextType.guild, InteractionContextType.bot_dm, InteractionContextType.private_channel} + self.contexts = { + InteractionContextType.guild, + InteractionContextType.bot_dm, + InteractionContextType.private_channel, + } def _prepare_cooldowns(self, ctx: ApplicationContext): if self._buckets.valid: @@ -667,7 +681,7 @@ class SlashCommand(ApplicationCommand): Returns a string that allows you to mention the slash command. guild_only: :class:`bool` Whether the command should only be usable inside a guild. - + .. deprecated:: 2.6 Use the ``contexts`` parameter instead. nsfw: :class:`bool` @@ -1225,7 +1239,9 @@ def __init__( self.id = None # Permissions - self.default_member_permissions: Permissions | None = kwargs.get("default_member_permissions", None) + self.default_member_permissions: Permissions | None = kwargs.get( + "default_member_permissions", None + ) self.nsfw: bool | None = kwargs.get("nsfw", None) integration_types = kwargs.get("integration_types", None) @@ -1234,18 +1250,26 @@ def __init__( if guild_only is not MISSING: warn_deprecated("guild_only", "contexts", "2.6") if contexts and guild_only: - raise InvalidArgument("cannot pass both 'contexts' and 'guild_only' to ApplicationCommand") - if self.guild_ids and ((contexts is not None) or guild_only or integration_types): - raise InvalidArgument("the 'contexts' and 'integration_types' parameters are not available for guild commands") + raise InvalidArgument( + "cannot pass both 'contexts' and 'guild_only' to ApplicationCommand" + ) + if self.guild_ids and ( + (contexts is not None) or guild_only or integration_types + ): + raise InvalidArgument( + "the 'contexts' and 'integration_types' parameters are not available for guild commands" + ) self.contexts: set[InteractionContextType] = contexts or { - InteractionContextType.guild, - InteractionContextType.bot_dm, + InteractionContextType.guild, + InteractionContextType.bot_dm, InteractionContextType.private_channel, } if guild_only: self.guild_only: bool | None = guild_only - self.integration_types: set[IntegrationType] = integration_types or {IntegrationType.guild_install} + self.integration_types: set[IntegrationType] = integration_types or { + IntegrationType.guild_install + } self.name_localizations: dict[str, str] = kwargs.get( "name_localizations", MISSING @@ -1296,7 +1320,11 @@ def guild_only(self, value: bool) -> None: if value: self.contexts = {InteractionContextType.guild} else: - self.contexts = {InteractionContextType.guild, InteractionContextType.bot_dm, InteractionContextType.private_channel} + self.contexts = { + InteractionContextType.guild, + InteractionContextType.bot_dm, + InteractionContextType.private_channel, + } def to_dict(self) -> dict: as_dict = { @@ -1557,7 +1585,7 @@ class ContextMenuCommand(ApplicationCommand): The ids of the guilds where this command will be registered. guild_only: :class:`bool` Whether the command should only be usable inside a guild. - + .. deprecated:: 2.6 Use the ``contexts`` parameter instead. nsfw: :class:`bool` diff --git a/discord/enums.py b/discord/enums.py index cf5dc73b41..8bfa973592 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -1035,7 +1035,6 @@ class InteractionContextType(Enum): private_channel = 2 - T = TypeVar("T") diff --git a/discord/interactions.py b/discord/interactions.py index a254e893d1..d8c1b942af 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -30,7 +30,12 @@ from . import utils from .channel import ChannelType, PartialMessageable, _threaded_channel_factory -from .enums import InteractionResponseType, InteractionContextType, InteractionType, try_enum +from .enums import ( + InteractionContextType, + InteractionResponseType, + InteractionType, + try_enum, +) from .errors import ClientException, InteractionResponded, InvalidArgument from .file import File from .flags import MessageFlags @@ -136,6 +141,7 @@ class Interaction: context: Optional[:class:`InteractionContextType`] The context in which this command was executed. """ + # TODO: authorizing_integration_owners __slots__: tuple[str, ...] = ( @@ -194,7 +200,11 @@ def _from_data(self, data: InteractionPayload): self.entitlements: list[Entitlement] = [ Entitlement(data=e, state=self._state) for e in data.get("entitlements", []) ] - self.context: InteractionContextType | None = try_enum(InteractionContextType, data["context"]) if "context" in data else None + self.context: InteractionContextType | None = ( + try_enum(InteractionContextType, data["context"]) + if "context" in data + else None + ) self.message: Message | None = None self.channel = None From 3f0c5578b762c1f15ce450f9b7ffd26cccfdded3 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sat, 23 Mar 2024 18:07:29 -0700 Subject: [PATCH 03/53] add authorizing_integration_owners --- discord/interactions.py | 63 ++++++++++++++++++++++++++++++++--- discord/types/interactions.py | 9 +++++ 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/discord/interactions.py b/discord/interactions.py index d8c1b942af..648d2f9cee 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -136,14 +136,12 @@ class Interaction: The guilds preferred locale, if invoked in a guild. custom_id: Optional[:class:`str`] The custom ID for the interaction. - authorizing_integration_owners: Any - TODO + authorizing_integration_owners: :class:`AuthorizingIntegrationOwners` + Contains the entities that authorized this interaction. context: Optional[:class:`InteractionContextType`] The context in which this command was executed. """ - # TODO: authorizing_integration_owners - __slots__: tuple[str, ...] = ( "id", "type", @@ -161,6 +159,7 @@ class Interaction: "custom_id", "entitlements", "context", + "authorizing_integration_owners", "_channel_data", "_message_data", "_guild_data", @@ -200,6 +199,9 @@ def _from_data(self, data: InteractionPayload): self.entitlements: list[Entitlement] = [ Entitlement(data=e, state=self._state) for e in data.get("entitlements", []) ] + self.authorizing_integration_owners: AuthorizingIntegrationOwners = ( + AuthorizingIntegrationOwners(data=data.get("authorizing_integration_owners"), state=self._state) + ) self.context: InteractionContextType | None = ( try_enum(InteractionContextType, data["context"]) if "context" in data @@ -1436,3 +1438,56 @@ def __init__(self, *, data: MessageInteractionPayload, state: ConnectionState): self.type: InteractionType = data["type"] self.name: str = data["name"] self.user: User = self._state.store_user(data["user"]) + + +class AuthorizingIntegrationOwners: + """Contains details on the authorizing user or server for the installation(s) relevant to the interaction. + + Attributes + ---------- + user_id: :class:`int` | :class:`MISSING` + The ID of the user that authorized the integration. + guild_id: :class:`int` | None | :class:`MISSING` + The ID of the guild that authorized the integration. + This will be ``None`` if the integration was triggered + from the user in the bot's DMs. + """ + + __slots__ = ("user_id", "guild_id", "_state", "_cs_user", "_cs_guild") + + def __init__(self, data: dict[str, Any], state: ConnectionState): + self._state = state + self.user_id = int(uid) if (uid := data.get("user_id", MISSING)) is not MISSING else MISSING + if (guild_id := data.get("guild_id", MISSING)) == "0": + self.guild_id = None + else: + self.guild_id = int(guild_id) if guild_id is not MISSING else MISSING + + def __repr__(self): + return f"" + + def __eq__(self, other): + return ( + isinstance(other, AuthorizingIntegrationOwners) + and self.user_id == other.user_id + and self.guild_id == other.guild_id + ) + + def __ne__(self, other): + return not self.__eq__(other) + + @utils.cached_slot_property("_cs_user") + def user(self) -> User | None: + """Optional[:class:`User`]: The user that authorized the integration. + Returns ``None`` if the user is not in cache, or if :attr:`user_id` is :class:`MISSING`.""" + if not self.user_id: + return None + return self._state.get_user(self.user_id) + + @utils.cached_slot_property("_cs_guild") + def guild(self) -> Guild | None: + """Optional[:class:`Guild`]: The guild that authorized the integration. + Returns ``None`` if the guild is not in cache, or if :attr:`guild_id` is :class:`MISSING` or ``None``.""" + if not self.guild_id: + return None + return self._state._get_guild(self.guild_id) diff --git a/discord/types/interactions.py b/discord/types/interactions.py index 8ad0295b40..46fe0c8567 100644 --- a/discord/types/interactions.py +++ b/discord/types/interactions.py @@ -221,6 +221,8 @@ class Interaction(TypedDict): token: str version: int entitlements: list[Entitlement] + authorizing_integration_owners: AuthorizingIntegrationOwners + context: InteractionContextType class InteractionApplicationCommandCallbackData(TypedDict, total=False): @@ -253,3 +255,10 @@ class EditApplicationCommand(TypedDict): type: NotRequired[ApplicationCommandType] name: str default_permission: bool + + +InteractionContextType = Literal[0, 1, 2] +ApplicationIntegrationType = Literal[0, 1] +_StringApplicationIntegrationType = Literal["0", "1"] + +AuthorizingIntegrationOwners = dict[_StringApplicationIntegrationType, Snowflake] \ No newline at end of file From 1d33c1f35eaf41a92d9091001f940d6331acb7fa Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 24 Mar 2024 01:08:13 +0000 Subject: [PATCH 04/53] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/interactions.py | 16 ++++++++++++---- discord/types/interactions.py | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/discord/interactions.py b/discord/interactions.py index 648d2f9cee..e358aa466c 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -200,7 +200,9 @@ def _from_data(self, data: InteractionPayload): Entitlement(data=e, state=self._state) for e in data.get("entitlements", []) ] self.authorizing_integration_owners: AuthorizingIntegrationOwners = ( - AuthorizingIntegrationOwners(data=data.get("authorizing_integration_owners"), state=self._state) + AuthorizingIntegrationOwners( + data=data.get("authorizing_integration_owners"), state=self._state + ) ) self.context: InteractionContextType | None = ( try_enum(InteractionContextType, data["context"]) @@ -1457,7 +1459,11 @@ class AuthorizingIntegrationOwners: def __init__(self, data: dict[str, Any], state: ConnectionState): self._state = state - self.user_id = int(uid) if (uid := data.get("user_id", MISSING)) is not MISSING else MISSING + self.user_id = ( + int(uid) + if (uid := data.get("user_id", MISSING)) is not MISSING + else MISSING + ) if (guild_id := data.get("guild_id", MISSING)) == "0": self.guild_id = None else: @@ -1479,7 +1485,8 @@ def __ne__(self, other): @utils.cached_slot_property("_cs_user") def user(self) -> User | None: """Optional[:class:`User`]: The user that authorized the integration. - Returns ``None`` if the user is not in cache, or if :attr:`user_id` is :class:`MISSING`.""" + Returns ``None`` if the user is not in cache, or if :attr:`user_id` is :class:`MISSING`. + """ if not self.user_id: return None return self._state.get_user(self.user_id) @@ -1487,7 +1494,8 @@ def user(self) -> User | None: @utils.cached_slot_property("_cs_guild") def guild(self) -> Guild | None: """Optional[:class:`Guild`]: The guild that authorized the integration. - Returns ``None`` if the guild is not in cache, or if :attr:`guild_id` is :class:`MISSING` or ``None``.""" + Returns ``None`` if the guild is not in cache, or if :attr:`guild_id` is :class:`MISSING` or ``None``. + """ if not self.guild_id: return None return self._state._get_guild(self.guild_id) diff --git a/discord/types/interactions.py b/discord/types/interactions.py index 46fe0c8567..8a1a555037 100644 --- a/discord/types/interactions.py +++ b/discord/types/interactions.py @@ -261,4 +261,4 @@ class EditApplicationCommand(TypedDict): ApplicationIntegrationType = Literal[0, 1] _StringApplicationIntegrationType = Literal["0", "1"] -AuthorizingIntegrationOwners = dict[_StringApplicationIntegrationType, Snowflake] \ No newline at end of file +AuthorizingIntegrationOwners = dict[_StringApplicationIntegrationType, Snowflake] From 3195f5e47e0888a12eb351a46a9abe293dc08eef Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sat, 23 Mar 2024 18:28:26 -0700 Subject: [PATCH 05/53] add application_metadata --- discord/interactions.py | 92 ++++++++++++++++++++++++++++++++++- discord/message.py | 15 +++++- discord/types/interactions.py | 10 ++++ discord/types/message.py | 3 +- 4 files changed, 117 insertions(+), 3 deletions(-) diff --git a/discord/interactions.py b/discord/interactions.py index 648d2f9cee..cc1049fc96 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -80,6 +80,7 @@ from .threads import Thread from .types.interactions import Interaction as InteractionPayload from .types.interactions import InteractionData + from .types.interactions import InteractionMetadata as InteractionMetadataPayload from .types.interactions import MessageInteraction as MessageInteractionPayload from .ui.modal import Modal from .ui.view import View @@ -103,7 +104,7 @@ class Interaction: """Represents a Discord interaction. An interaction happens when a user does an action that needs to - be notified. Current examples are slash commands and components. + be notified. Current examples are application commands, components, and modals. .. versionadded:: 2.0 @@ -1412,6 +1413,10 @@ class MessageInteraction: .. versionadded:: 2.0 + .. deprecated:: 2.6 + + See :class:`InteractionMetadata`. + .. note:: Responses to message components do not include this property. @@ -1440,6 +1445,91 @@ def __init__(self, *, data: MessageInteractionPayload, state: ConnectionState): self.user: User = self._state.store_user(data["user"]) +class InteractionMetadata: + """Represents metadata about an interaction. + + This is sent on the message object when the message is related to an interaction + + .. versionadded:: 2.6 + + Attributes + ---------- + id: :class:`int` + The interaction's ID. + type: :class:`InteractionType` + The interaction type. + user_id: :class:`int` + The ID of the user that sent the interaction. + authorizing_integration_owners: :class:`AuthorizingIntegrationOwners` + The authorizing user or server for the installation(s) relevant to the interaction. + original_response_message_id: Optional[:class:`int`] + The ID of the original response message. Only present on interaction follow-up messages. + interacted_message_id: Optional[:class:`int`] + The ID of the message that triggered the interaction. Only present on interactions of type + :attr:`InteractionType.component`. + triggering_interaction_metadata: Optional[:class:`InteractionMetadata`] + The metadata of the interaction that opened the model. Only present on interactions of type + :attr:`InteractionType.modal_submit`. + """ + + __slots__: tuple[str, ...] = ( + "id", + "type", + "user_id", + "authorizing_integration_owners", + "original_response_message_id", + "interacted_message_id", + "triggering_interaction_metadata", + "_state", + "_cs_user", + "_cs_original_response_message", + "_cs_interacted_message", + ) + + def __init__(self, *, data: InteractionMetadataPayload, state: ConnectionState): + self._state = state + self.id: int = int(data["id"]) + self.type: InteractionType = try_enum(InteractionType, data["type"]) + self.user_id: int = int(data["user_id"]) + self.authorizing_integration_owners: AuthorizingIntegrationOwners = AuthorizingIntegrationOwners( + data["authorizing_integration_owners"], state + ) + self.original_response_message_id: int | None = utils._get_as_snowflake( + data, "original_response_message_id" + ) + self.interacted_message_id: int | None = utils._get_as_snowflake(data, "interacted_message_id") + self.triggering_interaction_metadata: InteractionMetadata | None = None + if tim := data.get("triggering_interaction_metadata"): + self.triggering_interaction_metadata = InteractionMetadata( + data=tim, state=state + ) + + def __repr__(self): + return f"" + + @utils.cached_slot_property("_cs_user") + def user(self) -> User | None: + """Optional[:class:`User`]: The user that sent the interaction. + Returns ``None`` if the user is not in cache.""" + return self._state.get_user(self.user_id) + + @utils.cached_slot_property("_cs_original_response_message") + def original_response_message(self) -> Message | None: + """Optional[:class:`Message`]: The original response message. + Returns ``None`` if the message is not in cache, or if :attr:`original_response_message_id` is ``None``.""" + if not self.original_response_message_id: + return None + return self._state._get_message(self.original_response_message_id) + + @utils.cached_slot_property("_cs_interacted_message") + def interacted_message(self) -> Message | None: + """Optional[:class:`Message`]: The message that triggered the interaction. + Returns ``None`` if the message is not in cache, or if :attr:`interacted_message_id` is ``None``.""" + if not self.interacted_message_id: + return None + return self._state._get_message(self.interacted_message_id) + + class AuthorizingIntegrationOwners: """Contains details on the authorizing user or server for the installation(s) relevant to the interaction. diff --git a/discord/message.py b/discord/message.py index 30c1db9d39..1c6084df4c 100644 --- a/discord/message.py +++ b/discord/message.py @@ -50,6 +50,7 @@ from .file import File from .flags import AttachmentFlags, MessageFlags from .guild import Guild +from .interactions import InteractionMetadata from .member import Member from .mixins import Hashable from .partial_emoji import PartialEmoji @@ -722,6 +723,14 @@ class Message(Hashable): The guild that the message belongs to, if applicable. interaction: Optional[:class:`MessageInteraction`] The interaction associated with the message, if applicable. + + .. deprecated:: 2.6 + + Use :attr:`interaction_metadata` instead. + interaction_metadata: Optional[:class:`InteractionMetadata`] + The interaction metadata associated with the message, if applicable. + + .. versionadded:: 2.6 thread: Optional[:class:`Thread`] The thread created from this message, if applicable. @@ -760,6 +769,7 @@ class Message(Hashable): "components", "guild", "interaction", + "interaction_metadata", "thread", ) @@ -847,7 +857,10 @@ def __init__( self.interaction = MessageInteraction(data=data["interaction"], state=state) except KeyError: self.interaction = None - # TODO: deprecate and replace with interaction_metadata + try: + self.interaction_metadata = InteractionMetadata(data=data["interaction_metadata"], state=state) + except KeyError: + self.interaction_metadata = None self.thread: Thread | None try: diff --git a/discord/types/interactions.py b/discord/types/interactions.py index 46fe0c8567..89d5c5c698 100644 --- a/discord/types/interactions.py +++ b/discord/types/interactions.py @@ -225,6 +225,16 @@ class Interaction(TypedDict): context: InteractionContextType +class InteractionMetadata(TypedDict): + id: Snowflake + type: InteractionType + user_id: Snowflake + authorizing_integration_owners: AuthorizingIntegrationOwners + original_response_message_id: NotRequired[Snowflake] + interacted_message_id: NotRequired[Snowflake] + triggering_interaction_metadata: NotRequired[InteractionMetadata] + + class InteractionApplicationCommandCallbackData(TypedDict, total=False): tts: bool content: str diff --git a/discord/types/message.py b/discord/types/message.py index 10d819ebd4..f35acfd759 100644 --- a/discord/types/message.py +++ b/discord/types/message.py @@ -38,7 +38,7 @@ from .user import User if TYPE_CHECKING: - from .interactions import MessageInteraction + from .interactions import InteractionMetadata, MessageInteraction from .._typed_dict import NotRequired, TypedDict @@ -120,6 +120,7 @@ class Message(TypedDict): sticker_items: NotRequired[list[StickerItem]] referenced_message: NotRequired[Message | None] interaction: NotRequired[MessageInteraction] + interaction_metadata: NotRequired[InteractionMetadata] components: NotRequired[list[Component]] thread: NotRequired[Thread | None] id: Snowflake From 02df055b4b7bd4e8a7674aafd2fab5526981671f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 24 Mar 2024 01:29:05 +0000 Subject: [PATCH 06/53] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/interactions.py | 17 +++++++++++------ discord/message.py | 4 +++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/discord/interactions.py b/discord/interactions.py index 913399a063..8a0f57cc67 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -1493,13 +1493,15 @@ def __init__(self, *, data: InteractionMetadataPayload, state: ConnectionState): self.id: int = int(data["id"]) self.type: InteractionType = try_enum(InteractionType, data["type"]) self.user_id: int = int(data["user_id"]) - self.authorizing_integration_owners: AuthorizingIntegrationOwners = AuthorizingIntegrationOwners( - data["authorizing_integration_owners"], state + self.authorizing_integration_owners: AuthorizingIntegrationOwners = ( + AuthorizingIntegrationOwners(data["authorizing_integration_owners"], state) ) self.original_response_message_id: int | None = utils._get_as_snowflake( data, "original_response_message_id" ) - self.interacted_message_id: int | None = utils._get_as_snowflake(data, "interacted_message_id") + self.interacted_message_id: int | None = utils._get_as_snowflake( + data, "interacted_message_id" + ) self.triggering_interaction_metadata: InteractionMetadata | None = None if tim := data.get("triggering_interaction_metadata"): self.triggering_interaction_metadata = InteractionMetadata( @@ -1512,13 +1514,15 @@ def __repr__(self): @utils.cached_slot_property("_cs_user") def user(self) -> User | None: """Optional[:class:`User`]: The user that sent the interaction. - Returns ``None`` if the user is not in cache.""" + Returns ``None`` if the user is not in cache. + """ return self._state.get_user(self.user_id) @utils.cached_slot_property("_cs_original_response_message") def original_response_message(self) -> Message | None: """Optional[:class:`Message`]: The original response message. - Returns ``None`` if the message is not in cache, or if :attr:`original_response_message_id` is ``None``.""" + Returns ``None`` if the message is not in cache, or if :attr:`original_response_message_id` is ``None``. + """ if not self.original_response_message_id: return None return self._state._get_message(self.original_response_message_id) @@ -1526,7 +1530,8 @@ def original_response_message(self) -> Message | None: @utils.cached_slot_property("_cs_interacted_message") def interacted_message(self) -> Message | None: """Optional[:class:`Message`]: The message that triggered the interaction. - Returns ``None`` if the message is not in cache, or if :attr:`interacted_message_id` is ``None``.""" + Returns ``None`` if the message is not in cache, or if :attr:`interacted_message_id` is ``None``. + """ if not self.interacted_message_id: return None return self._state._get_message(self.interacted_message_id) diff --git a/discord/message.py b/discord/message.py index 1c6084df4c..a7cc894559 100644 --- a/discord/message.py +++ b/discord/message.py @@ -858,7 +858,9 @@ def __init__( except KeyError: self.interaction = None try: - self.interaction_metadata = InteractionMetadata(data=data["interaction_metadata"], state=state) + self.interaction_metadata = InteractionMetadata( + data=data["interaction_metadata"], state=state + ) except KeyError: self.interaction_metadata = None From e43dc62ff9f16c44611e8e3a6e5b9c651211c900 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sat, 23 Mar 2024 18:31:11 -0700 Subject: [PATCH 07/53] don't trust copilot --- discord/interactions.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/discord/interactions.py b/discord/interactions.py index 913399a063..e36069b485 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -1549,12 +1549,13 @@ class AuthorizingIntegrationOwners: def __init__(self, data: dict[str, Any], state: ConnectionState): self._state = state + # keys are Application Integration Types as strings self.user_id = ( int(uid) - if (uid := data.get("user_id", MISSING)) is not MISSING + if (uid := data.get("1", MISSING)) is not MISSING else MISSING ) - if (guild_id := data.get("guild_id", MISSING)) == "0": + if (guild_id := data.get("0", MISSING)) == "0": self.guild_id = None else: self.guild_id = int(guild_id) if guild_id is not MISSING else MISSING From bb7f2b8286118aa645952a2295ca25c6cc2efa17 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 24 Mar 2024 01:31:53 +0000 Subject: [PATCH 08/53] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/interactions.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/discord/interactions.py b/discord/interactions.py index e9198e7da8..66390766ef 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -1556,9 +1556,7 @@ def __init__(self, data: dict[str, Any], state: ConnectionState): self._state = state # keys are Application Integration Types as strings self.user_id = ( - int(uid) - if (uid := data.get("1", MISSING)) is not MISSING - else MISSING + int(uid) if (uid := data.get("1", MISSING)) is not MISSING else MISSING ) if (guild_id := data.get("0", MISSING)) == "0": self.guild_id = None From 350b8ae8cc84ab7731aab2e325a5aca85ac2b838 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sat, 23 Mar 2024 18:34:29 -0700 Subject: [PATCH 09/53] update __all__ --- discord/enums.py | 2 ++ discord/interactions.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/discord/enums.py b/discord/enums.py index 8bfa973592..3ed5d35f51 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -74,6 +74,8 @@ "SKUType", "EntitlementType", "EntitlementOwnerType", + "IntegrationType", + "InteractionContextType", ) diff --git a/discord/interactions.py b/discord/interactions.py index e9198e7da8..526d7b90b6 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -57,6 +57,8 @@ "InteractionMessage", "InteractionResponse", "MessageInteraction", + "InteractionMetadata", + "AuthorizingIntegrationOwners", ) if TYPE_CHECKING: From 800f5cf80a108abb4a47e0fb5df457608a4ac7d9 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sat, 23 Mar 2024 18:37:46 -0700 Subject: [PATCH 10/53] circular import --- discord/message.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/discord/message.py b/discord/message.py index a7cc894559..19c40305e0 100644 --- a/discord/message.py +++ b/discord/message.py @@ -50,7 +50,6 @@ from .file import File from .flags import AttachmentFlags, MessageFlags from .guild import Guild -from .interactions import InteractionMetadata from .member import Member from .mixins import Hashable from .partial_emoji import PartialEmoji @@ -850,7 +849,7 @@ def __init__( # the channel will be the correct type here ref.resolved = self.__class__(channel=chan, data=resolved, state=state) # type: ignore - from .interactions import MessageInteraction + from .interactions import MessageInteraction, InteractionMetadata self.interaction: MessageInteraction | None try: From 56208b1dad31e1871793f991b511bbced7926618 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 24 Mar 2024 01:38:06 +0000 Subject: [PATCH 11/53] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/message.py b/discord/message.py index 19c40305e0..8078868995 100644 --- a/discord/message.py +++ b/discord/message.py @@ -849,7 +849,7 @@ def __init__( # the channel will be the correct type here ref.resolved = self.__class__(channel=chan, data=resolved, state=state) # type: ignore - from .interactions import MessageInteraction, InteractionMetadata + from .interactions import InteractionMetadata, MessageInteraction self.interaction: MessageInteraction | None try: From 05245bd6abc0f4c7e0468e089d6310e78177ebd2 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sat, 23 Mar 2024 18:49:00 -0700 Subject: [PATCH 12/53] fix numbers --- discord/enums.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/enums.py b/discord/enums.py index 3ed5d35f51..1521a41aee 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -1025,8 +1025,8 @@ class EntitlementOwnerType(Enum): class IntegrationType(Enum): """The application's integration type""" - guild_install = 1 - user_install = 2 + guild_install = 0 + user_install = 1 class InteractionContextType(Enum): From 765eeee40473b9e03c74f11b85dd9f27d801ace3 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sat, 23 Mar 2024 19:31:40 -0700 Subject: [PATCH 13/53] h --- discord/interactions.py | 6 +++--- discord/state.py | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/discord/interactions.py b/discord/interactions.py index 1a950b9aea..44270b018e 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -204,8 +204,8 @@ def _from_data(self, data: InteractionPayload): ] self.authorizing_integration_owners: AuthorizingIntegrationOwners = ( AuthorizingIntegrationOwners( - data=data.get("authorizing_integration_owners"), state=self._state - ) + data=data["authorizing_integration_owners"], state=self._state + ) if "authorizing_integration_owners" in data else AuthorizingIntegrationOwners(data={}, state=self._state) ) self.context: InteractionContextType | None = ( try_enum(InteractionContextType, data["context"]) @@ -222,7 +222,7 @@ def _from_data(self, data: InteractionPayload): self._guild: Guild | None = None self._guild_data = data.get("guild") if self.guild is None and self._guild_data: - self._guild = Guild(data=self._guild_data, state=self) + self._guild = Guild(data=self._guild_data, state=self._state) # TODO: there's a potential data loss here if self.guild_id: diff --git a/discord/state.py b/discord/state.py index 5b9aea0e35..04cf5bae60 100644 --- a/discord/state.py +++ b/discord/state.py @@ -43,8 +43,6 @@ Union, ) -import discord - from . import utils from .activity import BaseActivity from .audit_logs import AuditLogEntry From 996e836bb7aa4503d448a20b4888644188fdd088 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 24 Mar 2024 02:32:09 +0000 Subject: [PATCH 14/53] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/interactions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/discord/interactions.py b/discord/interactions.py index 44270b018e..7cba5b3c8e 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -205,7 +205,9 @@ def _from_data(self, data: InteractionPayload): self.authorizing_integration_owners: AuthorizingIntegrationOwners = ( AuthorizingIntegrationOwners( data=data["authorizing_integration_owners"], state=self._state - ) if "authorizing_integration_owners" in data else AuthorizingIntegrationOwners(data={}, state=self._state) + ) + if "authorizing_integration_owners" in data + else AuthorizingIntegrationOwners(data={}, state=self._state) ) self.context: InteractionContextType | None = ( try_enum(InteractionContextType, data["context"]) From 60fdfd491f0be297efe3eae0034e95d5dd66214d Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sat, 23 Mar 2024 19:37:11 -0700 Subject: [PATCH 15/53] update guild_only deco to use contexts --- discord/commands/permissions.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/discord/commands/permissions.py b/discord/commands/permissions.py index df951ae001..89332b50ce 100644 --- a/discord/commands/permissions.py +++ b/discord/commands/permissions.py @@ -25,6 +25,7 @@ from typing import Callable +from ..enums import InteractionContextType from ..permissions import Permissions from .core import ApplicationCommand @@ -98,9 +99,9 @@ async def test(ctx): def inner(command: Callable): if isinstance(command, ApplicationCommand): - command.guild_only = True + command.contexts = {InteractionContextType.guild} else: - command.__guild_only__ = True + command.__contexts__ = {InteractionContextType.guild} return command From 0ca59505c74bf9db8b1932cde171c28d94d6e4ba Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sat, 23 Mar 2024 19:39:11 -0700 Subject: [PATCH 16/53] type --- discord/types/interactions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/types/interactions.py b/discord/types/interactions.py index 1d44c830a9..c54d3fe341 100644 --- a/discord/types/interactions.py +++ b/discord/types/interactions.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Literal, Union +from typing import Dict, TYPE_CHECKING, Literal, Union from ..permissions import Permissions from .channel import ChannelType @@ -271,4 +271,4 @@ class EditApplicationCommand(TypedDict): ApplicationIntegrationType = Literal[0, 1] _StringApplicationIntegrationType = Literal["0", "1"] -AuthorizingIntegrationOwners = dict[_StringApplicationIntegrationType, Snowflake] +AuthorizingIntegrationOwners = Dict[_StringApplicationIntegrationType, Snowflake] From 6fa2af5b7abe8d26cba882ee71530a5ca8fa6080 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 24 Mar 2024 02:39:32 +0000 Subject: [PATCH 17/53] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/types/interactions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/types/interactions.py b/discord/types/interactions.py index c54d3fe341..2c7fd520ab 100644 --- a/discord/types/interactions.py +++ b/discord/types/interactions.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import Dict, TYPE_CHECKING, Literal, Union +from typing import TYPE_CHECKING, Dict, Literal, Union from ..permissions import Permissions from .channel import ChannelType From babf68a94d291c143c210207c7a36dc6df248d96 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sat, 23 Mar 2024 19:53:04 -0700 Subject: [PATCH 18/53] deprecation warnings --- discord/commands/core.py | 26 ++++++++++++++++++-------- discord/message.py | 19 +++++++++++++++---- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/discord/commands/core.py b/discord/commands/core.py index bbafcf5c1a..b34f7a3e2f 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -309,12 +309,22 @@ def callback( @property def guild_only(self) -> bool: - warn_deprecated("guild_only", "contexts", "2.6") + warn_deprecated( + "guild_only", + "contexts", + "2.6", + reference="https://discord.com/developers/docs/change-log#userinstallable-apps-preview", + ) return InteractionContextType.guild in self.contexts and len(self.contexts) == 1 @guild_only.setter def guild_only(self, value: bool) -> None: - warn_deprecated("guild_only", "contexts", "2.6") + warn_deprecated( + "guild_only", + "contexts", + "2.6", + reference="https://discord.com/developers/docs/change-log#userinstallable-apps-preview", + ) if value: self.contexts = {InteractionContextType.guild} else: @@ -707,9 +717,9 @@ class SlashCommand(ApplicationCommand): description_localizations: Dict[:class:`str`, :class:`str`] The description localizations for this command. The values of this should be ``"locale": "description"``. See `here `_ for a list of valid locales. - integration_types: set[:class:`IntegrationType`] + integration_types: Set[:class:`IntegrationType`] The installation contexts where this command is available. Cannot be set if this is a guild command. - contexts: set[:class:`InteractionContextType`] + contexts: Set[:class:`InteractionContextType`] The interaction contexts where this command is available. Cannot be set if this is a guild command. """ @@ -1179,9 +1189,9 @@ class SlashCommandGroup(ApplicationCommand): description_localizations: Dict[:class:`str`, :class:`str`] The description localizations for this command. The values of this should be ``"locale": "description"``. See `here `_ for a list of valid locales. - integration_types: set[:class:`IntegrationType`] + integration_types: Set[:class:`IntegrationType`] The installation contexts where this command is available. Cannot be set if this is a guild command. - contexts: set[:class:`InteractionContextType`] + contexts: Set[:class:`InteractionContextType`] The interaction contexts where this command is available. Cannot be set if this is a guild command. """ @@ -1608,9 +1618,9 @@ class ContextMenuCommand(ApplicationCommand): name_localizations: Dict[:class:`str`, :class:`str`] The name localizations for this command. The values of this should be ``"locale": "name"``. See `here `_ for a list of valid locales. - integration_types: set[:class:`IntegrationType`] + integration_types: Set[:class:`IntegrationType`] The installation contexts where this command is available. Cannot be set if this is a guild command. - contexts: set[:class:`InteractionContextType`] + contexts: Set[:class:`InteractionContextType`] The interaction contexts where this command is available. Cannot be set if this is a guild command. """ diff --git a/discord/message.py b/discord/message.py index 8078868995..47e804909b 100644 --- a/discord/message.py +++ b/discord/message.py @@ -67,6 +67,7 @@ ) from .channel import TextChannel from .components import Component + from .interactions import MessageInteraction from .mentions import AllowedMentions from .role import Role from .state import ConnectionState @@ -767,7 +768,7 @@ class Message(Hashable): "stickers", "components", "guild", - "interaction", + "_interaction", "interaction_metadata", "thread", ) @@ -851,11 +852,11 @@ def __init__( from .interactions import InteractionMetadata, MessageInteraction - self.interaction: MessageInteraction | None + self._interaction: MessageInteraction | None try: - self.interaction = MessageInteraction(data=data["interaction"], state=state) + self._interaction = MessageInteraction(data=data["interaction"], state=state) except KeyError: - self.interaction = None + self._interaction = None try: self.interaction_metadata = InteractionMetadata( data=data["interaction_metadata"], state=state @@ -1054,6 +1055,16 @@ def _rebind_cached_references( self.guild = new_guild self.channel = new_channel + @property + def interaction(self) -> MessageInteraction | None: + utils.warn_deprecated( + "interaction", + "interaction_metadata", + "2.6", + reference="https://discord.com/developers/docs/change-log#userinstallable-apps-preview", + ) + return self._interaction + @utils.cached_slot_property("_cs_raw_mentions") def raw_mentions(self) -> list[int]: """A property that returns an array of user IDs matched with From 0f570c9721101ce20f0f455b4ea01ff04cfba1e4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 24 Mar 2024 02:53:42 +0000 Subject: [PATCH 19/53] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/message.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/discord/message.py b/discord/message.py index 47e804909b..d6ed434873 100644 --- a/discord/message.py +++ b/discord/message.py @@ -854,7 +854,9 @@ def __init__( self._interaction: MessageInteraction | None try: - self._interaction = MessageInteraction(data=data["interaction"], state=state) + self._interaction = MessageInteraction( + data=data["interaction"], state=state + ) except KeyError: self._interaction = None try: From 699ed6d8cce0f79e6fa5bef9a04a038a104d56c8 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sat, 23 Mar 2024 19:54:41 -0700 Subject: [PATCH 20/53] docs --- docs/api/enums.rst | 35 +++++++++++++++++++++++++++++++++++ docs/api/models.rst | 10 ++++++++++ 2 files changed, 45 insertions(+) diff --git a/docs/api/enums.rst b/docs/api/enums.rst index 0500e89318..d5113265d5 100644 --- a/docs/api/enums.rst +++ b/docs/api/enums.rst @@ -2402,3 +2402,38 @@ of :class:`enum.Enum`. .. attribute:: user Entitlement is owned by a user. + + +.. class:: IntegrationType + + The integration type for an application. + + .. versionadded:: 2.6 + + .. attribute:: guild_install + + The integration is added to a guild. + + .. attribute:: user_install + + The integration is added to a user account. + + + +.. class:: InteractionContextType + + The context an interaction occurs in. + + .. versionadded:: 2.6 + + .. attribute:: guild + + The interaction is in a guild. + + .. attribute:: bot_dm + + The interaction is in the bot's own DM channel. + + .. attribute:: private_channel + + The interaction is in a private DM or group DM channel. diff --git a/docs/api/models.rst b/docs/api/models.rst index 5fec2f0dd0..bea6b3c0de 100644 --- a/docs/api/models.rst +++ b/docs/api/models.rst @@ -353,6 +353,16 @@ Interactions .. autoclass:: MessageInteraction() :members: +.. attributetable:: InteractionMetadata + +.. autoclass:: InteractionMetadata() + :members: + +.. attributetable:: AuthorizingIntegrationOwners + +.. autoclass:: AuthorizingIntegrationOwners() + :members: + .. attributetable:: Component .. autoclass:: Component() From 030ff56d9cd3231f9e847d880e023586da6f78f9 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sat, 23 Mar 2024 20:03:35 -0700 Subject: [PATCH 21/53] example --- examples/app_commands/slash_users.py | 33 ++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 examples/app_commands/slash_users.py diff --git a/examples/app_commands/slash_users.py b/examples/app_commands/slash_users.py new file mode 100644 index 0000000000..1286b1b183 --- /dev/null +++ b/examples/app_commands/slash_users.py @@ -0,0 +1,33 @@ +import discord + +# debug_guilds must not be set if we want to set contexts and integration_types on commands +bot = discord.Bot() + + +@bot.slash_command( + # Can only be used in private messages + contexts={discord.InteractionContextType.private_channel}, + # Can only be used if the bot is installed to your user account, + # if left blank it can only be used when added to guilds + integration_types={discord.IntegrationType.user_install}, +) +async def greet(ctx: discord.ApplicationContext, user: discord.User): + await ctx.respond(f"Hello, {user}!") + + +@bot.slash_command( + # This command can be used by guild members, but also by users anywhere if they install it + integration_types={discord.IntegrationType.guild_install, discord.IntegrationType.user_install}, +) +async def say_hello(ctx: discord.ApplicationContext): + await ctx.respond(f"Hello!") + +""" +If a bot is not installed to a guild, and a user uses a command there, +the response will allways be ephemeral if the guild has more than 25 members. + +This is a Discord limitation, and is subject to change. +""" + + +bot.run("TOKEN") From b460a896131a908ba1e6f35dec6a4b4da0943c4c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 24 Mar 2024 03:03:56 +0000 Subject: [PATCH 22/53] style(pre-commit): auto fixes from pre-commit.com hooks --- examples/app_commands/slash_users.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/app_commands/slash_users.py b/examples/app_commands/slash_users.py index 1286b1b183..db65b77e4d 100644 --- a/examples/app_commands/slash_users.py +++ b/examples/app_commands/slash_users.py @@ -17,13 +17,17 @@ async def greet(ctx: discord.ApplicationContext, user: discord.User): @bot.slash_command( # This command can be used by guild members, but also by users anywhere if they install it - integration_types={discord.IntegrationType.guild_install, discord.IntegrationType.user_install}, + integration_types={ + discord.IntegrationType.guild_install, + discord.IntegrationType.user_install, + }, ) async def say_hello(ctx: discord.ApplicationContext): await ctx.respond(f"Hello!") + """ -If a bot is not installed to a guild, and a user uses a command there, +If a bot is not installed to a guild, and a user uses a command there, the response will allways be ephemeral if the guild has more than 25 members. This is a Discord limitation, and is subject to change. From 68b4040ce3cc11e30df7294d39304e4fbbcd7abd Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sat, 23 Mar 2024 20:08:40 -0700 Subject: [PATCH 23/53] edit docs --- discord/commands/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/commands/core.py b/discord/commands/core.py index b34f7a3e2f..3e4daff09c 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -693,7 +693,7 @@ class SlashCommand(ApplicationCommand): Whether the command should only be usable inside a guild. .. deprecated:: 2.6 - Use the ``contexts`` parameter instead. + Use the :attr:`contexts` parameter instead. nsfw: :class:`bool` Whether the command should be restricted to 18+ channels and users. Apps intending to be listed in the App Directory cannot have NSFW commands. @@ -1170,7 +1170,7 @@ class SlashCommandGroup(ApplicationCommand): Whether the command should only be usable inside a guild. .. deprecated:: 2.6 - Use the ``contexts`` parameter instead. + Use the :attr:`contexts` parameter instead. nsfw: :class:`bool` Whether the command should be restricted to 18+ channels and users. Apps intending to be listed in the App Directory cannot have NSFW commands. From 76a48c0b468945730ba7cca752929ce62313fa90 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sat, 23 Mar 2024 20:17:02 -0700 Subject: [PATCH 24/53] update changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbf54d9c22..951579e1df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ These changes are available on the `master` branch, but have not yet been releas ([#2396](https://github.com/Pycord-Development/pycord/pull/2396)) - Added `user` argument to `Paginator.edit`. ([#2390](https://github.com/Pycord-Development/pycord/pull/2390)) +- Added support for user-installable applications. + ((#2409)[https://github.com/Pycord-Development/pycord/pull/2409]) ### Fixed @@ -32,6 +34,8 @@ These changes are available on the `master` branch, but have not yet been releas ([#2400](https://github.com/Pycord-Development/pycord/pull/2400)) - Fixed `ScheduledEvent.subscribers` behavior with `limit=None`. ([#2407](https://github.com/Pycord-Development/pycord/pull/2407)) +- Fixed an issue with `Interaction` that would cause bots to crash if a guild was not cached. + ([#2409](https://github.com/Pycord-Development/pycord/pull/2409)) ### Changed @@ -39,6 +43,10 @@ These changes are available on the `master` branch, but have not yet been releas ([#2387](https://github.com/Pycord-Development/pycord/pull/2387)) - HTTP requests that fail with a 503 status are now re-tried. ([#2395](https://github.com/Pycord-Development/pycord/pull/2395)) +- `ApplicationCommand.guild_only` is now deprecated in favor of `ApplicationCommand.contexts`. + ([#2409](https://github.com/Pycord-Development/pycord/pull/2409)) +- `Message.interaction` is now deprecated in favor of `Message.interaction_metadata`. + ([#2409](https://github.com/Pycord-Development/pycord/pull/2409)) ## [2.5.0] - 2024-03-02 From eb3ae92960202722aa5ed9bc40ac7577b8478c37 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 24 Mar 2024 03:17:23 +0000 Subject: [PATCH 25/53] style(pre-commit): auto fixes from pre-commit.com hooks --- CHANGELOG.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 951579e1df..91c95d7ce7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,8 +34,8 @@ These changes are available on the `master` branch, but have not yet been releas ([#2400](https://github.com/Pycord-Development/pycord/pull/2400)) - Fixed `ScheduledEvent.subscribers` behavior with `limit=None`. ([#2407](https://github.com/Pycord-Development/pycord/pull/2407)) -- Fixed an issue with `Interaction` that would cause bots to crash if a guild was not cached. - ([#2409](https://github.com/Pycord-Development/pycord/pull/2409)) +- Fixed an issue with `Interaction` that would cause bots to crash if a guild was not + cached. ([#2409](https://github.com/Pycord-Development/pycord/pull/2409)) ### Changed @@ -43,7 +43,8 @@ These changes are available on the `master` branch, but have not yet been releas ([#2387](https://github.com/Pycord-Development/pycord/pull/2387)) - HTTP requests that fail with a 503 status are now re-tried. ([#2395](https://github.com/Pycord-Development/pycord/pull/2395)) -- `ApplicationCommand.guild_only` is now deprecated in favor of `ApplicationCommand.contexts`. +- `ApplicationCommand.guild_only` is now deprecated in favor of + `ApplicationCommand.contexts`. ([#2409](https://github.com/Pycord-Development/pycord/pull/2409)) - `Message.interaction` is now deprecated in favor of `Message.interaction_metadata`. ([#2409](https://github.com/Pycord-Development/pycord/pull/2409)) From bf4ae8e7d717bd763cfc29f8a3669d320dadc2ca Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sat, 23 Mar 2024 20:18:01 -0700 Subject: [PATCH 26/53] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 951579e1df..9f8f49a273 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ These changes are available on the `master` branch, but have not yet been releas - Added `user` argument to `Paginator.edit`. ([#2390](https://github.com/Pycord-Development/pycord/pull/2390)) - Added support for user-installable applications. - ((#2409)[https://github.com/Pycord-Development/pycord/pull/2409]) + ([#2409](https://github.com/Pycord-Development/pycord/pull/2409)) ### Fixed From aa1249b23261c8d241238d2128b6a58fa6ee32d2 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sun, 24 Mar 2024 12:46:54 -0700 Subject: [PATCH 27/53] add default contexts and integration_types values --- discord/bot.py | 25 ++++++++++++++++++++++++- discord/commands/core.py | 11 +++-------- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/discord/bot.py b/discord/bot.py index 7d561b52cc..537bc75b37 100644 --- a/discord/bot.py +++ b/discord/bot.py @@ -48,7 +48,7 @@ UserCommand, command, ) -from .enums import InteractionType +from .enums import InteractionType, InteractionContextType, IntegrationType from .errors import CheckFailure, DiscordException from .interactions import Interaction from .shard import AutoShardedClient @@ -125,6 +125,10 @@ def add_application_command(self, command: ApplicationCommand) -> None: if self._bot.debug_guilds and command.guild_ids is None: command.guild_ids = self._bot.debug_guilds + if self._bot.default_command_contexts and command.contexts is None: + command.contexts = self._bot.default_command_contexts + if self._bot.default_command_integration_types and command.integration_types is None: + command.integration_types = self._bot.default_command_integration_types for cmd in self.pending_application_commands: if cmd == command: @@ -1157,6 +1161,14 @@ def __init__(self, description=None, *args, **options): self.auto_sync_commands = options.get("auto_sync_commands", True) self.debug_guilds = options.pop("debug_guilds", None) + self.default_command_contexts = set(options.pop("default_command_contexts", { + InteractionContextType.guild, + InteractionContextType.bot_dm, + InteractionContextType.private_channel, + })) + self.default_command_integration_types = set(options.pop("default_command_integration_types", { + IntegrationType.guild_install, + })) if self.owner_id and self.owner_ids: raise TypeError("Both owner_id and owner_ids are set.") @@ -1447,6 +1459,17 @@ class Bot(BotBase, Client): :attr:`.process_application_commands` if the command is not found. Defaults to ``True``. .. versionadded:: 2.0 + default_command_contexts: Set[:class:`InteractionContextType`] + The default context types that the bot will use for commands. + Defaults to a set containing :attr:`InteractionContextType.guild`, :attr:`InteractionContextType.bot_dm`, and + :attr:`InteractionContextType.private_channel`. + + .. versionadded:: 2.6 + default_command_integration_types: Set[:class:`IntegrationType`] + The default integration types that the bot will use for commands. + Defaults to a set containing :attr:`IntegrationType.guild_install`. + + .. versionadded:: 2.6 """ @property diff --git a/discord/commands/core.py b/discord/commands/core.py index 3e4daff09c..a304cd299f 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -1270,16 +1270,11 @@ def __init__( "the 'contexts' and 'integration_types' parameters are not available for guild commands" ) - self.contexts: set[InteractionContextType] = contexts or { - InteractionContextType.guild, - InteractionContextType.bot_dm, - InteractionContextType.private_channel, - } + # These are set to None and their defaults are then set when added to the bot + self.contexts: set[InteractionContextType] | None = contexts if guild_only: self.guild_only: bool | None = guild_only - self.integration_types: set[IntegrationType] = integration_types or { - IntegrationType.guild_install - } + self.integration_types: set[IntegrationType] | None = integration_types self.name_localizations: dict[str, str] = kwargs.get( "name_localizations", MISSING From e0069359835fcb633179f10558c9776b71aed036 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 24 Mar 2024 19:47:27 +0000 Subject: [PATCH 28/53] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/bot.py | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/discord/bot.py b/discord/bot.py index 537bc75b37..380b431725 100644 --- a/discord/bot.py +++ b/discord/bot.py @@ -48,7 +48,7 @@ UserCommand, command, ) -from .enums import InteractionType, InteractionContextType, IntegrationType +from .enums import IntegrationType, InteractionContextType, InteractionType from .errors import CheckFailure, DiscordException from .interactions import Interaction from .shard import AutoShardedClient @@ -127,7 +127,10 @@ def add_application_command(self, command: ApplicationCommand) -> None: command.guild_ids = self._bot.debug_guilds if self._bot.default_command_contexts and command.contexts is None: command.contexts = self._bot.default_command_contexts - if self._bot.default_command_integration_types and command.integration_types is None: + if ( + self._bot.default_command_integration_types + and command.integration_types is None + ): command.integration_types = self._bot.default_command_integration_types for cmd in self.pending_application_commands: @@ -1161,14 +1164,24 @@ def __init__(self, description=None, *args, **options): self.auto_sync_commands = options.get("auto_sync_commands", True) self.debug_guilds = options.pop("debug_guilds", None) - self.default_command_contexts = set(options.pop("default_command_contexts", { - InteractionContextType.guild, - InteractionContextType.bot_dm, - InteractionContextType.private_channel, - })) - self.default_command_integration_types = set(options.pop("default_command_integration_types", { - IntegrationType.guild_install, - })) + self.default_command_contexts = set( + options.pop( + "default_command_contexts", + { + InteractionContextType.guild, + InteractionContextType.bot_dm, + InteractionContextType.private_channel, + }, + ) + ) + self.default_command_integration_types = set( + options.pop( + "default_command_integration_types", + { + IntegrationType.guild_install, + }, + ) + ) if self.owner_id and self.owner_ids: raise TypeError("Both owner_id and owner_ids are set.") From c4d6a0becf77b8d8b91c7130da804d47e8d2de06 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sun, 24 Mar 2024 13:11:06 -0700 Subject: [PATCH 29/53] add default contexts and integration_types values --- discord/commands/core.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/discord/commands/core.py b/discord/commands/core.py index a304cd299f..10dd7b0d20 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -253,16 +253,10 @@ def __init__(self, func: Callable, **kwargs) -> None: "the 'contexts' and 'integration_types' parameters are not available for guild commands" ) - self.contexts: set[InteractionContextType] = contexts or { - InteractionContextType.guild, - InteractionContextType.bot_dm, - InteractionContextType.private_channel, - } + self.contexts: set[InteractionContextType] | None = contexts if guild_only: self.guild_only: bool | None = guild_only - self.integration_types: set[IntegrationType] = integration_types or { - IntegrationType.guild_install - } + self.integration_types: set[IntegrationType] | None = integration_types def __repr__(self) -> str: return f"" From 062b9eada8bf5a23dbb14d04788f479db0f3cdf8 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sun, 24 Mar 2024 13:21:22 -0700 Subject: [PATCH 30/53] h --- discord/bot.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/discord/bot.py b/discord/bot.py index 380b431725..6c886249f2 100644 --- a/discord/bot.py +++ b/discord/bot.py @@ -1164,8 +1164,7 @@ def __init__(self, description=None, *args, **options): self.auto_sync_commands = options.get("auto_sync_commands", True) self.debug_guilds = options.pop("debug_guilds", None) - self.default_command_contexts = set( - options.pop( + self.default_command_contexts = options.pop( "default_command_contexts", { InteractionContextType.guild, @@ -1173,15 +1172,14 @@ def __init__(self, description=None, *args, **options): InteractionContextType.private_channel, }, ) - ) - self.default_command_integration_types = set( - options.pop( + + self.default_command_integration_types = options.pop( "default_command_integration_types", { IntegrationType.guild_install, }, ) - ) + if self.owner_id and self.owner_ids: raise TypeError("Both owner_id and owner_ids are set.") @@ -1192,6 +1190,20 @@ def __init__(self, description=None, *args, **options): raise TypeError( f"owner_ids must be a collection not {self.owner_ids.__class__!r}" ) + if not isinstance( + self.default_command_contexts, collections.abc.Collection + ): + raise TypeError( + f"default_command_contexts must be a collection not {self.default_command_contexts.__class__!r}" + ) + if not isinstance( + self.default_command_integration_types, collections.abc.Collection + ): + raise TypeError( + f"default_command_integration_types must be a collection not {self.default_command_integration_types.__class__!r}" + ) + self.default_command_contexts = set(self.default_command_contexts) + self.default_command_integration_types = set(self.default_command_integration_types) self._checks = [] self._check_once = [] @@ -1472,13 +1484,13 @@ class Bot(BotBase, Client): :attr:`.process_application_commands` if the command is not found. Defaults to ``True``. .. versionadded:: 2.0 - default_command_contexts: Set[:class:`InteractionContextType`] + default_command_contexts: Collection[:class:`InteractionContextType`] The default context types that the bot will use for commands. Defaults to a set containing :attr:`InteractionContextType.guild`, :attr:`InteractionContextType.bot_dm`, and :attr:`InteractionContextType.private_channel`. .. versionadded:: 2.6 - default_command_integration_types: Set[:class:`IntegrationType`] + default_command_integration_types: Collection[:class:`IntegrationType`]] The default integration types that the bot will use for commands. Defaults to a set containing :attr:`IntegrationType.guild_install`. From 007348d1b160bbbd82073e4b5086906bd9e4a98d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 24 Mar 2024 20:21:50 +0000 Subject: [PATCH 31/53] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/bot.py | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/discord/bot.py b/discord/bot.py index 6c886249f2..764366e08b 100644 --- a/discord/bot.py +++ b/discord/bot.py @@ -1165,21 +1165,20 @@ def __init__(self, description=None, *args, **options): self.debug_guilds = options.pop("debug_guilds", None) self.default_command_contexts = options.pop( - "default_command_contexts", - { - InteractionContextType.guild, - InteractionContextType.bot_dm, - InteractionContextType.private_channel, - }, - ) + "default_command_contexts", + { + InteractionContextType.guild, + InteractionContextType.bot_dm, + InteractionContextType.private_channel, + }, + ) self.default_command_integration_types = options.pop( - "default_command_integration_types", - { - IntegrationType.guild_install, - }, - ) - + "default_command_integration_types", + { + IntegrationType.guild_install, + }, + ) if self.owner_id and self.owner_ids: raise TypeError("Both owner_id and owner_ids are set.") @@ -1190,20 +1189,20 @@ def __init__(self, description=None, *args, **options): raise TypeError( f"owner_ids must be a collection not {self.owner_ids.__class__!r}" ) - if not isinstance( - self.default_command_contexts, collections.abc.Collection - ): + if not isinstance(self.default_command_contexts, collections.abc.Collection): raise TypeError( f"default_command_contexts must be a collection not {self.default_command_contexts.__class__!r}" ) if not isinstance( - self.default_command_integration_types, collections.abc.Collection + self.default_command_integration_types, collections.abc.Collection ): raise TypeError( f"default_command_integration_types must be a collection not {self.default_command_integration_types.__class__!r}" ) self.default_command_contexts = set(self.default_command_contexts) - self.default_command_integration_types = set(self.default_command_integration_types) + self.default_command_integration_types = set( + self.default_command_integration_types + ) self._checks = [] self._check_once = [] From 1c495d1f8239da15e1d595d379c6ddbcafb5f784 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sun, 24 Mar 2024 13:51:56 -0700 Subject: [PATCH 32/53] subcmds --- discord/commands/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/commands/core.py b/discord/commands/core.py index 10dd7b0d20..e2a16428f8 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -950,7 +950,7 @@ def to_dict(self) -> dict: self.default_member_permissions.value ) - if not self.guild_ids: + if not self.guild_ids and not self.is_subcommand: as_dict["integration_types"] = [it.value for it in self.integration_types] as_dict["contexts"] = [ctx.value for ctx in self.contexts] From 5b1ea9162ab4d4996d887c5060b04bb783b86ae5 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sun, 24 Mar 2024 13:52:31 -0700 Subject: [PATCH 33/53] subcmds --- discord/commands/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/commands/core.py b/discord/commands/core.py index e2a16428f8..67e1152fb5 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -1347,7 +1347,7 @@ def to_dict(self) -> dict: self.default_member_permissions.value ) - if not self.guild_ids: + if not self.guild_ids and self.parent is None: as_dict["integration_types"] = [it.value for it in self.integration_types] as_dict["contexts"] = [ctx.value for ctx in self.contexts] From 3d7a10fb78013a2c9dab36cee3c38f3ace258e38 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sun, 24 Mar 2024 13:56:09 -0700 Subject: [PATCH 34/53] update check for desynced cmds --- discord/bot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/discord/bot.py b/discord/bot.py index 764366e08b..19a1ca9bdc 100644 --- a/discord/bot.py +++ b/discord/bot.py @@ -278,7 +278,6 @@ def _check_command(cmd: ApplicationCommand, match: Mapping[str, Any]) -> bool: else: as_dict = cmd.to_dict() to_check = { - "dm_permission": None, "nsfw": None, "default_member_permissions": None, "name": None, @@ -294,6 +293,8 @@ def _check_command(cmd: ApplicationCommand, match: Mapping[str, Any]) -> bool: "name_localizations", "description_localizations", ], + "contexts": None, + "integration_types": None, } for check, value in to_check.items(): if type(to_check[check]) == list: From fa8beb4f8d73e98479682481a387b8d96a75b16b Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sun, 24 Mar 2024 14:03:30 -0700 Subject: [PATCH 35/53] h --- discord/commands/core.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/discord/commands/core.py b/discord/commands/core.py index 67e1152fb5..0726f54a89 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -241,7 +241,12 @@ def __init__(self, func: Callable, **kwargs) -> None: contexts = getattr(func, "__contexts__", kwargs.get("contexts", None)) guild_only = getattr(func, "__guild_only__", kwargs.get("guild_only", MISSING)) if guild_only is not MISSING: - warn_deprecated("guild_only", "contexts", "2.6") + warn_deprecated( + "guild_only", + "contexts", + "2.6", + reference="https://discord.com/developers/docs/change-log#userinstallable-apps-preview", + ) if contexts and guild_only: raise InvalidArgument( "cannot pass both 'contexts' and 'guild_only' to ApplicationCommand" @@ -253,9 +258,9 @@ def __init__(self, func: Callable, **kwargs) -> None: "the 'contexts' and 'integration_types' parameters are not available for guild commands" ) - self.contexts: set[InteractionContextType] | None = contexts if guild_only: - self.guild_only: bool | None = guild_only + contexts = {InteractionContextType.guild} + self.contexts: set[InteractionContextType] | None = contexts self.integration_types: set[IntegrationType] | None = integration_types def __repr__(self) -> str: From d924fb7109b7f8c5c8532ce12b64181ede6eff64 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sun, 24 Mar 2024 14:36:21 -0700 Subject: [PATCH 36/53] h --- discord/commands/permissions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/discord/commands/permissions.py b/discord/commands/permissions.py index 89332b50ce..daf633b05a 100644 --- a/discord/commands/permissions.py +++ b/discord/commands/permissions.py @@ -33,7 +33,7 @@ def default_permissions(**perms: bool) -> Callable: - """A decorator that limits the usage of a slash command to members with certain + """A decorator that limits the usage of an application command to members with certain permissions. The permissions passed in must be exactly like the properties shown under @@ -81,7 +81,7 @@ def inner(command: Callable): def guild_only() -> Callable: - """A decorator that limits the usage of a slash command to guild contexts. + """A decorator that limits the usage of an application command to guild contexts. The command won't be able to be used in private message channels. Example @@ -109,7 +109,7 @@ def inner(command: Callable): def is_nsfw() -> Callable: - """A decorator that limits the usage of a slash command to 18+ channels and users. + """A decorator that limits the usage of an application command to 18+ channels and users. In guilds, the command will only be able to be used in channels marked as NSFW. In DMs, users must have opted into age-restricted commands via privacy settings. From 063470de8068425a3b608636eafcce557e4a722a Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sun, 24 Mar 2024 19:56:23 -0700 Subject: [PATCH 37/53] Apply suggestions from code review Co-authored-by: JustaSqu1d <89910983+JustaSqu1d@users.noreply.github.com> Signed-off-by: plun1331 --- discord/commands/core.py | 8 ++++---- discord/interactions.py | 2 +- docs/api/enums.rst | 4 ++-- examples/app_commands/slash_users.py | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/discord/commands/core.py b/discord/commands/core.py index 0726f54a89..cf05c11447 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -717,9 +717,9 @@ class SlashCommand(ApplicationCommand): The description localizations for this command. The values of this should be ``"locale": "description"``. See `here `_ for a list of valid locales. integration_types: Set[:class:`IntegrationType`] - The installation contexts where this command is available. Cannot be set if this is a guild command. + The type of installation this command should be available to. For instance, if set to :attr:`IntegrationType.user_install`, the command will only be available to users with the application installed on their account. Cannot be set if this is a guild command. contexts: Set[:class:`InteractionContextType`] - The interaction contexts where this command is available. Cannot be set if this is a guild command. + The location where this command can be used. Cannot be set if this is a guild command. """ type = 1 @@ -1189,9 +1189,9 @@ class SlashCommandGroup(ApplicationCommand): The description localizations for this command. The values of this should be ``"locale": "description"``. See `here `_ for a list of valid locales. integration_types: Set[:class:`IntegrationType`] - The installation contexts where this command is available. Cannot be set if this is a guild command. + The type of installation this command should be available to. For instance, if set to :attr:`IntegrationType.user_install`, the command will only be available to users with the application installed on their account. Cannot be set if this is a guild command. contexts: Set[:class:`InteractionContextType`] - The interaction contexts where this command is available. Cannot be set if this is a guild command. + The location where this command can be used. Cannot be set if this is a guild command. """ __initial_commands__: list[SlashCommand | SlashCommandGroup] diff --git a/discord/interactions.py b/discord/interactions.py index 7cba5b3c8e..64f9503318 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -140,7 +140,7 @@ class Interaction: custom_id: Optional[:class:`str`] The custom ID for the interaction. authorizing_integration_owners: :class:`AuthorizingIntegrationOwners` - Contains the entities that authorized this interaction. + Contains the entities (users or guilds) that authorized this interaction. context: Optional[:class:`InteractionContextType`] The context in which this command was executed. """ diff --git a/docs/api/enums.rst b/docs/api/enums.rst index d5113265d5..ba948b5e06 100644 --- a/docs/api/enums.rst +++ b/docs/api/enums.rst @@ -2422,7 +2422,7 @@ of :class:`enum.Enum`. .. class:: InteractionContextType - The context an interaction occurs in. + The context where an interaction occurs. .. versionadded:: 2.6 @@ -2432,7 +2432,7 @@ of :class:`enum.Enum`. .. attribute:: bot_dm - The interaction is in the bot's own DM channel. + The interaction is in the bot's own DM channel with the user. .. attribute:: private_channel diff --git a/examples/app_commands/slash_users.py b/examples/app_commands/slash_users.py index db65b77e4d..f5311fcb5e 100644 --- a/examples/app_commands/slash_users.py +++ b/examples/app_commands/slash_users.py @@ -23,14 +23,14 @@ async def greet(ctx: discord.ApplicationContext, user: discord.User): }, ) async def say_hello(ctx: discord.ApplicationContext): - await ctx.respond(f"Hello!") + await ctx.respond("Hello!") """ -If a bot is not installed to a guild, and a user uses a command there, -the response will allways be ephemeral if the guild has more than 25 members. +If a bot is not installed to a guild and the guild has more than 25 members, +the response will always be ephemeral. -This is a Discord limitation, and is subject to change. +This is a Discord limitation and is subject to change. """ From 19a9751dc33d94ded4f21fbe6a6e694abcce99bf Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sat, 30 Mar 2024 11:57:05 -0700 Subject: [PATCH 38/53] Update CHANGELOG.md Seperate fix was made in #2411 Signed-off-by: plun1331 --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99468b9593..b4e3b81135 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,8 +36,6 @@ These changes are available on the `master` branch, but have not yet been releas ([#2407](https://github.com/Pycord-Development/pycord/pull/2407)) - Fixed invalid data being passed to `Interaction._guild` in certain cases. ([#2411](https://github.com/Pycord-Development/pycord/pull/2411)) -- Fixed an issue with `Interaction` that would cause bots to crash if a guild was not - cached. ([#2409](https://github.com/Pycord-Development/pycord/pull/2409)) ### Changed From fb7628d2c36880c9106c2fd20113d24a952db3ca Mon Sep 17 00:00:00 2001 From: plun1331 Date: Wed, 10 Apr 2024 08:30:38 -0700 Subject: [PATCH 39/53] allow Message.interaction to be settable ref. https://github.com/mahtoid/DiscordChatExporterPy/issues/106 Signed-off-by: plun1331 --- discord/message.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/discord/message.py b/discord/message.py index d6ed434873..750db7ac60 100644 --- a/discord/message.py +++ b/discord/message.py @@ -1067,6 +1067,16 @@ def interaction(self) -> MessageInteraction | None: ) return self._interaction + @interaction.setter + def interaction(self, value: MessageInteraction | None) -> None: + utils.warn_deprecated( + "interaction", + "interaction_metadata", + "2.6", + reference="https://discord.com/developers/docs/change-log#userinstallable-apps-preview", + ) + self._interaction = value + @utils.cached_slot_property("_cs_raw_mentions") def raw_mentions(self) -> list[int]: """A property that returns an array of user IDs matched with From 4f56ea5b4296dda4bf972de646dd4d1107cb469e Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sat, 20 Apr 2024 20:36:20 -0700 Subject: [PATCH 40/53] Update core.py Signed-off-by: plun1331 --- discord/commands/core.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/discord/commands/core.py b/discord/commands/core.py index cf05c11447..fa843aee51 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -1695,8 +1695,9 @@ def to_dict(self) -> dict[str, str | int]: "type": self.type, } - if self.guild_only is not None: - as_dict["dm_permission"] = not self.guild_only + if not self.guild_ids: + as_dict["integration_types"] = [it.value for it in self.integration_types] + as_dict["contexts"] = [ctx.value for ctx in self.contexts] if self.nsfw is not None: as_dict["nsfw"] = self.nsfw From 44931d78bf943b0330099481132cfea34b8c84a4 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Wed, 24 Apr 2024 15:57:54 -0700 Subject: [PATCH 41/53] Update interactions.py Signed-off-by: plun1331 --- discord/interactions.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/discord/interactions.py b/discord/interactions.py index 64f9503318..5bb6c2c87a 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -1464,8 +1464,8 @@ class InteractionMetadata: The interaction's ID. type: :class:`InteractionType` The interaction type. - user_id: :class:`int` - The ID of the user that sent the interaction. + user: :class:`User` + The user that sent the interaction. authorizing_integration_owners: :class:`AuthorizingIntegrationOwners` The authorizing user or server for the installation(s) relevant to the interaction. original_response_message_id: Optional[:class:`int`] @@ -1481,13 +1481,12 @@ class InteractionMetadata: __slots__: tuple[str, ...] = ( "id", "type", - "user_id", + "user", "authorizing_integration_owners", "original_response_message_id", "interacted_message_id", "triggering_interaction_metadata", "_state", - "_cs_user", "_cs_original_response_message", "_cs_interacted_message", ) @@ -1496,7 +1495,7 @@ def __init__(self, *, data: InteractionMetadataPayload, state: ConnectionState): self._state = state self.id: int = int(data["id"]) self.type: InteractionType = try_enum(InteractionType, data["type"]) - self.user_id: int = int(data["user_id"]) + self.user: User = User(state=state, data=data["user"]) self.authorizing_integration_owners: AuthorizingIntegrationOwners = ( AuthorizingIntegrationOwners(data["authorizing_integration_owners"], state) ) @@ -1513,14 +1512,7 @@ def __init__(self, *, data: InteractionMetadataPayload, state: ConnectionState): ) def __repr__(self): - return f"" - - @utils.cached_slot_property("_cs_user") - def user(self) -> User | None: - """Optional[:class:`User`]: The user that sent the interaction. - Returns ``None`` if the user is not in cache. - """ - return self._state.get_user(self.user_id) + return f"" @utils.cached_slot_property("_cs_original_response_message") def original_response_message(self) -> Message | None: From 2d9ca7267f125c2939fae7850d2f38230e9e1521 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 24 Apr 2024 22:58:29 +0000 Subject: [PATCH 42/53] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/interactions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/discord/interactions.py b/discord/interactions.py index 5bb6c2c87a..2b89a0f1ab 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -1512,7 +1512,9 @@ def __init__(self, *, data: InteractionMetadataPayload, state: ConnectionState): ) def __repr__(self): - return f"" + return ( + f"" + ) @utils.cached_slot_property("_cs_original_response_message") def original_response_message(self) -> Message | None: From fc27558aefeb313cd9b6c681320f8d52b9fa26c1 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Fri, 26 Apr 2024 13:50:07 -0700 Subject: [PATCH 43/53] Update core.py Signed-off-by: plun1331 --- discord/commands/core.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/discord/commands/core.py b/discord/commands/core.py index fa843aee51..ceee9c32d5 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -1850,20 +1850,10 @@ async def _invoke(self, ctx: ApplicationContext): for i, v in _data.items(): v["id"] = int(i) message = v - channel = ctx.interaction._state.get_channel(int(message["channel_id"])) - if channel is None: - author_id = int(message["author"]["id"]) - self_or_system_message: bool = ctx.bot.user.id == author_id or try_enum( - MessageType, message["type"] - ) not in ( - MessageType.default, - MessageType.reply, - MessageType.application_command, - MessageType.thread_starter_message, - ) - user_id = ctx.author.id if self_or_system_message else author_id - data = await ctx.interaction._state.http.start_private_message(user_id) - channel = ctx.interaction._state.add_dm_channel(data) + channel = ctx.interaction.channel + if channel.id != int(message["channel_id"]): + # we got weird stuff going on, make up a channel + channel = PartialMessageable(state=ctx.interaction._state, id=int(message["channel_id"])) target = Message(state=ctx.interaction._state, channel=channel, data=message) From 3398c2857bdee67a6119c3be6267a10acf259019 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 26 Apr 2024 20:50:29 +0000 Subject: [PATCH 44/53] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/commands/core.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/discord/commands/core.py b/discord/commands/core.py index ceee9c32d5..dcb60a4cb1 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -1853,7 +1853,9 @@ async def _invoke(self, ctx: ApplicationContext): channel = ctx.interaction.channel if channel.id != int(message["channel_id"]): # we got weird stuff going on, make up a channel - channel = PartialMessageable(state=ctx.interaction._state, id=int(message["channel_id"])) + channel = PartialMessageable( + state=ctx.interaction._state, id=int(message["channel_id"]) + ) target = Message(state=ctx.interaction._state, channel=channel, data=message) From bf4011653f75869a090c1bf88809568423c7c12e Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sun, 28 Apr 2024 20:17:32 -0700 Subject: [PATCH 45/53] Update core.py Signed-off-by: plun1331 --- discord/commands/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/commands/core.py b/discord/commands/core.py index dcb60a4cb1..323bc7d95a 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -45,7 +45,7 @@ Union, ) -from ..channel import _threaded_guild_channel_factory +from ..channel import PartialMessageable, _threaded_guild_channel_factory from ..enums import Enum as DiscordEnum from ..enums import ( IntegrationType, From ead5ffcdbcf51f5b84c4f72006b52b54c79e8aa5 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Tue, 25 Jun 2024 14:05:30 -0700 Subject: [PATCH 46/53] Apply suggestions from code review Co-authored-by: Dorukyum <53639936+Dorukyum@users.noreply.github.com> Signed-off-by: plun1331 --- discord/commands/core.py | 10 ++++++---- examples/app_commands/slash_users.py | 8 ++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/discord/commands/core.py b/discord/commands/core.py index 14e3428038..7b83d074ce 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -1189,9 +1189,11 @@ class SlashCommandGroup(ApplicationCommand): The description localizations for this command. The values of this should be ``"locale": "description"``. See `here `_ for a list of valid locales. integration_types: Set[:class:`IntegrationType`] - The type of installation this command should be available to. For instance, if set to :attr:`IntegrationType.user_install`, the command will only be available to users with the application installed on their account. Cannot be set if this is a guild command. + The type of installation this command should be available to. For instance, if set to + :attr:`IntegrationType.user_install`, the command will only be available to users with + the application installed on their account. Unapplicable for guild commands. contexts: Set[:class:`InteractionContextType`] - The location where this command can be used. Cannot be set if this is a guild command. + The location where this command can be used. Unapplicable for guild commands. """ __initial_commands__: list[SlashCommand | SlashCommandGroup] @@ -1613,9 +1615,9 @@ class ContextMenuCommand(ApplicationCommand): The name localizations for this command. The values of this should be ``"locale": "name"``. See `here `_ for a list of valid locales. integration_types: Set[:class:`IntegrationType`] - The installation contexts where this command is available. Cannot be set if this is a guild command. + The installation contexts where this command is available. Unapplicable for guild commands. contexts: Set[:class:`InteractionContextType`] - The interaction contexts where this command is available. Cannot be set if this is a guild command. + The interaction contexts where this command is available. Unapplicable for guild commands. """ def __new__(cls, *args, **kwargs) -> ContextMenuCommand: diff --git a/examples/app_commands/slash_users.py b/examples/app_commands/slash_users.py index f5311fcb5e..73c32cfbad 100644 --- a/examples/app_commands/slash_users.py +++ b/examples/app_commands/slash_users.py @@ -26,12 +26,8 @@ async def say_hello(ctx: discord.ApplicationContext): await ctx.respond("Hello!") -""" -If a bot is not installed to a guild and the guild has more than 25 members, -the response will always be ephemeral. - -This is a Discord limitation and is subject to change. -""" +# If a bot is not installed to a guild and the channel has the `USE_EXTERNAL_APPS` +# permission disabled, the response will always be ephemeral. bot.run("TOKEN") From 46045ef2cb0de6b1d996507fee674720326be420 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Tue, 25 Jun 2024 14:09:06 -0700 Subject: [PATCH 47/53] Update permissions.py Signed-off-by: plun1331 --- discord/permissions.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/discord/permissions.py b/discord/permissions.py index 75b71d57cd..dd34fe46a4 100644 --- a/discord/permissions.py +++ b/discord/permissions.py @@ -626,6 +626,17 @@ def set_voice_channel_status(self) -> int: """ return 1 << 48 + @flag_value + def use_external_apps(self) -> int + """:class:`bool`: Returns ``True`` if a member's user-installed apps can show public responses. + Users will still be able to use user-installed apps, but responses will be ephemeral. + + This only applies to apps that are also not installed to the guild. + + .. versionadded:: 2.6 + """ + return 1 << 50 + PO = TypeVar("PO", bound="PermissionOverwrite") From ce5967299341f2d3f91cea1772463a345303a8b4 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Tue, 25 Jun 2024 14:11:07 -0700 Subject: [PATCH 48/53] Update permissions.py Signed-off-by: plun1331 --- discord/permissions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/discord/permissions.py b/discord/permissions.py index dd34fe46a4..8d5e6b48ab 100644 --- a/discord/permissions.py +++ b/discord/permissions.py @@ -627,7 +627,7 @@ def set_voice_channel_status(self) -> int: return 1 << 48 @flag_value - def use_external_apps(self) -> int + def use_external_apps(self) -> int: """:class:`bool`: Returns ``True`` if a member's user-installed apps can show public responses. Users will still be able to use user-installed apps, but responses will be ephemeral. @@ -756,6 +756,8 @@ class PermissionOverwrite: moderate_members: bool | None send_voice_messages: bool | None set_voice_channel_status: bool | None + use_external_apps: bool | None + def __init__(self, **kwargs: bool | None): self._values: dict[str, bool | None] = {} From 2fe1a9ef450af1325f41518a9f42ea7c9aa57f50 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 25 Jun 2024 21:15:00 +0000 Subject: [PATCH 49/53] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/commands/core.py | 2 +- discord/enums.py | 4 ++-- discord/permissions.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/discord/commands/core.py b/discord/commands/core.py index 7b83d074ce..0d7206dc73 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -1189,7 +1189,7 @@ class SlashCommandGroup(ApplicationCommand): The description localizations for this command. The values of this should be ``"locale": "description"``. See `here `_ for a list of valid locales. integration_types: Set[:class:`IntegrationType`] - The type of installation this command should be available to. For instance, if set to + The type of installation this command should be available to. For instance, if set to :attr:`IntegrationType.user_install`, the command will only be available to users with the application installed on their account. Unapplicable for guild commands. contexts: Set[:class:`InteractionContextType`] diff --git a/discord/enums.py b/discord/enums.py index 3c41f4c61a..1446e1af7d 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -1021,7 +1021,7 @@ class EntitlementOwnerType(Enum): guild = 1 user = 2 - + class IntegrationType(Enum): """The application's integration type""" @@ -1036,7 +1036,7 @@ class InteractionContextType(Enum): bot_dm = 1 private_channel = 2 - + class PollLayoutType(Enum): """The poll's layout type.""" diff --git a/discord/permissions.py b/discord/permissions.py index aa199b7ae0..497ef597e1 100644 --- a/discord/permissions.py +++ b/discord/permissions.py @@ -633,12 +633,12 @@ def send_polls(self) -> int: .. versionadded:: 2.6 """ return 1 << 49 - + @flag_value def use_external_apps(self) -> int: """:class:`bool`: Returns ``True`` if a member's user-installed apps can show public responses. Users will still be able to use user-installed apps, but responses will be ephemeral. - + This only applies to apps that are also not installed to the guild. .. versionadded:: 2.6 From 72a059a8c1d668db35cc41fd90ce63d53b07ccba Mon Sep 17 00:00:00 2001 From: plun1331 Date: Tue, 25 Jun 2024 14:47:27 -0700 Subject: [PATCH 50/53] Update interactions.py Signed-off-by: plun1331 --- discord/interactions.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/discord/interactions.py b/discord/interactions.py index e057569e8b..053cb860ee 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -1549,11 +1549,11 @@ class AuthorizingIntegrationOwners: Attributes ---------- - user_id: :class:`int` | :class:`MISSING` + user_id: :class:`int` | None The ID of the user that authorized the integration. - guild_id: :class:`int` | None | :class:`MISSING` + guild_id: :class:`int` | None The ID of the guild that authorized the integration. - This will be ``None`` if the integration was triggered + This will be ``0`` if the integration was triggered from the user in the bot's DMs. """ @@ -1563,12 +1563,9 @@ def __init__(self, data: dict[str, Any], state: ConnectionState): self._state = state # keys are Application Integration Types as strings self.user_id = ( - int(uid) if (uid := data.get("1", MISSING)) is not MISSING else MISSING + int(uid) if (uid := data.get("1")) is not None else None ) - if (guild_id := data.get("0", MISSING)) == "0": - self.guild_id = None - else: - self.guild_id = int(guild_id) if guild_id is not MISSING else MISSING + self.guild_id = int(guild_id) if (guild_id := data.get("0", None) is not None else None def __repr__(self): return f"" @@ -1586,7 +1583,7 @@ def __ne__(self, other): @utils.cached_slot_property("_cs_user") def user(self) -> User | None: """Optional[:class:`User`]: The user that authorized the integration. - Returns ``None`` if the user is not in cache, or if :attr:`user_id` is :class:`MISSING`. + Returns ``None`` if the user is not in cache, or if :attr:`user_id` is ``None``. """ if not self.user_id: return None @@ -1595,8 +1592,8 @@ def user(self) -> User | None: @utils.cached_slot_property("_cs_guild") def guild(self) -> Guild | None: """Optional[:class:`Guild`]: The guild that authorized the integration. - Returns ``None`` if the guild is not in cache, or if :attr:`guild_id` is :class:`MISSING` or ``None``. + Returns ``None`` if the guild is not in cache, or if :attr:`guild_id` is ``0`` or ``None``. """ - if not self.guild_id: + if not self.guild_id or self.guild_id == 0: return None return self._state._get_guild(self.guild_id) From 48d4396b6d5c78daa76c49f2bc9536a4b6a3be42 Mon Sep 17 00:00:00 2001 From: Dorukyum <53639936+Dorukyum@users.noreply.github.com> Date: Wed, 26 Jun 2024 02:15:01 +0300 Subject: [PATCH 51/53] Apply suggestions from code review Signed-off-by: Dorukyum <53639936+Dorukyum@users.noreply.github.com> --- discord/commands/core.py | 4 +++- discord/interactions.py | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/discord/commands/core.py b/discord/commands/core.py index 0d7206dc73..d89ea0a5b2 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -717,7 +717,9 @@ class SlashCommand(ApplicationCommand): The description localizations for this command. The values of this should be ``"locale": "description"``. See `here `_ for a list of valid locales. integration_types: Set[:class:`IntegrationType`] - The type of installation this command should be available to. For instance, if set to :attr:`IntegrationType.user_install`, the command will only be available to users with the application installed on their account. Cannot be set if this is a guild command. + The type of installation this command should be available to. For instance, if set to + :attr:`IntegrationType.user_install`, the command will only be available to users with + the application installed on their account. Unapplicable for guild commands. contexts: Set[:class:`InteractionContextType`] The location where this command can be used. Cannot be set if this is a guild command. """ diff --git a/discord/interactions.py b/discord/interactions.py index 053cb860ee..1787f35691 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -142,8 +142,12 @@ class Interaction: The custom ID for the interaction. authorizing_integration_owners: :class:`AuthorizingIntegrationOwners` Contains the entities (users or guilds) that authorized this interaction. + + .. versionadded:: 2.6 context: Optional[:class:`InteractionContextType`] The context in which this command was executed. + + .. versionadded:: 2.6 """ __slots__: tuple[str, ...] = ( @@ -1547,6 +1551,8 @@ def interacted_message(self) -> Message | None: class AuthorizingIntegrationOwners: """Contains details on the authorizing user or server for the installation(s) relevant to the interaction. + .. versionadded:: 2.6 + Attributes ---------- user_id: :class:`int` | None @@ -1594,6 +1600,6 @@ def guild(self) -> Guild | None: """Optional[:class:`Guild`]: The guild that authorized the integration. Returns ``None`` if the guild is not in cache, or if :attr:`guild_id` is ``0`` or ``None``. """ - if not self.guild_id or self.guild_id == 0: + if not self.guild_id: return None return self._state._get_guild(self.guild_id) From 4b903f4550c07fcdb19fc8295c2b9dfd1b6de1b9 Mon Sep 17 00:00:00 2001 From: Dorukyum <53639936+Dorukyum@users.noreply.github.com> Date: Wed, 26 Jun 2024 02:19:20 +0300 Subject: [PATCH 52/53] Update discord/interactions.py Signed-off-by: Dorukyum <53639936+Dorukyum@users.noreply.github.com> --- discord/interactions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/interactions.py b/discord/interactions.py index 1787f35691..b13a88fb3f 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -1571,7 +1571,7 @@ def __init__(self, data: dict[str, Any], state: ConnectionState): self.user_id = ( int(uid) if (uid := data.get("1")) is not None else None ) - self.guild_id = int(guild_id) if (guild_id := data.get("0", None) is not None else None + self.guild_id = int(guild_id) if (guild_id := data.get("0", None)) is not None else None def __repr__(self): return f"" From f1e235f0afe619001ca4b42a2e49d0747358159e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 25 Jun 2024 23:19:39 +0000 Subject: [PATCH 53/53] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/interactions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/discord/interactions.py b/discord/interactions.py index b13a88fb3f..5725796d06 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -1568,10 +1568,10 @@ class AuthorizingIntegrationOwners: def __init__(self, data: dict[str, Any], state: ConnectionState): self._state = state # keys are Application Integration Types as strings - self.user_id = ( - int(uid) if (uid := data.get("1")) is not None else None + self.user_id = int(uid) if (uid := data.get("1")) is not None else None + self.guild_id = ( + int(guild_id) if (guild_id := data.get("0", None)) is not None else None ) - self.guild_id = int(guild_id) if (guild_id := data.get("0", None)) is not None else None def __repr__(self): return f""