Skip to content

Commit

Permalink
Interactions: the Buttons (bwmarrin#933)
Browse files Browse the repository at this point in the history
* Interactions: buttons

* Doc fix

* Gofmt fix

* Fix typo

* Remaking interaction data into interface

* Godoc fix

* Gofmt fix

* Godoc fix

* InteractionData helper functions and some fixes in slash commands example

* Fix components example

* Yet another fix of components example

* Fix interaction unmarshaling

* Gofmt fix

* Godoc fix

* Gofmt fix

* Corrected naming and docs

* Rolled back API version

* Requested fixes

* Added support of components to webhook and regular messages

* Fix components unmarshaling

* Godoc fix

* Requested fixes

* Fixed unmarshaling issues

* Components example: cleanup

* Added components tracking to state

* Requested fixes

* Renaming fix

* Remove more named returns

* Minor English fixes

Co-authored-by: Carson Hoffman <c@rsonhoffman.com>
  • Loading branch information
FedorLap2006 and CarsonHoffman committed Jul 30, 2021
1 parent e72c457 commit 8c70cbf
Show file tree
Hide file tree
Showing 9 changed files with 356 additions and 51 deletions.
97 changes: 97 additions & 0 deletions components.go
@@ -0,0 +1,97 @@
package discordgo

import (
"encoding/json"
)

// ComponentType is type of component.
type ComponentType uint

// Component types.
const (
ActionsRowComponent ComponentType = iota + 1
ButtonComponent
)

// Component is a base interface for all components
type Component interface {
json.Marshaler
Type() ComponentType
}

// ActionsRow is a container for components within one row.
type ActionsRow struct {
Components []Component `json:"components"`
}

func (r ActionsRow) MarshalJSON() ([]byte, error) {
type actionRow ActionsRow

return json.Marshal(struct {
actionRow
Type ComponentType `json:"type"`
}{
actionRow: actionRow(r),
Type: r.Type(),
})
}

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 = iota + 1
// SecondaryButton is a button with grey color.
SecondaryButton
// SuccessButton is a button with green color.
SuccessButton
// DangerButton is a button with red color.
DangerButton
// LinkButton is a special type of button which navigates to a URL. Has grey color.
LinkButton
)

// ButtonEmoji represents button emoji, if it does have one.
type ButtonEmoji 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 ButtonEmoji `json:"emoji"`

// NOTE: Only button with LinkButton style can have link. Also, Link is mutually exclusive with CustomID.
Link string `json:"url,omitempty"`
CustomID string `json:"custom_id,omitempty"`
}

func (b Button) MarshalJSON() ([]byte, error) {
type button Button

if b.Style == 0 {
b.Style = PrimaryButton
}

return json.Marshal(struct {
button
Type ComponentType `json:"type"`
}{
button: button(b),
Type: b.Type(),
})
}

func (b Button) Type() ComponentType {
return ButtonComponent
}
2 changes: 1 addition & 1 deletion endpoints.go
Expand Up @@ -14,7 +14,7 @@ package discordgo
import "strconv"

// APIVersion is the Discord API version used for the REST and Websocket API.
var APIVersion = "8"
var APIVersion = "9"

