mirror of
https://github.com/diamondburned/arikawa.git
synced 2024-11-19 21:32:49 +00:00
api: Finalized buttons implementation (#200)
* all: Added Components fields to message-related types * discord: Documented Reactions field * discord: Implement fix for Component * gateway: Added User and Message fields to InteractionCreateEvent * api: Made InteractionResponseData fields optional for UpdateMessage responses * api: Deprecated and updated interaction response types * gateway: Update optional interaction event fields * discord: Added ComponentWrap for json unmarshalling * state: Update components on MessageUpdate * Updated buttons example
This commit is contained in:
parent
e04b19eb0f
commit
10c8837000
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
"github.com/diamondburned/arikawa/v2/gateway"
|
"github.com/diamondburned/arikawa/v2/gateway"
|
||||||
"github.com/diamondburned/arikawa/v2/session"
|
"github.com/diamondburned/arikawa/v2/session"
|
||||||
|
"github.com/diamondburned/arikawa/v2/utils/json/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
// To run, do `APP_ID="APP ID" GUILD_ID="GUILD ID" BOT_TOKEN="TOKEN HERE" go run .`
|
// To run, do `APP_ID="APP ID" GUILD_ID="GUILD ID" BOT_TOKEN="TOKEN HERE" go run .`
|
||||||
|
@ -35,9 +36,9 @@ func main() {
|
||||||
Data: &api.InteractionResponseData{
|
Data: &api.InteractionResponseData{
|
||||||
Content: "This is a message with a button!",
|
Content: "This is a message with a button!",
|
||||||
Components: []discord.Component{
|
Components: []discord.Component{
|
||||||
discord.ActionRow{
|
discord.ActionRowComponent{
|
||||||
Components: []discord.Component{
|
Components: []discord.Component{
|
||||||
discord.Button{
|
discord.ButtonComponent{
|
||||||
Label: "Hello World!",
|
Label: "Hello World!",
|
||||||
CustomID: "first_button",
|
CustomID: "first_button",
|
||||||
Emoji: &discord.ButtonEmoji{
|
Emoji: &discord.ButtonEmoji{
|
||||||
|
@ -45,22 +46,22 @@ func main() {
|
||||||
},
|
},
|
||||||
Style: discord.PrimaryButton,
|
Style: discord.PrimaryButton,
|
||||||
},
|
},
|
||||||
discord.Button{
|
discord.ButtonComponent{
|
||||||
Label: "Secondary",
|
Label: "Secondary",
|
||||||
CustomID: "second_button",
|
CustomID: "second_button",
|
||||||
Style: discord.SecondaryButton,
|
Style: discord.SecondaryButton,
|
||||||
},
|
},
|
||||||
discord.Button{
|
discord.ButtonComponent{
|
||||||
Label: "Success",
|
Label: "Success",
|
||||||
CustomID: "success_button",
|
CustomID: "success_button",
|
||||||
Style: discord.SuccessButton,
|
Style: discord.SuccessButton,
|
||||||
},
|
},
|
||||||
discord.Button{
|
discord.ButtonComponent{
|
||||||
Label: "Danger",
|
Label: "Danger",
|
||||||
CustomID: "danger_button",
|
CustomID: "danger_button",
|
||||||
Style: discord.DangerButton,
|
Style: discord.DangerButton,
|
||||||
},
|
},
|
||||||
discord.Button{
|
discord.ButtonComponent{
|
||||||
Label: "Link",
|
Label: "Link",
|
||||||
URL: "https://google.com",
|
URL: "https://google.com",
|
||||||
Style: discord.LinkButton,
|
Style: discord.LinkButton,
|
||||||
|
@ -83,7 +84,7 @@ func main() {
|
||||||
data := api.InteractionResponse{
|
data := api.InteractionResponse{
|
||||||
Type: api.UpdateMessage,
|
Type: api.UpdateMessage,
|
||||||
Data: &api.InteractionResponseData{
|
Data: &api.InteractionResponseData{
|
||||||
Content: "Custom ID: " + e.Data.CustomID,
|
Content: option.NewNullableString("Custom ID: " + e.Data.CustomID),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,18 +3,20 @@ package api
|
||||||
import (
|
import (
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
"github.com/diamondburned/arikawa/v2/utils/httputil"
|
"github.com/diamondburned/arikawa/v2/utils/httputil"
|
||||||
|
"github.com/diamondburned/arikawa/v2/utils/json/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
var EndpointInteractions = Endpoint + "interactions/"
|
var EndpointInteractions = Endpoint + "interactions/"
|
||||||
|
|
||||||
type InteractionResponseType uint
|
type InteractionResponseType uint
|
||||||
|
|
||||||
|
// https://discord.com/developers/docs/interactions/slash-commands#interaction-response-interactioncallbacktype
|
||||||
const (
|
const (
|
||||||
PongInteraction InteractionResponseType = iota + 1
|
PongInteraction InteractionResponseType = iota + 1
|
||||||
AcknowledgeInteraction
|
_
|
||||||
MessageInteraction
|
_
|
||||||
MessageInteractionWithSource
|
MessageInteractionWithSource
|
||||||
AcknowledgeInteractionWithSource
|
DeferredMessageInteractionWithSource
|
||||||
DeferredMessageUpdate
|
DeferredMessageUpdate
|
||||||
UpdateMessage
|
UpdateMessage
|
||||||
)
|
)
|
||||||
|
@ -27,11 +29,11 @@ type InteractionResponse struct {
|
||||||
// InteractionResponseData is InteractionApplicationCommandCallbackData in the
|
// InteractionResponseData is InteractionApplicationCommandCallbackData in the
|
||||||
// official documentation.
|
// official documentation.
|
||||||
type InteractionResponseData struct {
|
type InteractionResponseData struct {
|
||||||
TTS bool `json:"tts"`
|
TTS option.NullableBool `json:"tts,omitempty"`
|
||||||
Content string `json:"content"`
|
Content option.NullableString `json:"content,omitempty"`
|
||||||
Components []discord.Component `json:"components,omitempty"`
|
Components *[]discord.Component `json:"components,omitempty"`
|
||||||
Embeds []discord.Embed `json:"embeds,omitempty"`
|
Embeds *[]discord.Embed `json:"embeds,omitempty"`
|
||||||
AllowedMentions AllowedMentions `json:"allowed_mentions,omitempty"`
|
AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RespondInteraction responds to an incoming interaction. It is also known as
|
// RespondInteraction responds to an incoming interaction. It is also known as
|
||||||
|
|
|
@ -291,6 +291,8 @@ type EditMessageData struct {
|
||||||
Content option.NullableString `json:"content,omitempty"`
|
Content option.NullableString `json:"content,omitempty"`
|
||||||
// Embed contains embedded rich content.
|
// Embed contains embedded rich content.
|
||||||
Embed *discord.Embed `json:"embed,omitempty"`
|
Embed *discord.Embed `json:"embed,omitempty"`
|
||||||
|
// Components contains the new components to attach.
|
||||||
|
Components *[]discord.Component `json:"components,omitempty"`
|
||||||
// AllowedMentions are the allowed mentions for a message.
|
// AllowedMentions are the allowed mentions for a message.
|
||||||
AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"`
|
AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"`
|
||||||
// Flags edits the flags of a message (only SUPPRESS_EMBEDS can currently
|
// Flags edits the flags of a message (only SUPPRESS_EMBEDS can currently
|
||||||
|
|
|
@ -105,6 +105,9 @@ type SendMessageData struct {
|
||||||
// Files is the list of file attachments to be uploaded. To reference a file
|
// Files is the list of file attachments to be uploaded. To reference a file
|
||||||
// in an embed, use (sendpart.File).AttachmentURI().
|
// in an embed, use (sendpart.File).AttachmentURI().
|
||||||
Files []sendpart.File `json:"-"`
|
Files []sendpart.File `json:"-"`
|
||||||
|
// Components is the list of components (such as buttons) to be attached to
|
||||||
|
// the message.
|
||||||
|
Components []discord.Component `json:"components,omitempty"`
|
||||||
|
|
||||||
// AllowedMentions are the allowed mentions for a message.
|
// AllowedMentions are the allowed mentions for a message.
|
||||||
AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"`
|
AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"`
|
||||||
|
|
|
@ -1,15 +1,70 @@
|
||||||
package discord
|
package discord
|
||||||
|
|
||||||
import "encoding/json"
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/diamondburned/arikawa/v2/utils/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrNestedActionRow = errors.New("action row cannot have action row as a child")
|
||||||
|
|
||||||
// ComponentType is the type of a component.
|
// ComponentType is the type of a component.
|
||||||
type ComponentType uint
|
type ComponentType uint
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ActionRowComponent ComponentType = iota + 1
|
ActionRowComponentType ComponentType = iota + 1
|
||||||
ButtonComponent
|
ButtonComponentType
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ComponentWrap wraps component for the purpose of JSON unmarshalling.
|
||||||
|
// Type assetions should be made on Component to access the underlying data.
|
||||||
|
type ComponentWrap struct {
|
||||||
|
Component Component
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnwrapComponents returns a slice of the underlying component interfaces.
|
||||||
|
func UnwrapComponents(wraps []ComponentWrap) []Component {
|
||||||
|
components := make([]Component, len(wraps))
|
||||||
|
for i, w := range wraps {
|
||||||
|
components[i] = w.Component
|
||||||
|
}
|
||||||
|
|
||||||
|
return components
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns the underlying component's type.
|
||||||
|
func (c ComponentWrap) Type() ComponentType {
|
||||||
|
return c.Component.Type()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON marshals the component in the format Discord expects.
|
||||||
|
func (c *ComponentWrap) MarshalJSON() ([]byte, error) {
|
||||||
|
return c.Component.MarshalJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals json into the component.
|
||||||
|
func (c *ComponentWrap) UnmarshalJSON(b []byte) error {
|
||||||
|
var t struct {
|
||||||
|
Type ComponentType `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.Unmarshal(b, &t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t.Type {
|
||||||
|
case ActionRowComponentType:
|
||||||
|
c.Component = &ActionRowComponent{}
|
||||||
|
case ButtonComponentType:
|
||||||
|
c.Component = &ButtonComponent{}
|
||||||
|
default:
|
||||||
|
c.Component = &UnknownComponent{typ: t.Type}
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Unmarshal(b, c.Component)
|
||||||
|
}
|
||||||
|
|
||||||
// Component is a component that can be attached to an interaction response.
|
// Component is a component that can be attached to an interaction response.
|
||||||
type Component interface {
|
type Component interface {
|
||||||
json.Marshaler
|
json.Marshaler
|
||||||
|
@ -17,30 +72,70 @@ type Component interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActionRow is a row of components at the bottom of a message.
|
// ActionRow is a row of components at the bottom of a message.
|
||||||
type ActionRow struct {
|
type ActionRowComponent struct {
|
||||||
Components []Component `json:"components"`
|
Components []Component `json:"components"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type implements the InteractionComponent interface.
|
// Type implements the Component Data interface.
|
||||||
func (ActionRow) Type() ComponentType {
|
func (ActionRowComponent) Type() ComponentType {
|
||||||
return ActionRowComponent
|
return ActionRowComponentType
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON marshals the action row in the format Discord expects.
|
// MarshalJSON marshals the action row in the format Discord expects.
|
||||||
func (a ActionRow) MarshalJSON() ([]byte, error) {
|
func (a ActionRowComponent) MarshalJSON() ([]byte, error) {
|
||||||
type actionRow ActionRow
|
type actionRow ActionRowComponent
|
||||||
|
|
||||||
return json.Marshal(struct {
|
return json.Marshal(struct {
|
||||||
actionRow
|
actionRow
|
||||||
Type ComponentType `json:"type"`
|
Type ComponentType `json:"type"`
|
||||||
}{
|
}{
|
||||||
actionRow: actionRow(a),
|
actionRow: actionRow(a),
|
||||||
Type: ActionRowComponent,
|
Type: ActionRowComponentType,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals json into the components.
|
||||||
|
func (a *ActionRowComponent) UnmarshalJSON(b []byte) error {
|
||||||
|
type actionRow ActionRowComponent
|
||||||
|
|
||||||
|
type rowTypes struct {
|
||||||
|
Components []struct {
|
||||||
|
Type ComponentType `json:"type"`
|
||||||
|
} `json:"components"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var r rowTypes
|
||||||
|
err := json.Unmarshal(b, &r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Components = make([]Component, len(r.Components))
|
||||||
|
for i, t := range r.Components {
|
||||||
|
switch t.Type {
|
||||||
|
case ActionRowComponentType:
|
||||||
|
// ActionRow cannot have child components of type Actionrow
|
||||||
|
return ErrNestedActionRow
|
||||||
|
case ButtonComponentType:
|
||||||
|
a.Components[i] = &ButtonComponent{}
|
||||||
|
default:
|
||||||
|
a.Components[i] = &UnknownComponent{typ: t.Type}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
alias := actionRow(*a)
|
||||||
|
err = json.Unmarshal(b, &alias)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*a = ActionRowComponent(alias)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Button is a clickable button that may be added to an interaction response.
|
// Button is a clickable button that may be added to an interaction response.
|
||||||
type Button struct {
|
type ButtonComponent struct {
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
// CustomID attached to InteractionCreate event when clicked.
|
// CustomID attached to InteractionCreate event when clicked.
|
||||||
CustomID string `json:"custom_id"`
|
CustomID string `json:"custom_id"`
|
||||||
|
@ -51,6 +146,11 @@ type Button struct {
|
||||||
Disabled bool `json:"disabled,omitempty"`
|
Disabled bool `json:"disabled,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Type implements the Component Data interface.
|
||||||
|
func (ButtonComponent) Type() ComponentType {
|
||||||
|
return ButtonComponentType
|
||||||
|
}
|
||||||
|
|
||||||
// ButtonStyle is the style to display a button in.
|
// ButtonStyle is the style to display a button in.
|
||||||
type ButtonStyle uint
|
type ButtonStyle uint
|
||||||
|
|
||||||
|
@ -70,14 +170,9 @@ type ButtonEmoji struct {
|
||||||
Animated bool `json:"animated,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.
|
// MarshalJSON marshals the button in the format Discord expects.
|
||||||
func (b Button) MarshalJSON() ([]byte, error) {
|
func (b ButtonComponent) MarshalJSON() ([]byte, error) {
|
||||||
type button Button
|
type button ButtonComponent
|
||||||
|
|
||||||
if b.Style == 0 {
|
if b.Style == 0 {
|
||||||
b.Style = PrimaryButton // Sane default for button.
|
b.Style = PrimaryButton // Sane default for button.
|
||||||
|
@ -88,6 +183,18 @@ func (b Button) MarshalJSON() ([]byte, error) {
|
||||||
Type ComponentType `json:"type"`
|
Type ComponentType `json:"type"`
|
||||||
}{
|
}{
|
||||||
button: button(b),
|
button: button(b),
|
||||||
Type: ButtonComponent,
|
Type: ButtonComponentType,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnknownComponent is reserved for components with unknown or not yet
|
||||||
|
// implemented components types.
|
||||||
|
type UnknownComponent struct {
|
||||||
|
json.Raw
|
||||||
|
typ ComponentType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type implements the Component Data interface.
|
||||||
|
func (u UnknownComponent) Type() ComponentType {
|
||||||
|
return u.typ
|
||||||
|
}
|
||||||
|
|
|
@ -72,8 +72,10 @@ type Message struct {
|
||||||
Attachments []Attachment `json:"attachments"`
|
Attachments []Attachment `json:"attachments"`
|
||||||
// Embeds contains any embedded content.
|
// Embeds contains any embedded content.
|
||||||
Embeds []Embed `json:"embeds"`
|
Embeds []Embed `json:"embeds"`
|
||||||
|
// Reactions contains any reactions to the message.
|
||||||
Reactions []Reaction `json:"reactions,omitempty"`
|
Reactions []Reaction `json:"reactions,omitempty"`
|
||||||
|
// Components contains any attached components.
|
||||||
|
Components []ComponentWrap `json:"components,omitempty"`
|
||||||
|
|
||||||
// Used for validating a message was sent
|
// Used for validating a message was sent
|
||||||
Nonce string `json:"nonce,omitempty"`
|
Nonce string `json:"nonce,omitempty"`
|
||||||
|
|
|
@ -372,14 +372,16 @@ type (
|
||||||
type (
|
type (
|
||||||
InteractionCreateEvent struct {
|
InteractionCreateEvent struct {
|
||||||
ID discord.InteractionID `json:"id"`
|
ID discord.InteractionID `json:"id"`
|
||||||
Type InteractionType `json:"type"`
|
|
||||||
Data InteractionData `json:"data"`
|
|
||||||
GuildID discord.GuildID `json:"guild_id"`
|
|
||||||
ChannelID discord.ChannelID `json:"channel_id"`
|
|
||||||
AppID discord.AppID `json:"application_id"`
|
AppID discord.AppID `json:"application_id"`
|
||||||
Member discord.Member `json:"member"`
|
Type InteractionType `json:"type"`
|
||||||
|
Data *InteractionData `json:"data,omitempty"`
|
||||||
|
GuildID discord.GuildID `json:"guild_id,omitempty"`
|
||||||
|
ChannelID discord.ChannelID `json:"channel_id,omitempty"`
|
||||||
|
Member *discord.Member `json:"member,omitempty"`
|
||||||
|
User *discord.User `json:"user,omitempty"`
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
Version int `json:"version"`
|
Version int `json:"version"`
|
||||||
|
Message *discord.Message `json:"message"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -164,6 +164,9 @@ func DiffMessage(src discord.Message, dst *discord.Message) {
|
||||||
if src.Reactions != nil {
|
if src.Reactions != nil {
|
||||||
dst.Reactions = src.Reactions
|
dst.Reactions = src.Reactions
|
||||||
}
|
}
|
||||||
|
if src.Components != nil {
|
||||||
|
dst.Components = src.Components
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Message) MessageRemove(channelID discord.ChannelID, messageID discord.MessageID) error {
|
func (s *Message) MessageRemove(channelID discord.ChannelID, messageID discord.MessageID) error {
|
||||||
|
|
Loading…
Reference in a new issue