31
31
import re
32
32
import types
33
33
from collections import OrderedDict
34
- from typing import Any , Callable , Dict , List , Optional , Union , TYPE_CHECKING
34
+ from typing import Any , Callable , Dict , List , Optional , Type , Union , TYPE_CHECKING
35
35
36
36
from .context import ApplicationContext , AutocompleteContext
37
37
from .errors import ApplicationCommandError , CheckFailure , ApplicationCommandInvokeError
@@ -318,6 +318,9 @@ def qualified_name(self) -> str:
318
318
else :
319
319
return self .name
320
320
321
+ def _set_cog (self , cog ):
322
+ self .cog = cog
323
+
321
324
class SlashCommand (ApplicationCommand ):
322
325
r"""A class that implements the protocol for a slash command.
323
326
@@ -386,7 +389,7 @@ def __init__(self, func: Callable, *args, **kwargs) -> None:
386
389
validate_chat_input_description (description )
387
390
self .description : str = description
388
391
self .parent = kwargs .get ('parent' )
389
- self .is_subcommand : bool = self . parent is not None
392
+ self .attached_to_group : bool = False
390
393
391
394
self .cog = None
392
395
@@ -476,6 +479,10 @@ def _is_typing_union(self, annotation):
476
479
def _is_typing_optional (self , annotation ):
477
480
return self ._is_typing_union (annotation ) and type (None ) in annotation .__args__ # type: ignore
478
481
482
+ @property
483
+ def is_subcommand (self ) -> bool :
484
+ return self .parent is not None
485
+
479
486
def to_dict (self ) -> Dict :
480
487
as_dict = {
481
488
"name" : self .name ,
@@ -528,6 +535,8 @@ async def _invoke(self, ctx: ApplicationContext) -> None:
528
535
529
536
if self .cog is not None :
530
537
await self .callback (self .cog , ctx , ** kwargs )
538
+ elif self .parent is not None and self .attached_to_group is True :
539
+ await self .callback (self .parent , ctx , ** kwargs )
531
540
else :
532
541
await self .callback (ctx , ** kwargs )
533
542
@@ -729,8 +738,24 @@ class SlashCommandGroup(ApplicationCommand, Option):
729
738
730
739
def __new__ (cls , * args , ** kwargs ) -> SlashCommandGroup :
731
740
self = super ().__new__ (cls )
732
-
733
741
self .__original_kwargs__ = kwargs .copy ()
742
+
743
+ self .__initial_commands__ = []
744
+ for i , c in cls .__dict__ .items ():
745
+ if isinstance (c , type ) and SlashCommandGroup in c .__bases__ :
746
+ c = c (
747
+ c .__name__ ,
748
+ (
749
+ inspect .cleandoc (cls .__doc__ ).splitlines ()[0 ]
750
+ if cls .__doc__ is not None
751
+ else "No description provided"
752
+ )
753
+ )
754
+ if isinstance (c , (SlashCommand , SlashCommandGroup )):
755
+ c .parent = self
756
+ c .attached_to_group = True
757
+ self .__initial_commands__ .append (c )
758
+
734
759
return self
735
760
736
761
def __init__ (
@@ -748,7 +773,7 @@ def __init__(
748
773
name = name ,
749
774
description = description ,
750
775
)
751
- self .subcommands : List [Union [SlashCommand , SlashCommandGroup ]] = []
776
+ self .subcommands : List [Union [SlashCommand , SlashCommandGroup ]] = self . __initial_commands__
752
777
self .guild_ids = guild_ids
753
778
self .parent = parent
754
779
self .checks = []
@@ -768,6 +793,7 @@ def to_dict(self) -> Dict:
768
793
"name" : self .name ,
769
794
"description" : self .description ,
770
795
"options" : [c .to_dict () for c in self .subcommands ],
796
+ "default_permission" : self .default_permission ,
771
797
}
772
798
773
799
if self .parent is not None :
@@ -783,7 +809,7 @@ def wrap(func) -> SlashCommand:
783
809
784
810
return wrap
785
811
786
- def command_group (self , name , description ) -> SlashCommandGroup :
812
+ def create_subgroup (self , name , description ) -> SlashCommandGroup :
787
813
if self .parent is not None :
788
814
# TODO: Improve this error message
789
815
raise Exception ("Subcommands can only be nested once" )
@@ -792,6 +818,47 @@ def command_group(self, name, description) -> SlashCommandGroup:
792
818
self .subcommands .append (sub_command_group )
793
819
return sub_command_group
794
820
821
+ def subgroup (
822
+ self ,
823
+ name : str ,
824
+ description : str = None ,
825
+ guild_ids : Optional [List [int ]] = None ,
826
+ ) -> Callable [[Type [SlashCommandGroup ]], SlashCommandGroup ]:
827
+ """A shortcut decorator that initializes the provided subclass of :class:`.SlashCommandGroup`
828
+ as a subgroup.
829
+
830
+ .. versionadded:: 2.0
831
+
832
+ Parameters
833
+ ----------
834
+ name: :class:`str`
835
+ The name of the group to create.
836
+ description: Optional[:class:`str`]
837
+ The description of the group to create.
838
+ guild_ids: Optional[List[:class:`int`]]
839
+ A list of the IDs of each guild this group should be added to, making it a guild command.
840
+ This will be a global command if ``None`` is passed.
841
+
842
+ Returns
843
+ --------
844
+ Callable[[Type[SlashCommandGroup]], SlashCommandGroup]
845
+ The slash command group that was created.
846
+ """
847
+ def inner (cls : Type [SlashCommandGroup ]) -> SlashCommandGroup :
848
+ group = cls (
849
+ name ,
850
+ description or (
851
+ inspect .cleandoc (cls .__doc__ ).splitlines ()[0 ]
852
+ if cls .__doc__ is not None
853
+ else "No description provided"
854
+ ),
855
+ guild_ids = guild_ids ,
856
+ parent = self ,
857
+ )
858
+ self .subcommands .append (group )
859
+ return group
860
+ return inner
861
+
795
862
async def _invoke (self , ctx : ApplicationContext ) -> None :
796
863
option = ctx .interaction .data ["options" ][0 ]
797
864
command = find (lambda x : x .name == option ["name" ], self .subcommands )
@@ -804,6 +871,50 @@ async def invoke_autocomplete_callback(self, ctx: AutocompleteContext) -> None:
804
871
ctx .interaction .data = option
805
872
await command .invoke_autocomplete_callback (ctx )
806
873
874
+ def copy (self ):
875
+ """Creates a copy of this command group.
876
+
877
+ Returns
878
+ --------
879
+ :class:`SlashCommandGroup`
880
+ A new instance of this command.
881
+ """
882
+ ret = self .__class__ (
883
+ self .name ,
884
+ self .description ,
885
+ ** self .__original_kwargs__ ,
886
+ )
887
+ return self ._ensure_assignment_on_copy (ret )
888
+
889
+ def _ensure_assignment_on_copy (self , other ):
890
+ other .parent = self .parent
891
+
892
+ other ._before_invoke = self ._before_invoke
893
+ other ._after_invoke = self ._after_invoke
894
+
895
+ if self .subcommands != other .subcommands :
896
+ other .subcommands = self .subcommands .copy ()
897
+
898
+ if self .checks != other .checks :
899
+ other .checks = self .checks .copy ()
900
+
901
+ return other
902
+
903
+ def _update_copy (self , kwargs : Dict [str , Any ]):
904
+ if kwargs :
905
+ kw = kwargs .copy ()
906
+ kw .update (self .__original_kwargs__ )
907
+ copy = self .__class__ (self .callback , ** kw )
908
+ return self ._ensure_assignment_on_copy (copy )
909
+ else :
910
+ return self .copy ()
911
+
912
+ def _set_cog (self , cog ):
913
+ self .cog = cog
914
+ for subcommand in self .subcommands :
915
+ subcommand ._set_cog (cog )
916
+
917
+
807
918
808
919
class ContextMenuCommand (ApplicationCommand ):
809
920
r"""A class that implements the protocol for context menu commands.
0 commit comments