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

refactor app command sync and other fixes #666

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ed76909
feat: add ID attributes to ApplicationCommand classes
onerandomusername Jul 27, 2022
8e6cc3c
feat: make InvokableCommands also subclasses of ApplicationCommand cl…
onerandomusername Oct 1, 2022
a62168a
feat: support providing ids to sync commands
onerandomusername Jul 27, 2022
8595828
fix: if a user-provided command ID is invalid ignore it
onerandomusername Jul 27, 2022
237ac58
feat: move command sync config to flags
onerandomusername Jul 27, 2022
a625e20
feat: add never_delete and global or guild command syncing options
onerandomusername Jul 27, 2022
a1356e8
chore: invert never_delete to allow deletion
onerandomusername Jul 27, 2022
8fca143
feat: containerize each slash command type
onerandomusername Jul 27, 2022
9230d8e
"fix" pyright issues
onerandomusername Jul 27, 2022
fe9d277
fix: sync command ids when bulk updating guild slash commands
onerandomusername Jul 27, 2022
ec79472
chore: add parents properties
onerandomusername Jul 28, 2022
e5c619b
feat: add slash command mentions
onerandomusername Jul 28, 2022
7cec79e
fix some bugs
onerandomusername Aug 14, 2022
6468099
chore: remove reassigning imported attributes
onerandomusername Sep 8, 2022
9695975
remove removed parameter documentation
onerandomusername Sep 8, 2022
7eda7e4
chore: resolve lint and typing issues
onerandomusername Oct 1, 2022
e824ed8
Revert "feat: move command sync config to flags"
onerandomusername Oct 21, 2022
7115195
chore: remove other parts of command sync
onerandomusername Oct 21, 2022
911f124
Merge branch 'master' into refactor/app-commands
onerandomusername Oct 26, 2022
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
25 changes: 25 additions & 0 deletions disnake/app_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,12 +468,14 @@ def __init__(
name: LocalizedRequired,
dm_permission: Optional[bool] = None,
default_member_permissions: Optional[Union[Permissions, int]] = None,
id: Optional[int] = None,
):
self.type: ApplicationCommandType = enum_if_int(ApplicationCommandType, type)

name_loc = Localized._cast(name, True)
self.name: str = name_loc.string
self.name_localizations: LocalizationValue = name_loc.localizations
self.id: Optional[int] = id

self.dm_permission: bool = True if dm_permission is None else dm_permission

Expand Down Expand Up @@ -535,6 +537,11 @@ def __eq__(self, other) -> bool:
and self._default_permission == other._default_permission
)

def need_sync(self, other: ApplicationCommand) -> bool:
if self != other:
return True
return self.id is not None and self.id != other.id

