Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: user-installable apps #2409

Merged
merged 74 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
6fbb08e
add enums, do command stuff, add context to interaction
plun1331 Mar 21, 2024
0017c33
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 23, 2024
3f0c557
add authorizing_integration_owners
plun1331 Mar 24, 2024
1d33c1f
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 24, 2024
3195f5e
add application_metadata
plun1331 Mar 24, 2024
b97ee6d
Merge remote-tracking branch 'upstream/feat/ua' into feat/ua
plun1331 Mar 24, 2024
02df055
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 24, 2024
e43dc62
don't trust copilot
plun1331 Mar 24, 2024
6399aa0
Merge remote-tracking branch 'upstream/feat/ua' into feat/ua
plun1331 Mar 24, 2024
bb7f2b8
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 24, 2024
caabdb5
Merge branch 'master' into feat/ua
plun1331 Mar 24, 2024
350b8ae
update __all__
plun1331 Mar 24, 2024
cec5904
Merge remote-tracking branch 'upstream/feat/ua' into feat/ua
plun1331 Mar 24, 2024
800f5cf
circular import
plun1331 Mar 24, 2024
56208b1
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 24, 2024
05245bd
fix numbers
plun1331 Mar 24, 2024
183c542
Merge remote-tracking branch 'upstream/feat/ua' into feat/ua
plun1331 Mar 24, 2024
765eeee
h
plun1331 Mar 24, 2024
996e836
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 24, 2024
60fdfd4
update guild_only deco to use contexts
plun1331 Mar 24, 2024
5109eaf
Merge remote-tracking branch 'upstream/feat/ua' into feat/ua
plun1331 Mar 24, 2024
0ca5950
type
plun1331 Mar 24, 2024
6fa2af5
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 24, 2024
babf68a
deprecation warnings
plun1331 Mar 24, 2024
c153f30
Merge remote-tracking branch 'upstream/feat/ua' into feat/ua
plun1331 Mar 24, 2024
0f570c9
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 24, 2024
699ed6d
docs
plun1331 Mar 24, 2024
ca6f496
Merge remote-tracking branch 'upstream/feat/ua' into feat/ua
plun1331 Mar 24, 2024
030ff56
example
plun1331 Mar 24, 2024
b460a89
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 24, 2024
68b4040
edit docs
plun1331 Mar 24, 2024
be524d7
Merge remote-tracking branch 'upstream/feat/ua' into feat/ua
plun1331 Mar 24, 2024
76a48c0
update changelog
plun1331 Mar 24, 2024
eb3ae92
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 24, 2024
bf4ae8e
update changelog
plun1331 Mar 24, 2024
d30c457
Merge remote-tracking branch 'upstream/feat/ua' into feat/ua
plun1331 Mar 24, 2024
aa1249b
add default contexts and integration_types values
plun1331 Mar 24, 2024
e006935
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 24, 2024
c4d6a0b
add default contexts and integration_types values
plun1331 Mar 24, 2024
fb5d92a
Merge remote-tracking branch 'upstream/feat/ua' into feat/ua
plun1331 Mar 24, 2024
062b9ea
h
plun1331 Mar 24, 2024
007348d
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 24, 2024
1c495d1
subcmds
plun1331 Mar 24, 2024
5b1ea91
subcmds
plun1331 Mar 24, 2024
3d7a10f
update check for desynced cmds
plun1331 Mar 24, 2024
fa8beb4
h
plun1331 Mar 24, 2024
d924fb7
h
plun1331 Mar 24, 2024
063470d
Apply suggestions from code review
plun1331 Mar 25, 2024
a687a30
Merge branch 'master' into feat/ua
plun1331 Mar 30, 2024
19a9751
Update CHANGELOG.md
plun1331 Mar 30, 2024
fb7628d
allow Message.interaction to be settable
plun1331 Apr 10, 2024
d63534f
Merge branch 'master' into feat/ua
plun1331 Apr 10, 2024
8faa082
Merge branch 'master' into feat/ua
plun1331 Apr 16, 2024
4f56ea5
Update core.py
plun1331 Apr 21, 2024
830c7a0
Merge branch 'master' into feat/ua
plun1331 Apr 21, 2024
44931d7
Update interactions.py
plun1331 Apr 24, 2024
2d9ca72
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 24, 2024
54d61fa
Merge branch 'master' into feat/ua
Lulalaby Apr 25, 2024
fc27558
Update core.py
plun1331 Apr 26, 2024
3398c28
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 26, 2024
3755a5a
Merge branch 'master' into feat/ua
plun1331 Apr 26, 2024
029015d
Merge branch 'master' into feat/ua
Lulalaby Apr 26, 2024
34df02f
Merge branch 'master' into feat/ua
plun1331 Apr 27, 2024
bf40116
Update core.py
plun1331 Apr 29, 2024
e778c58
Merge branch 'master' into feat/ua
plun1331 Jun 6, 2024
ead5ffc
Apply suggestions from code review
plun1331 Jun 25, 2024
46045ef
Update permissions.py
plun1331 Jun 25, 2024
ce59672
Update permissions.py
plun1331 Jun 25, 2024
61dc2cd
Merge branch 'master' into feat/ua
plun1331 Jun 25, 2024
2fe1a9e
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 25, 2024
72a059a
Update interactions.py
plun1331 Jun 25, 2024
48d4396
Apply suggestions from code review
Dorukyum Jun 25, 2024
4b903f4
Update discord/interactions.py
Dorukyum Jun 25, 2024
f1e235f
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -41,6 +43,11 @@ 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

