1
0
Fork 0
mirror of https://github.com/diamondburned/arikawa.git synced 2024-11-27 09:12:53 +00:00

interaction: Implement buttons

This commit is contained in:
Chan Wen Xu 2021-05-12 13:36:03 +08:00 committed by diamondburned
parent 7785887719
commit c880cb2fc8
4 changed files with 248 additions and 5 deletions

138
_example/buttons/main.go Normal file
View file

@ -0,0 +1,138 @@
package main
import (
"log"
"os"
"github.com/diamondburned/arikawa/v2/api"
"github.com/diamondburned/arikawa/v2/discord"
"github.com/diamondburned/arikawa/v2/gateway"
"github.com/diamondburned/arikawa/v2/session"
)
// To run, do `APP_ID="APP ID" GUILD_ID="GUILD ID" BOT_TOKEN="TOKEN HERE" go run .`
func main() {
appID := discord.AppID(mustSnowflakeEnv("APP_ID"))
guildID := discord.GuildID(mustSnowflakeEnv("GUILD_ID"))
token := os.Getenv("BOT_TOKEN")
if token == "" {
log.Fatalln("No $BOT_TOKEN given.")
}
s, err := session.New("Bot " + token)
if err != nil {
log.Fatalln("Session failed:", err)
return
}
s.AddHandler(func(e *gateway.InteractionCreateEvent) {
if e.Type == gateway.CommandInteraction {
// Send a message with a button back on slash commands.
data := api.InteractionResponse{
Type: api.MessageInteractionWithSource,
Data: &api.InteractionResponseData{
Content: "This is a message with a button!",
Components: []discord.Component{
discord.ActionRow{
Components: []discord.Component{
discord.Button{
Label: "Hello World!",
CustomID: "first_button",
Emoji: &discord.ButtonEmoji{
Name: "👋",
},
Style: discord.PrimaryButton,
},
discord.Button{
Label: "Secondary",
CustomID: "second_button",
Style: discord.SecondaryButton,
},
discord.Button{
Label: "Success",
CustomID: "success_button",
Style: discord.SuccessButton,
},
discord.Button{
Label: "Danger",
CustomID: "danger_button",
Style: discord.DangerButton,
},
discord.Button{
Label: "Link",
URL: "https://google.com",
Style: discord.LinkButton,
},
},
},
},
},
}
if err := s.RespondInteraction(e.ID, e.Token, data); err != nil {
log.Println("failed to send interaction callback:", err)
}
}
if e.Type != gateway.ButtonInteraction {
return
}
data := api.InteractionResponse{
Type: api.UpdateMessage,
Data: &api.InteractionResponseData{
Content: "Custom ID: " + e.Data.CustomID,
},
}
if err := s.RespondInteraction(e.ID, e.Token, data); err != nil {
log.Println("failed to send interaction callback:", err)
}
})
s.Gateway.AddIntents(gateway.IntentGuilds)
s.Gateway.AddIntents(gateway.IntentGuildMessages)
if err := s.Open(); err != nil {
log.Fatalln("failed to open:", err)
}
defer s.Close()
log.Println("Gateway connected. Getting all guild commands.")
commands, err := s.GuildCommands(appID, guildID)
if err != nil {
log.Fatalln("failed to get guild commands:", err)
}
for _, command := range commands {
log.Println("Existing command", command.Name, "found.")
}
newCommands := []api.CreateCommandData{
{
Name: "buttons",
Description: "Send an interactable message.",
},
}
for _, command := range newCommands {
_, err := s.CreateGuildCommand(appID, guildID, command)
if err != nil {
log.Fatalln("failed to create guild command:", err)
}
}
// Block forever.
select {}
}
func mustSnowflakeEnv(env string) discord.Snowflake {
s, err := discord.ParseSnowflake(os.Getenv(env))
if err != nil {
log.Fatalf("Invalid snowflake for $%s: %v", env, err)
}
return s
}

View file