def to_dict(self) -> EditApplicationCommandPayload:
data: EditApplicationCommandPayload = {
"type": try_enum_to_int(self.type),
Expand All @@ -550,6 +557,9 @@ def to_dict(self) -> EditApplicationCommandPayload:
if (loc := self.name_localizations.data) is not None:
data["name_localizations"] = loc

if self.id is not None:
data["id"] = self.id

return data

def localize(self, store: LocalizationProtocol) -> None:
Expand Down Expand Up @@ -595,12 +605,14 @@ def __init__(
name: LocalizedRequired,
dm_permission: Optional[bool] = None,
default_member_permissions: Optional[Union[Permissions, int]] = None,
id: Optional[int] = None,
):
super().__init__(
type=ApplicationCommandType.user,
name=name,
dm_permission=dm_permission,
default_member_permissions=default_member_permissions,
id=id,
)


Expand Down Expand Up @@ -636,6 +648,9 @@ class APIUserCommand(UserCommand, _APIApplicationCommandMixin):

__repr_info__ = UserCommand.__repr_info__ + _APIApplicationCommandMixin.__repr_info__

if TYPE_CHECKING:
id: int

@classmethod
def from_dict(cls, data: ApplicationCommandPayload) -> Self:
cmd_type = data.get("type", 0)
Expand Down Expand Up @@ -678,12 +693,14 @@ def __init__(
name: LocalizedRequired,
dm_permission: Optional[bool] = None,
default_member_permissions: Optional[Union[Permissions, int]] = None,
id: Optional[int] = None,
):
super().__init__(
type=ApplicationCommandType.message,
name=name,
dm_permission=dm_permission,
default_member_permissions=default_member_permissions,
id=id,
)


Expand Down Expand Up @@ -719,6 +736,9 @@ class APIMessageCommand(MessageCommand, _APIApplicationCommandMixin):

__repr_info__ = MessageCommand.__repr_info__ + _APIApplicationCommandMixin.__repr_info__

if TYPE_CHECKING:
id: int

@classmethod
def from_dict(cls, data: ApplicationCommandPayload) -> Self:
cmd_type = data.get("type", 0)
Expand Down Expand Up @@ -779,12 +799,14 @@ def __init__(
options: Optional[List[Option]] = None,
dm_permission: Optional[bool] = None,
default_member_permissions: Optional[Union[Permissions, int]] = None,
id: Optional[int] = None,
):
super().__init__(
type=ApplicationCommandType.chat_input,
name=name,
dm_permission=dm_permission,
default_member_permissions=default_member_permissions,
id=id,
)
_validate_name(self.name)

Expand Down Expand Up @@ -898,6 +920,9 @@ class APISlashCommand(SlashCommand, _APIApplicationCommandMixin):

__repr_info__ = SlashCommand.__repr_info__ + _APIApplicationCommandMixin.__repr_info__

if TYPE_CHECKING:
id: int

@classmethod
def from_dict(cls, data: ApplicationCommandPayload) -> Self:
cmd_type = data.get("type", 0)
Expand Down
30 changes: 28 additions & 2 deletions disnake/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2478,6 +2478,10 @@ async def bulk_overwrite_global_commands(

.. versionadded:: 2.1

.. versionchanged:: 2.6

Modifies the commands' ``id`` attribute to correspond to the version on the API.

Parameters
----------
application_commands: List[:class:`.ApplicationCommand`]
Expand All @@ -2490,7 +2494,16 @@ async def bulk_overwrite_global_commands(
"""
for cmd in application_commands:
cmd.localize(self.i18n)
return await self._connection.bulk_overwrite_global_commands(application_commands)
res = await self._connection.bulk_overwrite_global_commands(application_commands)

for api_command in res:
cmd = utils.get(application_commands, name=api_command.name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible to have 2 app commands of different types (or guilds) with identical names, so this should be modified

if not cmd:
# consider a warning
continue
cmd.id = api_command.id

return res

# Application commands (guild)

Expand Down Expand Up @@ -2634,7 +2647,16 @@ async def bulk_overwrite_guild_commands(
"""
for cmd in application_commands:
cmd.localize(self.i18n)
return await self._connection.bulk_overwrite_guild_commands(guild_id, application_commands)
res = await self._connection.bulk_overwrite_guild_commands(guild_id, application_commands)

for api_command in res:
cmd = utils.get(application_commands, name=api_command.name)
if not cmd:
# consider a warning
continue
cmd.id = api_command.id

return res

# Application command permissions

Expand All @@ -2647,6 +2669,10 @@ async def bulk_fetch_command_permissions(

.. versionadded:: 2.1

.. versionchanged:: 2.6

Modifies the commands' ``id`` attribute to correspond to the version on the API.

Parameters
----------
guild_id: :class:`int`
Expand Down
18 changes: 17 additions & 1 deletion disnake/ext/commands/base_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ def __init__(self, func: CommandCallback, *, name: Optional[str] = None, **kwarg
self.__command_flag__ = None
self._callback: CommandCallback = func
self.name: str = name or func.__name__
self.qualified_name: str = self.name
# Annotation parser needs this attribute because body doesn't exist at this moment.
# We will use this attribute later in order to set the dm_permission.
self._guild_only: bool = kwargs.get("guild_only", False)
Expand Down Expand Up @@ -236,11 +235,28 @@ def _update_copy(self: AppCommandT, kwargs: Dict[str, Any]) -> AppCommandT:
else:
return self.copy()

@property
def id(self) -> Optional[int]:
return self.body.id

@id.setter
def id(self, id: int):
self.body.id = id

@property
def dm_permission(self) -> bool:
""":class:`bool`: Whether this command can be used in DMs."""
return self.body.dm_permission

@property
def qualified_name(self) -> str:
return self.name

@property
def mention(self) -> str:
# todo: add docs and make ID non-nullable
return f"</{self.qualified_name}:{self.id}>"

@property
def default_member_permissions(self) -> Optional[Permissions]:
"""Optional[:class:`.Permissions`]: The default required member permissions for this command.
Expand Down
40 changes: 36 additions & 4 deletions disnake/ext/commands/ctx_menus_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
from __future__ import annotations

import asyncio
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Sequence, Tuple, Union
from typing import TYPE_CHECKING, Any, Callable, Dict, Literal, Optional, Sequence, Tuple, Union

from disnake.app_commands import MessageCommand, UserCommand
from disnake.enums import ApplicationCommandType
from disnake.i18n import Localized
from disnake.permissions import Permissions

Expand All @@ -16,7 +17,7 @@
if TYPE_CHECKING:
from typing_extensions import ParamSpec

from disnake.i18n import LocalizedOptional
from disnake.i18n import LocalizationValue, LocalizedOptional
from disnake.interactions import (
ApplicationCommandInteraction,
MessageCommandInteraction,
Expand All @@ -30,7 +31,7 @@
__all__ = ("InvokableUserCommand", "InvokableMessageCommand", "user_command", "message_command")


class InvokableUserCommand(InvokableApplicationCommand):
class InvokableUserCommand(InvokableApplicationCommand, UserCommand):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the point of subclassing UserCommand? The chain of super() calls never reaches it (see InvokableApplicationCommand.__init__) and it doesn't seem to make any typing improvements

"""A class that implements the protocol for a bot user command (context menu).

These are not created manually, instead they are created via the
Expand Down Expand Up @@ -77,6 +78,7 @@ def __init__(
default_member_permissions: Optional[Union[Permissions, int]] = None,
guild_ids: Optional[Sequence[int]] = None,
auto_sync: Optional[bool] = None,
id: Optional[int] = None,
**kwargs,
):
name_loc = Localized._cast(name, False)
Expand All @@ -101,8 +103,23 @@ def __init__(
name=name_loc._upgrade(self.name),
dm_permission=dm_permission and not self._guild_only,
default_member_permissions=default_member_permissions,
id=id,
)

self._name_localised = name_loc

@property
def type(self) -> Literal[ApplicationCommandType.user]:
return ApplicationCommandType.user

@property
def qualified_name(self) -> str:
return self.name

@property
def name_localizations(self) -> LocalizationValue:
return self._name_localised.localizations

async def _call_external_error_handlers(
self, inter: ApplicationCommandInteraction, error: CommandError
) -> None:
Expand Down Expand Up @@ -130,7 +147,7 @@ async def __call__(
await safe_call(self.callback, interaction, *args, **kwargs)


class InvokableMessageCommand(InvokableApplicationCommand):
class InvokableMessageCommand(InvokableApplicationCommand, MessageCommand):
"""A class that implements the protocol for a bot message command (context menu).

These are not created manually, instead they are created via the
Expand Down Expand Up @@ -177,6 +194,7 @@ def __init__(
default_member_permissions: Optional[Union[Permissions, int]] = None,
guild_ids: Optional[Sequence[int]] = None,
auto_sync: Optional[bool] = None,
id: Optional[int] = None,
**kwargs,
):
name_loc = Localized._cast(name, False)
Expand All @@ -195,7 +213,21 @@ def __init__(
name=name_loc._upgrade(self.name),
dm_permission=dm_permission and not self._guild_only,
default_member_permissions=default_member_permissions,
id=id,
)
self._name_localised = name_loc

@property
def type(self) -> Literal[ApplicationCommandType.message]:
return ApplicationCommandType.message

@property
def qualified_name(self) -> str:
return self.name

@property
def name_localizations(self) -> LocalizationValue:
return self._name_localised.localizations

async def _call_external_error_handlers(
self, inter: ApplicationCommandInteraction, error: CommandError
Expand Down
9 changes: 7 additions & 2 deletions disnake/ext/commands/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -1016,11 +1016,16 @@ class CommandRegistrationError(ClientException):
Whether the name that conflicts is an alias of the command we try to add.
"""

def __init__(self, name: str, *, alias_conflict: bool = False) -> None:
def __init__(
self, name: str, *, alias_conflict: bool = False, guild_id: Optional[int] = None
) -> None:
self.name: str = name
self.alias_conflict: bool = alias_conflict
self.guild_id: Optional[int] = guild_id
type_ = "alias" if alias_conflict else "command"
super().__init__(f"The {type_} {name} is already an existing command or alias.")
msg = f"The {type_} {name} is already an existing command or alias"
msg += "." if not guild_id else f" in guild ID {guild_id}."
super().__init__(msg)


class FlagError(BadArgument):
Expand Down
Loading