Browse Source

Implement Application Command Permissions models

pull/8121/head
Soheab 3 years ago
committed by GitHub
parent
commit
3aa55ba1ed
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 156
      discord/app_commands/models.py
  2. 87
      discord/audit_logs.py
  3. 7
      discord/enums.py
  4. 14
      discord/http.py
  5. 8
      docs/api.rst
  6. 32
      docs/interactions/api.rst

156
discord/app_commands/models.py

@ -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]:

87
discord/audit_logs.py

@ -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:

7
discord/enums.py

@ -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}'

14
discord/http.py

@ -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]:

8
docs/api.rst

@ -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

32
docs/interactions/api.rst

@ -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…
Cancel
Save