// Known Discord API Endpoints.
var (
Expand Down
20 changes: 20 additions & 0 deletions events.go
Expand Up @@ -162,19 +162,34 @@ type MessageCreate struct {
*Message
}

// UnmarshalJSON is a helper function to unmarshal MessageCreate object.
func (m *MessageCreate) UnmarshalJSON(b []byte) error {
return json.Unmarshal(b, &m.Message)
}

// MessageUpdate is the data for a MessageUpdate event.
type MessageUpdate struct {
*Message
// BeforeUpdate will be nil if the Message was not previously cached in the state cache.
BeforeUpdate *Message `json:"-"`
}

// UnmarshalJSON is a helper function to unmarshal MessageUpdate object.
func (m *MessageUpdate) UnmarshalJSON(b []byte) error {
return json.Unmarshal(b, &m.Message)
}

// MessageDelete is the data for a MessageDelete event.
type MessageDelete struct {
*Message
BeforeDelete *Message `json:"-"`
}

// UnmarshalJSON is a helper function to unmarshal MessageDelete object.
func (m *MessageDelete) UnmarshalJSON(b []byte) error {
return json.Unmarshal(b, &m.Message)
}

// MessageReactionAdd is the data for a MessageReactionAdd event.
type MessageReactionAdd struct {
*MessageReaction
Expand Down Expand Up @@ -272,3 +287,8 @@ type WebhooksUpdate struct {
type InteractionCreate struct {
*Interaction
}

// UnmarshalJSON is a helper function to unmarshal Interaction object.
func (i *InteractionCreate) UnmarshalJSON(b []byte) error {
return json.Unmarshal(b, &i.Interaction)
}
152 changes: 152 additions & 0 deletions examples/components/main.go
@@ -0,0 +1,152 @@
package main

import (
"flag"
"log"
"os"
"os/signal"

"github.com/bwmarrin/discordgo"
)

// Bot parameters
var (
GuildID = flag.String("guild", "", "Test guild ID")
ChannelID = flag.String("channel", "", "Test channel ID")
BotToken = flag.String("token", "", "Bot access token")
AppID = flag.String("app", "", "Application ID")
)

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)
}
}

func main() {
s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) {
log.Println("Bot is up!")
})
// Buttons are part of interactions, so we register InteractionCreate handler
s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) {
if i.Type == discordgo.InteractionApplicationCommand {
if i.Data.Name == "feedback" {
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Are you satisfied with Buttons?",
// Buttons and other components are specified in Components field.
Components: []discordgo.Component{
// ActionRow is a container of all buttons in the same raw.
discordgo.ActionsRow{
Components: []discordgo.Component{
discordgo.Button{
Label: "Yes",
Style: discordgo.SuccessButton,
Disabled: false,
CustomID: "yes_btn",
},
discordgo.Button{
Label: "No",
Style: discordgo.DangerButton,
Disabled: false,
CustomID: "no_btn",
},
discordgo.Button{
Label: "I don't know",
Style: discordgo.LinkButton,
Disabled: false,
// Link buttons doesn't require CustomID and does not trigger the gateway/HTTP event
Link: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
Emoji: discordgo.ButtonEmoji{
Name: "🤷‍♂️",
},
},
},
},
// The message may have multiple actions rows.
discordgo.ActionsRow{
Components: []discordgo.Component{
discordgo.Button{
Label: "Discord Developers server",
Style: discordgo.LinkButton,
Disabled: false,
Link: "https://discord.gg/discord-developers",
},
},
},
},
},
})
if err != nil {
panic(err)
}
}
return
}
// Type for button press will be always InteractionButton (3)
if i.Type != discordgo.InteractionButton {
return
}

content := "Thanks for your feedback "

// CustomID field contains the same id as when was sent. It's used to identify the which button was clicked.
switch i.Data.CustomID {
case "yes_btn":
content += "(yes)"
case "no_btn":
content += "(no)"
}

s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
// Buttons also may update the message which they was attached to.
// Or may just acknowledge (InteractionResponseDeferredMessageUpdate) that the event was received and not update the message.
// To update it later you need to use interaction response edit endpoint.
Type: discordgo.InteractionResponseUpdateMessage,
Data: &discordgo.InteractionResponseData{
Content: content,
Components: []discordgo.Component{
discordgo.ActionsRow{
Components: []discordgo.Component{
discordgo.Button{
Label: "Our sponsor",
Style: discordgo.LinkButton,
Disabled: false,
Link: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
Emoji: discordgo.ButtonEmoji{
Name: "💠",
},
},
},
},
},
},
})
})
_, err := s.ApplicationCommandCreate(*AppID, *GuildID, &discordgo.ApplicationCommand{
Name: "feedback",
Description: "Give your feedback",
})

if err != nil {
log.Fatalf("Cannot create slash command: %v", err)
}

err = s.Open()
if err != nil {
log.Fatalf("Cannot open the session: %v", err)
}
defer s.Close()

stop := make(chan os.Signal)
signal.Notify(stop, os.Interrupt)
<-stop
log.Println("Graceful shutdown")
}

0 comments on commit 8c70cbf

Please sign in to comment.