Skip to content

Commit 22ebd11

Browse files
authored
Merge pull request #540 from HexF/groups
Add support for slash-command groups on cogs
2 parents d8c4da6 + 94d35cf commit 22ebd11

File tree

5 files changed

+111
-2
lines changed

5 files changed

+111
-2
lines changed

discord/bot.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@ def add_application_command(self, command: ApplicationCommand) -> None:
118118
command: :class:`.ApplicationCommand`
119119
The command to add.
120120
"""
121+
try:
122+
if getattr("parent", command) is not None:
123+
raise TypeError("The provided command is a sub-command of group")
124+
except AttributeError:
125+
pass
126+
121127
if self.debug_guilds and command.guild_ids is None:
122128
command.guild_ids = self.debug_guilds
123129
self._pending_application_commands.append(command)

discord/cog.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import discord.utils
3030
import types
3131
from . import errors
32-
from .commands import SlashCommand, UserCommand, MessageCommand, ApplicationCommand
32+
from .commands import SlashCommand, UserCommand, MessageCommand, ApplicationCommand, SlashCommandGroup
3333

3434
from typing import Any, Callable, Mapping, ClassVar, Dict, Generator, List, Optional, TYPE_CHECKING, Tuple, TypeVar, Type
3535

@@ -145,6 +145,13 @@ def __new__(cls: Type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta:
145145
if elem in listeners:
146146
del listeners[elem]
147147

148+
try:
149+
if getattr(value, "parent") is not None:
150+
# Skip commands if they are a part of a group
151+
continue
152+
except AttributeError:
153+
pass
154+
148155
is_static_method = isinstance(value, staticmethod)
149156
if is_static_method:
150157
value = value.__func__
@@ -445,7 +452,8 @@ def _inject(self: CogT, bot) -> CogT:
445452
# we've added so far for some form of atomic loading.
446453

447454
for index, command in enumerate(self.__cog_commands__):
448-
command.cog = self
455+
command._set_cog(self)
456+
449457
if not isinstance(command, ApplicationCommand):
450458
if command.parent is None:
451459
try:

discord/commands/commands.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,9 @@ def qualified_name(self) -> str:
317317
else:
318318
return self.name
319319

320+
def _set_cog(self, cog):
321+
self.cog = cog
322+
320323
class SlashCommand(ApplicationCommand):
321324
r"""A class that implements the protocol for a slash command.
322325
@@ -771,6 +774,7 @@ def to_dict(self) -> Dict:
771774
"name": self.name,
772775
"description": self.description,
773776
"options": [c.to_dict() for c in self.subcommands],
777+
"default_permission": self.default_permission,
774778
}
775779

776780
if self.parent is not None:
@@ -848,6 +852,50 @@ async def invoke_autocomplete_callback(self, ctx: AutocompleteContext) -> None:
848852
ctx.interaction.data = option
849853
await command.invoke_autocomplete_callback(ctx)
850854

855+
def copy(self):
856+
"""Creates a copy of this command group.
857+
858+
Returns
859+
--------
860+
:class:`SlashCommandGroup`
861+
A new instance of this command.
862+
"""
863+
ret = self.__class__(
864+
self.name,
865+
self.description,
866+
**self.__original_kwargs__,
867+
)
868+
return self._ensure_assignment_on_copy(ret)
869+
870+
def _ensure_assignment_on_copy(self, other):
871+
other.parent = self.parent
872+
873+
other._before_invoke = self._before_invoke
874+
other._after_invoke = self._after_invoke
875+
876+
if self.subcommands != other.subcommands:
877+
other.subcommands = self.subcommands.copy()
878+
879+
if self.checks != other.checks:
880+
other.checks = self.checks.copy()
881+
882+
return other
883+
884+
def _update_copy(self, kwargs: Dict[str, Any]):
885+
if kwargs:
886+
kw = kwargs.copy()
887+
kw.update(self.__original_kwargs__)
888+
copy = self.__class__(self.callback, **kw)
889+
return self._ensure_assignment_on_copy(copy)
890+
else:
891+
return self.copy()
892+
893+
def _set_cog(self, cog):
894+
self.cog = cog
895+
for subcommand in self.subcommands:
896+
subcommand._set_cog(cog)
897+
898+
851899

852900
class ContextMenuCommand(ApplicationCommand):
853901
r"""A class that implements the protocol for context menu commands.

discord/ext/commands/core.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,6 +1144,9 @@ async def can_run(self, ctx: Context) -> bool:
11441144
finally:
11451145
ctx.command = original
11461146

1147+
def _set_cog(self, cog):
1148+
self.cog = cog
1149+
11471150
class GroupMixin(Generic[CogT]):
11481151
"""A mixin that implements common functionality for classes that behave
11491152
similar to :class:`.Group` and are allowed to register commands.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from discord.commands.permissions import Permission
2+
import discord
3+
from discord.commands.commands import SlashCommandGroup
4+
from discord.ext import commands
5+
6+
# Set these
7+
GUILD_ID=0
8+
OWNER_ID=0
9+
10+
bot = discord.Bot(debug_guild=GUILD_ID, owner_id=OWNER_ID)
11+
12+
class Example(commands.Cog):
13+
def __init__(self, bot):
14+
self.bot = bot
15+
16+
greetings = SlashCommandGroup("greetings", "Various greeting from cogs!")
17+
international_greetings = greetings.command_group("international", "International greetings")
18+
19+
secret_greetings = SlashCommandGroup("secret_greetings", "Secret greetings", permissions=[
20+
Permission("owner", 2, True) # Ensures the owner_id user can access this, and no one else
21+
])
22+
23+
@greetings.command()
24+
async def hello(self, ctx):
25+
await ctx.respond("Hi, this is a slash command from a cog!")
26+
27+
@international_greetings.command()
28+
async def aloha(self, ctx):
29+
await ctx.respond("Aloha, a Hawaiian greeting")
30+
31+
32+
@greetings.command()
33+
async def hi(self, ctx):
34+
await ctx.respond(f"Hi, this is a slash sub-command from a cog!")
35+
36+
@secret_greetings.command()
37+
async def secret_handshake(self, ctx, member: discord.Member):
38+
await ctx.respond(f"{member.mention} secret handshakes you")
39+
40+
41+
bot.add_cog(Example(bot))
42+
43+
44+
bot.run("TOKEN")

0 commit comments

Comments
 (0)