mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-07-07 02:21:54 +00:00
Implement Application Command Permissions models
This commit is contained in:
parent
4e7529138c
commit
3aa55ba1ed
@ -27,16 +27,20 @@ from datetime import datetime
|
|||||||
|
|
||||||
from .errors import MissingApplicationID
|
from .errors import MissingApplicationID
|
||||||
from ..permissions import Permissions
|
from ..permissions import Permissions
|
||||||
from ..enums import AppCommandOptionType, AppCommandType, ChannelType, try_enum
|
from ..enums import AppCommandOptionType, AppCommandType, AppCommandPermissionType, ChannelType, try_enum
|
||||||
from ..mixins import Hashable
|
from ..mixins import Hashable
|
||||||
from ..utils import _get_as_snowflake, parse_time, snowflake_time, MISSING
|
from ..utils import _get_as_snowflake, parse_time, snowflake_time, MISSING
|
||||||
from typing import Generic, List, TYPE_CHECKING, Optional, TypeVar, Union
|
from ..object import Object
|
||||||
|
|
||||||
|
from typing import Any, Dict, Generic, List, TYPE_CHECKING, Optional, TypeVar, Union
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'AppCommand',
|
'AppCommand',
|
||||||
'AppCommandGroup',
|
'AppCommandGroup',
|
||||||
'AppCommandChannel',
|
'AppCommandChannel',
|
||||||
'AppCommandThread',
|
'AppCommandThread',
|
||||||
|
'AppCommandPermissions',
|
||||||
|
'GuildAppCommandPermissions',
|
||||||
'Argument',
|
'Argument',
|
||||||
'Choice',
|
'Choice',
|
||||||
'AllChannels',
|
'AllChannels',
|
||||||
@ -54,6 +58,8 @@ if TYPE_CHECKING:
|
|||||||
ApplicationCommand as ApplicationCommandPayload,
|
ApplicationCommand as ApplicationCommandPayload,
|
||||||
ApplicationCommandOptionChoice,
|
ApplicationCommandOptionChoice,
|
||||||
ApplicationCommandOption,
|
ApplicationCommandOption,
|
||||||
|
ApplicationCommandPermissions,
|
||||||
|
GuildApplicationCommandPermissions,
|
||||||
)
|
)
|
||||||
from ..types.interactions import (
|
from ..types.interactions import (
|
||||||
PartialChannel,
|
PartialChannel,
|
||||||
@ -63,10 +69,15 @@ if TYPE_CHECKING:
|
|||||||
ThreadMetadata,
|
ThreadMetadata,
|
||||||
ThreadArchiveDuration,
|
ThreadArchiveDuration,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from ..abc import Snowflake
|
||||||
from ..state import ConnectionState
|
from ..state import ConnectionState
|
||||||
from ..guild import GuildChannel, Guild
|
from ..guild import GuildChannel, Guild
|
||||||
from ..channel import TextChannel
|
from ..channel import TextChannel
|
||||||
from ..threads import Thread
|
from ..threads import Thread
|
||||||
|
from ..role import Role
|
||||||
|
from ..user import User
|
||||||
|
from ..member import Member
|
||||||
|
|
||||||
ApplicationCommandParent = Union['AppCommand', 'AppCommandGroup']
|
ApplicationCommandParent = Union['AppCommand', 'AppCommandGroup']
|
||||||
|
|
||||||
@ -328,6 +339,45 @@ class AppCommand(Hashable):
|
|||||||
)
|
)
|
||||||
return AppCommand(data=data, state=state)
|
return AppCommand(data=data, state=state)
|
||||||
|
|
||||||
|
async def fetch_permissions(self, guild: Snowflake) -> GuildAppCommandPermissions:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Retrieves this command's permission in the guild.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
guild: :class:`~discord.abc.Snowflake`
|
||||||
|
The guild to retrieve the permissions from.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
Forbidden
|
||||||
|
You do not have permission to fetch the application command's permissions.
|
||||||
|
HTTPException
|
||||||
|
Fetching the application command's permissions failed.
|
||||||
|
MissingApplicationID
|
||||||
|
The client does not have an application ID.
|
||||||
|
NotFound
|
||||||
|
The application command's permissions could not be found.
|
||||||
|
This can also indicate that the permissions are synced with the guild
|
||||||
|
(i.e. they are unchanged from the default).
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`GuildAppCommandPermissions`
|
||||||
|
An object representing the application command's permissions in the guild.
|
||||||
|
"""
|
||||||
|
state = self._state
|
||||||
|
if not state.application_id:
|
||||||
|
raise MissingApplicationID
|
||||||
|
|
||||||
|
data = await state.http.get_application_command_permissions(
|
||||||
|
state.application_id,
|
||||||
|
guild.id,
|
||||||
|
self.id,
|
||||||
|
)
|
||||||
|
return GuildAppCommandPermissions(data=data, state=state, command=self)
|
||||||
|
|
||||||
|
|
||||||
class Choice(Generic[ChoiceT]):
|
class Choice(Generic[ChoiceT]):
|
||||||
"""Represents an application command argument choice.
|
"""Represents an application command argument choice.
|
||||||
@ -804,6 +854,108 @@ class AppCommandGroup:
|
|||||||
} # type: ignore # Type checker does not understand this literal.
|
} # type: ignore # Type checker does not understand this literal.
|
||||||
|
|
||||||
|
|
||||||
|
class AppCommandPermissions:
|
||||||
|
"""Represents the permissions for an application command.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
-----------
|
||||||
|
guild: :class:`~discord.Guild`
|
||||||
|
The guild assosiated with this permission.
|
||||||
|
id: :class:`int`
|
||||||
|
The ID of the permission target, such as a role, channel, or guild.
|
||||||
|
The special ``guild_id - 1`` sentinel is used to represent "all channels".
|
||||||
|
target: Any
|
||||||
|
The role, user, or channel associated with this permission. This could also be the :class:`AllChannels` sentinel type.
|
||||||
|
Falls back to :class:`~discord.Object` if the target could not be found in the cache.
|
||||||
|
type: :class:`.AppCommandPermissionType`
|
||||||
|
The type of permission.
|
||||||
|
permission: :class:`bool`
|
||||||
|
The permission value. True for allow, False for deny.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ('id', 'type', 'permission', 'target', 'guild', '_state')
|
||||||
|
|
||||||
|
def __init__(self, *, data: ApplicationCommandPermissions, guild: Optional[Guild], state: ConnectionState) -> None:
|
||||||
|
self._state: ConnectionState = state
|
||||||
|
self.guild: Optional[Guild] = guild
|
||||||
|
|
||||||
|
self.id: int = int(data['id'])
|
||||||
|
self.type: AppCommandPermissionType = try_enum(AppCommandPermissionType, data['type'])
|
||||||
|
self.permission: bool = data['permission']
|
||||||
|
|
||||||
|
_object = None
|
||||||
|
|
||||||
|
if self.type is AppCommandPermissionType.user:
|
||||||
|
if guild:
|
||||||
|
_object = guild.get_member(self.id)
|
||||||
|
else:
|
||||||
|
_object = self._state.get_user(self.id)
|
||||||
|
elif guild and self.type is AppCommandPermissionType.channel:
|
||||||
|
if self.id == (guild.id - 1):
|
||||||
|
_object = AllChannels(guild)
|
||||||
|
else:
|
||||||
|
_object = guild.get_channel(self.id)
|
||||||
|
elif guild and self.type is AppCommandPermissionType.role:
|
||||||
|
_object = guild.get_role(self.id)
|
||||||
|
|
||||||
|
if _object is None:
|
||||||
|
_object = Object(id=self.id)
|
||||||
|
|
||||||
|
self.target: Union[Object, User, Member, Role, AllChannels, GuildChannel] = _object
|
||||||
|
|
||||||
|
def to_dict(self) -> ApplicationCommandPermissions:
|
||||||
|
return {
|
||||||
|
'id': self.target.id,
|
||||||
|
'type': self.type.value,
|
||||||
|
'permission': self.permission,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class GuildAppCommandPermissions:
|
||||||
|
"""Represents the permissions for an application command in a guild.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
-----------
|
||||||
|
application_id: :class:`int`
|
||||||
|
The application ID.
|
||||||
|
command: :class:`.AppCommand`
|
||||||
|
The application command associated with the permissions.
|
||||||
|
id: :class:`int`
|
||||||
|
ID of the command or the application ID.
|
||||||
|
When this is the application ID instead of a command ID,
|
||||||
|
the permissions apply to all commands that do not contain explicit overwrites.
|
||||||
|
guild_id: :class:`int`
|
||||||
|
The guild ID associated with the permissions.
|
||||||
|
permissions: List[:class:`AppCommandPermissions`]
|
||||||
|
The permissions, this is a max of 100.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ('id', 'application_id', 'command', 'guild_id', 'permissions', '_state')
|
||||||
|
|
||||||
|
def __init__(self, *, data: GuildApplicationCommandPermissions, state: ConnectionState, command: AppCommand) -> None:
|
||||||
|
self._state: ConnectionState = state
|
||||||
|
self.command: AppCommand = command
|
||||||
|
|
||||||
|
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[AppCommandPermissions] = [
|
||||||
|
AppCommandPermissions(data=value, guild=self.guild, state=self._state) for value in data['permissions']
|
||||||
|
]
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
return {'permissions': [p.to_dict() for p in self.permissions]}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def guild(self) -> Optional[Guild]:
|
||||||
|
"""Optional[:class:`~discord.Guild`]: The guild associated with the permissions."""
|
||||||
|
return self._state._get_guild(self.guild_id)
|
||||||
|
|
||||||
|
|
||||||
def app_command_option_factory(
|
def app_command_option_factory(
|
||||||
parent: ApplicationCommandParent, data: ApplicationCommandOption, *, state: Optional[ConnectionState] = None
|
parent: ApplicationCommandParent, data: ApplicationCommandOption, *, state: Optional[ConnectionState] = None
|
||||||
) -> Union[Argument, AppCommandGroup]:
|
) -> Union[Argument, AppCommandGroup]:
|
||||||
|
@ -67,7 +67,7 @@ if TYPE_CHECKING:
|
|||||||
from .sticker import GuildSticker
|
from .sticker import GuildSticker
|
||||||
from .threads import Thread
|
from .threads import Thread
|
||||||
from .integrations import PartialIntegration
|
from .integrations import PartialIntegration
|
||||||
from .app_commands import AppCommand
|
from .app_commands import AppCommand, AppCommandPermissions
|
||||||
|
|
||||||
TargetType = Union[
|
TargetType = Union[
|
||||||
Guild,
|
Guild,
|
||||||
@ -98,6 +98,20 @@ def _transform_snowflake(entry: AuditLogEntry, data: Snowflake) -> int:
|
|||||||
return int(data)
|
return int(data)
|
||||||
|
|
||||||
|
|
||||||
|
def _transform_app_command_permissions(
|
||||||
|
entry: AuditLogEntry, data: ApplicationCommandPermissions
|
||||||
|
) -> Optional[AppCommandPermissions]:
|
||||||
|
# avoid circular import
|
||||||
|
from discord.app_commands.models import AppCommandPermissions
|
||||||
|
|
||||||
|
if data is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
state = entry._state
|
||||||
|
guild = entry.guild
|
||||||
|
return AppCommandPermissions(data=data, guild=guild, state=state)
|
||||||
|
|
||||||
|
|
||||||
def _transform_channel(entry: AuditLogEntry, data: Optional[Snowflake]) -> Optional[Union[abc.GuildChannel, Object]]:
|
def _transform_channel(entry: AuditLogEntry, data: Optional[Snowflake]) -> Optional[Union[abc.GuildChannel, Object]]:
|
||||||
if data is None:
|
if data is None:
|
||||||
return None
|
return None
|
||||||
@ -261,6 +275,7 @@ class AuditLogChanges:
|
|||||||
'entity_type': (None, _enum_transformer(enums.EntityType)),
|
'entity_type': (None, _enum_transformer(enums.EntityType)),
|
||||||
'preferred_locale': (None, _enum_transformer(enums.Locale)),
|
'preferred_locale': (None, _enum_transformer(enums.Locale)),
|
||||||
'image_hash': ('cover_image', _transform_cover_image),
|
'image_hash': ('cover_image', _transform_cover_image),
|
||||||
|
'app_command_permission_update': ('app_command_permissions', _transform_app_command_permissions),
|
||||||
}
|
}
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
||||||
@ -268,26 +283,14 @@ class AuditLogChanges:
|
|||||||
self.before: AuditLogDiff = AuditLogDiff()
|
self.before: AuditLogDiff = AuditLogDiff()
|
||||||
self.after: AuditLogDiff = AuditLogDiff()
|
self.after: AuditLogDiff = AuditLogDiff()
|
||||||
|
|
||||||
if entry.action is enums.AuditLogAction.app_command_permission_update:
|
for elem in data:
|
||||||
# special case entire process since each
|
# special case entire process since each
|
||||||
# element in data is a different target
|
# element in data is a different target
|
||||||
self.before.app_command_permissions = []
|
# key is the target id
|
||||||
self.after.app_command_permissions = []
|
if entry.action is enums.AuditLogAction.app_command_permission_update:
|
||||||
|
attr = entry.action.name
|
||||||
for d in data:
|
else:
|
||||||
|
attr = elem['key']
|
||||||
self._handle_app_command_permissions(
|
|
||||||
self.before,
|
|
||||||
self.after,
|
|
||||||
entry,
|
|
||||||
int(d['key']),
|
|
||||||
d.get('old_value'), # type: ignore # old value will be an ApplicationCommandPermissions if present
|
|
||||||
d.get('new_value'), # type: ignore # new value will be an ApplicationCommandPermissions if present
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
for elem in data:
|
|
||||||
attr = elem['key']
|
|
||||||
|
|
||||||
# special cases for role add/remove
|
# special cases for role add/remove
|
||||||
if attr == '$add':
|
if attr == '$add':
|
||||||
@ -357,52 +360,6 @@ class AuditLogChanges:
|
|||||||
|
|
||||||
setattr(second, 'roles', data)
|
setattr(second, 'roles', data)
|
||||||
|
|
||||||
def _handle_app_command_permissions(
|
|
||||||
self,
|
|
||||||
before: AuditLogDiff,
|
|
||||||
after: AuditLogDiff,
|
|
||||||
entry: AuditLogEntry,
|
|
||||||
target_id: int,
|
|
||||||
old_value: Optional[ApplicationCommandPermissions],
|
|
||||||
new_value: Optional[ApplicationCommandPermissions],
|
|
||||||
):
|
|
||||||
guild = entry.guild
|
|
||||||
|
|
||||||
old_permission = new_permission = target = None
|
|
||||||
|
|
||||||
if target_id == (guild.id - 1):
|
|
||||||
# avoid circular import
|
|
||||||
from .app_commands import AllChannels
|
|
||||||
|
|
||||||
# all channels
|
|
||||||
target = AllChannels(guild)
|
|
||||||
else:
|
|
||||||
# get type and determine role, user or channel
|
|
||||||
_value = old_value or new_value
|
|
||||||
if _value is None:
|
|
||||||
return
|
|
||||||
permission_type = _value['type']
|
|
||||||
if permission_type == 1:
|
|
||||||
# role
|
|
||||||
target = guild.get_role(target_id)
|
|
||||||
elif permission_type == 2:
|
|
||||||
# user
|
|
||||||
target = entry._get_member(target_id)
|
|
||||||
elif permission_type == 3:
|
|
||||||
# channel
|
|
||||||
target = guild.get_channel(target_id)
|
|
||||||
|
|
||||||
if target is None:
|
|
||||||
target = Object(target_id)
|
|
||||||
|
|
||||||
if old_value is not None:
|
|
||||||
old_permission = old_value['permission']
|
|
||||||
before.app_command_permissions.append((target, old_permission))
|
|
||||||
|
|
||||||
if new_value is not None:
|
|
||||||
new_permission = new_value['permission']
|
|
||||||
after.app_command_permissions.append((target, new_permission))
|
|
||||||
|
|
||||||
|
|
||||||
class _AuditLogProxy:
|
class _AuditLogProxy:
|
||||||
def __init__(self, **kwargs: Any) -> None:
|
def __init__(self, **kwargs: Any) -> None:
|
||||||
|
@ -62,6 +62,7 @@ __all__ = (
|
|||||||
'EventStatus',
|
'EventStatus',
|
||||||
'AppCommandType',
|
'AppCommandType',
|
||||||
'AppCommandOptionType',
|
'AppCommandOptionType',
|
||||||
|
'AppCommandPermissionType',
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -682,6 +683,12 @@ class AppCommandType(Enum):
|
|||||||
message = 3
|
message = 3
|
||||||
|
|
||||||
|
|
||||||
|
class AppCommandPermissionType(Enum):
|
||||||
|
role = 1
|
||||||
|
user = 2
|
||||||
|
channel = 3
|
||||||
|
|
||||||
|
|
||||||
def create_unknown_value(cls: Type[E], val: Any) -> E:
|
def create_unknown_value(cls: Type[E], val: Any) -> E:
|
||||||
value_cls = cls._enum_value_cls_ # type: ignore # This is narrowed below
|
value_cls = cls._enum_value_cls_ # type: ignore # This is narrowed below
|
||||||
name = f'unknown_{val}'
|
name = f'unknown_{val}'
|
||||||
|
@ -2039,20 +2039,6 @@ class HTTPClient:
|
|||||||
)
|
)
|
||||||
return self.request(r, json=payload)
|
return self.request(r, json=payload)
|
||||||
|
|
||||||
def bulk_edit_guild_application_command_permissions(
|
|
||||||
self,
|
|
||||||
application_id: Snowflake,
|
|
||||||
guild_id: Snowflake,
|
|
||||||
payload: List[Dict[str, Any]],
|
|
||||||
) -> Response[None]:
|
|
||||||
r = Route(
|
|
||||||
'PUT',
|
|
||||||
'/applications/{application_id}/guilds/{guild_id}/commands/permissions',
|
|
||||||
application_id=application_id,
|
|
||||||
guild_id=guild_id,
|
|
||||||
)
|
|
||||||
return self.request(r, json=payload)
|
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
|
|
||||||
def application_info(self) -> Response[appinfo.AppInfo]:
|
def application_info(self) -> Response[appinfo.AppInfo]:
|
||||||
|
@ -3528,13 +3528,9 @@ AuditLogDiff
|
|||||||
|
|
||||||
.. attribute:: app_command_permissions
|
.. attribute:: app_command_permissions
|
||||||
|
|
||||||
A list of application command permission tuples that represents a
|
The permissions of the app command.
|
||||||
target and a :class:`bool` for said target.
|
|
||||||
|
|
||||||
The first element is the object being targeted, which can either
|
:type: :class:`~discord.app_commands.AppCommandPermissions`
|
||||||
be a :class:`Member`, :class:`abc.GuildChannel`,
|
|
||||||
:class:`~discord.app_commands.AllChannels`, or :class:`Role`.
|
|
||||||
:type: List[Tuple[target, :class:`bool`]]
|
|
||||||
|
|
||||||
.. this is currently missing the following keys: reason and application_id
|
.. this is currently missing the following keys: reason and application_id
|
||||||
I'm not sure how to about porting these
|
I'm not sure how to about porting these
|
||||||
|
@ -121,6 +121,22 @@ AppCommandThread
|
|||||||
.. autoclass:: discord.app_commands.AppCommandThread()
|
.. autoclass:: discord.app_commands.AppCommandThread()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
AppCommandPermissions
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: discord.app_commands.AppCommandPermissions
|
||||||
|
|
||||||
|
.. autoclass:: discord.app_commands.AppCommandPermissions()
|
||||||
|
:members:
|
||||||
|
|
||||||
|
GuildAppCommandPermissions
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: discord.app_commands.GuildAppCommandPermissions
|
||||||
|
|
||||||
|
.. autoclass:: discord.app_commands.GuildAppCommandPermissions()
|
||||||
|
:members:
|
||||||
|
|
||||||
Argument
|
Argument
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
@ -360,6 +376,22 @@ Enumerations
|
|||||||
|
|
||||||
A message context menu command.
|
A message context menu command.
|
||||||
|
|
||||||
|
.. class:: AppCommandPermissionType
|
||||||
|
|
||||||
|
The application command's permission type.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. attribute:: role
|
||||||
|
|
||||||
|
The permission is for a role.
|
||||||
|
.. attribute:: channel
|
||||||
|
|
||||||
|
The permission is for one or all channels.
|
||||||
|
.. attribute:: user
|
||||||
|
|
||||||
|
The permission is for a user.
|
||||||
|
|
||||||
.. _discord_ui_kit:
|
.. _discord_ui_kit:
|
||||||
|
|
||||||
Bot UI Kit
|
Bot UI Kit
|
||||||
|
Loading…
x
Reference in New Issue
Block a user