mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-04-19 15:36:02 +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 ..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 ..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__ = (
|
||||
'AppCommand',
|
||||
'AppCommandGroup',
|
||||
'AppCommandChannel',
|
||||
'AppCommandThread',
|
||||
'AppCommandPermissions',
|
||||
'GuildAppCommandPermissions',
|
||||
'Argument',
|
||||
'Choice',
|
||||
'AllChannels',
|
||||
@ -54,6 +58,8 @@ if TYPE_CHECKING:
|
||||
ApplicationCommand as ApplicationCommandPayload,
|
||||
ApplicationCommandOptionChoice,
|
||||
ApplicationCommandOption,
|
||||
ApplicationCommandPermissions,
|
||||
GuildApplicationCommandPermissions,
|
||||
)
|
||||
from ..types.interactions import (
|
||||
PartialChannel,
|
||||
@ -63,10 +69,15 @@ if TYPE_CHECKING:
|
||||
ThreadMetadata,
|
||||
ThreadArchiveDuration,
|
||||
)
|
||||
|
||||
from ..abc import Snowflake
|
||||
from ..state import ConnectionState
|
||||
from ..guild import GuildChannel, Guild
|
||||
from ..channel import TextChannel
|
||||
from ..threads import Thread
|
||||
from ..role import Role
|
||||
from ..user import User
|
||||
from ..member import Member
|
||||
|
||||
ApplicationCommandParent = Union['AppCommand', 'AppCommandGroup']
|
||||
|
||||
@ -328,6 +339,45 @@ class AppCommand(Hashable):
|
||||
)
|
||||
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]):
|
||||
"""Represents an application command argument choice.
|
||||
@ -804,6 +854,108 @@ class AppCommandGroup:
|
||||
} # 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(
|
||||
parent: ApplicationCommandParent, data: ApplicationCommandOption, *, state: Optional[ConnectionState] = None
|
||||
) -> Union[Argument, AppCommandGroup]:
|
||||
|
@ -67,7 +67,7 @@ if TYPE_CHECKING:
|
||||
from .sticker import GuildSticker
|
||||
from .threads import Thread
|
||||
from .integrations import PartialIntegration
|
||||
from .app_commands import AppCommand
|
||||
from .app_commands import AppCommand, AppCommandPermissions
|
||||
|
||||
TargetType = Union[
|
||||
Guild,
|
||||
@ -98,6 +98,20 @@ def _transform_snowflake(entry: AuditLogEntry, data: Snowflake) -> int:
|
||||
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]]:
|
||||
if data is None:
|
||||
return None
|
||||
@ -261,6 +275,7 @@ class AuditLogChanges:
|
||||
'entity_type': (None, _enum_transformer(enums.EntityType)),
|
||||
'preferred_locale': (None, _enum_transformer(enums.Locale)),
|
||||
'image_hash': ('cover_image', _transform_cover_image),
|
||||
'app_command_permission_update': ('app_command_permissions', _transform_app_command_permissions),
|
||||
}
|
||||
# fmt: on
|
||||
|
||||
@ -268,26 +283,14 @@ class AuditLogChanges:
|
||||
self.before: 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
|
||||
# element in data is a different target
|
||||
self.before.app_command_permissions = []
|
||||
self.after.app_command_permissions = []
|
||||
|
||||
for d in data:
|
||||
|
||||
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']
|
||||
# key is the target id
|
||||
if entry.action is enums.AuditLogAction.app_command_permission_update:
|
||||
attr = entry.action.name
|
||||
else:
|
||||
attr = elem['key']
|
||||
|
||||
# special cases for role add/remove
|
||||
if attr == '$add':
|
||||
@ -357,52 +360,6 @@ class AuditLogChanges:
|
||||
|
||||
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:
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
|
@ -62,6 +62,7 @@ __all__ = (
|
||||
'EventStatus',
|
||||
'AppCommandType',
|
||||
'AppCommandOptionType',
|
||||
'AppCommandPermissionType',
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -682,6 +683,12 @@ class AppCommandType(Enum):
|
||||
message = 3
|
||||
|
||||
|
||||
class AppCommandPermissionType(Enum):
|
||||
role = 1
|
||||
user = 2
|
||||
channel = 3
|
||||
|
||||
|
||||
def create_unknown_value(cls: Type[E], val: Any) -> E:
|
||||
value_cls = cls._enum_value_cls_ # type: ignore # This is narrowed below
|
||||
name = f'unknown_{val}'
|
||||
|
@ -2039,20 +2039,6 @@ class HTTPClient:
|
||||
)
|
||||
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
|
||||
|
||||
def application_info(self) -> Response[appinfo.AppInfo]:
|
||||
|
@ -3528,13 +3528,9 @@ AuditLogDiff
|
||||
|
||||
.. attribute:: app_command_permissions
|
||||
|
||||
A list of application command permission tuples that represents a
|
||||
target and a :class:`bool` for said target.
|
||||
The permissions of the app command.
|
||||
|
||||
The first element is the object being targeted, which can either
|
||||
be a :class:`Member`, :class:`abc.GuildChannel`,
|
||||
:class:`~discord.app_commands.AllChannels`, or :class:`Role`.
|
||||
:type: List[Tuple[target, :class:`bool`]]
|
||||
:type: :class:`~discord.app_commands.AppCommandPermissions`
|
||||
|
||||
.. this is currently missing the following keys: reason and application_id
|
||||
I'm not sure how to about porting these
|
||||
|
@ -121,6 +121,22 @@ AppCommandThread
|
||||
.. autoclass:: discord.app_commands.AppCommandThread()
|
||||
: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
|
||||
~~~~~~~~~~
|
||||
|
||||
@ -360,6 +376,22 @@ Enumerations
|
||||
|
||||
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:
|
||||
|
||||
Bot UI Kit
|
||||
|
Loading…
x
Reference in New Issue
Block a user