Skip to content

Commit d6127f8

Browse files
committed
Added permission decorators; Improved auto sync
1 parent c2fc0be commit d6127f8

File tree

7 files changed

+258
-62
lines changed

7 files changed

+258
-62
lines changed

disnake/app_commands.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -471,13 +471,20 @@ class ApplicationCommandPermissions:
471471
__slots__ = ("id", "type", "permission")
472472

473473
def __init__(self, *, data: Dict[str, Any]):
474-
self.id: int = data["id"]
474+
self.id: int = int(data["id"])
475475
self.type: int = data["type"]
476476
self.permission: bool = data["permission"]
477477

478478
def __repr__(self):
479479
return f"<ApplicationCommandPermissions id={self.id!r} type={self.type!r} permission={self.permission!r}>"
480480

481+
def __eq__(self, other):
482+
return (
483+
self.id == other.id
484+
and self.type == other.type
485+
and self.permission == other.permission
486+
)
487+
481488
def to_dict(self) -> Dict[str, Any]:
482489
return {
483490
"id": self.id,

disnake/client.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
from .backoff import ExponentialBackoff
6464
from .channel import PartialMessageable, _threaded_channel_factory
6565
from .emoji import Emoji
66-
from .enums import ChannelType, Status, VoiceRegion
66+
from .enums import ApplicationCommandType, ChannelType, Status, VoiceRegion
6767
from .errors import *
6868
from .flags import ApplicationFlags, Intents
6969
from .gateway import *
@@ -1134,23 +1134,25 @@ def get_guild_command(self, guild_id: int, id: int) -> Optional[ApplicationComma
11341134
"""
11351135
return self._connection._get_guild_application_command(guild_id, id)
11361136

1137-
def get_global_command_named(self, name: str) -> Optional[ApplicationCommand]:
1137+
def get_global_command_named(self, name: str, cmd_type: ApplicationCommandType = None) -> Optional[ApplicationCommand]:
11381138
"""
11391139
Returns a global application command matching the specified name.
11401140
11411141
Parameters
11421142
----------
11431143
name: :class:`str`
1144-
The name to search for.
1144+
The name to look for.
1145+
cmd_type: :class:`ApplicationCommandType`
1146+
The type to look for. By default, no types are checked.
11451147
11461148
Returns
11471149
-------
11481150
:class:`ApplicationCommand`
11491151
The application command.
11501152
"""
1151-
return self._connection._get_global_command_named(name)
1153+
return self._connection._get_global_command_named(name, cmd_type)
11521154

1153-
def get_guild_command_named(self, guild_id: int, name: str) -> Optional[ApplicationCommand]:
1155+
def get_guild_command_named(self, guild_id: int, name: str, cmd_type: ApplicationCommandType = None) -> Optional[ApplicationCommand]:
11541156
"""
11551157
Returns a guild application command matching the name.
11561158
@@ -1160,13 +1162,15 @@ def get_guild_command_named(self, guild_id: int, name: str) -> Optional[Applicat
11601162
The guild ID to search for.
11611163
name: :class:`str`
11621164
The command name to search for.
1165+
cmd_type: :class:`ApplicationCommandType`
1166+
The type to look for. By default, no types are checked.
11631167
11641168
Returns
11651169
-------
11661170
:class:`ApplicationCommand`
11671171
The application command.
11681172
"""
1169-
return self._connection._get_guild_command_named(guild_id, name)
1173+
return self._connection._get_guild_command_named(guild_id, name, cmd_type)
11701174

11711175
# listeners/waiters
11721176

disnake/ext/commands/base_core.py

+42-2
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,17 @@
2525
from typing import (
2626
Any,
2727
Callable,
28+
Dict,
2829
List,
30+
Mapping,
2931
Optional,
3032
TypeVar,
3133
TYPE_CHECKING
3234
)
3335
import asyncio
3436
import datetime
3537

36-
from disnake.app_commands import ApplicationCommand
38+
from disnake.app_commands import ApplicationCommand, PartialGuildApplicationCommandPermissions
3739
from disnake.enums import ApplicationCommandType
3840
from disnake.utils import async_all, maybe_coroutine
3941

@@ -47,7 +49,7 @@
4749
from .cog import Cog
4850

4951

50-
__all__ = ('InvokableApplicationCommand',)
52+
__all__ = ('InvokableApplicationCommand', 'guild_permissions')
5153

5254

5355
T = TypeVar('T')
@@ -81,9 +83,16 @@ def __init__(self, func, *, name: str = None, **kwargs):
8183
self.qualified_name: str = self.name
8284
# only an internal feature for now
8385
self.guild_only: bool = kwargs.get('guild_only', False)
86+
8487
if not isinstance(self.name, str):
8588
raise TypeError('Name of a command must be a string.')
8689

90+
try:
91+
perms = func.__app_command_permissions__
92+
except AttributeError:
93+
perms = {}
94+
self.permissions: Dict[int, PartialGuildApplicationCommandPermissions] = perms
95+
8796
try:
8897
checks = func.__commands_checks__
8998
checks.reverse()
@@ -496,3 +505,34 @@ async def can_run(self, inter: ApplicationCommandInteraction) -> bool:
496505
return await async_all(predicate(inter) for predicate in predicates) # type: ignore
497506
finally:
498507
inter.application_command = original
508+
509+
510+
def guild_permissions(
511+
guild_id: int,
512+
role_ids: Mapping[int, bool] = None,
513+
user_ids: Mapping[int, bool] = None,
514+
) -> Callable[[T], T]:
515+
"""
516+
A decorator that sets application command permissions in the specified guild.
517+
This type of permissions "greys out" the command in the command picker.
518+
If you want to change this type of permissions dynamically, this decorator is not useful.
519+
520+
Parameters
521+
----------
522+
guild_id: :class:`int`
523+
the ID of the guild to apply the permissions to.
524+
role_ids: Mapping[:class:`int`, :class:`bool`]
525+
a mapping of role IDs to boolean values indicating the permission. ``True`` = allow, ``False`` = deny.
526+
user_ids: Mapping[:class:`int`, :class:`bool`]
527+
a mapping of user IDs to boolean values indicating the permission. ``True`` = allow, ``False`` = deny.
528+
"""
529+
perms = PartialGuildApplicationCommandPermissions(0, role_ids=role_ids, user_ids=user_ids)
530+
def decorator(func: T) -> T:
531+
if isinstance(func, InvokableApplicationCommand):
532+
func.permissions[guild_id] = perms
533+
else:
534+
if not hasattr(func, "__app_command_permissions__"):
535+
func.__app_command_permissions__ = {} # type: ignore
536+
func.__app_command_permissions__[guild_id] = perms # type: ignore
537+
return func
538+
return decorator

disnake/ext/commands/bot.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ class Bot(BotBase, InteractionBotBase, disnake.Client):
138138
sync_commands: :class:`bool`
139139
Whether to enable automatic synchronization of application commands in your code.
140140
Defaults to ``True``, which means that commands in API are automatically synced
141-
with the commands in your code. Defaults to ``True``.
141+
with the commands in your code.
142142
143143
.. versionadded:: 2.1
144144
sync_commands_on_cog_unload: :class:`bool`
@@ -151,6 +151,9 @@ class Bot(BotBase, InteractionBotBase, disnake.Client):
151151
Defaults to ``False``.
152152
153153
.. versionadded:: 2.1
154+
sync_permissions: :class:`bool`
155+
Whether to enable automatic synchronization of app command permissions in your code.
156+
Defaults to ``False``.
154157
reload: :class:`bool`
155158
Whether to enable automatic extension reloading on file modification for debugging.
156159
Whenever you save an extension with reloading enabled the file will be automatically
@@ -199,7 +202,7 @@ class InteractionBot(InteractionBotBase, disnake.Client):
199202
sync_commands: :class:`bool`
200203
Whether to enable automatic synchronization of application commands in your code.
201204
Defaults to ``True``, which means that commands in API are automatically synced
202-
with the commands in your code. Defaults to ``True``.
205+
with the commands in your code.
203206
204207
.. versionadded:: 2.1
205208
sync_commands_on_cog_unload: :class:`bool`
@@ -211,6 +214,11 @@ class InteractionBot(InteractionBotBase, disnake.Client):
211214
Useful for tracking the commands being registered in the API.
212215
Defaults to ``False``.
213216
217+
.. versionadded:: 2.1
218+
sync_permissions: :class:`bool`
219+
Whether to enable automatic synchronization of app command permissions in your code.
220+
Defaults to ``False``.
221+
214222
.. versionadded:: 2.1
215223
reload: :class:`bool`
216224
Whether to enable automatic extension reloading on file modification for debugging.

disnake/ext/commands/ctx_menus_core.py

+46-10
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,16 @@ class InvokableUserCommand(InvokableApplicationCommand):
7373
Whether to sync the command in the API with ``body`` or not.
7474
"""
7575

76-
def __init__(self, func, *, name: str = None, default_permission: bool = True, guild_ids: Sequence[int] = None, auto_sync: bool = True, **kwargs):
76+
def __init__(
77+
self,
78+
func,
79+
*,
80+
name: str = None,
81+
default_permission: bool = True,
82+
guild_ids: Sequence[int] = None,
83+
auto_sync: bool = True,
84+
**kwargs
85+
):
7786
super().__init__(func, name=name, **kwargs)
7887
self.guild_ids: Optional[Sequence[int]] = guild_ids
7988
self.auto_sync: bool = auto_sync
@@ -130,7 +139,16 @@ class InvokableMessageCommand(InvokableApplicationCommand):
130139
Whether to sync the command in the API with ``body`` or not.
131140
"""
132141

133-
def __init__(self, func, *, name: str = None, default_permission: bool = True, guild_ids: Sequence[int] = None, auto_sync: bool = True, **kwargs):
142+
def __init__(
143+
self,
144+
func,
145+
*,
146+
name: str = None,
147+
default_permission: bool = True,
148+
guild_ids: Sequence[int] = None,
149+
auto_sync: bool = True,
150+
**kwargs
151+
):
134152
super().__init__(func, name=name, **kwargs)
135153
self.guild_ids: Optional[Sequence[int]] = guild_ids
136154
self.auto_sync: bool = auto_sync
@@ -174,11 +192,13 @@ def user_command(
174192
175193
Parameters
176194
----------
177-
auto_sync: :class:`bool`
178-
whether to automatically register / edit the command or not. Defaults to ``True``
179195
name: :class:`str`
180196
name of the user command you want to respond to (equals to function name by default).
181-
guild_ids: List[:class:`int`]
197+
default_permission: :class:`bool`
198+
whether the command is enabled by default when the app is added to a guild.
199+
auto_sync: :class:`bool`
200+
whether to automatically register / edit the command or not. Defaults to ``True``.
201+
guild_ids: Sequence[:class:`int`]
182202
if specified, the client will register the command in these guilds.
183203
Otherwise this command will be registered globally in ~1 hour.
184204
@@ -198,7 +218,14 @@ def decorator(
198218
raise TypeError(f"<{func.__qualname__}> must be a coroutine function")
199219
if hasattr(func, "__command_flag__"):
200220
raise TypeError("Callback is already a command.")
201-
return InvokableUserCommand(func, name=name, default_permission=default_permission, guild_ids=guild_ids, auto_sync=auto_sync, **kwargs)
221+
return InvokableUserCommand(
222+
func,
223+
name=name,
224+
default_permission=default_permission,
225+
guild_ids=guild_ids,
226+
auto_sync=auto_sync,
227+
**kwargs
228+
)
202229

203230
return decorator
204231

@@ -219,11 +246,13 @@ def message_command(
219246
220247
Parameters
221248
----------
222-
auto_sync: :class:`bool`
223-
whether to automatically register / edit the command or not. Defaults to ``True``
224249
name: :class:`str`
225250
name of the message command you want to respond to (equals to function name by default).
226-
guild_ids: List[:class:`int`]
251+
default_permission: :class:`bool`
252+
whether the command is enabled by default when the app is added to a guild.
253+
auto_sync: :class:`bool`
254+
whether to automatically register / edit the command or not. Defaults to ``True``.
255+
guild_ids: Sequence[:class:`int`]
227256
if specified, the client will register the command in these guilds.
228257
Otherwise this command will be registered globally in ~1 hour.
229258
@@ -243,6 +272,13 @@ def decorator(
243272
raise TypeError(f"<{func.__qualname__}> must be a coroutine function")
244273
if hasattr(func, "__command_flag__"):
245274
raise TypeError("Callback is already a command.")
246-
return InvokableMessageCommand(func, name=name, default_permission=default_permission, guild_ids=guild_ids, auto_sync=auto_sync, **kwargs)
275+
return InvokableMessageCommand(
276+
func,
277+
name=name,
278+
default_permission=default_permission,
279+
guild_ids=guild_ids,
280+
auto_sync=auto_sync,
281+
**kwargs
282+
)
247283

248284
return decorator

0 commit comments

Comments
 (0)