Skip to content

Commit 4907643

Browse files
committed
Added subcommand auto-registering
1 parent 363213a commit 4907643

File tree

3 files changed

+116
-11
lines changed

3 files changed

+116
-11
lines changed

discord_slash/client.py

Lines changed: 89 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import logging
22
import typing
33
import discord
4+
from inspect import iscoroutinefunction
45
from discord.ext import commands
56
from . import http
67
from . import model
78
from . import error
89
from .utils import manage_commands
9-
from inspect import iscoroutinefunction
1010

1111

1212
class SlashCommand:
@@ -90,7 +90,7 @@ def get_cog_commands(self, cog: commands.Cog):
9090
else:
9191
_cmd = {
9292
"func": None,
93-
"description": "No description.",
93+
"description": x.base_desc,
9494
"auto_convert": {},
9595
"guild_ids": x.allowed_guild_ids,
9696
"api_options": [],
@@ -150,10 +150,59 @@ async def register_all_commands(self):
150150
self.logger.info("Registering commands...")
151151
for x in self.commands.keys():
152152
selected = self.commands[x]
153+
if selected.has_subcommands and hasattr(selected, "invoke"):
154+
# Registering both subcommand and command with same base name / name
155+
# will result in only one type of command being registered,
156+
# so we will only register subcommands.
157+
self.logger.warning(f"Detected command name with same subcommand base name! Skipping registering this command: {x}")
158+
continue
153159
if selected.has_subcommands and not hasattr(selected, "invoke"):
154-
# Just in case it has subcommands but also has base command.
155-
# More specific, it will skip if it has subcommands and doesn't have base command coroutine.
156-
self.logger.debug("Skipping registering subcommands.")
160+
tgt = self.subcommands[x]
161+
options = []
162+
for y in tgt.keys():
163+
sub = tgt[y]
164+
if isinstance(sub, model.SubcommandObject):
165+
_dict = {
166+
"name": sub.name,
167+
"description": sub.description if sub.description else "No Description.",
168+
"type": 1,
169+
"options": sub.options if sub.options else []
170+
}
171+
options.append(_dict)
172+
else:
173+
base_dict = {
174+
"name": sub.subcommand_group,
175+
"description": "No Description.",
176+
"type": 2,
177+
"options": []
178+
}
179+
for z in sub.keys():
180+
sub_sub = sub[z]
181+
_dict = {
182+
"name": sub_sub.name,
183+
"description": sub_sub.description if sub_sub.description else "No Description.",
184+
"type": 1,
185+
"options": sub_sub.options if sub_sub.options else []
186+
}
187+
base_dict["options"].append(_dict)
188+
if sub_sub.sub_group_desc:
189+
base_dict["description"] = sub_sub.sub_group_desc
190+
options.append(base_dict)
191+
if selected.allowed_guild_ids:
192+
for y in selected.allowed_guild_ids:
193+
await manage_commands.add_slash_command(self._discord.user.id,
194+
self._discord.http.token,
195+
y,
196+
x,
197+
selected.description,
198+
options)
199+
else:
200+
await manage_commands.add_slash_command(self._discord.user.id,
201+
self._discord.http.token,
202+
None,
203+
x,
204+
selected.description,
205+
options)
157206
continue
158207
if selected.allowed_guild_ids:
159208
for y in selected.allowed_guild_ids:
@@ -260,8 +309,11 @@ def add_subcommand(self,
260309
subcommand_group=None,
261310
name=None,
262311
description: str = None,
312+
base_desc: str = None,
313+
sub_group_desc: str = None,
263314
auto_convert: dict = None,
264-
guild_ids: typing.List[int] = None):
315+
guild_ids: typing.List[int] = None,
316+
options: list = None):
265317
"""
266318
Registers subcommand to SlashCommand.
267319
@@ -275,10 +327,16 @@ def add_subcommand(self,
275327
:type name: str
276328
:param description: Description of the subcommand. Default ``None``.
277329
:type description: str
330+
:param base_desc: Description of the base command. Default ``None``.
331+
:type base_desc: str
332+
:param sub_group_desc: Description of the subcommand_group. Default ``None``.
333+
:type sub_group_desc: str
278334
:param auto_convert: Dictionary of how to convert option values. Default ``None``.
279335
:type auto_convert: dict
280336
:param guild_ids: List of guild ID of where the command will be used. Default ``None``, which will be global command.
281337
:type guild_ids: List[int]
338+
:param options: Options of the subcommand.
339+
:type options: list
282340
"""
283341
base = base.lower()
284342
subcommand_group = subcommand_group.lower() if subcommand_group else subcommand_group
@@ -296,8 +354,11 @@ def add_subcommand(self,
296354
"func": cmd,
297355
"name": name,
298356
"description": description,
357+
"base_desc": base_desc,
358+
"sub_group_desc": sub_group_desc,
299359
"auto_convert": auto_convert,
300360
"guild_ids": guild_ids,
361+
"api_options": options if options else []
301362
}
302363
if base not in self.commands.keys():
303364
self.commands[base] = model.CommandObject(base, _cmd)
@@ -328,7 +389,7 @@ def slash(self,
328389
All decorator args must be passed as keyword-only args.\n
329390
1 arg for command coroutine is required for ctx(:class:`.model.SlashContext`),
330391
and if your slash command has some args, then those args are also required.\n
331-
All args are passed in order.
392+
All args must be passed as keyword-args.
332393
333394
.. note::
334395
Role, User, and Channel types are passed as id if you don't set ``auto_convert``, since API doesn't give type of the option for now.\n
@@ -396,11 +457,15 @@ def subcommand(self,
396457
subcommand_group=None,
397458
name=None,
398459
description: str = None,
460+
base_desc: str = None,
461+
sub_group_desc: str = None,
399462
auto_convert: dict = None,
400-
guild_ids: typing.List[int] = None):
463+
guild_ids: typing.List[int] = None,
464+
options: typing.List[dict] = None):
401465
"""
402466
Decorator that registers subcommand.\n
403-
Unlike discord.py, you don't need base command.
467+
Unlike discord.py, you don't need base command.\n
468+
All args must be passed as keyword-args.
404469
405470
Example:
406471
@@ -431,14 +496,28 @@ async def _group_kick_user(ctx, user):
431496
:type name: str
432497
:param description: Description of the subcommand. Default ``None``.
433498
:type description: str
499+
:param base_desc: Description of the base command. Default ``None``.
500+
:type base_desc: str
501+
:param sub_group_desc: Description of the subcommand_group. Default ``None``.
502+
:type sub_group_desc: str
434503
:param auto_convert: Dictionary of how to convert option values. Default ``None``.
435504
:type auto_convert: dict
436505
:param guild_ids: List of guild ID of where the command will be used. Default ``None``, which will be global command.
437506
:type guild_ids: List[int]
507+
:param options: Options of the subcommand. This will affect ``auto_convert`` and command data at Discord API. Default ``None``.
508+
:type options: List[dict]
438509
"""
439510

