package discordgo import ( "encoding/json" "fmt" ) // ComponentType is type of component. type ComponentType uint // MessageComponent types. const ( ActionsRowComponent ComponentType = 1 ButtonComponent ComponentType = 2 SelectMenuComponent ComponentType = 3 TextInputComponent ComponentType = 4 UserSelectMenuComponent ComponentType = 5 RoleSelectMenuComponent ComponentType = 6 MentionableSelectMenuComponent ComponentType = 7 ChannelSelectMenuComponent ComponentType = 8 SectionComponent ComponentType = 9 TextDisplayComponent ComponentType = 10 ThumbnailComponent ComponentType = 11 MediaGalleryComponent ComponentType = 12 FileComponentType ComponentType = 13 SeparatorComponent ComponentType = 14 ContentInventoryEntryComponent ComponentType = 16 ContainerComponent ComponentType = 17 ) // MessageComponent is a base interface for all message components. type MessageComponent interface { json.Marshaler Type() ComponentType } type unmarshalableMessageComponent struct { MessageComponent } // UnmarshalJSON is a helper function to unmarshal MessageComponent object. func (umc *unmarshalableMessageComponent) UnmarshalJSON(src []byte) error { var v struct { Type ComponentType `json:"type"` } err := json.Unmarshal(src, &v) if err != nil { return err } switch v.Type { case ActionsRowComponent: umc.MessageComponent = &ActionsRow{} case ButtonComponent: umc.MessageComponent = &Button{} case SelectMenuComponent, ChannelSelectMenuComponent, UserSelectMenuComponent, RoleSelectMenuComponent, MentionableSelectMenuComponent: umc.MessageComponent = &SelectMenu{} case TextInputComponent: umc.MessageComponent = &TextInput{} case SectionComponent: umc.MessageComponent = &Section{} case TextDisplayComponent: umc.MessageComponent = &TextDisplay{} case ThumbnailComponent: umc.MessageComponent = &Thumbnail{} case MediaGalleryComponent: umc.MessageComponent = &MediaGallery{} case FileComponentType: umc.MessageComponent = &FileComponent{} case SeparatorComponent: umc.MessageComponent = &Separator{} case ContainerComponent: umc.MessageComponent = &Container{} default: return fmt.Errorf("unknown component type: %d", v.Type) } return json.Unmarshal(src, umc.MessageComponent) } // MessageComponentFromJSON is a helper function for unmarshaling message components func MessageComponentFromJSON(b []byte) (MessageComponent, error) { var u unmarshalableMessageComponent err := u.UnmarshalJSON(b) if err != nil { return nil, fmt.Errorf("failed to unmarshal into MessageComponent: %w", err) } return u.MessageComponent, nil } // ActionsRow is a top-level container component for displaying a row of interactive components. type ActionsRow struct { // Can contain Button, SelectMenu and TextInput. // NOTE: maximum of 5. Components []MessageComponent `json:"components"` // Unique identifier for the component; auto populated through increment if not provided. ID int `json:"id,omitempty"` } // MarshalJSON is a method for marshaling ActionsRow to a JSON object. func (r ActionsRow) MarshalJSON() ([]byte, error) { type actionsRow ActionsRow return Marshal(struct { actionsRow Type ComponentType `json:"type"` }{ actionsRow: actionsRow(r), Type: r.Type(), }) } // UnmarshalJSON is a helper function to unmarshal Actions Row. func (r *ActionsRow) UnmarshalJSON(data []byte) error { var v struct { RawComponents []unmarshalableMessageComponent `json:"components"` } err := json.Unmarshal(data, &v) if err != nil { return err } r.Components = make([]MessageComponent, len(v.RawComponents)) for i, v := range v.RawComponents { r.Components[i] = v.MessageComponent } return err } // Type is a method to get the type of a component. func (r ActionsRow) Type() ComponentType { return ActionsRowComponent } // ButtonStyle is style of button. type ButtonStyle uint // Button styles. const ( // PrimaryButton is a button with blurple color. PrimaryButton ButtonStyle = 1 // SecondaryButton is a button with grey color. SecondaryButton ButtonStyle = 2 // SuccessButton is a button with green color. SuccessButton ButtonStyle = 3 // DangerButton is a button with red color. DangerButton ButtonStyle = 4 // LinkButton is a special type of button which navigates to a URL. Has grey color. LinkButton ButtonStyle = 5 // PremiumButton is a special type of button with a blurple color that links to a SKU. PremiumButton ButtonStyle = 6 ) // ComponentEmoji represents button emoji, if it does have one. type ComponentEmoji struct { Name string `json:"name,omitempty"` ID string `json:"id,omitempty"` Animated bool `json:"animated,omitempty"` } // Button represents button component. type Button struct { Label string `json:"label"` Style ButtonStyle `json:"style"` Disabled bool `json:"disabled"` Emoji *ComponentEmoji `json:"emoji,omitempty"` // NOTE: Only button with LinkButton style can have link. Also, URL is mutually exclusive with CustomID. URL string `json:"url,omitempty"` CustomID string `json:"custom_id,omitempty"` // Identifier for a purchasable SKU. Only available when using premium-style buttons. SKUID string `json:"sku_id,omitempty"` // Unique identifier for the component; auto populated through increment if not provided. ID int `json:"id,omitempty"` } // MarshalJSON is a method for marshaling Button to a JSON object. func (b Button) MarshalJSON() ([]byte, error) { type button Button if b.Style == 0 { b.Style = PrimaryButton } return Marshal(struct { button Type ComponentType `json:"type"` }{ button: button(b), Type: b.Type(), }) } // Type is a method to get the type of a component. func (Button) Type() ComponentType { return ButtonComponent } // SelectMenuOption represents an option for a select menu. type SelectMenuOption struct { Label string `json:"label,omitempty"` Value string `json:"value"` Description string `json:"description"` Emoji *ComponentEmoji `json:"emoji,omitempty"` // Determines whenever option is selected by default or not. Default bool `json:"default"` } // SelectMenuDefaultValueType represents the type of an entity selected by default in auto-populated select menus. type SelectMenuDefaultValueType string // SelectMenuDefaultValue types. const ( SelectMenuDefaultValueUser SelectMenuDefaultValueType = "user" SelectMenuDefaultValueRole SelectMenuDefaultValueType = "role" SelectMenuDefaultValueChannel SelectMenuDefaultValueType = "channel" ) // SelectMenuDefaultValue represents an entity selected by default in auto-populated select menus. type SelectMenuDefaultValue struct { // ID of the entity. ID string `json:"id"` // Type of the entity. Type SelectMenuDefaultValueType `json:"type"` } // SelectMenuType represents select menu type. type SelectMenuType ComponentType // SelectMenu types. const ( StringSelectMenu = SelectMenuType(SelectMenuComponent) UserSelectMenu = SelectMenuType(UserSelectMenuComponent) RoleSelectMenu = SelectMenuType(RoleSelectMenuComponent) MentionableSelectMenu = SelectMenuType(MentionableSelectMenuComponent) ChannelSelectMenu = SelectMenuType(ChannelSelectMenuComponent) ) // SelectMenu represents select menu component. type SelectMenu struct { // Type of the select menu. MenuType SelectMenuType `json:"type,omitempty"` // CustomID is a developer-defined identifier for the select menu. CustomID string `json:"custom_id,omitempty"` // The text which will be shown in the menu if there's no default options or all options was deselected and component was closed. Placeholder string `json:"placeholder"` // This value determines the minimal amount of selected items in the menu. MinValues *int `json:"min_values,omitempty"` // This value determines the maximal amount of selected items in the menu. // If MaxValues or MinValues are greater than one then the user can select multiple items in the component. MaxValues int `json:"max_values,omitempty"` // List of default values for auto-populated select menus. // NOTE: Number of entries should be in the range defined by MinValues and MaxValues. DefaultValues []SelectMenuDefaultValue `json:"default_values,omitempty"` Options []SelectMenuOption `json:"options,omitempty"` Disabled bool `json:"disabled"` // NOTE: Can only be used in SelectMenu with Channel menu type. ChannelTypes []ChannelType `json:"channel_types,omitempty"` // Unique identifier for the component; auto populated through increment if not provided. ID int `json:"id,omitempty"` } // Type is a method to get the type of a component. func (s SelectMenu) Type() ComponentType { if s.MenuType != 0 { return ComponentType(s.MenuType) } return SelectMenuComponent } // MarshalJSON is a method for marshaling SelectMenu to a JSON object. func (s SelectMenu) MarshalJSON() ([]byte, error) { type selectMenu SelectMenu return Marshal(struct { selectMenu Type ComponentType `json:"type"` }{ selectMenu: selectMenu(s), Type: s.Type(), }) } // TextInput represents text input component. type TextInput struct { CustomID string `json:"custom_id"` Label string `json:"label"` Style TextInputStyle `json:"style"` Placeholder string `json:"placeholder,omitempty"` Value string `json:"value,omitempty"` Required bool `json:"required"` MinLength int `json:"min_length,omitempty"` MaxLength int `json:"max_length,omitempty"` // Unique identifier for the component; auto populated through increment if not provided. ID int `json:"id,omitempty"` } // Type is a method to get the type of a component. func (TextInput) Type() ComponentType { return TextInputComponent } // MarshalJSON is a method for marshaling TextInput to a JSON object. func (m TextInput) MarshalJSON() ([]byte, error) { type inputText TextInput return Marshal(struct { inputText Type ComponentType `json:"type"` }{ inputText: inputText(m), Type: m.Type(), }) } // TextInputStyle is style of text in TextInput component. type TextInputStyle uint // Text styles const ( TextInputShort TextInputStyle = 1 TextInputParagraph TextInputStyle = 2 ) // Section is a top-level layout component that allows you to join text contextually with an accessory. type Section struct { // Unique identifier for the component; auto populated through increment if not provided. ID int `json:"id,omitempty"` // Array of text display components; max of 3. Components []MessageComponent `json:"components"` // Can be Button or Thumbnail Accessory MessageComponent `json:"accessory"` } // Type is a method to get the type of a component. func (Section) Type() ComponentType { return SectionComponent } // MarshalJSON is a method for marshaling Section to a JSON object. func (s Section) MarshalJSON() ([]byte, error) { type section Section return Marshal(struct { section Type ComponentType `json:"type"` }{ section: section(s), Type: s.Type(), }) } // UnmarshalJSON is a method for unmarshaling Section from JSON. // origin https://github.com/bwmarrin/discordgo/pull/1616 func (s *Section) UnmarshalJSON(data []byte) error { type sectionAlias Section aux := struct { sectionAlias RawComponents []json.RawMessage `json:"components"` RawAccessory json.RawMessage `json:"accessory"` }{} if err := json.Unmarshal(data, &aux); err != nil { return err } *s = Section(aux.sectionAlias) s.Components = make([]MessageComponent, len(aux.RawComponents)) for i, raw := range aux.RawComponents { component, err := MessageComponentFromJSON(raw) if err != nil { return fmt.Errorf("failed to unmarshal section component %d: %w", i, err) } s.Components[i] = component } if len(aux.RawAccessory) > 0 && string(aux.RawAccessory) != "null" { accessory, err := MessageComponentFromJSON(aux.RawAccessory) if err != nil { return fmt.Errorf("failed to unmarshal accessory: %w", err) } s.Accessory = accessory } return nil } // TextDisplay is a top-level component that allows you to add markdown-formatted text to the message. type TextDisplay struct { Content string `json:"content"` } // Type is a method to get the type of a component. func (TextDisplay) Type() ComponentType { return TextDisplayComponent } // MarshalJSON is a method for marshaling TextDisplay to a JSON object. func (t TextDisplay) MarshalJSON() ([]byte, error) { type textDisplay TextDisplay return Marshal(struct { textDisplay Type ComponentType `json:"type"` }{ textDisplay: textDisplay(t), Type: t.Type(), }) } // Thumbnail component can be used as an accessory for a section component. type Thumbnail struct { // Unique identifier for the component; auto populated through increment if not provided. ID int `json:"id,omitempty"` Media UnfurledMediaItem `json:"media"` Description *string `json:"description,omitempty"` Spoiler bool `json:"spoiler,omitempty"` } // Type is a method to get the type of a component. func (Thumbnail) Type() ComponentType { return ThumbnailComponent } // MarshalJSON is a method for marshaling Thumbnail to a JSON object. func (t Thumbnail) MarshalJSON() ([]byte, error) { type thumbnail Thumbnail return Marshal(struct { thumbnail Type ComponentType `json:"type"` }{ thumbnail: thumbnail(t), Type: t.Type(), }) } // MediaGallery is a top-level component allows you to group images, videos or gifs into a gallery grid. type MediaGallery struct { // Unique identifier for the component; auto populated through increment if not provided. ID int `json:"id,omitempty"` // Array of media gallery items; max of 10. Items []MediaGalleryItem `json:"items"` } // Type is a method to get the type of a component. func (MediaGallery) Type() ComponentType { return MediaGalleryComponent } // MarshalJSON is a method for marshaling MediaGallery to a JSON object. func (m MediaGallery) MarshalJSON() ([]byte, error) { type mediaGallery MediaGallery return Marshal(struct { mediaGallery Type ComponentType `json:"type"` }{ mediaGallery: mediaGallery(m), Type: m.Type(), }) } // MediaGalleryItem represents an item used in MediaGallery. type MediaGalleryItem struct { Media UnfurledMediaItem `json:"media"` Description *string `json:"description,omitempty"` Spoiler bool `json:"spoiler"` } // FileComponent is a top-level component that allows you to display an uploaded file as an attachment to the message and reference it in the component. type FileComponent struct { // Unique identifier for the component; auto populated through increment if not provided. ID int `json:"id,omitempty"` File UnfurledMediaItem `json:"file"` Spoiler bool `json:"spoiler"` } // Type is a method to get the type of a component. func (FileComponent) Type() ComponentType { return FileComponentType } // MarshalJSON is a method for marshaling FileComponent to a JSON object. func (f FileComponent) MarshalJSON() ([]byte, error) { type fileComponent FileComponent return Marshal(struct { fileComponent Type ComponentType `json:"type"` }{ fileComponent: fileComponent(f), Type: f.Type(), }) } // SeparatorSpacingSize represents spacing size around the separator. type SeparatorSpacingSize uint // Separator spacing sizes. const ( SeparatorSpacingSizeSmall SeparatorSpacingSize = 1 SeparatorSpacingSizeLarge SeparatorSpacingSize = 2 ) // Separator is a top-level layout component that adds vertical padding and visual division between other components. type Separator struct { // Unique identifier for the component; auto populated through increment if not provided. ID int `json:"id,omitempty"` Divider *bool `json:"divider,omitempty"` Spacing *SeparatorSpacingSize `json:"spacing,omitempty"` } // Type is a method to get the type of a component. func (Separator) Type() ComponentType { return SeparatorComponent } // MarshalJSON is a method for marshaling Separator to a JSON object. func (s Separator) MarshalJSON() ([]byte, error) { type separator Separator return Marshal(struct { separator Type ComponentType `json:"type"` }{ separator: separator(s), Type: s.Type(), }) } // Container is a top-level layout component. // Containers are visually distinct from surrounding components and have an optional customizable color bar (similar to embeds). type Container struct { // Unique identifier for the component; auto populated through increment if not provided. ID int `json:"id,omitempty"` AccentColor *int `json:"accent_color,omitempty"` Spoiler bool `json:"spoiler"` Components []MessageComponent `json:"components"` } // Type is a method to get the type of a component. func (Container) Type() ComponentType { return ContainerComponent } // MarshalJSON is a method for marshaling Container to a JSON object. func (c Container) MarshalJSON() ([]byte, error) { type container Container return Marshal(struct { container Type ComponentType `json:"type"` }{ container: container(c), Type: c.Type(), }) } // UnmarshalJSON is a method for unmarshaling Container from JSON. // origin https://github.com/bwmarrin/discordgo/pull/1616 func (c *Container) UnmarshalJSON(data []byte) error { type containerAlias Container aux := struct { containerAlias RawComponents []json.RawMessage `json:"components"` }{} if err := json.Unmarshal(data, &aux); err != nil { return err } *c = Container(aux.containerAlias) c.Components = make([]MessageComponent, len(aux.RawComponents)) for i, raw := range aux.RawComponents { component, err := MessageComponentFromJSON(raw) if err != nil { return fmt.Errorf("failed to parse component at index %d: %w", i, err) } c.Components[i] = component } return nil } // UnfurledMediaItem represents an unfurled media item. type UnfurledMediaItem struct { URL string `json:"url"` } // UnfurledMediaItemLoadingState is the loading state of the unfurled media item. type UnfurledMediaItemLoadingState uint // Unfurled media item loading states. const ( UnfurledMediaItemLoadingStateUnknown UnfurledMediaItemLoadingState = 0 UnfurledMediaItemLoadingStateLoading UnfurledMediaItemLoadingState = 1 UnfurledMediaItemLoadingStateLoadingSuccess UnfurledMediaItemLoadingState = 2 UnfurledMediaItemLoadingStateLoadedNotFound UnfurledMediaItemLoadingState = 3 ) // ResolvedUnfurledMediaItem represents a resolved unfurled media item. type ResolvedUnfurledMediaItem struct { URL string `json:"url"` ProxyURL string `json:"proxy_url"` Width int `json:"width"` Height int `json:"height"` ContentType string `json:"content_type"` }