3535 SeparatorSpacingSize ,
3636 try_enum ,
3737)
38+ from .colour import Colour
3839from .partial_emoji import PartialEmoji , _EmojiTag
3940from .utils import MISSING , get_slots
4041
@@ -586,6 +587,219 @@ def to_dict(self) -> TextDisplayComponentPayload:
586587 return {"type" : int (self .type ), "id" : self .id , "content" : self .content }
587588
588589
590+ class UnfurledMediaItem :
591+
592+ def __init__ (self , data : UnfurledMediaItemPayload ):
593+ self .url = data .get ("url" )
594+ # need to test this more
595+
596+ def to_dict (self ):
597+ return {"url" : self .url }
598+
599+
600+ class Thumbnail (Component ):
601+ """Represents a Thumbnail from Components V2.
602+
603+ This is a component that displays media such as images and videos.
604+
605+ This inherits from :class:`Component`.
606+
607+ .. versionadded:: 2.7
608+
609+ Attributes
610+ ----------
611+ media: :class:`UnfurledMediaItem`
612+ The component's media URL.
613+ description: Optional[:class:`str`]
614+ The thumbnail's description, up to 1024 characters.
615+ spoiler: Optional[:class:`bool`]
616+ Whether the thumbnail is a spoiler.
617+ """
618+
619+ __slots__ : tuple [str , ...] = ("media" , "description" , "spoiler" , )
620+
621+ __repr_info__ : ClassVar [tuple [str , ...]] = __slots__
622+
623+ def __init__ (self , data : ThumbnailComponentPayload ):
624+ self .type : ComponentType = try_enum (ComponentType , data ["type" ])
625+ self .id : str = data .get ("id" )
626+ self .media : UnfurledMediaItem = (umi := data .get ("media" )) and UnfurledMediaItem (umi )
627+ self .description : str | None = data .get ("description" )
628+ self .spoiler : bool | None = data .get ("spoiler" )
629+
630+ def to_dict (self ) -> ThumbnailComponentPayload :
631+ payload = {
632+ "type" : int (self .type ),
633+ "id" : self .id ,
634+ "media" : self .media .to_dict ()
635+ }
636+ if self .description :
637+ payload ["description" ] = self .description
638+ if self .spoiler is not None :
639+ payload ["spoiler" ] = self .spoiler
640+ return payload
641+
642+
643+ class MediaGalleryItem :
644+
645+ def __init__ (self , data : MediaGalleryItemPayload ):
646+ self .media : UnfurledMediaItem = (umi := data .get ("media" )) and UnfurledMediaItem (umi )
647+ self .description : str | None = data .get ("description" )
648+ self .spoiler : bool | None = data .get ("spoiler" )
649+
650+ def to_dict (self ):
651+ payload = {
652+ "media" : self .media .to_dict ()
653+ }
654+ if self .description :
655+ payload ["description" ] = self .description
656+ if self .spoiler is not None :
657+ payload ["spoiler" ] = self .spoiler
658+ return payload
659+
660+
661+ class MediaGallery (Component ):
662+ """Represents a Media Gallery from Components V2.
663+
664+ This is a component that displays up to 10 different :class:`MediaGalleryItem`s.
665+
666+ This inherits from :class:`Component`.
667+
668+ .. versionadded:: 2.7
669+
670+ Attributes
671+ ----------
672+ items: List[:class:`MediaGalleryItem`]
673+ The media this gallery contains.
674+ """
675+
676+ __slots__ : tuple [str , ...] = ("items" , )
677+
678+ __repr_info__ : ClassVar [tuple [str , ...]] = __slots__
679+
680+ def __init__ (self , data : MediaGalleryComponentPayload ):
681+ self .type : ComponentType = try_enum (ComponentType , data ["type" ])
682+ self .id : str = data .get ("id" )
683+ self .items : list [MediaGalleryItem ] = [MediaGalleryItem (d ) for d in data .get ("items" , [])]
684+
685+ def to_dict (self ) -> MediaGalleryComponentPayload :
686+ return {
687+ "type" : int (self .type ),
688+ "id" : self .id ,
689+ "items" : [i .to_dict () for i in self .items ]
690+ }
691+
692+
693+ class FileComponent (Component ):
694+ """Represents a File from Components V2.
695+
696+ This is a component that displays some file (elaborate?).
697+
698+ This inherits from :class:`Component`.
699+
700+ .. versionadded:: 2.7
701+
702+ Attributes
703+ ----------
704+ file: :class:`UnfurledMediaItem`
705+ The file's media URL.
706+ spoiler: Optional[:class:`bool`]
707+ Whether the file is a spoiler.
708+ """
709+
710+ __slots__ : tuple [str , ...] = ("file" , "spoiler" , )
711+
712+ __repr_info__ : ClassVar [tuple [str , ...]] = __slots__
713+
714+ def __init__ (self , data : FileComponentPayload ):
715+ self .type : ComponentType = try_enum (ComponentType , data ["type" ])
716+ self .id : str = data .get ("id" )
717+ self .file : UnfurledMediaItem = (umi := data .get ("media" )) and UnfurledMediaItem (umi )
718+ self .spoiler : bool | None = data .get ("spoiler" )
719+
720+ def to_dict (self ) -> FileComponentPayload :
721+ payload = {
722+ "type" : int (self .type ),
723+ "id" : self .id ,
724+ "file" : self .file .to_dict ()
725+ }
726+ if self .spoiler is not None :
727+ payload ["spoiler" ] = self .spoiler
728+ return payload
729+
730+
731+ class Separator (Component ):
732+ """Represents a Separator from Components V2.
733+
734+ This is a component that separates components.
735+
736+ This inherits from :class:`Component`.
737+
738+ .. versionadded:: 2.7
739+
740+ Attributes
741+ ----------
742+ divider: :class:`bool`
743+ Whether the separator is a divider (provide example?)
744+ spacing: Optional[:class:`SeparatorSpacingSize`]
745+ The separator's spacing size.
746+ """
747+
748+ __slots__ : tuple [str , ...] = ("divider" , "spacing" ,)
749+
750+ __repr_info__ : ClassVar [tuple [str , ...]] = __slots__
751+
752+ def __init__ (self , data : SeparatorComponentPayload ):
753+ self .type : ComponentType = try_enum (ComponentType , data ["type" ])
754+ self .divider : bool = data .get ("divider" )
755+ self .spacing : SeparatorSpacingSize = try_enum (SeparatorSpacingSize , data .get ("spacing" , 1 ))
756+
757+ def to_dict (self ) -> SeparatorComponentPayload :
758+ return {"type" : int (self .type ), "id" : self .id , "divider" : self .divider , "spacing" : int (self .spacing )}
759+
760+
761+ class Container (Component ):
762+ """Represents a Container from Components V2.
763+
764+ This is a component that contains up to 10 different :class:`Component`s.
765+ It may only contain :class:`ActionRow`, :class:`TextDisplay`, :class:`Section`, :class:`MediaGallery`, :class:`Separator`, and :class:`FileComponent`.
766+
767+ This inherits from :class:`Component`.
768+
769+ .. versionadded:: 2.7
770+
771+ Attributes
772+ ----------
773+ items: List[:class:`MediaGalleryItem`]
774+ The media this gallery contains.
775+ """
776+
777+ __slots__ : tuple [str , ...] = ("accent_color" , "spoiler" , "components" , )
778+
779+ __repr_info__ : ClassVar [tuple [str , ...]] = __slots__
780+
781+ def __init__ (self , data : ContainerComponentPayload ):
782+ self .type : ComponentType = try_enum (ComponentType , data ["type" ])
783+ self .id : str = data .get ("id" )
784+ self .accent_color : Colour | None = (c := data .get ("accent_color" )) and Colour (c ) # at this point, not adding alternative spelling
785+ self .spoiler : bool | None = data .get ("spoiler" )
786+ self .components : list [Component ] = [
787+ _component_factory (d ) for d in data .get ("components" , [])
788+ ]
789+
790+ def to_dict (self ) -> ContainerComponentPayload :
791+ payload = {
792+ "type" : int (self .type ),
793+ "id" : self .id ,
794+ "components" : [c .to_dict () for c in self .components ],
795+ }
796+ if self .accent_color :
797+ payload ["accent_color" ] = self .accent_color .value
798+ if self .spoiler is not None :
799+ payload ["spoiler" ] = self .spoiler
800+ return payload
801+
802+
589803COMPONENT_MAPPINGS = {
590804 1 : ActionRow ,
591805 2 : Button ,
@@ -597,11 +811,11 @@ def to_dict(self) -> TextDisplayComponentPayload:
597811 8 : SelectMenu ,
598812 9 : Section ,
599813 10 : TextDisplay ,
600- 11 : None ,
601- 12 : None ,
602- 13 : None ,
603- 14 : None ,
604- 17 : None ,
814+ 11 : Thumbnail ,
815+ 12 : MediaGallery ,
816+ 13 : FileComponent ,
817+ 14 : Separator ,
818+ 17 : Container ,
605819}
606820
607821
0 commit comments