Skip to content

Commit 8e20fad

Browse files
authored
Merge pull request #156 from benwoo1110/permissions
Add Application Permissions Support
2 parents 6377c5e + 54e79e7 commit 8e20fad

File tree

6 files changed

+531
-41
lines changed

6 files changed

+531
-41
lines changed

discord_slash/client.py

Lines changed: 171 additions & 31 deletions
Large diffs are not rendered by default.

discord_slash/cog_ext.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import typing
22
import inspect
3-
from .model import CogCommandObject, CogSubcommandObject
3+
from .model import CogBaseCommandObject, CogSubcommandObject
44
from .utils import manage_commands
55

66

@@ -9,6 +9,8 @@ def cog_slash(*,
99
description: str = None,
1010
guild_ids: typing.List[int] = None,
1111
options: typing.List[dict] = None,
12+
default_permission: bool = True,
13+
permissions: dict = None,
1214
connector: dict = None):
1315
"""
1416
Decorator for Cog to add slash command.\n
@@ -34,6 +36,10 @@ async def ping(self, ctx: SlashContext):
3436
:type guild_ids: List[int]
3537
:param options: Options of the slash command. This will affect ``auto_convert`` and command data at Discord API. Default ``None``.
3638
:type options: List[dict]
39+
:param default_permission: Sets if users have permission to run slash command by default, when no permissions are set. Default ``True``.
40+
:type default_permission: bool
41+
:param permissions: Permission requirements of the slash command. Default ``None``.
42+
:type permissions: dict
3743
:param connector: Kwargs connector for the command. Default ``None``.
3844
:type connector: dict
3945
"""
@@ -49,10 +55,12 @@ def wrapper(cmd):
4955
"description": desc,
5056
"guild_ids": guild_ids,
5157
"api_options": opts,
58+
"default_permission": default_permission,
59+
"api_permissions": permissions,
5260
"connector": connector,
5361
"has_subcommands": False
5462
}
55-
return CogCommandObject(name or cmd.__name__, _cmd)
63+
return CogBaseCommandObject(name or cmd.__name__, _cmd)
5664
return wrapper
5765

5866