Expand Down
52 changes: 50 additions & 2 deletions discord/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
UserCommand,
command,
)
from .enums import InteractionType
from .enums import IntegrationType, InteractionContextType, InteractionType
from .errors import CheckFailure, DiscordException
from .interactions import Interaction
from .shard import AutoShardedClient
Expand Down Expand Up @@ -125,6 +125,13 @@ 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:
Expand Down Expand Up @@ -271,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,
Expand All @@ -287,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:
Expand Down Expand Up @@ -1157,6 +1165,21 @@ 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 = options.pop(
"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,
},
)

if self.owner_id and self.owner_ids:
raise TypeError("Both owner_id and owner_ids are set.")
Expand All @@ -1167,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 = []
Expand Down Expand Up @@ -1447,6 +1484,17 @@ class Bot(BotBase, Client):
:attr:`.process_application_commands` if the command is not found. Defaults to ``True``.

.. versionadded:: 2.0
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: Collection[: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
Expand Down
144 changes: 132 additions & 12 deletions discord/commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,19 @@

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
Expand All @@ -61,7 +68,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

Expand Down Expand Up @@ -226,11 +233,36 @@ 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",
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"
)
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"
)

if guild_only:
contexts = {InteractionContextType.guild}
self.contexts: set[InteractionContextType] | None = contexts
self.integration_types: set[IntegrationType] | None = integration_types

def __repr__(self) -> str:
return f"<discord.commands.{self.__class__.__name__} name={self.name}>"

Expand Down Expand Up @@ -274,6 +306,33 @@ def callback(
unwrap = unwrap_function(function)
self.module = unwrap.__module__

@property
def guild_only(self) -> bool:
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",
reference="https://discord.com/developers/docs/change-log#userinstallable-apps-preview",
)
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()
Expand Down Expand Up @@ -631,6 +690,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 :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.
Expand All @@ -654,6 +716,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 <https://discord.com/developers/docs/reference#locales>`_ 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.
Dorukyum marked this conversation as resolved.
Show resolved Hide resolved
contexts: Set[:class:`InteractionContextType`]
The location where this command can be used. Cannot be set if this is a guild command.
"""

type = 1
Expand Down Expand Up @@ -881,9 +947,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

Expand All @@ -892,6 +955,10 @@ def to_dict(self) -> dict:
self.default_member_permissions.value
)

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]

return as_dict

async def _invoke(self, ctx: ApplicationContext) -> None:
Expand Down Expand Up @@ -1100,6 +1167,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 :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.
Expand All @@ -1118,6 +1188,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 <https://discord.com/developers/docs/reference#locales>`_ 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.
plun1331 marked this conversation as resolved.
Show resolved Hide resolved
contexts: Set[:class:`InteractionContextType`]
The location where this command can be used. Cannot be set if this is a guild command.
plun1331 marked this conversation as resolved.
Show resolved Hide resolved
"""

__initial_commands__: list[SlashCommand | SlashCommandGroup]
Expand Down Expand Up @@ -1177,9 +1251,30 @@ def __init__(
self.default_member_permissions: Permissions | None = kwargs.get(
"default_member_permissions", None
)
self.guild_only: bool | None = kwargs.get("guild_only", 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"
)

# 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] | None = integration_types

self.name_localizations: dict[str, str] = kwargs.get(
"name_localizations", MISSING
)
Expand Down Expand Up @@ -1218,6 +1313,23 @@ 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,
Expand All @@ -1232,9 +1344,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

Expand All @@ -1243,6 +1352,10 @@ def to_dict(self) -> dict:
self.default_member_permissions.value
)

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]

return as_dict

def add_command(self, command: SlashCommand) -> None:
Expand Down Expand Up @@ -1476,6 +1589,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.
Expand All @@ -1496,6 +1612,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 <https://discord.com/developers/docs/reference#locales>`_ 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.
plun1331 marked this conversation as resolved.
Show resolved Hide resolved
"""

def __new__(cls, *args, **kwargs) -> ContextMenuCommand:
Expand Down
Loading
Loading