stormbrigade_sheriff/sbsheriff/Lib/site-packages/disnake/app_commands.py

1066 lines
36 KiB
Python

# SPDX-License-Identifier: MIT
from __future__ import annotations
import math
import re
from abc import ABC
from typing import TYPE_CHECKING, ClassVar, Dict, List, Mapping, Optional, Tuple, Union
from .enums import (
ApplicationCommandPermissionType,
ApplicationCommandType,
ChannelType,
Locale,
OptionType,
enum_if_int,
try_enum,
try_enum_to_int,
)
from .i18n import Localized
from .permissions import Permissions
from .utils import MISSING, _get_as_snowflake, _maybe_cast
if TYPE_CHECKING:
from typing_extensions import Self
from .i18n import LocalizationProtocol, LocalizationValue, LocalizedOptional, LocalizedRequired
from .state import ConnectionState
from .types.interactions import (
ApplicationCommand as ApplicationCommandPayload,
ApplicationCommandOption as ApplicationCommandOptionPayload,
ApplicationCommandOptionChoice as ApplicationCommandOptionChoicePayload,
ApplicationCommandOptionChoiceValue,
ApplicationCommandPermissions as ApplicationCommandPermissionsPayload,
EditApplicationCommand as EditApplicationCommandPayload,
GuildApplicationCommandPermissions as GuildApplicationCommandPermissionsPayload,
)
Choices = Union[
List["OptionChoice"],
List[ApplicationCommandOptionChoiceValue],
Dict[str, ApplicationCommandOptionChoiceValue],
List[Localized[str]],
]
APIApplicationCommand = Union["APIUserCommand", "APIMessageCommand", "APISlashCommand"]
__all__ = (
"application_command_factory",
"ApplicationCommand",
"SlashCommand",
"APISlashCommand",
"UserCommand",
"APIUserCommand",
"MessageCommand",
"APIMessageCommand",
"OptionChoice",
"Option",
"ApplicationCommandPermissions",
"GuildApplicationCommandPermissions",
)
def application_command_factory(data: ApplicationCommandPayload) -> APIApplicationCommand:
cmd_type = try_enum(ApplicationCommandType, data.get("type", 1))
if cmd_type is ApplicationCommandType.chat_input:
return APISlashCommand.from_dict(data)
if cmd_type is ApplicationCommandType.user:
return APIUserCommand.from_dict(data)
if cmd_type is ApplicationCommandType.message:
return APIMessageCommand.from_dict(data)
raise TypeError(f"Application command of type {cmd_type} is not valid")
def _validate_name(name: str) -> None:
# used for slash command names and option names
# see https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-naming
if not isinstance(name, str):
raise TypeError(
f"Slash command name and option names must be an instance of class 'str', received '{name.__class__}'"
)
if name != name.lower() or not re.fullmatch(r"[\w-]{1,32}", name):
raise ValueError(
f"Slash command or option name '{name}' should be lowercase, "
"between 1 and 32 characters long, and only consist of "
"these symbols: a-z, 0-9, -, _, and other languages'/scripts' symbols"
)
class OptionChoice:
"""Represents an option choice.
Parameters
----------
name: Union[:class:`str`, :class:`.Localized`]
The name of the option choice (visible to users).
.. versionchanged:: 2.5
Added support for localizations.
value: Union[:class:`str`, :class:`int`]
The value of the option choice.
"""
def __init__(
self,
name: LocalizedRequired,
value: ApplicationCommandOptionChoiceValue,
) -> None:
name_loc = Localized._cast(name, True)
self.name: str = name_loc.string
self.name_localizations: LocalizationValue = name_loc.localizations
self.value: ApplicationCommandOptionChoiceValue = value
def __repr__(self) -> str:
return f"<OptionChoice name={self.name!r} value={self.value!r}>"
def __eq__(self, other) -> bool:
return (
self.name == other.name
and self.value == other.value
and self.name_localizations == other.name_localizations
)
def to_dict(self, *, locale: Optional[Locale] = None) -> ApplicationCommandOptionChoicePayload:
localizations = self.name_localizations.data
name: Optional[str] = None
# if `locale` provided, get localized name from dict
if locale is not None and localizations:
name = localizations.get(str(locale))
# fall back to default name if no locale or no localized name
if name is None:
name = self.name
payload: ApplicationCommandOptionChoicePayload = {
"name": name,
"value": self.value,
}
# if no `locale` provided, include all localizations in payload
if locale is None and localizations:
payload["name_localizations"] = localizations
return payload
@classmethod
def from_dict(cls, data: ApplicationCommandOptionChoicePayload):
return OptionChoice(
name=Localized(data["name"], data=data.get("name_localizations")),
value=data["value"],
)
def localize(self, store: LocalizationProtocol) -> None:
self.name_localizations._link(store)
class Option:
"""Represents a slash command option.
Parameters
----------
name: Union[:class:`str`, :class:`.Localized`]
The option's name.
.. versionchanged:: 2.5
Added support for localizations.
description: Optional[Union[:class:`str`, :class:`.Localized`]]
The option's description.
.. versionchanged:: 2.5
Added support for localizations.
type: :class:`OptionType`
The option type, e.g. :class:`OptionType.user`.
required: :class:`bool`
Whether this option is required.
choices: Union[List[:class:`OptionChoice`], List[Union[:class:`str`, :class:`int`]], Dict[:class:`str`, Union[:class:`str`, :class:`int`]]]
The list of option choices.
options: List[:class:`Option`]
The list of sub options. Normally you don't have to specify it directly,
instead consider using ``@main_cmd.sub_command`` or ``@main_cmd.sub_command_group`` decorators.
channel_types: List[:class:`ChannelType`]
The list of channel types that your option supports, if the type is :class:`OptionType.channel`.
By default, it supports all channel types.
autocomplete: :class:`bool`
Whether this option can be autocompleted.
min_value: Union[:class:`int`, :class:`float`]
The minimum value permitted.
max_value: Union[:class:`int`, :class:`float`]
The maximum value permitted.
min_length: :class:`int`
The minimum length for this option if this is a string option.
.. versionadded:: 2.6
max_length: :class:`int`
The maximum length for this option if this is a string option.
.. versionadded:: 2.6
"""
__slots__ = (
"name",
"description",
"type",
"required",
"choices",
"options",
"channel_types",
"autocomplete",
"min_value",
"max_value",
"name_localizations",
"description_localizations",
"min_length",
"max_length",
)
def __init__(
self,
name: LocalizedRequired,
description: LocalizedOptional = None,
type: Optional[Union[OptionType, int]] = None,
required: bool = False,
choices: Optional[Choices] = None,
options: Optional[list] = None,
channel_types: Optional[List[ChannelType]] = None,
autocomplete: bool = False,
min_value: Optional[float] = None,
max_value: Optional[float] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
) -> None:
name_loc = Localized._cast(name, True)
_validate_name(name_loc.string)
self.name: str = name_loc.string
self.name_localizations: LocalizationValue = name_loc.localizations
desc_loc = Localized._cast(description, False)
self.description: str = desc_loc.string or "-"
self.description_localizations: LocalizationValue = desc_loc.localizations
self.type: OptionType = enum_if_int(OptionType, type) or OptionType.string
self.required: bool = required
self.options: List[Option] = options or []
if min_value and self.type is OptionType.integer:
min_value = math.ceil(min_value)
if max_value and self.type is OptionType.integer:
max_value = math.floor(max_value)
self.min_value: Optional[float] = min_value
self.max_value: Optional[float] = max_value
self.min_length: Optional[int] = min_length
self.max_length: Optional[int] = max_length
if channel_types is not None and not all(isinstance(t, ChannelType) for t in channel_types):
raise TypeError("channel_types must be a list of `ChannelType`s")
self.channel_types: List[ChannelType] = channel_types or []
self.choices: List[OptionChoice] = []
if choices is not None:
if autocomplete:
raise TypeError("can not specify both choices and autocomplete args")
if isinstance(choices, Mapping):
self.choices = [OptionChoice(name, value) for name, value in choices.items()]
else:
for c in choices:
if isinstance(c, Localized):
c = OptionChoice(c, c.string)
elif not isinstance(c, OptionChoice):
c = OptionChoice(str(c), c)
self.choices.append(c)
self.autocomplete: bool = autocomplete
def __repr__(self) -> str:
return (
f"<Option name={self.name!r} description={self.description!r}"
f" type={self.type!r} required={self.required!r} choices={self.choices!r}"
f" options={self.options!r} min_value={self.min_value!r} max_value={self.max_value!r}"
f" min_length={self.min_length!r} max_length={self.max_length!r}>"
)
def __eq__(self, other) -> bool:
return (
self.name == other.name
and self.description == other.description
and self.type == other.type
and self.required == other.required
and self.choices == other.choices
and self.options == other.options
and set(self.channel_types) == set(other.channel_types)
and self.autocomplete == other.autocomplete
and self.min_value == other.min_value
and self.max_value == other.max_value
and self.min_length == other.min_length
and self.max_length == other.max_length
and self.name_localizations == other.name_localizations
and self.description_localizations == other.description_localizations
)
@classmethod
def from_dict(cls, data: ApplicationCommandOptionPayload) -> Option:
return Option(
name=Localized(data["name"], data=data.get("name_localizations")),
description=Localized(
data.get("description"), data=data.get("description_localizations")
),
type=data.get("type"),
required=data.get("required", False),
choices=_maybe_cast(
data.get("choices", MISSING), lambda x: list(map(OptionChoice.from_dict, x))
),
options=_maybe_cast(
data.get("options", MISSING), lambda x: list(map(Option.from_dict, x))
),
channel_types=_maybe_cast(
data.get("channel_types", MISSING), lambda x: [try_enum(ChannelType, t) for t in x]
),
autocomplete=data.get("autocomplete", False),
min_value=data.get("min_value"),
max_value=data.get("max_value"),
min_length=data.get("min_length"),
max_length=data.get("max_length"),
)
def add_choice(
self,
name: LocalizedRequired,
value: Union[str, int],
) -> None:
"""Adds an OptionChoice to the list of current choices,
parameters are the same as for :class:`OptionChoice`.
"""
self.choices.append(
OptionChoice(
name=name,
value=value,
)
)
def add_option(
self,
name: LocalizedRequired,
description: LocalizedOptional = None,
type: Optional[OptionType] = None,
required: bool = False,
choices: Optional[List[OptionChoice]] = None,
options: Optional[list] = None,
channel_types: Optional[List[ChannelType]] = None,
autocomplete: bool = False,
min_value: Optional[float] = None,
max_value: Optional[float] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
) -> None:
"""Adds an option to the current list of options,
parameters are the same as for :class:`Option`.
"""
type = type or OptionType.string
self.options.append(
Option(
name=name,
description=description,
type=type,
required=required,
choices=choices,
options=options,
channel_types=channel_types,
autocomplete=autocomplete,
min_value=min_value,
max_value=max_value,
min_length=min_length,
max_length=max_length,
)
)
def to_dict(self) -> ApplicationCommandOptionPayload:
payload: ApplicationCommandOptionPayload = {
"name": self.name,
"description": self.description,
"type": try_enum_to_int(self.type),
}
if self.required:
payload["required"] = True
if self.autocomplete:
payload["autocomplete"] = True
if self.choices:
payload["choices"] = [c.to_dict() for c in self.choices]
if self.options:
payload["options"] = [o.to_dict() for o in self.options]
if self.channel_types:
payload["channel_types"] = [v.value for v in self.channel_types]
if self.min_value is not None:
payload["min_value"] = self.min_value
if self.max_value is not None:
payload["max_value"] = self.max_value
if self.min_length is not None:
payload["min_length"] = self.min_length
if self.max_length is not None:
payload["max_length"] = self.max_length
if (loc := self.name_localizations.data) is not None:
payload["name_localizations"] = loc
if (loc := self.description_localizations.data) is not None:
payload["description_localizations"] = loc
return payload
def localize(self, store: LocalizationProtocol) -> None:
self.name_localizations._link(store)
self.description_localizations._link(store)
if (name_loc := self.name_localizations.data) is not None:
for value in name_loc.values():
_validate_name(value)
for c in self.choices:
c.localize(store)
for o in self.options:
o.localize(store)
class ApplicationCommand(ABC):
"""The base class for application commands.
The following classes implement this ABC:
- :class:`~.SlashCommand`
- :class:`~.MessageCommand`
- :class:`~.UserCommand`
Attributes
----------
type: :class:`ApplicationCommandType`
The command type
name: :class:`str`
The command name
name_localizations: :class:`.LocalizationValue`
Localizations for ``name``.
.. versionadded:: 2.5
dm_permission: :class:`bool`
Whether this command can be used in DMs.
Defaults to ``True``.
.. versionadded:: 2.5
nsfw: :class:`bool`
Whether this command is :ddocs:`age-restricted <interactions/application-commands#agerestricted-commands>`.
Defaults to ``False``.
.. versionadded:: 2.8
"""
__repr_info__: ClassVar[Tuple[str, ...]] = (
"type",
"name",
"dm_permission",
"default_member_permisions",
"nsfw",
)
def __init__(
self,
type: ApplicationCommandType,
name: LocalizedRequired,
dm_permission: Optional[bool] = None,
default_member_permissions: Optional[Union[Permissions, int]] = None,
nsfw: Optional[bool] = None,
) -> 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.nsfw: bool = False if nsfw is None else nsfw
self.dm_permission: bool = True if dm_permission is None else dm_permission
self._default_member_permissions: Optional[int]
if default_member_permissions is None:
# allow everyone to use the command if its not supplied
self._default_member_permissions = None
elif isinstance(default_member_permissions, bool):
raise TypeError("`default_member_permissions` cannot be a bool")
elif isinstance(default_member_permissions, int):
self._default_member_permissions = default_member_permissions
else:
self._default_member_permissions = default_member_permissions.value
self._always_synced: bool = False
# reset `default_permission` if set before
self._default_permission: bool = True
@property
def default_member_permissions(self) -> Optional[Permissions]:
"""Optional[:class:`Permissions`]: The default required member permissions for this command.
A member must have *all* these permissions to be able to invoke the command in a guild.
This is a default value, the set of users/roles that may invoke this command can be
overridden by moderators on a guild-specific basis, disregarding this setting.
If ``None`` is returned, it means everyone can use the command by default.
If an empty :class:`Permissions` object is returned (that is, all permissions set to ``False``),
this means no one can use the command.
.. versionadded:: 2.5
"""
if self._default_member_permissions is None:
return None
return Permissions(self._default_member_permissions)
def __repr__(self) -> str:
attrs = " ".join(f"{key}={getattr(self, key)!r}" for key in self.__repr_info__)
return f"<{type(self).__name__} {attrs}>"
def __str__(self) -> str:
return self.name
def __eq__(self, other) -> bool:
return (
self.type == other.type
and self.name == other.name
and self.name_localizations == other.name_localizations
and self.nsfw == other.nsfw
and self._default_member_permissions == other._default_member_permissions
# ignore `dm_permission` if comparing guild commands
and (
any(
(isinstance(obj, _APIApplicationCommandMixin) and obj.guild_id)
for obj in (self, other)
)
or self.dm_permission == other.dm_permission
)
and self._default_permission == other._default_permission
)
def to_dict(self) -> EditApplicationCommandPayload:
data: EditApplicationCommandPayload = {
"type": try_enum_to_int(self.type),
"name": self.name,
"dm_permission": self.dm_permission,
"default_permission": True,
"nsfw": self.nsfw,
}
if self._default_member_permissions is None:
data["default_member_permissions"] = None
else:
data["default_member_permissions"] = str(self._default_member_permissions)
if (loc := self.name_localizations.data) is not None:
data["name_localizations"] = loc
return data
def localize(self, store: LocalizationProtocol) -> None:
self.name_localizations._link(store)
class _APIApplicationCommandMixin:
__repr_info__ = ("id",)
def _update_common(self, data: ApplicationCommandPayload) -> None:
self.id: int = int(data["id"])
self.application_id: int = int(data["application_id"])
self.guild_id: Optional[int] = _get_as_snowflake(data, "guild_id")
self.version: int = int(data["version"])
# deprecated, but kept until API stops returning this field
self._default_permission = data.get("default_permission") is not False
class UserCommand(ApplicationCommand):
"""A user context menu command.
Attributes
----------
name: :class:`str`
The user command's name.
name_localizations: :class:`.LocalizationValue`
Localizations for ``name``.
.. versionadded:: 2.5
dm_permission: :class:`bool`
Whether this command can be used in DMs.
Defaults to ``True``.
.. versionadded:: 2.5
nsfw: :class:`bool`
Whether this command is :ddocs:`age-restricted <interactions/application-commands#agerestricted-commands>`.
Defaults to ``False``.
.. versionadded:: 2.8
"""
__repr_info__ = ("name", "dm_permission", "default_member_permissions")
def __init__(
self,
name: LocalizedRequired,
dm_permission: Optional[bool] = None,
default_member_permissions: Optional[Union[Permissions, int]] = None,
nsfw: Optional[bool] = None,
) -> None:
super().__init__(
type=ApplicationCommandType.user,
name=name,
dm_permission=dm_permission,
default_member_permissions=default_member_permissions,
nsfw=nsfw,
)
class APIUserCommand(UserCommand, _APIApplicationCommandMixin):
"""A user context menu command returned by the API.
.. versionadded:: 2.4
Attributes
----------
name: :class:`str`
The user command's name.
name_localizations: :class:`.LocalizationValue`
Localizations for ``name``.
.. versionadded:: 2.5
dm_permission: :class:`bool`
Whether this command can be used in DMs.
.. versionadded:: 2.5
nsfw: :class:`bool`
Whether this command is :ddocs:`age-restricted <interactions/application-commands#agerestricted-commands>`.
.. versionadded:: 2.8
id: :class:`int`
The user command's ID.
application_id: :class:`int`
The application ID this command belongs to.
guild_id: Optional[:class:`int`]
The ID of the guild this user command is enabled in, or ``None`` if it's global.
version: :class:`int`
Autoincrementing version identifier updated during substantial record changes.
"""
__repr_info__ = UserCommand.__repr_info__ + _APIApplicationCommandMixin.__repr_info__
@classmethod
def from_dict(cls, data: ApplicationCommandPayload) -> Self:
cmd_type = data.get("type", 0)
if cmd_type != ApplicationCommandType.user.value:
raise ValueError(f"Invalid payload type for UserCommand: {cmd_type}")
self = cls(
name=Localized(data["name"], data=data.get("name_localizations")),
dm_permission=data.get("dm_permission") is not False,
default_member_permissions=_get_as_snowflake(data, "default_member_permissions"),
nsfw=data.get("nsfw"),
)
self._update_common(data)
return self
class MessageCommand(ApplicationCommand):
"""A message context menu command
Attributes
----------
name: :class:`str`
The message command's name.
name_localizations: :class:`.LocalizationValue`
Localizations for ``name``.
.. versionadded:: 2.5
dm_permission: :class:`bool`
Whether this command can be used in DMs.
Defaults to ``True``.
.. versionadded:: 2.5
nsfw: :class:`bool`
Whether this command is :ddocs:`age-restricted <interactions/application-commands#agerestricted-commands>`.
Defaults to ``False``.
.. versionadded:: 2.8
"""
__repr_info__ = ("name", "dm_permission", "default_member_permissions")
def __init__(
self,
name: LocalizedRequired,
dm_permission: Optional[bool] = None,
default_member_permissions: Optional[Union[Permissions, int]] = None,
nsfw: Optional[bool] = None,
) -> None:
super().__init__(
type=ApplicationCommandType.message,
name=name,
dm_permission=dm_permission,
default_member_permissions=default_member_permissions,
nsfw=nsfw,
)
class APIMessageCommand(MessageCommand, _APIApplicationCommandMixin):
"""A message context menu command returned by the API.
.. versionadded:: 2.4
Attributes
----------
name: :class:`str`
The message command's name.
name_localizations: :class:`.LocalizationValue`
Localizations for ``name``.
.. versionadded:: 2.5
dm_permission: :class:`bool`
Whether this command can be used in DMs.
.. versionadded:: 2.5
nsfw: :class:`bool`
Whether this command is :ddocs:`age-restricted <interactions/application-commands#agerestricted-commands>`.
.. versionadded:: 2.8
id: :class:`int`
The message command's ID.
application_id: :class:`int`
The application ID this command belongs to.
guild_id: Optional[:class:`int`]
The ID of the guild this message command is enabled in, or ``None`` if it's global.
version: :class:`int`
Autoincrementing version identifier updated during substantial record changes.
"""
__repr_info__ = MessageCommand.__repr_info__ + _APIApplicationCommandMixin.__repr_info__
@classmethod
def from_dict(cls, data: ApplicationCommandPayload) -> Self:
cmd_type = data.get("type", 0)
if cmd_type != ApplicationCommandType.message.value:
raise ValueError(f"Invalid payload type for MessageCommand: {cmd_type}")
self = cls(
name=Localized(data["name"], data=data.get("name_localizations")),
dm_permission=data.get("dm_permission") is not False,
default_member_permissions=_get_as_snowflake(data, "default_member_permissions"),
nsfw=data.get("nsfw"),
)
self._update_common(data)
return self
class SlashCommand(ApplicationCommand):
"""The base class for building slash commands.
Attributes
----------
name: :class:`str`
The slash command's name.
name_localizations: :class:`.LocalizationValue`
Localizations for ``name``.
.. versionadded:: 2.5
description: :class:`str`
The slash command's description.
description_localizations: :class:`.LocalizationValue`
Localizations for ``description``.
.. versionadded:: 2.5
dm_permission: :class:`bool`
Whether this command can be used in DMs.
Defaults to ``True``.
.. versionadded:: 2.5
nsfw: :class:`bool`
Whether this command is :ddocs:`age-restricted <interactions/application-commands#agerestricted-commands>`.
Defaults to ``False``.
.. versionadded:: 2.8
options: List[:class:`Option`]
The list of options the slash command has.
"""
__repr_info__ = (
"name",
"description",
"options",
"dm_permission",
"default_member_permissions",
)
def __init__(
self,
name: LocalizedRequired,
description: LocalizedRequired,
options: Optional[List[Option]] = None,
dm_permission: Optional[bool] = None,
default_member_permissions: Optional[Union[Permissions, int]] = None,
nsfw: Optional[bool] = None,
) -> None:
super().__init__(
type=ApplicationCommandType.chat_input,
name=name,
dm_permission=dm_permission,
default_member_permissions=default_member_permissions,
nsfw=nsfw,
)
_validate_name(self.name)
desc_loc = Localized._cast(description, True)
self.description: str = desc_loc.string
self.description_localizations: LocalizationValue = desc_loc.localizations
self.options: List[Option] = options or []
def __eq__(self, other) -> bool:
return (
super().__eq__(other)
and self.description == other.description
and self.options == other.options
and self.description_localizations == other.description_localizations
)
def add_option(
self,
name: LocalizedRequired,
description: LocalizedOptional = None,
type: Optional[OptionType] = None,
required: bool = False,
choices: Optional[List[OptionChoice]] = None,
options: Optional[list] = None,
channel_types: Optional[List[ChannelType]] = None,
autocomplete: bool = False,
min_value: Optional[float] = None,
max_value: Optional[float] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
) -> None:
"""Adds an option to the current list of options,
parameters are the same as for :class:`Option`
"""
self.options.append(
Option(
name=name,
description=description,
type=type or OptionType.string,
required=required,
choices=choices,
options=options,
channel_types=channel_types,
autocomplete=autocomplete,
min_value=min_value,
max_value=max_value,
min_length=min_length,
max_length=max_length,
)
)
def to_dict(self) -> EditApplicationCommandPayload:
res = super().to_dict()
res["description"] = self.description
res["options"] = [o.to_dict() for o in self.options]
if (loc := self.description_localizations.data) is not None:
res["description_localizations"] = loc
return res
def localize(self, store: LocalizationProtocol) -> None:
super().localize(store)
if (name_loc := self.name_localizations.data) is not None:
for value in name_loc.values():
_validate_name(value)
self.description_localizations._link(store)
for o in self.options:
o.localize(store)
class APISlashCommand(SlashCommand, _APIApplicationCommandMixin):
"""A slash command returned by the API.
.. versionadded:: 2.4
Attributes
----------
name: :class:`str`
The slash command's name.
name_localizations: :class:`.LocalizationValue`
Localizations for ``name``.
.. versionadded:: 2.5
description: :class:`str`
The slash command's description.
description_localizations: :class:`.LocalizationValue`
Localizations for ``description``.
.. versionadded:: 2.5
dm_permission: :class:`bool`
Whether this command can be used in DMs.
.. versionadded:: 2.5
nsfw: :class:`bool`
Whether this command is :ddocs:`age-restricted <interactions/application-commands#agerestricted-commands>`.
.. versionadded:: 2.8
id: :class:`int`
The slash command's ID.
options: List[:class:`Option`]
The list of options the slash command has.
application_id: :class:`int`
The application ID this command belongs to.
guild_id: Optional[:class:`int`]
The ID of the guild this slash command is enabled in, or ``None`` if it's global.
version: :class:`int`
Autoincrementing version identifier updated during substantial record changes.
"""
__repr_info__ = SlashCommand.__repr_info__ + _APIApplicationCommandMixin.__repr_info__
@classmethod
def from_dict(cls, data: ApplicationCommandPayload) -> Self:
cmd_type = data.get("type", 0)
if cmd_type != ApplicationCommandType.chat_input.value:
raise ValueError(f"Invalid payload type for SlashCommand: {cmd_type}")
self = cls(
name=Localized(data["name"], data=data.get("name_localizations")),
description=Localized(data["description"], data=data.get("description_localizations")),
options=_maybe_cast(
data.get("options", MISSING), lambda x: list(map(Option.from_dict, x))
),
dm_permission=data.get("dm_permission") is not False,
default_member_permissions=_get_as_snowflake(data, "default_member_permissions"),
nsfw=data.get("nsfw"),
)
self._update_common(data)
return self
class ApplicationCommandPermissions:
"""Represents application command permissions for a role, user, or channel.
Attributes
----------
id: :class:`int`
The ID of the role, user, or channel.
type: :class:`ApplicationCommandPermissionType`
The type of the target.
permission: :class:`bool`
Whether to allow or deny the access to the application command.
"""
__slots__ = ("id", "type", "permission", "_guild_id")
def __init__(self, *, data: ApplicationCommandPermissionsPayload, guild_id: int) -> None:
self.id: int = int(data["id"])
self.type: ApplicationCommandPermissionType = try_enum(
ApplicationCommandPermissionType, data["type"]
)
self.permission: bool = data["permission"]
self._guild_id: int = guild_id
def __repr__(self) -> str:
return f"<ApplicationCommandPermissions id={self.id!r} type={self.type!r} permission={self.permission!r}>"
def __eq__(self, other):
return (
self.id == other.id and self.type == other.type and self.permission == other.permission
)
def to_dict(self) -> ApplicationCommandPermissionsPayload:
return {"id": self.id, "type": int(self.type), "permission": self.permission} # type: ignore
def is_everyone(self) -> bool:
"""Whether this permission object is affecting the @everyone role.
.. versionadded:: 2.5
:return type: :class:`bool`
"""
return self.id == self._guild_id
def is_all_channels(self) -> bool:
"""Whether this permission object is affecting all channels.
.. versionadded:: 2.5
:return type: :class:`bool`
"""
return self.id == self._guild_id - 1
class GuildApplicationCommandPermissions:
"""Represents application command permissions in a guild.
.. versionchanged:: 2.5
Can now also represent application-wide permissions that apply to every command by default.
Attributes
----------
id: :class:`int`
The application command's ID, or the application ID if these are application-wide permissions.
application_id: :class:`int`
The application ID this command belongs to.
guild_id: :class:`int`
The ID of the guild where these permissions are applied.
permissions: List[:class:`ApplicationCommandPermissions`]
A list of :class:`ApplicationCommandPermissions`.
"""
__slots__ = ("_state", "id", "application_id", "guild_id", "permissions")
def __init__(
self, *, data: GuildApplicationCommandPermissionsPayload, state: ConnectionState
) -> None:
self._state: ConnectionState = state
self.id: int = int(data["id"])
self.application_id: int = int(data["application_id"])
self.guild_id: int = int(data["guild_id"])
self.permissions: List[ApplicationCommandPermissions] = [
ApplicationCommandPermissions(data=elem, guild_id=self.guild_id)
for elem in data["permissions"]
]
def __repr__(self) -> str:
return (
f"<GuildApplicationCommandPermissions id={self.id!r} application_id={self.application_id!r}"
f" guild_id={self.guild_id!r} permissions={self.permissions!r}>"
)
def to_dict(self) -> GuildApplicationCommandPermissionsPayload:
return {
"id": self.id,
"application_id": self.application_id,
"guild_id": self.guild_id,
"permissions": [perm.to_dict() for perm in self.permissions],
}