mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-04-16 06:03:11 +00:00
Add support for setting and receiving permissions v2
Closes #7592 This does not include audit log changes or the remaining endpoints. That will come in a different commit.
This commit is contained in:
parent
b678effb76
commit
3b3d4d3880
@ -42,6 +42,7 @@ from typing import (
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
overload,
|
||||
)
|
||||
from textwrap import TextWrapper
|
||||
|
||||
@ -54,6 +55,7 @@ from .errors import AppCommandError, CheckFailure, CommandInvokeError, CommandSi
|
||||
from ..message import Message
|
||||
from ..user import User
|
||||
from ..member import Member
|
||||
from ..permissions import Permissions
|
||||
from ..utils import resolve_annotation, MISSING, is_inside_class, maybe_coroutine, async_all
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -80,6 +82,8 @@ __all__ = (
|
||||
'choices',
|
||||
'autocomplete',
|
||||
'guilds',
|
||||
'guild_only',
|
||||
'default_permissions',
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -88,6 +92,7 @@ else:
|
||||
P = TypeVar('P')
|
||||
|
||||
T = TypeVar('T')
|
||||
F = TypeVar('F', bound=Callable[..., Any])
|
||||
GroupT = TypeVar('GroupT', bound='Binding')
|
||||
Coro = Coroutine[Any, Any, T]
|
||||
UnboundError = Callable[['Interaction', AppCommandError], Coro[Any]]
|
||||
@ -460,6 +465,16 @@ class Command(Generic[GroupT, P, T]):
|
||||
is necessary to be thrown to signal failure, then one inherited from
|
||||
:exc:`AppCommandError` should be used. If all the checks fail without
|
||||
propagating an exception, :exc:`CheckFailure` is raised.
|
||||
default_permissions: Optional[:class:`Permissions`]
|
||||
The default permissions that can execute this command on Discord. Note
|
||||
that server administrators can override this value in the client.
|
||||
|
||||
Due to a Discord limitation, this does not work on subcommands.
|
||||
guild_only: :class:`bool`
|
||||
Whether the command should only be usable in guild contexts.
|
||||
Defaults to ``False``.
|
||||
|
||||
Due to a Discord limitation, this does not work on subcommands.
|
||||
parent: Optional[:class:`Group`]
|
||||
The parent application command. ``None`` if there isn't one.
|
||||
"""
|
||||
@ -494,6 +509,10 @@ class Command(Generic[GroupT, P, T]):
|
||||
self._guild_ids: Optional[List[int]] = guild_ids or getattr(
|
||||
callback, '__discord_app_commands_default_guilds__', None
|
||||
)
|
||||
self.default_permissions: Optional[Permissions] = getattr(
|
||||
callback, '__discord_app_commands_default_permissions__', None
|
||||
)
|
||||
self.guild_only: bool = getattr(callback, '__discord_app_commands_guild_only__', False)
|
||||
|
||||
if self._guild_ids is not None and self.parent is not None:
|
||||
raise ValueError('child commands cannot have default guilds set, consider setting them in the parent instead')
|
||||
@ -522,6 +541,8 @@ class Command(Generic[GroupT, P, T]):
|
||||
copy._guild_ids = self._guild_ids
|
||||
copy.checks = self.checks
|
||||
copy.description = self.description
|
||||
copy.default_permissions = self.default_permissions
|
||||
copy.guild_only = self.guild_only
|
||||
copy._attr = self._attr
|
||||
copy._callback = self._callback
|
||||
copy.on_error = self.on_error
|
||||
@ -539,13 +560,19 @@ class Command(Generic[GroupT, P, T]):
|
||||
# If we have a parent then our type is a subcommand
|
||||
# Otherwise, the type falls back to the specific command type (e.g. slash command or context menu)
|
||||
option_type = AppCommandType.chat_input.value if self.parent is None else AppCommandOptionType.subcommand.value
|
||||
return {
|
||||
base: Dict[str, Any] = {
|
||||
'name': self.name,
|
||||
'description': self.description,
|
||||
'type': option_type,
|
||||
'options': [param.to_dict() for param in self._params.values()],
|
||||
}
|
||||
|
||||
if self.parent is None:
|
||||
base['dm_permissions'] = not self.guild_only
|
||||
base['default_member_permissions'] = self.default_permissions and self.default_permissions.value
|
||||
|
||||
return base
|
||||
|
||||
async def _invoke_error_handler(self, interaction: Interaction, error: AppCommandError) -> None:
|
||||
# These type ignores are because the type checker can't narrow this type properly.
|
||||
if self.on_error is not None:
|
||||
@ -844,6 +871,12 @@ class ContextMenu:
|
||||
type: :class:`.AppCommandType`
|
||||
The type of context menu application command. By default, this is inferred
|
||||
by the parameter of the callback.
|
||||
default_permissions: Optional[:class:`Permissions`]
|
||||
The default permissions that can execute this command on Discord. Note
|
||||
that server administrators can override this value in the client.
|
||||
guild_only: :class:`bool`
|
||||
Whether the command should only be usable in guild contexts.
|
||||
Defaults to ``False``.
|
||||
checks
|
||||
A list of predicates that take a :class:`~discord.Interaction` parameter
|
||||
to indicate whether the command callback should be executed. If an exception
|
||||
@ -875,6 +908,10 @@ class ContextMenu:
|
||||
self.module: Optional[str] = callback.__module__
|
||||
self._guild_ids = guild_ids or getattr(callback, '__discord_app_commands_default_guilds__', None)
|
||||
self.on_error: Optional[UnboundError] = None
|
||||
self.default_permissions: Optional[Permissions] = getattr(
|
||||
callback, '__discord_app_commands_default_permissions__', None
|
||||
)
|
||||
self.guild_only: bool = getattr(callback, '__discord_app_commands_guild_only__', False)
|
||||
self.checks: List[Check] = getattr(callback, '__discord_app_commands_checks__', [])
|
||||
|
||||
@property
|
||||
@ -886,6 +923,8 @@ class ContextMenu:
|
||||
return {
|
||||
'name': self.name,
|
||||
'type': self.type.value,
|
||||
'dm_permissions': not self.guild_only,
|
||||
'default_member_permissions': self.default_permissions and self.default_permissions.value,
|
||||
}
|
||||
|
||||
async def _check_can_run(self, interaction: Interaction) -> bool:
|
||||
@ -983,6 +1022,16 @@ class Group:
|
||||
The description of the group. This shows up in the UI to describe
|
||||
the group. If not given, it defaults to the docstring of the
|
||||
class shortened to 100 characters.
|
||||
default_permissions: Optional[:class:`Permissions`]
|
||||
The default permissions that can execute this group on Discord. Note
|
||||
that server administrators can override this value in the client.
|
||||
|
||||
Due to a Discord limitation, this does not work on subcommands.
|
||||
guild_only: :class:`bool`
|
||||
Whether the group should only be usable in guild contexts.
|
||||
Defaults to ``False``.
|
||||
|
||||
Due to a Discord limitation, this does not work on subcommands.
|
||||
parent: Optional[:class:`Group`]
|
||||
The parent group. ``None`` if there isn't one.
|
||||
"""
|
||||
@ -1033,6 +1082,8 @@ class Group:
|
||||
description: str = MISSING,
|
||||
parent: Optional[Group] = None,
|
||||
guild_ids: Optional[List[int]] = None,
|
||||
guild_only: bool = False,
|
||||
default_permissions: Optional[Permissions] = None,
|
||||
):
|
||||
cls = self.__class__
|
||||
self.name: str = validate_name(name) if name is not MISSING else cls.__discord_app_commands_group_name__
|
||||
@ -1040,6 +1091,8 @@ class Group:
|
||||
self._attr: Optional[str] = None
|
||||
self._owner_cls: Optional[Type[Any]] = None
|
||||
self._guild_ids: Optional[List[int]] = guild_ids
|
||||
self.default_permissions: Optional[Permissions] = default_permissions
|
||||
self.guild_only: bool = guild_only
|
||||
|
||||
if not self.description:
|
||||
raise TypeError('groups must have a description')
|
||||
@ -1099,6 +1152,8 @@ class Group:
|
||||
copy.description = self.description
|
||||
copy.parent = parent
|
||||
copy.module = self.module
|
||||
copy.default_permissions = self.default_permissions
|
||||
copy.guild_only = self.guild_only
|
||||
copy._attr = self._attr
|
||||
copy._owner_cls = self._owner_cls
|
||||
copy._children = {}
|
||||
@ -1125,13 +1180,19 @@ class Group:
|
||||
# If this has a parent command then it's part of a subcommand group
|
||||
# Otherwise, it's just a regular command
|
||||
option_type = 1 if self.parent is None else AppCommandOptionType.subcommand_group.value
|
||||
return {
|
||||
base: Dict[str, Any] = {
|
||||
'name': self.name,
|
||||
'description': self.description,
|
||||
'type': option_type,
|
||||
'options': [child.to_dict() for child in self._children.values()],
|
||||
}
|
||||
|
||||
if self.parent is None:
|
||||
base['dm_permissions'] = not self.guild_only
|
||||
base['default_member_permissions'] = self.default_permissions and self.default_permissions.value
|
||||
|
||||
return base
|
||||
|
||||
@property
|
||||
def root_parent(self) -> Optional[Group]:
|
||||
"""Optional[:class:`Group`]: The parent of this group."""
|
||||
@ -1712,3 +1773,95 @@ def check(predicate: Check) -> Callable[[T], T]:
|
||||
return func
|
||||
|
||||
return decorator # type: ignore
|
||||
|
||||
|
||||
@overload
|
||||
def guild_only(func: None = ...) -> Callable[[T], T]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def guild_only(func: T) -> T:
|
||||
...
|
||||
|
||||
|
||||
def guild_only(func: Optional[T] = None) -> Union[T, Callable[[T], T]]:
|
||||
"""A decorator that indicates this command can only be used in a guild context.
|
||||
|
||||
This is **not** implemented as a :func:`check`, and is instead verified by Discord server side.
|
||||
Therefore, there is no error handler called when a command is used within a private message.
|
||||
|
||||
This decorator can be called with or without parentheses.
|
||||
|
||||
Due to a Discord limitation, this decorator does nothing in subcommands and is ignored.
|
||||
|
||||
Examples
|
||||
---------
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
@app_commands.command()
|
||||
@app_commands.guild_only()
|
||||
async def my_guild_only_command(interaction: discord.Interaction) -> None:
|
||||
await interaction.response.send_message('I am only available in guilds!')
|
||||
"""
|
||||
|
||||
def inner(f: T) -> T:
|
||||
if isinstance(f, (Command, Group, ContextMenu)):
|
||||
f.guild_only = True
|
||||
else:
|
||||
f.__discord_app_commands_guild_only__ = True # type: ignore # Runtime attribute assignment
|
||||
return f
|
||||
|
||||
# Check if called with parentheses or not
|
||||
if func is None:
|
||||
# Called with parentheses
|
||||
return inner
|
||||
else:
|
||||
return inner(func)
|
||||
|
||||
|
||||
def default_permissions(**perms: bool) -> Callable[[T], T]:
|
||||
r"""A decorator that sets the default permissions needed to execute this command.
|
||||
|
||||
When this decorator is used, by default users must have these permissions to execute the command.
|
||||
However, an administrator can change the permissions needed to execute this command using the official
|
||||
client. Therefore, this only serves as a hint.
|
||||
|
||||
This is sent to Discord server side, and is not a :func:`check`. Therefore, error handlers are not called.
|
||||
|
||||
Due to a Discord limitation, this decorator does nothing in subcommands and is ignored.
|
||||
|
||||
.. warning::
|
||||
|
||||
This serves as a *hint* and members are *not* required to have the permissions given to actually
|
||||
execute this command. If you want to ensure that members have the permissions needed, consider using
|
||||
:func:`~discord.app_commands.checks.has_permissions` instead.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
\*\*perms: :class:`bool`
|
||||
Keyword arguments denoting the permissions to set as the default.
|
||||
|
||||
Example
|
||||
---------
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
@app_commands.command()
|
||||
@app_commands.default_permissions(manage_messages=True)
|
||||
async def test(interaction: discord.Interaction):
|
||||
await interaction.response.send_message('You may or may not have manage messages.')
|
||||
"""
|
||||
|
||||
permissions = Permissions(**perms)
|
||||
|
||||
def decorator(func: T) -> T:
|
||||
if isinstance(func, (Command, Group, ContextMenu)):
|
||||
func.default_permissions = permissions
|
||||
else:
|
||||
func.__discord_app_commands_default_permissions__ = permissions # type: ignore # Runtime attribute assignment
|
||||
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
@ -108,6 +108,10 @@ class AppCommand(Hashable):
|
||||
The application command's name.
|
||||
description: :class:`str`
|
||||
The application command's description.
|
||||
default_member_permissions: Optional[:class:`~discord.Permissions`]
|
||||
The default member permissions that can run this command.
|
||||
dm_permissions: :class:`bool`
|
||||
A boolean that indicates whether this command can be run in direct messages.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
@ -117,6 +121,8 @@ class AppCommand(Hashable):
|
||||
'name',
|
||||
'description',
|
||||
'options',
|
||||
'default_member_permissions',
|
||||
'dm_permissions',
|
||||
'_state',
|
||||
)
|
||||
|
||||
@ -133,6 +139,19 @@ class AppCommand(Hashable):
|
||||
self.options: List[Union[Argument, AppCommandGroup]] = [
|
||||
app_command_option_factory(data=d, parent=self, state=self._state) for d in data.get('options', [])
|
||||
]
|
||||
self.default_member_permissions: Optional[Permissions]
|
||||
permissions = data.get('default_member_permissions')
|
||||
if permissions is None:
|
||||
self.default_member_permissions = None
|
||||
else:
|
||||
self.default_member_permissions = Permissions(int(permissions))
|
||||
|
||||
dm_permissions = data.get('dm_permissions')
|
||||
# For some reason this field can be explicit null and mean True
|
||||
if dm_permissions is None:
|
||||
dm_permissions = True
|
||||
|
||||
self.dm_permissions: bool = dm_permissions
|
||||
|
||||
def to_dict(self) -> ApplicationCommandPayload:
|
||||
return {
|
||||
|
@ -24,7 +24,7 @@ DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Literal, TypedDict, Union
|
||||
from typing import List, Literal, Optional, TypedDict, Union
|
||||
from typing_extensions import NotRequired, Required
|
||||
|
||||
from .channel import ChannelType
|
||||
@ -134,6 +134,8 @@ class _BaseApplicationCommand(TypedDict):
|
||||
id: Snowflake
|
||||
application_id: Snowflake
|
||||
name: str
|
||||
dm_permissions: NotRequired[Optional[bool]]
|
||||
default_member_permissions: NotRequired[Optional[str]]
|
||||
version: Snowflake
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user