commit
82c5aae791
3 changed files with 258 additions and 0 deletions
|
@ -13,6 +13,7 @@ const (
|
|||
ActionsRowComponent ComponentType = 1
|
||||
ButtonComponent ComponentType = 2
|
||||
SelectMenuComponent ComponentType = 3
|
||||
TextInputComponent ComponentType = 4
|
||||
)
|
||||
|
||||
// MessageComponent is a base interface for all message components.
|
||||
|
@ -42,6 +43,8 @@ func (umc *unmarshalableMessageComponent) UnmarshalJSON(src []byte) error {
|
|||
umc.MessageComponent = &Button{}
|
||||
case SelectMenuComponent:
|
||||
umc.MessageComponent = &SelectMenu{}
|
||||
case TextInputComponent:
|
||||
umc.MessageComponent = &TextInput{}
|
||||
default:
|
||||
return fmt.Errorf("unknown component type: %d", v.Type)
|
||||
}
|
||||
|
@ -195,3 +198,42 @@ func (m SelectMenu) MarshalJSON() ([]byte, error) {
|
|||
Type: m.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,omitempty"`
|
||||
MinLength int `json:"min_length,omitempty"`
|
||||
MaxLength int `json:"max_length,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 json.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
|
||||
)
|
||||
|
|
160
examples/modals/main.go
Normal file
160
examples/modals/main.go
Normal file
|
@ -0,0 +1,160 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
// Bot parameters
|
||||
var (
|
||||
GuildID = flag.String("guild", "", "Test guild ID")
|
||||
BotToken = flag.String("token", "", "Bot access token")
|
||||
AppID = flag.String("app", "", "Application ID")
|
||||
Cleanup = flag.Bool("cleanup", true, "Cleanup of commands")
|
||||
ResultsChannel = flag.String("results", "", "Channel where send survey results to")
|
||||
)
|
||||
|
||||
var s *discordgo.Session
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
s, err = discordgo.New("Bot " + *BotToken)
|
||||
if err != nil {
|
||||
log.Fatalf("Invalid bot parameters: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
commands = []discordgo.ApplicationCommand{
|
||||
{
|
||||
Name: "modals-survey",
|
||||
Description: "Take a survey about modals",
|
||||
},
|
||||
}
|
||||
commandsHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){
|
||||
"modals-survey": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseModal,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
CustomID: "modals_survey_" + i.Interaction.Member.User.ID,
|
||||
Title: "Modals survey",
|
||||
Components: []discordgo.MessageComponent{
|
||||
discordgo.ActionsRow{
|
||||
Components: []discordgo.MessageComponent{
|
||||
discordgo.TextInput{
|
||||
CustomID: "opinion",
|
||||
Label: "What is your opinion on them?",
|
||||
Style: discordgo.TextInputShort,
|
||||
Placeholder: "Don't be shy, share your opinion with us",
|
||||
Required: true,
|
||||
MaxLength: 300,
|
||||
MinLength: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
discordgo.ActionsRow{
|
||||
Components: []discordgo.MessageComponent{
|
||||
discordgo.TextInput{
|
||||
CustomID: "suggestions",
|
||||
Label: "What would you suggest to improve them?",
|
||||
Style: discordgo.TextInputParagraph,
|
||||
Required: false,
|
||||
MaxLength: 2000,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func main() {
|
||||
s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) {
|
||||
log.Println("Bot is up!")
|
||||
})
|
||||
|
||||
s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
switch i.Type {
|
||||
case discordgo.InteractionApplicationCommand:
|
||||
if h, ok := commandsHandlers[i.ApplicationCommandData().Name]; ok {
|
||||
h(s, i)
|
||||
}
|
||||
case discordgo.InteractionModalSubmit:
|
||||
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Thank you for taking your time to fill this survey",
|
||||
Flags: 1 << 6,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
data := i.ModalSubmitData()
|
||||
|
||||
if !strings.HasPrefix(data.CustomID, "modals_survey") {
|
||||
return
|
||||
}
|
||||
|
||||
userid := strings.Split(data.CustomID, "_")[2]
|
||||
_, err = s.ChannelMessageSend(*ResultsChannel, fmt.Sprintf(
|
||||
"Feedback received. From <@%s>\n\n**Opinion**:\n%s\n\n**Suggestions**:\n%s",
|
||||
userid,
|
||||
data.Components[0].(*discordgo.ActionsRow).Components[0].(*discordgo.TextInput).Value,
|
||||
data.Components[1].(*discordgo.ActionsRow).Components[0].(*discordgo.TextInput).Value,
|
||||
))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
cmdIDs := make(map[string]string, len(commands))
|
||||
|
||||
for _, cmd := range commands {
|
||||
rcmd, err := s.ApplicationCommandCreate(*AppID, *GuildID, &cmd)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot create slash command %q: %v", cmd.Name, err)
|
||||
}
|
||||
|
||||
cmdIDs[rcmd.ID] = rcmd.Name
|
||||
}
|
||||
|
||||
err := s.Open()
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot open the session: %v", err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
stop := make(chan os.Signal, 1)
|
||||
signal.Notify(stop, os.Interrupt)
|
||||
<-stop
|
||||
log.Println("Graceful shutdown")
|
||||
|
||||
if !*Cleanup {
|
||||
return
|
||||
}
|
||||
|
||||
for id, name := range cmdIDs {
|
||||
err := s.ApplicationCommandDelete(*AppID, *GuildID, id)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot delete slash command %q: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -113,6 +113,7 @@ const (
|
|||
InteractionApplicationCommand InteractionType = 2
|
||||
InteractionMessageComponent InteractionType = 3
|
||||
InteractionApplicationCommandAutocomplete InteractionType = 4
|
||||
InteractionModalSubmit InteractionType = 5
|
||||
)
|
||||
|
||||
func (t InteractionType) String() string {
|
||||
|
@ -123,6 +124,8 @@ func (t InteractionType) String() string {
|
|||
return "ApplicationCommand"
|
||||
case InteractionMessageComponent:
|
||||
return "MessageComponent"
|
||||
case InteractionModalSubmit:
|
||||
return "ModalSubmit"
|
||||
}
|
||||
return fmt.Sprintf("InteractionType(%d)", t)
|
||||
}
|
||||
|
@ -192,6 +195,13 @@ func (i *Interaction) UnmarshalJSON(raw []byte) error {
|
|||
return err
|
||||
}
|
||||
i.Data = v
|
||||
case InteractionModalSubmit:
|
||||
v := ModalSubmitInteractionData{}
|
||||
err = json.Unmarshal(tmp.Data, &v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.Data = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -214,6 +224,15 @@ func (i Interaction) ApplicationCommandData() (data ApplicationCommandInteractio
|
|||
return i.Data.(ApplicationCommandInteractionData)
|
||||
}
|
||||
|
||||
// ModalSubmitData is helper function to assert the inner InteractionData to ModalSubmitInteractionData.
|
||||
// Make sure to check that the Type of the interaction is InteractionModalSubmit before calling.
|
||||
func (i Interaction) ModalSubmitData() (data ModalSubmitInteractionData) {
|
||||
if i.Type != InteractionModalSubmit {
|
||||
panic("ModalSubmitData called on interaction of type " + i.Type.String())
|
||||
}
|
||||
return i.Data.(ModalSubmitInteractionData)
|
||||
}
|
||||
|
||||
// InteractionData is a common interface for all types of interaction data.
|
||||
type InteractionData interface {
|
||||
Type() InteractionType
|
||||
|
@ -262,6 +281,36 @@ func (MessageComponentInteractionData) Type() InteractionType {
|
|||
return InteractionMessageComponent
|
||||
}
|
||||
|
||||
// ModalSubmitInteractionData contains the data of modal submit interaction.
|
||||
type ModalSubmitInteractionData struct {
|
||||
CustomID string `json:"custom_id"`
|
||||
Components []MessageComponent `json:"-"`
|
||||
}
|
||||
|
||||
// Type returns the type of interaction data.
|
||||
func (ModalSubmitInteractionData) Type() InteractionType {
|
||||
return InteractionModalSubmit
|
||||
}
|
||||
|
||||
// UnmarshalJSON is a helper function to correctly unmarshal Components.
|
||||
func (d *ModalSubmitInteractionData) UnmarshalJSON(data []byte) error {
|
||||
type modalSubmitInteractionData ModalSubmitInteractionData
|
||||
var v struct {
|
||||
modalSubmitInteractionData
|
||||
RawComponents []unmarshalableMessageComponent `json:"components"`
|
||||
}
|
||||
err := json.Unmarshal(data, &v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*d = ModalSubmitInteractionData(v.modalSubmitInteractionData)
|
||||
d.Components = make([]MessageComponent, len(v.RawComponents))
|
||||
for i, v := range v.RawComponents {
|
||||
d.Components[i] = v.MessageComponent
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ApplicationCommandInteractionDataOption represents an option of a slash command.
|
||||
type ApplicationCommandInteractionDataOption struct {
|
||||
Name string `json:"name"`
|
||||
|
@ -404,6 +453,8 @@ const (
|
|||
InteractionResponseUpdateMessage InteractionResponseType = 7
|
||||
// InteractionApplicationCommandAutocompleteResult shows autocompletion results. Autocomplete interaction only.
|
||||
InteractionApplicationCommandAutocompleteResult InteractionResponseType = 8
|
||||
// InteractionResponseModal is for responding to an interaction with a modal window.
|
||||
InteractionResponseModal InteractionResponseType = 9
|
||||
)
|
||||
|
||||
// InteractionResponse represents a response for an interaction event.
|
||||
|
@ -424,6 +475,11 @@ type InteractionResponseData struct {
|
|||
|
||||
// NOTE: autocomplete interaction only.
|
||||
Choices []*ApplicationCommandOptionChoice `json:"choices,omitempty"`
|
||||
|
||||
// NOTE: modal interaction only.
|
||||
|
||||
CustomID string `json:"custom_id,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
}
|
||||
|
||||
// VerifyInteraction implements message verification of the discord interactions api
|
||||
|
|
Loading…
Reference in a new issue