511+
if options:
512+
# Overrides original auto_convert.
513+
auto_convert = {}
514+
for x in options:
515+
if x["type"] < 3:
516+
raise Exception("You can't use subcommand or subcommand_group type!")
517+
auto_convert[x["name"]] = x["type"]
518+
440519
def wrapper(cmd):
441-
self.add_subcommand(cmd, base, subcommand_group, name, description, auto_convert, guild_ids)
520+
self.add_subcommand(cmd, base, subcommand_group, sub_group_desc, name, description, auto_convert, guild_ids, options)
442521
return cmd
443522

444523
return wrapper

discord_slash/cog_ext.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,11 @@ def cog_subcommand(*,
6868
subcommand_group=None,
6969
name=None,
7070
description: str = None,
71+
base_desc: str = None,
72+
sub_group_desc: str = None,
7173
auto_convert: dict = None,
72-
guild_ids: typing.List[int] = None):
74+
guild_ids: typing.List[int] = None,
75+
options: typing.List[dict] = None):
7376
"""
7477
Decorator for Cog to add subcommand.\n
7578
Almost same as :func:`.client.SlashCommand.subcommand`.
@@ -101,18 +104,36 @@ async def group_say(self, ctx: SlashContext, text: str):
101104
:type name: str
102105
:param description: Description of the subcommand. Default ``None``.
103106
:type description: str
107+
:param base_desc: Description of the base command. Default ``None``.
108+
:type base_desc: str
109+
:param sub_group_desc: Description of the subcommand_group. Default ``None``.
110+
:type sub_group_desc: str
104111
:param auto_convert: Dictionary of how to convert option values. Default ``None``.
105112
:type auto_convert: dict
106113
:param guild_ids: List of guild ID of where the command will be used. Default ``None``, which will be global command.
107114
:type guild_ids: List[int]
115+
:param options: Options of the subcommand. This will affect ``auto_convert`` and command data at Discord API. Default ``None``.
116+
:type options: List[dict]
108117
"""
118+
119+
if options:
120+
# Overrides original auto_convert.
121+
auto_convert = {}
122+
for x in options:
123+
if x["type"] < 3:
124+
raise Exception("You can't use subcommand or subcommand_group type!")
125+
auto_convert[x["name"]] = x["type"]
126+
109127
def wrapper(cmd):
110128
_sub = {
111129
"func": cmd,
112130
"name": cmd.__name__ if not name else name,
113131
"description": description,
132+
"base_desc": base_desc,
133+
"sub_group_desc": sub_group_desc,
114134
"auto_convert": auto_convert,
115135
"guild_ids": guild_ids,
136+
"api_options": options if options else []
116137
}
117138
return CogSubcommandObject(_sub, base, cmd.__name__ if not name else name, subcommand_group)
118139
return wrapper

discord_slash/model.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,8 @@ class SubcommandObject:
215215
:ivar name: Name of the subcommand.
216216
:ivar func: The coroutine of the command.
217217
:ivar description: Description of the command.
218+
:ivar base_desc: Description of the base command.
219+
:ivar sub_group_desc: Description of the subcommand_group.
218220
:ivar auto_convert: Dictionary of the `auto_convert` of the command.
219221
:ivar allowed_guild_ids: List of the allowed guild id.
220222
"""
@@ -224,8 +226,11 @@ def __init__(self, sub, base, name, sub_group=None):
224226
self.name = name.lower()
225227
self.func = sub["func"]
226228
self.description = sub["description"]
229+
self.base_desc = sub["base_desc"]
230+
self.sub_group_desc = sub["sub_group_desc"]
227231
self.auto_convert = sub["auto_convert"] if sub["auto_convert"] else {}
228232
self.allowed_guild_ids = sub["guild_ids"] if sub["guild_ids"] else []
233+
self.options = sub["api_options"] if sub["api_options"] else []
229234

230235
def invoke(self, *args):
231236
"""

0 commit comments

Comments
 (0)