@ -15,6 +15,8 @@ const (
MessageInteraction
MessageInteractionWithSource
AcknowledgeInteractionWithSource
DeferredMessageUpdate
UpdateMessage
)
type InteractionResponse struct {
@ -25,17 +27,17 @@ type InteractionResponse struct {
// InteractionResponseData is InteractionApplicationCommandCallbackData in the
// official documentation.
type InteractionResponseData struct {
TTS bool `json:"tts"`
Content string `json:"content"`
Embeds []discord.Embed `json:"embeds,omitempty"`
AllowedMentions AllowedMentions `json:"allowed_mentions,omitempty"`
TTS bool `json:"tts"`
Content string `json:"content"`
Components []discord.Component `json:"components,omitempty"`
Embeds []discord.Embed `json:"embeds,omitempty"`
AllowedMentions AllowedMentions `json:"allowed_mentions,omitempty"`
}
// RespondInteraction responds to an incoming interaction. It is also known as
// an "interaction callback".
func (c *Client) RespondInteraction(
id discord.InteractionID, token string, data InteractionResponse) error {
return c.FastRequest(
"POST",
EndpointInteractions+id.String()+"/"+token+"/callback",

93
discord/component.go Normal file
View file

@ -0,0 +1,93 @@
package discord
import "encoding/json"
// ComponentType is the type of a component.
type ComponentType uint
const (
ActionRowComponent ComponentType = iota + 1
ButtonComponent
)
// Component is a component that can be attached to an interaction response.
type Component interface {
json.Marshaler
Type() ComponentType
}
// ActionRow is a row of components at the bottom of a message.
type ActionRow struct {
Components []Component `json:"components"`
}
// Type implements the InteractionComponent interface.
func (ActionRow) Type() ComponentType {
return ActionRowComponent
}
// MarshalJSON marshals the action row in the format Discord expects.
func (a ActionRow) MarshalJSON() ([]byte, error) {
type actionRow ActionRow
return json.Marshal(struct {
actionRow
Type ComponentType `json:"type"`
}{
actionRow: actionRow(a),
Type: ActionRowComponent,
})
}
// Button is a clickable button that may be added to an interaction response.
type Button struct {
Label string `json:"label"`
// CustomID attached to InteractionCreate event when clicked.
CustomID string `json:"custom_id"`
Style ButtonStyle `json:"style"`
Emoji *ButtonEmoji `json:"emoji,omitempty"`
// Present on link-style buttons.
URL string `json:"url,omitempty"`
Disabled bool `json:"disabled,omitempty"`
}
// ButtonStyle is the style to display a button in.
type ButtonStyle uint
// All types of ButtonStyle documented.
const (
PrimaryButton ButtonStyle = iota + 1 // Blurple button.
SecondaryButton // Grey button.
SuccessButton // Green button.
DangerButton // Red button.
LinkButton // Button that navigates to a URL.
)
// ButtonEmoji is the emoji displayed on the button before the text.
type ButtonEmoji struct {
Name string `json:"name,omitempty"`
ID EmojiID `json:"id,omitempty"`
Animated bool `json:"animated,omitempty"`
}
// Type implements the InteractionComponent interface.
func (Button) Type() ComponentType {
return ButtonComponent
}
// MarshalJSON marshals the button in the format Discord expects.
func (b Button) MarshalJSON() ([]byte, error) {
type button Button
if b.Style == 0 {
b.Style = PrimaryButton // Sane default for button.
}
return json.Marshal(struct {
button
Type ComponentType `json:"type"`
}{
button: button(b),
Type: ButtonComponent,
})
}

View file

@ -376,6 +376,7 @@ type (
Data InteractionData `json:"data"`
GuildID discord.GuildID `json:"guild_id"`
ChannelID discord.ChannelID `json:"channel_id"`
AppID discord.AppID `json:"application_id"`
Member discord.Member `json:"member"`
Token string `json:"token"`
Version int `json:"version"`
@ -387,12 +388,21 @@ type InteractionType uint
const (
PingInteraction InteractionType = iota + 1
CommandInteraction
ButtonInteraction
)
// TODO: InteractionData is being overloaded by Slash Command and Button at the moment.
// Separate them when v3 rolls out.
type InteractionData struct {
// Slash commands
ID discord.CommandID `json:"id"`
Name string `json:"name"`
Options []InteractionOption `json:"options"`
// Button
CustomID string `json:"custom_id"`
ComponentType discord.ComponentType `json:"component_type"`
}
type InteractionOption struct {