12
12
from interactions .client .mixins .serialization import DictSerializationMixin
13
13
from interactions .models .discord .base import DiscordObject
14
14
from interactions .models .discord .emoji import PartialEmoji , process_emoji
15
- from interactions .models .discord .enums import ButtonStyle , ChannelType , ComponentType
15
+ from interactions .models .discord .enums import (
16
+ ButtonStyle ,
17
+ ChannelType ,
18
+ ComponentType ,
19
+ UnfurledMediaItemLoadingState ,
20
+ SeparatorSpacingSize ,
21
+ )
16
22
17
23
if TYPE_CHECKING :
18
24
import interactions .models .discord
38
44
)
39
45
40
46
47
+ class UnfurledMediaItem (DictSerializationMixin ):
48
+ """A basic object for making media items."""
49
+
50
+ url : str
51
+ proxy_url : Optional [str ] = None
52
+ height : Optional [int ] = None
53
+ width : Optional [int ] = None
54
+ content_type : Optional [str ] = None
55
+ loading_state : Optional [UnfurledMediaItemLoadingState ] = None
56
+
57
+ def __init__ (self , url : str ):
58
+ self .url = url
59
+
60
+ @classmethod
61
+ def from_dict (cls , data : dict ) -> "UnfurledMediaItem" :
62
+ item = cls (data ["url" ])
63
+ item .proxy_url = data .get ("proxy_url" )
64
+ item .height = data .get ("height" )
65
+ item .width = data .get ("width" )
66
+ item .content_type = data .get ("content_type" )
67
+ item .loading_state = (
68
+ UnfurledMediaItemLoadingState (data .get ("loading_state" )) if data .get ("loading_state" ) else None
69
+ )
70
+ return item
71
+
72
+ def __repr__ (self ) -> str :
73
+ return f"<{ self .__class__ .__name__ } url={ self .url } >"
74
+
75
+ def to_dict (self ) -> Dict [str , Any ]:
76
+ return {"url" : self .url }
77
+
78
+
41
79
class BaseComponent (DictSerializationMixin ):
42
80
"""
43
81
A base component class.
@@ -48,6 +86,7 @@ class BaseComponent(DictSerializationMixin):
48
86
"""
49
87
50
88
type : ComponentType
89
+ id : Optional [int ] = None
51
90
52
91
def __repr__ (self ) -> str :
53
92
return f"<{ self .__class__ .__name__ } type={ self .type } >"
@@ -763,6 +802,227 @@ def to_dict(self) -> discord_typings.SelectMenuComponentData:
763
802
}
764
803
765
804
805
+ class SectionComponent (BaseComponent ):
806
+ components : "list[TextDisplayComponent]"
807
+ accessory : "Button | ThumbnailComponent"
808
+
809
+ def __init__ (
810
+ self , * , components : "list[TextDisplayComponent] | None" = None , accessory : "Button | ThumbnailComponent"
811
+ ):
812
+ self .components = components or []
813
+ self .accessory = accessory
814
+ self .type = ComponentType .SECTION
815
+
816
+ @classmethod
817
+ def from_dict (cls , data : dict ) -> "SectionComponent" :
818
+ return cls (
819
+ components = TextDisplayComponent .from_list (data ["components" ]), accessory = Button .from_dict (data ["accessory" ])
820
+ )
821
+
822
+ def __repr__ (self ) -> str :
823
+ return f"<{ self .__class__ .__name__ } type={ self .type } components={ self .components } accessory={ self .accessory } >"
824
+
825
+ def to_dict (self ) -> dict :
826
+ return {
827
+ "type" : self .type .value ,
828
+ "components" : [c .to_dict () for c in self .components ],
829
+ "accessory" : self .accessory .to_dict (),
830
+ }
831
+
832
+
833
+ class TextDisplayComponent (BaseComponent ):
834
+ content : str
835
+
836
+ def __init__ (self , content : str ):
837
+ self .content = content
838
+ self .type = ComponentType .TEXT_DISPLAY
839
+
840
+ @classmethod
841
+ def from_dict (cls , data : dict ) -> "TextDisplayComponent" :
842
+ return cls (data ["content" ])
843
+
844
+ def __repr__ (self ) -> str :
845
+ return f"<{ self .__class__ .__name__ } type={ self .type } style={ self .content } >"
846
+
847
+ def to_dict (self ) -> dict :
848
+ return {
849
+ "type" : self .type .value ,
850
+ "content" : self .content ,
851
+ }
852
+
853
+
854
+ class ThumbnailComponent (BaseComponent ):
855
+ media : UnfurledMediaItem
856
+ description : Optional [str ] = None
857
+ spoiler : bool = False
858
+
859
+ def __init__ (self , media : UnfurledMediaItem , * , description : Optional [str ] = None , spoiler : bool = False ):
860
+ self .media = media
861
+ self .description = description
862
+ self .spoiler = spoiler
863
+ self .type = ComponentType .THUMBNAIL
864
+
865
+ @classmethod
866
+ def from_dict (cls , data : dict ) -> "ThumbnailComponent" :
867
+ return cls (
868
+ media = UnfurledMediaItem .from_dict (data ["media" ]),
869
+ description = data .get ("description" ),
870
+ spoiler = data .get ("spoiler" , False ),
871
+ )
872
+
873
+ def __repr__ (self ) -> str :
874
+ return f"<{ self .__class__ .__name__ } type={ self .type } media={ self .media } description={ self .description } spoiler={ self .spoiler } >"
875
+
876
+ def to_dict (self ) -> dict :
877
+ return {
878
+ "type" : self .type .value ,
879
+ "media" : self .media .to_dict (),
880
+ "description" : self .description ,
881
+ "spoiler" : self .spoiler ,
882
+ }
883
+
884
+
885
+ class MediaGalleryItem (DictSerializationMixin ):
886
+ media : UnfurledMediaItem
887
+ description : Optional [str ] = None
888
+ spoiler : bool = False
889
+
890
+ def __init__ (self , media : UnfurledMediaItem , * , description : Optional [str ] = None , spoiler : bool = False ):
891
+ self .media = media
892
+ self .description = description
893
+ self .spoiler = spoiler
894
+
895
+ @classmethod
896
+ def from_dict (cls , data : dict ) -> "MediaGalleryItem" :
897
+ return cls (
898
+ media = UnfurledMediaItem .from_dict (data ["media" ]),
899
+ description = data .get ("description" ),
900
+ spoiler = data .get ("spoiler" , False ),
901
+ )
902
+
903
+ def __repr__ (self ) -> str :
904
+ return f"<{ self .__class__ .__name__ } media={ self .media } description={ self .description } spoiler={ self .spoiler } >"
905
+
906
+ def to_dict (self ) -> dict :
907
+ return {
908
+ "media" : self .media .to_dict (),
909
+ "description" : self .description ,
910
+ "spoiler" : self .spoiler ,
911
+ }
912
+
913
+
914
+ class MediaGalleryComponent (BaseComponent ):
915
+ items : list [MediaGalleryItem ]
916
+
917
+ def __init__ (self , items : list [MediaGalleryItem ] | None = None ):
918
+ self .items = items or []
919
+ self .type = ComponentType .MEDIA_GALLERY
920
+
921
+ @classmethod
922
+ def from_dict (cls , data : dict ) -> "MediaGalleryComponent" :
923
+ return cls ([MediaGalleryItem .from_dict (item ) for item in data ["items" ]])
924
+
925
+ def __repr__ (self ) -> str :
926
+ return f"<{ self .__class__ .__name__ } type={ self .type } items={ self .items } >"
927
+
928
+ def to_dict (self ) -> dict :
929
+ return {
930
+ "type" : self .type .value ,
931
+ "items" : [item .to_dict () for item in self .items ],
932
+ }
933
+
934
+
935
+ class FileComponent (BaseComponent ):
936
+ file : UnfurledMediaItem
937
+ spoiler : bool = False
938
+
939
+ def __init__ (self , file : UnfurledMediaItem , * , spoiler : bool = False ):
940
+ self .file = file
941
+ self .spoiler = spoiler
942
+ self .type = ComponentType .FILE
943
+
944
+ @classmethod
945
+ def from_dict (cls , data : dict ) -> "FileComponent" :
946
+ return cls (file = UnfurledMediaItem .from_dict (data ["file" ]), spoiler = data .get ("spoiler" , False ))
947
+
948
+ def __repr__ (self ) -> str :
949
+ return f"<{ self .__class__ .__name__ } type={ self .type } file={ self .file } spoiler={ self .spoiler } >"
950
+
951
+ def to_dict (self ) -> dict :
952
+ return {
953
+ "type" : self .type .value ,
954
+ "file" : self .file .to_dict (),
955
+ "spoiler" : self .spoiler ,
956
+ }
957
+
958
+
959
+ class SeparatorComponent (BaseComponent ):
960
+ divider : bool = False
961
+ spacing : SeparatorSpacingSize = SeparatorSpacingSize .SMALL
962
+
963
+ def __init__ (self , * , divider : bool = False , spacing : SeparatorSpacingSize | int = SeparatorSpacingSize .SMALL ):
964
+ self .divider = divider
965
+ self .spacing = SeparatorSpacingSize (spacing )
966
+ self .type = ComponentType .SEPARATOR
967
+
968
+ @classmethod
969
+ def from_dict (cls , data : dict ) -> "SeparatorComponent" :
970
+ return cls (divider = data .get ("divider" , False ), spacing = data .get ("spacing" , SeparatorSpacingSize .SMALL ))
971
+
972
+ def __repr__ (self ) -> str :
973
+ return f"<{ self .__class__ .__name__ } type={ self .type } divider={ self .divider } spacing={ self .spacing } >"
974
+
975
+ def to_dict (self ) -> dict :
976
+ return {
977
+ "type" : self .type .value ,
978
+ "divider" : self .divider ,
979
+ "spacing" : self .spacing ,
980
+ }
981
+
982
+
983
+ class ContainerComponent (BaseComponent ):
984
+ components : list [
985
+ ActionRow | SectionComponent | TextDisplayComponent | MediaGalleryComponent | FileComponent | SeparatorComponent
986
+ ]
987
+ accent_color : Optional [int ] = None
988
+ spoiler : bool = False
989
+
990
+ def __init__ (
991
+ self ,
992
+ * components : ActionRow
993
+ | SectionComponent
994
+ | TextDisplayComponent
995
+ | MediaGalleryComponent
996
+ | FileComponent
997
+ | SeparatorComponent ,
998
+ accent_color : Optional [int ] = None ,
999
+ spoiler : bool = False ,
1000
+ ):
1001
+ self .components = list (components )
1002
+ self .accent_color = accent_color
1003
+ self .spoiler = spoiler
1004
+ self .type = ComponentType .CONTAINER
1005
+
1006
+ @classmethod
1007
+ def from_dict (cls , data : dict ) -> "ContainerComponent" :
1008
+ return cls (
1009
+ * [BaseComponent .from_dict_factory (component ) for component in data ["components" ]],
1010
+ accent_color = data .get ("accent_color" ),
1011
+ spoiler = data .get ("spoiler" , False ),
1012
+ )
1013
+
1014
+ def __repr__ (self ) -> str :
1015
+ return f"<{ self .__class__ .__name__ } type={ self .type } components={ self .components } accent_color={ self .accent_color } spoiler={ self .spoiler } >"
1016
+
1017
+ def to_dict (self ) -> dict :
1018
+ return {
1019
+ "type" : self .type .value ,
1020
+ "components" : [component .to_dict () for component in self .components ],
1021
+ "accent_color" : self .accent_color ,
1022
+ "spoiler" : self .spoiler ,
1023
+ }
1024
+
1025
+
766
1026
def process_components (
767
1027
components : Optional [
768
1028
Union [
@@ -806,6 +1066,7 @@ def process_components(
806
1066
807
1067
if all (isinstance (c , list ) for c in components ):
808
1068
# list of lists... actionRow-less sending
1069
+ # note: we're assuming if someone passes a list of lists, they mean to use v1 components
809
1070
return [ActionRow (* row ).to_dict () for row in components ]
810
1071
811
1072
if all (issubclass (type (c ), InteractiveComponent ) for c in components ):
@@ -816,6 +1077,9 @@ def process_components(
816
1077
# we have a list of action rows
817
1078
return [action_row .to_dict () for action_row in components ]
818
1079
1080
+ # assume just a list of components
1081
+ return [c if isinstance (c , dict ) else c .to_dict () for c in components ]
1082
+
819
1083
raise ValueError (f"Invalid components: { components } " )
820
1084
821
1085
@@ -880,4 +1144,11 @@ def get_components_ids(component: Union[str, dict, list, InteractiveComponent])
880
1144
ComponentType .CHANNEL_SELECT : ChannelSelectMenu ,
881
1145
ComponentType .ROLE_SELECT : RoleSelectMenu ,
882
1146
ComponentType .MENTIONABLE_SELECT : MentionableSelectMenu ,
1147
+ ComponentType .SECTION : SectionComponent ,
1148
+ ComponentType .TEXT_DISPLAY : TextDisplayComponent ,
1149
+ ComponentType .THUMBNAIL : ThumbnailComponent ,
1150
+ ComponentType .MEDIA_GALLERY : MediaGalleryComponent ,
1151
+ ComponentType .FILE : FileComponent ,
1152
+ ComponentType .SEPARATOR : SeparatorComponent ,
1153
+ ComponentType .CONTAINER : ContainerComponent ,
883
1154
}
0 commit comments