@@ -63,6 +71,8 @@ def cog_subcommand(*,
6371
description: str = None,
6472
base_description: str = None,
6573
base_desc: str = None,
74+
base_default_permission: bool = True,
75+
base_permissions: dict = None,
6676
subcommand_group_description: str = None,
6777
sub_group_desc: str = None,
6878
guild_ids: typing.List[int] = None,
@@ -95,6 +105,10 @@ async def group_say(self, ctx: SlashContext, text: str):
95105
:param base_description: Description of the base command. Default ``None``.
96106
:type base_description: str
97107
:param base_desc: Alias of ``base_description``.
108+
:param base_default_permission: Sets if users have permission to run slash command by default, when no permissions are set. Default ``True``.
109+
:type base_default_permission: bool
110+
:param base_permissions: Permission requirements of the slash command. Default ``None``.
111+
:type base_permissions: dict
98112
:param subcommand_group_description: Description of the subcommand_group. Default ``None``.
99113
:type subcommand_group_description: str
100114
:param sub_group_desc: Alias of ``subcommand_group_description``.
@@ -107,6 +121,7 @@ async def group_say(self, ctx: SlashContext, text: str):
107121
"""
108122
base_description = base_description or base_desc
109123
subcommand_group_description = subcommand_group_description or sub_group_desc
124+
guild_ids = guild_ids if guild_ids else []
110125

111126
def wrapper(cmd):
112127
desc = description or inspect.getdoc(cmd)
@@ -115,6 +130,17 @@ def wrapper(cmd):
115130
else:
116131
opts = options
117132

133+
_cmd = {
134+
"func": None,
135+
"description": base_description,
136+
"guild_ids": guild_ids.copy(),
137+
"api_options": [],
138+
"default_permission": base_default_permission,
139+
"api_permissions": base_permissions,
140+
"connector": {},
141+
"has_subcommands": True
142+
}
143+
118144
_sub = {
119145
"func": cmd,
120146
"name": name or cmd.__name__,
@@ -125,5 +151,5 @@ def wrapper(cmd):
125151
"api_options": opts,
126152
"connector": connector
127153
}
128-
return CogSubcommandObject(_sub, base, name or cmd.__name__, subcommand_group)
154+
return CogSubcommandObject(base, _cmd, subcommand_group, name or cmd.__name__, _sub)
129155
return wrapper

discord_slash/http.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,24 @@ def get_all_commands(self, guild_id=None):
5454
"""
5555
return self.command_request(method="GET", guild_id=guild_id)
5656

57+
def get_all_guild_commands_permissions(self, guild_id):
58+
"""
59+
Sends a slash command get request to Discord API for all permissions of a guild.
60+
61+
:param guild_id: ID of the target guild to get registered command permissions of.
62+
:return: JSON Response of the request.
63+
"""
64+
return self.command_request(method="GET", guild_id=guild_id, url_ending="/permissions")
65+
66+
def update_guild_commands_permissions(self, guild_id, perms_dict):
67+
"""
68+
Sends a slash command put request to the Discord API for setting all command permissions of a guild.
69+
70+
:param guild_id: ID of the target guild to register command permissions.
71+
:return: JSON Response of the request.
72+
"""
73+
return self.command_request(method="PUT", guild_id=guild_id, json=perms_dict, url_ending="/permissions")
74+
5775
def add_slash_command(
5876
self, guild_id, cmd_name: str, description: str, options: list = None
5977
):
@@ -167,4 +185,3 @@ def delete(self, token, message_id="@original"):
167185
"""
168186
req_url = f"/messages/{message_id}"
169187
return self.command_response(token, True, "DELETE", url_ending = req_url)
170-

discord_slash/model.py

Lines changed: 90 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@ def __init__(self, name, cmd): # Let's reuse old command formatting.
123123
self.allowed_guild_ids = cmd["guild_ids"] or []
124124
self.options = cmd["api_options"] or []
125125
self.connector = cmd["connector"] or {}
126-
self.has_subcommands = cmd["has_subcommands"]
127126
# Ref https://github.com/Rapptz/discord.py/blob/master/discord/ext/commands/core.py#L1447
128127
# Since this isn't inherited from `discord.ext.commands.Command`, discord.py's check decorator will
129128
# add checks at this var.
@@ -176,6 +175,28 @@ async def can_run(self, ctx) -> bool:
176175
return False not in res
177176

178177

178+
class BaseCommandObject(CommandObject):
179+
"""
180+
BaseCommand object of this extension.
181+
182+
.. note::
183+
This model inherits :class:`.model.CommandObject`, so this has every variables from that.
184+
185+
.. warning::
186+
Do not manually init this model.
187+
188+
:ivar has_subcommands: Indicates whether this base command has subcommands.
189+
:ivar default_permission: Indicates whether users should have permissions to run this command by default.
190+
:ivar permissions: Permissions to restrict use of this command.
191+
"""
192+
193+
def __init__(self, name, cmd): # Let's reuse old command formatting.
194+
super().__init__(name, cmd)
195+
self.has_subcommands = cmd["has_subcommands"]
196+
self.default_permission = cmd["default_permission"]
197+
self.permissions = cmd["api_permissions"] or []
198+
199+
179200
class SubcommandObject(CommandObject):
180201
"""
181202
Subcommand object of this extension.
@@ -193,15 +214,14 @@ class SubcommandObject(CommandObject):
193214
"""
194215

195216
def __init__(self, sub, base, name, sub_group=None):
196-
sub["has_subcommands"] = True # For the inherited class.
197217
super().__init__(name, sub)
198218
self.base = base.lower()
199219
self.subcommand_group = sub_group.lower() if sub_group else sub_group
200220
self.base_description = sub["base_desc"]
201221
self.subcommand_group_description = sub["sub_group_desc"]
202222

203223

204-
class CogCommandObject(CommandObject):
224+
class CogBaseCommandObject(BaseCommandObject):
205225
"""
206226
Slash command object but for Cog.
207227
@@ -235,8 +255,9 @@ class CogSubcommandObject(SubcommandObject):
235255
Do not manually init this model.
236256
"""
237257

238-
def __init__(self, *args):
239-
super().__init__(*args)
258+
def __init__(self, base, cmd, sub_group, name, sub):
259+
super().__init__(sub, base, name, sub_group)
260+
self.base_command_data = cmd
240261
self.cog = None # Manually set this later.
241262

242263
async def invoke(self, *args, **kwargs):
@@ -358,3 +379,67 @@ async def wrap():
358379
await self._http.delete(self.__interaction_token, self.id)
359380

360381
self._state.loop.create_task(wrap())
382+
383+
384+
class PermissionData:
385+
"""
386+
Single slash permission data.
387+
388+
:ivar id: User or role id, based on following type specfic.
389+
:ivar type: The ``SlashCommandPermissionsType`` type of this permission.
390+
:ivar permission: State of permission. ``True`` to allow, ``False`` to disallow.
391+
"""
392+
def __init__(self, id, type, permission, **kwargs):
393+
self.id = id
394+
self.type = type
395+
self.permission = permission
396+
397+
def __eq__(self, other):
398+
if isinstance(other, PermissionData):
399+
return (
400+
self.id == other.id
401+
and self.type == other.id
402+
and self.permission == other.permission
403+
)
404+
else:
405+
return False
406+
407+
408+
class GuildPermissionsData:
409+
"""
410+
Slash permissions data for a command in a guild.
411+
412+
:ivar id: Command id, provided by discord.
413+
:ivar guild_id: Guild id that the permissions are in.
414+
:ivar permissions: List of permissions dict.
415+
"""
416+
def __init__(self, id, guild_id, permissions, **kwargs):
417+
self.id = id
418+
self.guild_id = guild_id
419+
self.permissions = []
420+
if permissions:
421+
for permission in permissions:
422+
self.permissions.append(PermissionData(**permission))
423+
424+
def __eq__(self, other):
425+
if isinstance(other, GuildPermissionsData):
426+
return (
427+
self.id == other.id
428+
and self.guild_id == other.guild_id
429+
and self.permissions == other.permissions
430+
)
431+
else:
432+
return False
433+
434+
435+
class SlashCommandPermissionType(IntEnum):
436+
"""
437+
Equivalent of `ApplicationCommandPermissionType <https://discord.com/developers/docs/interactions/slash-commands#applicationcommandpermissiontype>`_ in the Discord API.
438+
"""
439+
ROLE = 1
440+
USER = 2
441+
442+
@classmethod
443+
def from_type(cls, t: type):
444+
if issubclass(t, discord.abc.Role): return cls.ROLE
445+
if issubclass(t, discord.abc.User): return cls.USER

0 commit comments

Comments
 (0)