11import logging
22import typing
33import discord
4+ from inspect import iscoroutinefunction
45from discord .ext import commands
56from . import http
67from . import model
78from . import error
89from .utils import manage_commands
9- from inspect import iscoroutinefunction
1010
1111
1212class 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
0 commit comments