Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interactions: the Buttons #933

Merged
merged 33 commits into from Jun 27, 2021
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e6cc744
Interactions: buttons
FedorLap2006 May 16, 2021
4a8d880
Fix merge
FedorLap2006 May 16, 2021
8930b92
Doc fix
FedorLap2006 May 16, 2021
23fb77c
Gofmt fix
FedorLap2006 May 16, 2021
93b0bcc
Fix typo
FedorLap2006 May 17, 2021
26e0fe2
Remaking interaction data into interface
FedorLap2006 May 18, 2021
5656869
Godoc fix
FedorLap2006 May 18, 2021
4ed73c2
Gofmt fix
FedorLap2006 May 18, 2021
e10d270
Godoc fix
FedorLap2006 May 18, 2021
9646e09
InteractionData helper functions and some fixes in slash commands exa…
FedorLap2006 May 18, 2021
e946bc8
Fix components example
FedorLap2006 May 18, 2021
5dfd40e
Yet another fix of components example
FedorLap2006 May 18, 2021
a081bca
Merge branch 'bwmarrin:master' into master
FedorLap2006 May 20, 2021
3c6bda6
Fix interaction unmarshaling
FedorLap2006 May 21, 2021
72aac18
Gofmt fix
FedorLap2006 May 21, 2021
8c792ad
Godoc fix
FedorLap2006 May 21, 2021
f5c78d0
Gofmt fix
FedorLap2006 May 23, 2021
e7fb87f
Corrected naming and docs
FedorLap2006 May 26, 2021
495990c
Rolled back API version
FedorLap2006 May 30, 2021
2f86d0c
Requested fixes
FedorLap2006 May 30, 2021
931b4e6
Merge and enum fix
FedorLap2006 May 30, 2021
8670b0e
Added support of components to webhook and regular messages
FedorLap2006 Jun 15, 2021
adc5b70
Fix components unmarshaling
FedorLap2006 Jun 18, 2021
afb1057
Godoc fix
FedorLap2006 Jun 18, 2021
23a197a
Requested fixes
FedorLap2006 Jun 19, 2021
7b457eb
Fixed unmarshaling issues
FedorLap2006 Jun 22, 2021
9dbe959
Components example: cleanup
FedorLap2006 Jun 22, 2021
6f3aa6b
Added components tracking to state
FedorLap2006 Jun 22, 2021
881a24b
Requested fixes
FedorLap2006 Jun 23, 2021
bcc0a2f
Renaming fix
FedorLap2006 Jun 23, 2021
5149e24
Remove more named returns
CarsonHoffman Jun 23, 2021
10b295f
Merge branch 'bwmarrin:master' into master
FedorLap2006 Jun 26, 2021
3bc7e7b
Minor English fixes
CarsonHoffman Jun 27, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
135 changes: 135 additions & 0 deletions components.go
@@ -0,0 +1,135 @@
package discordgo

import (
"encoding/json"
)

// ComponentType is type of component.
type ComponentType uint

// MessageComponent types.
const (
ActionsRowComponent ComponentType = 1
ButtonComponent ComponentType = 2
)

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

// UnmarshableMessageComponent a helper for components which need to be unmarshaled (like the ones in message object).
// Since interfaces can't be unmarshaled this exists.
type UnmarshableMessageComponent struct {
FedorLap2006 marked this conversation as resolved.
Show resolved Hide resolved
MessageComponent
}

// UnmarshalJSON is a helper function to unmarshal MessageComponent object.
func (umc *UnmarshableMessageComponent) UnmarshalJSON(src []byte) (err error) {
var v struct {
Type ComponentType `json:"type"`
}
err = json.Unmarshal(src, &v)
if err != nil {
return
}

var data MessageComponent
switch v.Type {
case ActionsRowComponent:
v := ActionsRow{}
err = json.Unmarshal(src, &v)
CarsonHoffman marked this conversation as resolved.
Show resolved Hide resolved
data = v
case ButtonComponent:
v := Button{}
err = json.Unmarshal(src, &v)
data = v
}
if err != nil {
return
}
umc.MessageComponent = data
return
}

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

// MarshalJSON is a method for marshaling ActionsRow to a JSON object.
func (r ActionsRow) MarshalJSON() ([]byte, error) {
type actionsRow ActionsRow

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

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

// 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"`
CarsonHoffman marked this conversation as resolved.
Show resolved Hide resolved
CustomID string `json:"custom_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 json.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 (b Button) Type() ComponentType {
return ButtonComponent
}
8 changes: 8 additions & 0 deletions events.go
Expand Up @@ -272,3 +272,11 @@ type WebhooksUpdate struct {
type InteractionCreate struct {
*Interaction
}

// UnmarshalJSON is a helper function to unmarshal Interaction object.
// Since it's a pointer json.Unmarshal does not unmarshals it correctly (Interaction field is nil).
// And so we need to unmarshal it manually.
func (i *InteractionCreate) UnmarshalJSON(b []byte) error {
i.Interaction = new(Interaction)
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.ApplicationCommandData().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.MessageComponent{
// ActionRow is a container of all buttons within the same row.
discordgo.ActionsRow{
Components: []discordgo.MessageComponent{
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.MessageComponent{
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.InteractionMessageComponent {
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.MessageComponentData().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.MessageComponent{
discordgo.ActionsRow{
Components: []discordgo.MessageComponent{
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")
}