1212from interactions .client .mixins .serialization import DictSerializationMixin
1313from interactions .models .discord .base import DiscordObject
1414from 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+ )
1622
1723if TYPE_CHECKING :
1824 import interactions .models .discord
3844)
3945
4046
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+
4179class BaseComponent (DictSerializationMixin ):
4280 """
4381 A base component class.
@@ -48,6 +86,7 @@ class BaseComponent(DictSerializationMixin):
4886 """
4987
5088 type : ComponentType
89+ id : Optional [int ] = None
5190
5291 def __repr__ (self ) -> str :
5392 return f"<{ self .__class__ .__name__ } type={ self .type } >"
@@ -763,6 +802,227 @@ def to_dict(self) -> discord_typings.SelectMenuComponentData:
763802 }
764803
765804
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+
7661026def process_components (
7671027 components : Optional [
7681028 Union [
@@ -806,6 +1066,7 @@ def process_components(
8061066
8071067 if all (isinstance (c , list ) for c in components ):
8081068 # list of lists... actionRow-less sending
1069+ # note: we're assuming if someone passes a list of lists, they mean to use v1 components
8091070 return [ActionRow (* row ).to_dict () for row in components ]
8101071
8111072 if all (issubclass (type (c ), InteractiveComponent ) for c in components ):
@@ -816,6 +1077,9 @@ def process_components(
8161077 # we have a list of action rows
8171078 return [action_row .to_dict () for action_row in components ]
8181079
1080+ # assume just a list of components
1081+ return [c if isinstance (c , dict ) else c .to_dict () for c in components ]
1082+
8191083 raise ValueError (f"Invalid components: { components } " )
8201084
8211085
@@ -880,4 +1144,11 @@ def get_components_ids(component: Union[str, dict, list, InteractiveComponent])
8801144 ComponentType .CHANNEL_SELECT : ChannelSelectMenu ,
8811145 ComponentType .ROLE_SELECT : RoleSelectMenu ,
8821146 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 ,
8831154}
0 commit comments