discord: Refactor interaction commands and components

This commit is an amalgamation of the following local commits:

    - Fix UnknownCommandOption unmarshaling
    - Add missing ButtonComponent.UnmarshalJSON
    - Fix ButtonComponents Style unmarshaling
    - Remove debug logging
    - SelectComponent.ComponentEmoji
    - Fix incorrect CommandOption interface
    - Switch to pointer types for interfaces
    - Remove CommandOptionMeta
    - Less verbose CommandOption type names
    - Fix unused CommandInteractionOption
    - Remove ComponentInteractionData
This commit is contained in:
diamondburned 2021-10-10 15:44:31 -07:00
parent 123f8bc41f
commit 80b1dc3831
No known key found for this signature in database
GPG Key ID: D78C4471CE776659
21 changed files with 1740 additions and 710 deletions

View File

@ -19,82 +19,85 @@ func main() {
token := os.Getenv("BOT_TOKEN")
if token == "" {
log.Fatalln("No $BOT_TOKEN given.")
log.Fatalln("no $BOT_TOKEN given")
}
s, err := session.New("Bot " + token)
if err != nil {
log.Fatalln("Session failed:", err)
log.Fatalln("session failed:", err)
return
}
app, err := s.CurrentApplication()
if err != nil {
log.Fatalln("Failed to get application ID:", err)
log.Fatalln("failed to get application ID:", err)
}
appID := app.ID
s.AddHandler(func(e *gateway.InteractionCreateEvent) {
if e.Type == discord.CommandInteraction {
var resp api.InteractionResponse
switch data := e.Data.(type) {
case discord.CommandInteraction:
if data.Name != "buttons" {
resp = api.InteractionResponse{
Type: api.MessageInteractionWithSource,
Data: &api.InteractionResponseData{
Content: option.NewNullableString("Unknown command: " + data.Name),
},
}
break
}
// Send a message with a button back on slash commands.
data := api.InteractionResponse{
resp = api.InteractionResponse{
Type: api.MessageInteractionWithSource,
Data: &api.InteractionResponseData{
Content: option.NewNullableString("This is a message with a button!"),
Components: &[]discord.Component{
&discord.ActionRowComponent{
Components: []discord.Component{
&discord.ButtonComponent{
Label: "Hello World!",
CustomID: "first_button",
Emoji: &discord.ButtonEmoji{
Name: "👋",
},
Style: discord.PrimaryButton,
},
&discord.ButtonComponent{
Label: "Secondary",
CustomID: "second_button",
Style: discord.SecondaryButton,
},
&discord.ButtonComponent{
Label: "Success",
CustomID: "success_button",
Style: discord.SuccessButton,
},
&discord.ButtonComponent{
Label: "Danger",
CustomID: "danger_button",
Style: discord.DangerButton,
},
&discord.ButtonComponent{
Label: "Link",
URL: "https://google.com",
Style: discord.LinkButton,
},
Components: discord.ComponentsPtr(
discord.ActionRowComponent{
discord.ButtonComponent{
Label: "Hello World!",
CustomID: "first_button",
Emoji: &discord.ComponentEmoji{Name: "👋"},
Style: discord.PrimaryButtonStyle(),
},
discord.ButtonComponent{
Label: "Secondary",
CustomID: "second_button",
Style: discord.SecondaryButtonStyle(),
},
discord.ButtonComponent{
Label: "Success",
CustomID: "success_button",
Style: discord.SuccessButtonStyle(),
},
discord.ButtonComponent{
Label: "Danger",
CustomID: "danger_button",
Style: discord.DangerButtonStyle(),
},
},
},
// This is automatically put into its own row.
discord.ButtonComponent{
Label: "Link",
Style: discord.LinkButtonStyle("https://google.com"),
},
),
},
}
if err := s.RespondInteraction(e.ID, e.Token, data); err != nil {
log.Println("failed to send interaction callback:", err)
case discord.ComponentInteraction:
resp = api.InteractionResponse{
Type: api.UpdateMessage,
Data: &api.InteractionResponseData{
Content: option.NewNullableString("Custom ID: " + string(data.ID())),
},
}
}
if e.Type != discord.ComponentInteraction {
default:
log.Printf("unknown interaction type %T", e.Data)
return
}
customID := e.Data.(*discord.ComponentInteractionData).CustomID
data := api.InteractionResponse{
Type: api.UpdateMessage,
Data: &api.InteractionResponseData{
Content: option.NewNullableString("Custom ID: " + customID),
},
}
if err := s.RespondInteraction(e.ID, e.Token, data); err != nil {
if err := s.RespondInteraction(e.ID, e.Token, resp); err != nil {
log.Println("failed to send interaction callback:", err)
}
})
@ -125,6 +128,8 @@ func main() {
},
}
log.Println("Creating guild commands...")
for _, command := range newCommands {
_, err := s.CreateGuildCommand(appID, guildID, command)
if err != nil {
@ -132,6 +137,8 @@ func main() {
}
}
log.Println("Guild commands created. Bot is ready.")
// Block forever.
select {}
}

View File

@ -8,7 +8,7 @@ import (
"github.com/diamondburned/arikawa/v3/api"
"github.com/diamondburned/arikawa/v3/discord"
"github.com/diamondburned/arikawa/v3/gateway"
"github.com/diamondburned/arikawa/v3/session"
"github.com/diamondburned/arikawa/v3/state"
"github.com/diamondburned/arikawa/v3/utils/json/option"
)
@ -22,7 +22,7 @@ func main() {
log.Fatalln("No $BOT_TOKEN given.")
}
s, err := session.New("Bot " + token)
s, err := state.New("Bot " + token)
if err != nil {
log.Fatalln("Session failed:", err)
return
@ -32,7 +32,6 @@ func main() {
if err != nil {
log.Fatalln("Failed to get application ID:", err)
}
appID := app.ID
s.AddHandler(func(e *gateway.InteractionCreateEvent) {
data := api.InteractionResponse{
@ -57,7 +56,7 @@ func main() {
log.Println("Gateway connected. Getting all guild commands.")
commands, err := s.GuildCommands(appID, guildID)
commands, err := s.GuildCommands(app.ID, guildID)
if err != nil {
log.Fatalln("failed to get guild commands:", err)
}
@ -74,7 +73,7 @@ func main() {
}
for _, command := range newCommands {
_, err := s.CreateGuildCommand(appID, guildID, command)
_, err := s.CreateGuildCommand(app.ID, guildID, command)
if err != nil {
log.Fatalln("failed to create guild command:", err)
}

View File

@ -21,11 +21,11 @@ func (c *Client) CurrentApplication() (*discord.Application, error) {
// https://discord.com/developers/docs/interactions/slash-commands#create-global-application-command-json-params
type CreateCommandData struct {
Name string `json:"name"`
Description string `json:"description"`
Options []discord.CommandOption `json:"options,omitempty"`
NoDefaultPermission bool `json:"-"`
Type discord.CommandType `json:"type,omitempty"`
Name string `json:"name"`
Description string `json:"description"`
Options discord.CommandOptions `json:"options,omitempty"`
NoDefaultPermission bool `json:"-"`
Type discord.CommandType `json:"type,omitempty"`
}
func (c CreateCommandData) MarshalJSON() ([]byte, error) {

View File

@ -63,7 +63,7 @@ type InteractionResponseData struct {
Embeds *[]discord.Embed `json:"embeds,omitempty"`
// Components is the list of components (such as buttons) to be attached to
// the message.
Components *[]discord.Component `json:"components,omitempty"`
Components *discord.ContainerComponents `json:"components,omitempty"`
// AllowedMentions are the allowed mentions for the message.
AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"`
// Flags are the interaction application command callback data flags.
@ -149,7 +149,7 @@ type EditInteractionResponseData struct {
// Embeds contains embedded rich content.
Embeds *[]discord.Embed `json:"embeds,omitempty"`
// Components contains the new components to attach.
Components *[]discord.Component `json:"components,omitempty"`
Components *discord.ContainerComponents `json:"components,omitempty"`
// AllowedMentions are the allowed mentions for the message.
AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"`
// Attachments are the attached files to keep.

View File

@ -287,7 +287,7 @@ type EditMessageData struct {
// Embeds contains embedded rich content.
Embeds *[]discord.Embed `json:"embeds,omitempty"`
// Components contains the new components to attach.
Components *[]discord.Component `json:"components,omitempty"`
Components *discord.ContainerComponents `json:"components,omitempty"`
// AllowedMentions are the allowed mentions for a message.
AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"`
// Attachments are the attached files to keep

View File

@ -108,7 +108,7 @@ type SendMessageData struct {
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"`
Components discord.ContainerComponents `json:"components,omitempty"`
// AllowedMentions are the allowed mentions for a message.
AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"`

View File

@ -147,7 +147,7 @@ type ExecuteData struct {
// Components is the list of components (such as buttons) to be attached to
// the message.
Components []discord.Component `json:"components,omitempty"`
Components discord.ContainerComponents `json:"components,omitempty"`
// Files represents a list of files to upload. This will not be
// JSON-encoded and will only be available through WriteMultipart.
@ -238,7 +238,7 @@ type EditMessageData struct {
// Embeds contains embedded rich content.
Embeds *[]discord.Embed `json:"embeds,omitempty"`
// Components contains the new components to attach.
Components *[]discord.Component `json:"components,omitempty"`
Components *discord.ContainerComponents `json:"components,omitempty"`
// AllowedMentions are the allowed mentions for a message.
AllowedMentions *api.AllowedMentions `json:"allowed_mentions,omitempty"`
// Attachments are the attached files to keep

View File

@ -1,20 +1,14 @@
package discord
import (
"time"
"github.com/diamondburned/arikawa/v3/utils/json"
)
type Application struct {
// ID is the ID of the app.
ID AppID `json:"id"`
// Name is the name of the app.
Name string `json:"string"`
Name string `json:"name"`
// Icon is the icon hash of the app.
Icon *Hash `json:"icon"`
// Description is the description of the app.
Description string `json:"string"`
Description string `json:"description"`
// RPCOrigins is the RPC origin urls, if RPC is enabled.
RPCOrigins []string `json:"rpc_origins"`
// BotPublic is whether users besides the app owner can join the app's bot
@ -54,6 +48,7 @@ type Application struct {
// Slug is the URL slug that links to the game's store page.
Slug string `json:"slug"`
}
type ApplicationFlags uint32
const (
@ -96,158 +91,6 @@ const (
MembershipAccepted
)
// Command is the base "command" model that belongs to an application. This is
// what you are creating when you POST a new command.
//
// https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-structure
type Command struct {
// ID is the unique id of the command.
ID CommandID `json:"id"`
// Type is the type of command.
Type CommandType `json:"type,omitempty"`
// AppID is the unique id of the parent application.
AppID AppID `json:"application_id"`
// GuildID is the guild id of the command, if not global.
GuildID GuildID `json:"guild_id,omitempty"`
// Name is the 1-32 lowercase character name matching ^[\w-]{1,32}$.
Name string `json:"name"`
// Description is the 1-100 character description.
Description string `json:"description"`
// Options are the parameters for the command.
//
// Note that required options must be listed before optional options, and
// a command, or each individual subcommand, can have a maximum of 25
// options.
//
// It is only present on ChatInputCommands.
Options []CommandOption `json:"options,omitempty"`
// NoDefaultPermissions defines whether the command is NOT enabled by
// default when the app is added to a guild.
NoDefaultPermission bool `json:"-"`
// Version is an autoincrementing version identifier updated during
// substantial record changes
Version Snowflake `json:"version,omitempty"`
}
type CommandType uint
const (
ChatInputCommand CommandType = iota + 1
UserCommand
MessageCommand
)
func (c Command) MarshalJSON() ([]byte, error) {
type RawCommand Command
cmd := struct {
RawCommand
DefaultPermission bool `json:"default_permission"`
}{RawCommand: RawCommand(c)}
// Discord defaults default_permission to true, so we need to invert the
// meaning of the field (>No<DefaultPermission) to match Go's default
// value, false.
cmd.DefaultPermission = !c.NoDefaultPermission
return json.Marshal(cmd)
}
func (c *Command) UnmarshalJSON(data []byte) error {
type RawCommand Command
cmd := struct {
*RawCommand
DefaultPermission bool `json:"default_permission"`
}{RawCommand: (*RawCommand)(c)}
if err := json.Unmarshal(data, &cmd); err != nil {
return err
}
// Discord defaults default_permission to true, so we need to invert the
// meaning of the field (>No<DefaultPermission) to match Go's default
// value, false.
c.NoDefaultPermission = !cmd.DefaultPermission
// Discord defaults type to 1 if omitted.
if c.Type == 0 {
c.Type = ChatInputCommand
}
return nil
}
// CreatedAt returns a time object representing when the command was created.
func (c Command) CreatedAt() time.Time {
return c.ID.Time()
}
type CommandOption struct {
Type CommandOptionType `json:"type"`
Name string `json:"name"`
Description string `json:"description"`
Required bool `json:"required"`
Choices []CommandOptionChoice `json:"choices,omitempty"`
Options []CommandOption `json:"options,omitempty"`
// If this option is a channel type, the channels shown will be restricted to these types
ChannelTypes []ChannelType `json:"-"`
}
func (c CommandOption) MarshalJSON() ([]byte, error) {
type RawOption CommandOption
option := struct {
RawOption
ChannelTypes []uint16 `json:"channel_types,omitempty"`
}{RawOption: RawOption(c)}
// []uint8 is marshalled as a base64 string, so we marshal a []uint64 instead.
if len(c.ChannelTypes) > 0 {
option.ChannelTypes = make([]uint16, 0, len(c.ChannelTypes))
for _, t := range c.ChannelTypes {
option.ChannelTypes = append(option.ChannelTypes, uint16(t))
}
}
return json.Marshal(option)
}
func (c *CommandOption) UnmarshalJSON(data []byte) error {
type RawOption CommandOption
cmd := struct {
*RawOption
ChannelTypes []uint16 `json:"channel_types,omitempty"`
}{RawOption: (*RawOption)(c)}
if err := json.Unmarshal(data, &cmd); err != nil {
return err
}
c.ChannelTypes = make([]ChannelType, 0, len(cmd.ChannelTypes))
for _, t := range cmd.ChannelTypes {
c.ChannelTypes = append(c.ChannelTypes, ChannelType(t))
}
return nil
}
type CommandOptionType uint
const (
SubcommandOption CommandOptionType = iota + 1
SubcommandGroupOption
StringOption
IntegerOption
BooleanOption
UserOption
ChannelOption
RoleOption
MentionableOption
NumberOption
)
type CommandOptionChoice struct {
Name string `json:"name"`
Value string `json:"value"`
}
// https://discord.com/developers/docs/interactions/slash-commands#application-command-permissions-object-guild-application-command-permissions-structure
type GuildCommandPermissions struct {
ID CommandID `json:"id"`

View File

@ -144,9 +144,11 @@ func (ch Channel) IconURLWithType(t ImageType) string {
ch.ID.String() + "/" + t.format(ch.Icon)
}
type ChannelType uint8
// ChannelType describes the type of the channel.
//
// https://discord.com/developers/docs/resources/channel#channel-object-channel-types
type ChannelType uint16
const (
// GuildText is a text channel within a server.
GuildText ChannelType = iota

505
discord/command.go Normal file
View File

@ -0,0 +1,505 @@
package discord
import (
"fmt"
"time"
"github.com/diamondburned/arikawa/v3/utils/json"
"github.com/pkg/errors"
)
// CommandType is the type of the command, which describes the intended
// invokation source of the command.
type CommandType uint
const (
ChatInputCommand CommandType = iota + 1
UserCommand
MessageCommand
)
// Command is the base "command" model that belongs to an application. This is
// what you are creating when you POST a new command.
//
// https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-structure
type Command struct {
// ID is the unique id of the command.
ID CommandID `json:"id"`
// Type is the intended source of the command.
Type CommandType `json:"type,omitempty"`
// AppID is the unique id of the parent application.
AppID AppID `json:"application_id"`
// GuildID is the guild id of the command, if not global.
GuildID GuildID `json:"guild_id,omitempty"`
// Name is the 1-32 lowercase character name matching ^[\w-]{1,32}$.
Name string `json:"name"`
// Description is the 1-100 character description.
Description string `json:"description"`
// Options are the parameters for the command. Its types are value types,
// which can either be a SubcommandOption or a SubcommandGroupOption.
//
// Note that required options must be listed before optional options, and
// a command, or each individual subcommand, can have a maximum of 25
// options.
//
// It is only present on ChatInputCommands.
Options CommandOptions `json:"options,omitempty"`
// NoDefaultPermissions defines whether the command is NOT enabled by
// default when the app is added to a guild.
NoDefaultPermission bool `json:"-"`
// Version is an autoincrementing version identifier updated during
// substantial record changes
Version Snowflake `json:"version,omitempty"`
}
// CreatedAt returns a time object representing when the command was created.
func (c *Command) CreatedAt() time.Time {
return c.ID.Time()
}
func (c *Command) MarshalJSON() ([]byte, error) {
type RawCommand Command
cmd := struct {
*RawCommand
DefaultPermission bool `json:"default_permission"`
}{RawCommand: (*RawCommand)(c)}
// Discord defaults default_permission to true, so we need to invert the
// meaning of the field (>No<DefaultPermission) to match Go's default
// value, false.
cmd.DefaultPermission = !c.NoDefaultPermission
return json.Marshal(cmd)
}
func (c *Command) UnmarshalJSON(data []byte) error {
type rawCommand Command
cmd := struct {
*rawCommand
DefaultPermission bool `json:"default_permission"`
}{
rawCommand: (*rawCommand)(c),
}
if err := json.Unmarshal(data, &cmd); err != nil {
return err
}
// Discord defaults default_permission to true, so we need to invert the
// meaning of the field (>No<DefaultPermission) to match Go's default
// value, false.
c.NoDefaultPermission = !cmd.DefaultPermission
// Discord defaults type to 1 if omitted.
if c.Type == 0 {
c.Type = ChatInputCommand
}
return nil
}
// commandTypeCheckError is returned if a one of Command's Options fails the
// type check.
type commandTypeCheckError struct {
name string
got interface{}
expect string
}
// Name returns the name of the erroneous command.
func (err commandTypeCheckError) Name() string {
return err.name
}
// Data returns the erroneous data that belongs to this error. It is usually
// either a CommandOption or a CommandOptionValue.
func (err commandTypeCheckError) Data() interface{} {
return err.got
}
// Error implements error.
func (err commandTypeCheckError) Error() string {
return fmt.Sprintf(
"error at option name %q: expected %s, got %T",
err.name, err.expect, err.got,
)
}
// CommandOptions is used primarily for unmarshaling.
type CommandOptions []CommandOption
// UnmarshalJSON unmarshals b into these CommandOptions.
func (c *CommandOptions) UnmarshalJSON(b []byte) error {
var unknowns []UnknownCommandOption
if err := json.Unmarshal(b, &unknowns); err != nil {
return err
}
if len(unknowns) == 0 {
*c = nil
return nil
}
*c = make([]CommandOption, len(unknowns))
for i, v := range unknowns {
(*c)[i] = v.data
}
return nil
}
// UnknownCommandOption is used for unknown or unmarshaled CommandOption values.
// It is used in the unmarshaling stage for all CommandOption types.
//
// An UnknownCommandOption will satisfy both CommandOption and
// CommandOptionValue. Code that type-switches on either of them should not
// assume that only the expected types are used.
type UnknownCommandOption struct {
OptionName string `json:"name"`
OptionType CommandOptionType `json:"type"`
raw json.Raw
data CommandOption
}
// Name returns the supposeed name for this UnknownCommandOption.
func (u *UnknownCommandOption) Name() string {
return u.OptionName
}
// Type returns the supposed type for this UnknownCommandOption.
func (u *UnknownCommandOption) Type() CommandOptionType {
return u.OptionType
}
// Raw returns the raw JSON of this UnknownCommandOption. It will only return a
// non-nil blob of JSON if the command option's type cannot be found. If this
// method doesn't return nil, then Data's type will be UnknownCommandOption.
func (u *UnknownCommandOption) Raw() json.Raw {
return u.raw
}
// Data returns the underlying data type, which is a type that satisfies either
// CommandOption or CommandOptionValue.
func (u *UnknownCommandOption) Data() CommandOption {
return u.data
}
// Implement both CommandOption and CommandOptionValue.
func (u *UnknownCommandOption) _val() {}
// UnmarshalJSON parses the JSON into the struct as-is then reads all its
// children Options/Choices (if subcommand(group)). Typed command options are
// created into u.Data, or u.Raw if the type is unknown. This is done from the
// bottom up.
func (u *UnknownCommandOption) UnmarshalJSON(b []byte) error {
type unknown UnknownCommandOption
if err := json.Unmarshal(b, (*unknown)(u)); err != nil {
return errors.Wrap(err, "failed to unmarshal unknown")
}
switch u.Type() {
case SubcommandOptionType:
u.data = &SubcommandOption{}
case SubcommandGroupOptionType:
u.data = &SubcommandGroupOption{}
case StringOptionType:
u.data = &StringOption{}
case IntegerOptionType:
u.data = &IntegerOption{}
case BooleanOptionType:
u.data = &BooleanOption{}
case UserOptionType:
u.data = &UserOption{}
case ChannelOptionType:
u.data = &ChannelOption{}
case RoleOptionType:
u.data = &RoleOption{}
case MentionableOptionType:
u.data = &MentionableOption{}
case NumberOptionType:
u.data = &NumberOption{}
default:
// Copy the blob of bytes into a new slice.
u.raw = append(json.Raw(nil), b...)
u.data = u
return nil
}
if err := json.Unmarshal(b, u.data); err != nil {
return errors.Wrapf(err, "failed to unmarshal type %d", u.Type())
}
return nil
}
// CommandOptionType is the enumerated integer type for command options. The
// user usually won't have to touch any of these enum constants.
type CommandOptionType uint
const (
SubcommandOptionType CommandOptionType = iota + 1
SubcommandGroupOptionType
StringOptionType
IntegerOptionType
BooleanOptionType
UserOptionType
ChannelOptionType
RoleOptionType
MentionableOptionType
NumberOptionType
maxOptionType // for bound checking
)
// CommandOption is a union of command option types. The constructors for
// CommandOption will hint the types that can be a CommandOption.
type CommandOption interface {
Name() string
Type() CommandOptionType
}
// Maintaining these structs is quite an effort. If a new field is added into
// the generic CommandOption type, you MUST update ALL CommandOption structs.
// This means copy-pasting, yes.
// SubcommandGroupOption is a subcommand group that fits into a CommandOption.
type SubcommandGroupOption struct {
OptionName string `json:"name"`
Description string `json:"description"`
Required bool `json:"required"`
Subcommands []*SubcommandOption `json:"options"`
}
// Name implements CommandOption.
func (s *SubcommandGroupOption) Name() string { return s.OptionName }
// Type implements CommandOption.
func (s *SubcommandGroupOption) Type() CommandOptionType { return SubcommandGroupOptionType }
// SubcommandOption is a subcommand option that fits into a CommandOption.
type SubcommandOption struct {
OptionName string `json:"name"`
Description string `json:"description"`
Required bool `json:"required"`
// Options contains command option values. All CommandOption types except
// for SubcommandOption and SubcommandGroupOption will implement this
// interface.
Options []CommandOptionValue `json:"options"`
}
// Name implements CommandOption.
func (s *SubcommandOption) Name() string { return s.OptionName }
// Type implements CommandOption.
func (s *SubcommandOption) Type() CommandOptionType { return SubcommandOptionType }
// UnmarshalJSON unmarshals the given JSON bytes. It actually does
// type-checking.
func (s *SubcommandOption) UnmarshalJSON(b []byte) error {
type raw SubcommandOption
var opt struct {
*raw
Type CommandOptionType `json:"type"`
Options []UnknownCommandOption `json:"options"`
}
opt.raw = (*raw)(s)
if err := json.Unmarshal(b, &opt); err != nil {
return err
}
if opt.Type != SubcommandOptionType {
return fmt.Errorf("unexpected (not SubcommandOption) type %d", s.Type())
}
s.Options = make([]CommandOptionValue, len(opt.Options))
for i, opt := range opt.Options {
ov, ok := opt.data.(CommandOptionValue)
if !ok {
return commandTypeCheckError{opt.OptionName, opt.data, "CommandOptionValue"}
}
s.Options[i] = ov
}
return nil
}
// CommandOptionValue is a subcommand option that fits into a subcommand.
type CommandOptionValue interface {
CommandOption
_val()
}
// StringOption is a subcommand option that fits into a CommandOptionValue.
type StringOption struct {
OptionName string `json:"name"`
Description string `json:"description"`
Required bool `json:"required"`
Choices []StringChoice `json:"choices,omitempty"`
}
// Name implements CommandOption.
func (s *StringOption) Name() string { return s.OptionName }
// Type implements CommandOptionValue.
func (s *StringOption) Type() CommandOptionType { return StringOptionType }
func (s *StringOption) _val() {}
// StringChoice is a pair of string key to a string.
type StringChoice struct {
Name string `json:"name"`
Value string `json:"value"`
}
// IntegerOption is a subcommand option that fits into a CommandOptionValue.
type IntegerOption struct {
OptionName string `json:"name"`
Description string `json:"description"`
Required bool `json:"required"`
Choices []IntegerChoice `json:"choices,omitempty"`
}
// Name implements CommandOption.
func (i *IntegerOption) Name() string { return i.OptionName }
// Type implements CommandOptionValue.
func (i *IntegerOption) Type() CommandOptionType { return IntegerOptionType }
func (i *IntegerOption) _val() {}
// IntegerChoice is a pair of string key to an integer.
type IntegerChoice struct {
Name string `json:"name"`
Value int `json:"value"`
}
// BooleanOption is a subcommand option that fits into a CommandOptionValue.
type BooleanOption struct {
OptionName string `json:"name"`
Description string `json:"description"`
Required bool `json:"required"`
Choices []BooleanChoice `json:"choices,omitempty"`
}
// Name implements CommandOption.
func (b *BooleanOption) Name() string { return b.OptionName }
// Type implements CommandOptionValue.
func (b *BooleanOption) Type() CommandOptionType { return BooleanOptionType }
func (b *BooleanOption) _val() {}
// BooleanChoice is a pair of string key to a boolean.
type BooleanChoice struct {
Name string `json:"name"`
Value bool `json:"value"`
}
// UserOption is a subcommand option that fits into a CommandOptionValue.
type UserOption struct {
OptionName string `json:"name"`
Description string `json:"description"`
Required bool `json:"required"`
Choices []UserChoice `json:"choices,omitempty"`
}
// Name implements CommandOption.
func (u *UserOption) Name() string { return u.OptionName }
// Type implements CommandOptionValue.
func (u *UserOption) Type() CommandOptionType { return UserOptionType }
func (u *UserOption) _val() {}
// UserChoice is a pair of string key to a user ID.
type UserChoice struct {
Name string `json:"name"`
Value UserID `json:"value,string"`
}
// ChannelOption is a subcommand option that fits into a CommandOptionValue.
type ChannelOption struct {
OptionName string `json:"name"`
Description string `json:"description"`
Required bool `json:"required"`
Choices []ChannelChoice `json:"choices,omitempty"`
ChannelTypes []ChannelType `json:"channel_types,omitempty"`
}
// Name implements CommandOption.
func (c *ChannelOption) Name() string { return c.OptionName }
// Type implements CommandOptionValue.
func (c *ChannelOption) Type() CommandOptionType { return ChannelOptionType }
func (c *ChannelOption) _val() {}
// ChannelChoice is a pair of string key to a channel ID.
type ChannelChoice struct {
Name string `json:"name"`
Value ChannelID `json:"value,string"`
}
// RoleOption is a subcommand option that fits into a CommandOptionValue.
type RoleOption struct {
OptionName string `json:"name"`
Description string `json:"description"`
Required bool `json:"required"`
Choices []RoleChoice `json:"choices,omitempty"`
}
// Name implements CommandOption.
func (r *RoleOption) Name() string { return r.OptionName }
// Type implements CommandOptionValue.
func (r *RoleOption) Type() CommandOptionType { return RoleOptionType }
func (r *RoleOption) _val() {}
// RoleChoice is a pair of string key to a role ID.
type RoleChoice struct {
Name string `json:"name"`
Value RoleID `json:"value,string"`
}
// MentionableOption is a subcommand option that fits into a CommandOptionValue.
type MentionableOption struct {
OptionName string `json:"name"`
Description string `json:"description"`
Required bool `json:"required"`
Choices []MentionableChoice `json:"choices,omitempty"`
}
// Name implements CommandOption.
func (m *MentionableOption) Name() string { return m.OptionName }
// Type implements CommandOptionValue.
func (m *MentionableOption) Type() CommandOptionType { return MentionableOptionType }
func (m *MentionableOption) _val() {}
// MentionableChoice is a pair of string key to a mentionable snowflake IDs. To
// use this correctly, use the Resolved field.
type MentionableChoice struct {
Name string `json:"name"`
Value Snowflake `json:"value,string"`
}
// NumberOption is a subcommand option that fits into a CommandOptionValue.
type NumberOption struct {
OptionName string `json:"name"`
Description string `json:"description"`
Required bool `json:"required"`
Choices []NumberChoice `json:"choices,omitempty"`
}
// Name implements CommandOption.
func (n *NumberOption) Name() string { return n.OptionName }
// Type implements CommandOptionValue.
func (n *NumberOption) Type() CommandOptionType { return NumberOptionType }
func (n *NumberOption) _val() {}
// NumberChoice is a pair of string key to a float64 values.
type NumberChoice struct {
Name string `json:"name"`
Value float64 `json:"value"`
}

View File

@ -1,246 +1,455 @@
package discord
import (
"errors"
"fmt"
"github.com/diamondburned/arikawa/v3/utils/json"
"github.com/diamondburned/arikawa/v3/utils/json/option"
"github.com/pkg/errors"
)
var ErrNestedActionRow = errors.New("action row cannot have action row as a child")
// ComponentType is the type of a component.
type ComponentType uint
const (
ActionRowComponentType ComponentType = iota + 1
_ ComponentType = iota
ActionRowComponentType
ButtonComponentType
SelectComponentType
)
// ComponentWrap wraps Component for the purpose of JSON unmarshalling.
// Type assertions should be made on Component to access the underlying data.
// The underlying types of the Component are pointer types.
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 {
// String formats Type's name as a string.
func (t ComponentType) String() string {
switch t {
case ActionRowComponentType:
c.Component = &ActionRowComponent{}
return "ActionRow"
case ButtonComponentType:
c.Component = &ButtonComponent{}
return "Button"
case SelectComponentType:
return "Select"
default:
c.Component = &UnknownComponent{typ: t.Type}
return fmt.Sprintf("ComponentType(%d)", int(t))
}
return json.Unmarshal(b, c.Component)
}
// Component is a component that can be attached to an interaction response.
type Component interface {
json.Marshaler
Type() ComponentType
}
// ContainerComponents is primarily used for unmarshaling. It is the top-level
// type for component lists.
type ContainerComponents []ContainerComponent
// ActionRowComponent is a row of components at the bottom of a message.
type ActionRowComponent struct {
Components []Component `json:"components"`
}
// Type implements the Component interface.
func (*ActionRowComponent) Type() ComponentType {
return ActionRowComponentType
}
// MarshalJSON marshals the action row in the format Discord expects.
func (a ActionRowComponent) MarshalJSON() ([]byte, error) {
type actionRow ActionRowComponent
return json.Marshal(struct {
actionRow
Type ComponentType `json:"type"`
}{
actionRow: actionRow(a),
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 {
// UnmarshalJSON unmarshals JSON into the component. It does type-checking and
// will only accept container components.
func (c *ContainerComponents) UnmarshalJSON(b []byte) error {
var jsons []json.Raw
if err := json.Unmarshal(b, &jsons); 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}
*c = make([]ContainerComponent, len(jsons))
for i, b := range jsons {
p, err := ParseComponent(b)
if err != nil {
return err
}
}
alias := actionRow(*a)
err = json.Unmarshal(b, &alias)
if err != nil {
return err
cc, ok := p.(ContainerComponent)
if !ok {
return fmt.Errorf("expected container, got %T", p)
}
(*c)[i] = cc
}
*a = ActionRowComponent(alias)
return nil
}
// ButtonComponent is a clickable button that may be added to an interaction
// response.
type ButtonComponent 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"`
// URL is only present on link-style buttons.
URL URL `json:"url,omitempty"`
Disabled bool `json:"disabled,omitempty"`
// Component is a component that can be attached to an interaction response. A
// Component is either an InteractiveComponent or a ContainerComponent. See
// those appropriate types for more information.
type Component interface {
// Type returns the type of the underlying component.
Type() ComponentType
_cmp()
}
// InteractiveComponent extends the Component for components that are
// interactible, or components that aren't containers (like ActionRow). This is
// useful for ActionRow to type-check that no nested ActionRows are allowed.
type InteractiveComponent interface {
Component
// ID returns the ID of the underlying component.
ID() ComponentID
_icp()
}
// ContainerComponent is the opposite of InteractiveComponent: it describes
// components that only contain other components. The only component that
// satisfies that is ActionRow.
type ContainerComponent interface {
Component
_ctn()
}
// NewComponent returns a new Component from the given type that's matched with
// the global ComponentFunc map. If the type is unknown, then Unknown is used.
func ParseComponent(b []byte) (Component, error) {
var t struct {
Type ComponentType
}
if err := json.Unmarshal(b, &t); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal component type")
}
var c Component
switch t.Type {
case ActionRowComponentType:
c = &ActionRowComponent{}
case ButtonComponentType:
c = &ButtonComponent{}
case SelectComponentType:
c = &SelectComponent{}
default:
c = &UnknownComponent{typ: t.Type}
}
if err := json.Unmarshal(b, c); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal component body")
}
return c, nil
}
// ActionRow is a row of components at the bottom of a message. Its type,
// InteractiveComponent, ensures that only non-ActionRow components are allowed
// on it.
type ActionRowComponent []InteractiveComponent
// Components wraps the given list of components inside ActionRows if it's not
// already in one. This is a convenient function that wraps components inside
// ActionRows for the user. It panics if any of the action rows have nested
// action rows in them.
//
// Here's an example of how to use it:
//
// discord.Components(
// discord.TextButtonComponent("Hello, world!"),
// discord.Components(
// discord.TextButtonComponent("Hello!"),
// discord.TextButtonComponent("Delete."),
// ),
// )
//
func Components(components ...Component) ContainerComponents {
new := make([]ContainerComponent, len(components))
for i, comp := range components {
cc, ok := comp.(ContainerComponent)
if !ok {
// Wrap. We're asserting that comp is either a ContainerComponent or
// an InteractiveComponent. Neither would be a bug, therefore panic.
cc = &ActionRowComponent{comp.(InteractiveComponent)}
}
new[i] = cc
}
return new
}
// ComponentsPtr returns the pointer to Components' return. This is a
// convenient function.
func ComponentsPtr(components ...Component) *ContainerComponents {
v := Components(components...)
return &v
}
// Type implements the Component interface.
func (*ButtonComponent) Type() ComponentType {
return ButtonComponentType
func (a *ActionRowComponent) Type() ComponentType {
return ActionRowComponentType
}
// ButtonStyle is the style to display a button in.
type ButtonStyle uint
func (a *ActionRowComponent) _cmp() {}
func (a *ActionRowComponent) _ctn() {}
// All types of ButtonStyle documented.
const (
// PrimaryButton is a blurple button.
PrimaryButton ButtonStyle = iota + 1
// SecondaryButton is a grey button.
SecondaryButton
// SuccessButton is a green button.
SuccessButton
// DangerButton is a red button.
DangerButton
// LinkButton is a button that navigates to a URL.
LinkButton
)
// MarshalJSON marshals the action row in the format Discord expects.
func (a *ActionRowComponent) MarshalJSON() ([]byte, error) {
var actionRow struct {
Type ComponentType `json:"type"`
Components *[]InteractiveComponent `json:"components"`
}
// ButtonEmoji is the emoji displayed on the button before the text.
type ButtonEmoji struct {
Name string `json:"name,omitempty"`
actionRow.Components = (*[]InteractiveComponent)(a)
actionRow.Type = a.Type()
return json.Marshal(actionRow)
}
// UnmarshalJSON unmarshals JSON into the components. It does type-checking and
// will only accept interactive components.
func (a *ActionRowComponent) UnmarshalJSON(b []byte) error {
var row struct {
Components []json.Raw `json:"components"`
}
if err := json.Unmarshal(b, &row); err != nil {
return err
}
*a = make(ActionRowComponent, len(row.Components))
for i, b := range row.Components {
p, err := ParseComponent(b)
if err != nil {
return errors.Wrapf(err, "failed to parse component %d", i)
}
ic, ok := p.(InteractiveComponent)
if !ok {
return fmt.Errorf("expected interactive, got %T", p)
}
(*a)[i] = ic
}
return nil
}
// ComponentID is the type for a component's custom ID. It is NOT a snowflake,
// but rather a user-defined opaque string.
type ComponentID string
// ComponentEmoji is the emoji displayed on the button before the text. For more
// information, see Emoji.
type ComponentEmoji struct {
ID EmojiID `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Animated bool `json:"animated,omitempty"`
}
// MarshalJSON marshals the button in the format Discord expects.
func (b ButtonComponent) MarshalJSON() ([]byte, error) {
type button ButtonComponent
if b.Style == 0 {
b.Style = PrimaryButton // Sane default for button.
}
return json.Marshal(struct {
button
Type ComponentType `json:"type"`
}{
button: button(b),
Type: ButtonComponentType,
})
// ButtonComponentStyle is the style to display a button in. Use one of the
// ButtonStyle constructor functions.
type ButtonComponentStyle interface {
style() int
}
// SelectComponent is a clickable button that may be added to an interaction
type basicButtonStyle int
func (s basicButtonStyle) style() int { return int(s) }
const (
_ basicButtonStyle = iota
primaryButtonStyle
secondaryButtonStyle
successButtonStyle
dangerButtonStyle
linkButtonStyleNum
basicButtonStyleLen
)
// PrimaryButtonStyle is a style for a blurple button.
func PrimaryButtonStyle() ButtonComponentStyle { return primaryButtonStyle }
// SecondaryButtonStyle is a style for a grey button.
func SecondaryButtonStyle() ButtonComponentStyle { return secondaryButtonStyle }
// SuccessButtonStyle is a style for a green button.
func SuccessButtonStyle() ButtonComponentStyle { return successButtonStyle }
// DangerButtonStyle is a style for a red button.
func DangerButtonStyle() ButtonComponentStyle { return dangerButtonStyle }
type linkButtonStyle URL
func (s linkButtonStyle) style() int { return int(linkButtonStyleNum) }
// LinkButtonStyle is a button style that navigates to a URL.
func LinkButtonStyle(url URL) ButtonComponentStyle { return linkButtonStyle(url) }
// Button is a clickable button that may be added to an interaction
// response.
type SelectComponent struct {
CustomID string `json:"custom_id"`
Options []SelectComponentOption `json:"options"`
Placeholder string `json:"placeholder,omitempty"`
MinValues option.Int `json:"min_values,omitempty"`
MaxValues int `json:"max_values,omitempty"`
Disabled bool `json:"disabled,omitempty"`
type ButtonComponent struct {
// Style is one of the button styles.
Style ButtonComponentStyle `json:"style"`
// CustomID attached to InteractionCreate event when clicked.
CustomID ComponentID `json:"custom_id,omitempty"`
// Label is the text that appears on the button. It can have maximum 100
// characters.
Label string `json:"label,omitempty"`
// Emoji should have Name, ID and Animated filled.
Emoji *ComponentEmoji `json:"emoji,omitempty"`
// Disabled determines whether the button is disabled.
Disabled bool `json:"disabled,omitempty"`
}
type SelectComponentOption struct {
Label string `json:"label"`
Value string `json:"value"`
Description string `json:"description,omitempty"`
Emoji *ButtonEmoji `json:"emoji,omitempty"`
Default bool `json:"default,omitempty"`
// TextButtonComponent creates a new button with the given label used for the label and
// the custom ID.
func TextButtonComponent(style ButtonComponentStyle, label string) ButtonComponent {
return ButtonComponent{
Style: style,
Label: label,
CustomID: ComponentID(label),
}
}
// ID implements the Component interface.
func (b *ButtonComponent) ID() ComponentID { return b.CustomID }
// Type implements the Component interface.
func (*SelectComponent) Type() ComponentType {
func (b *ButtonComponent) Type() ComponentType {
return ButtonComponentType
}
func (b *ButtonComponent) _cmp() {}
func (b *ButtonComponent) _icp() {}
// MarshalJSON marshals the button in the format Discord expects.
func (b *ButtonComponent) MarshalJSON() ([]byte, error) {
if b.Style == nil {
b.Style = PrimaryButtonStyle() // Sane default for button.
}
type button ButtonComponent
type Msg struct {
*button
Type ComponentType `json:"type"`
Style int `json:"style"`
URL URL `json:"url,omitempty"`
}
msg := Msg{
Type: ButtonComponentType,
Style: b.Style.style(),
button: (*button)(b),
}
if link, ok := b.Style.(linkButtonStyle); ok {
msg.URL = URL(link)
}
return json.Marshal(msg)
}
// UnmarshalJSON unmarshals a component JSON into the button. It does NOT do
// type-checking; use ParseComponent for that.
func (b *ButtonComponent) UnmarshalJSON(j []byte) error {
type button ButtonComponent
msg := struct {
*button
Style basicButtonStyle `json:"style"`
URL URL `json:"url,omitempty"`
}{
button: (*button)(b),
}
if err := json.Unmarshal(j, &msg); err != nil {
return err
}
if 0 > msg.Style || msg.Style >= basicButtonStyleLen {
return fmt.Errorf("unknown button style %d", msg.Style)
}
switch msg.Style {
case linkButtonStyleNum:
b.Style = LinkButtonStyle(msg.URL)
default:
b.Style = msg.Style
}
return nil
}
// Select is a clickable button that may be added to an interaction
// response.
type SelectComponent struct {
// Options are the choices in the select.
Options []SelectOption `json:"options"`
// CustomID is the custom unique ID.
CustomID ComponentID `json:"custom_id,omitempty"`
// Placeholder is the custom placeholder text if nothing is selected. Max
// 100 characters.
Placeholder string `json:"placeholder,omitempty"`
// ValueLimits is the minimum and maximum number of items that can be
// chosen. The default is [1, 1] if ValueLimits is a zero-value.
ValueLimits [2]int `json:"-"`
// Disabled disables the select if true.
Disabled bool `json:"disabled,omitempty"`
}
// SelectOption is an option in the select component.
type SelectOption struct {
// Label is the user-facing name of the option. Max 100 characters.
Label string `json:"label"`
// Value is the internal value that is echoed back to the program. It's
// similar to the custom ID. Max 100 characters.
Value string `json:"value"`
// Description is the additional description of an option.
Description string `json:"description,omitempty"`
// Emoji is the optional emoji object.
Emoji *ComponentEmoji `json:"emoji,omitempty"`
// Default will render this option as selected by default if true.
Default bool `json:"default,omitempty"`
}
// ID implements the Component interface.
func (s *SelectComponent) ID() ComponentID { return s.CustomID }
// Type implements the Component interface.
func (s *SelectComponent) Type() ComponentType {
return SelectComponentType
}
// MarshalJSON marshals the select in the format Discord expects.
func (s SelectComponent) MarshalJSON() ([]byte, error) {
type selectComponent SelectComponent
func (s *SelectComponent) _cmp() {}
func (s *SelectComponent) _icp() {}
return json.Marshal(struct {
selectComponent
// MarshalJSON marshals the select in the format Discord expects.
func (s *SelectComponent) MarshalJSON() ([]byte, error) {
type sel SelectComponent
type Msg struct {
Type ComponentType `json:"type"`
}{
selectComponent: selectComponent(s),
Type: SelectComponentType,
})
*sel
MinValues *int `json:"min_values,omitempty"`
MaxValues *int `json:"max_values,omitempty"`
}
msg := Msg{
Type: SelectComponentType,
sel: (*sel)(s),
}
if s.ValueLimits != [2]int{0, 0} {
msg.MinValues = new(int)
msg.MaxValues = new(int)
*msg.MinValues = s.ValueLimits[0]
*msg.MaxValues = s.ValueLimits[1]
}
return json.Marshal(msg)
}
// UnknownComponent is reserved for components with unknown or not yet
// implemented components types.
// Unknown is reserved for components with unknown or not yet implemented
// components types. It can also be used in place of a ComponentInteraction.
type UnknownComponent struct {
json.Raw
id ComponentID
typ ComponentType
}
// Type implements the Component interface.
func (u *UnknownComponent) Type() ComponentType {
return u.typ
// ID implements the Component and ComponentInteraction interfaces.
func (u *UnknownComponent) ID() ComponentID { return u.id }
// Type implements the Component and ComponentInteraction interfaces.
func (u *UnknownComponent) Type() ComponentType { return u.typ }
// Type implements InteractionData.
func (u *UnknownComponent) InteractionType() InteractionDataType {
return ComponentInteractionType
}
func (u *UnknownComponent) resp() {}
func (u *UnknownComponent) data() {}
func (u *UnknownComponent) _cmp() {}
func (u *UnknownComponent) _icp() {}

View File

@ -4,14 +4,17 @@ import (
"strconv"
"github.com/diamondburned/arikawa/v3/utils/json"
"github.com/pkg/errors"
)
// InteractionEvent describes the full incoming interaction event. It may be a
// gateway event or a webhook event.
//
// https://discord.com/developers/docs/topics/gateway#interactions
type Interaction struct {
type InteractionEvent struct {
ID InteractionID `json:"id"`
Data InteractionData `json:"data"`
AppID AppID `json:"application_id"`
Type InteractionType `json:"type"`
Data InteractionData `json:"data,omitempty"`
ChannelID ChannelID `json:"channel_id,omitempty"`
Token string `json:"token"`
Version int `json:"version"`
@ -20,119 +23,286 @@ type Interaction struct {
// Only present for component interactions, not command interactions.
Message *Message `json:"message,omitempty"`
// Member is only present if this came from a guild.
// Member is only present if this came from a guild. To get a user, use the
// Sender method.
Member *Member `json:"member,omitempty"`
GuildID GuildID `json:"guild_id,omitempty"`
// User is only present if this didn't come from a guild.
// User is only present if this didn't come from a guild. To get a user, use
// the Sender method.
User *User `json:"user,omitempty"`
}
func (i *Interaction) UnmarshalJSON(p []byte) error {
type interaction Interaction
v := struct {
Data json.Raw `json:"data,omitempty"`
*interaction
}{interaction: (*interaction)(i)}
if err := json.Unmarshal(p, &v); err != nil {
// Sender returns the sender of this event from either the Member field or the
// User field. If neither of those fields are available, then nil is returned.
func (e *InteractionEvent) Sender() *User {
if e.User != nil {
return e.User
}
if e.Member != nil {
return &e.Member.User
}
return nil
}
// SenderID returns the sender's ID. See Sender for more information. If Sender
// returns nil, then 0 is returned.
func (e *InteractionEvent) SenderID() UserID {
if sender := e.Sender(); sender != nil {
return sender.ID
}
return 0
}
func (e *InteractionEvent) UnmarshalJSON(b []byte) error {
type event InteractionEvent
target := struct {
Type InteractionDataType `json:"type"`
Data json.Raw `json:"data"`
*event
}{
event: (*event)(e),
}
if err := json.Unmarshal(b, &target); err != nil {
return err
}
switch v.Type {
case PingInteraction:
var err error
switch target.Type {
case PingInteractionType:
e.Data = &PingInteraction{}
case CommandInteractionType:
e.Data = &CommandInteraction{}
case ComponentInteractionType:
d, err := ParseComponentInteraction(target.Data)
if err != nil {
return errors.Wrap(err, "failed to unmarshal component interaction event data")
}
e.Data = d
return nil
case ComponentInteraction:
i.Data = &ComponentInteractionData{}
case CommandInteraction:
i.Data = &CommandInteractionData{}
default:
i.Data = &UnknownInteractionData{typ: v.Type}
e.Data = &UnknownInteractionData{
Raw: target.Data,
typ: target.Type,
}
return nil
}
return json.Unmarshal(v.Data, i.Data)
if err := json.Unmarshal(target.Data, e.Data); err != nil {
return errors.Wrap(err, "failed to unmarshal interaction event data")
}
return err
}
type InteractionType uint
func (e *InteractionEvent) MarshalJSON() ([]byte, error) {
type event InteractionEvent
if e.Data == nil {
return nil, errors.New("missing InteractionEvent.Data")
}
if e.Data.InteractionType() == 0 {
return nil, errors.New("unexpected 0 InteractionEvent.Data.Type")
}
v := struct {
Type InteractionDataType `json:"type"`
*event
}{
Type: e.Data.InteractionType(),
event: (*event)(e),
}
return json.Marshal(v)
}
// InteractionDataType is the type of each Interaction, enumerated in
// integers.
type InteractionDataType uint
const (
PingInteraction InteractionType = iota + 1
CommandInteraction
ComponentInteraction
_ InteractionDataType = iota
PingInteractionType
CommandInteractionType
ComponentInteractionType
)
// InteractionData holds the data of an interaction.
// Type assertions should be made on InteractionData to access the underlying data.
// The underlying types of the InteractionData are pointer types.
// InteractionData holds the respose data of an interaction, or more
// specifically, the data that Discord sends to us. Type assertions should be
// made on it to access the underlying data. The underlying types of the
// Responses are value types. See the constructors for the possible types.
type InteractionData interface {
Type() InteractionType
InteractionType() InteractionDataType
data()
}
type ComponentInteractionData struct {
CustomID string `json:"custom_id"`
ComponentType ComponentType `json:"component_type"`
Values []string `json:"values"`
// PingInteraction is a ping Interaction response.
type PingInteraction struct{}
// InteractionType implements InteractionData.
func (*PingInteraction) InteractionType() InteractionDataType { return PingInteractionType }
func (*PingInteraction) data() {}
// ComponentInteraction is a union component interaction response types. The
// types can be whatever the constructors for this type will return. Underlying
// types of Response are all value types.
type ComponentInteraction interface {
InteractionData
// ID returns the ID of the component in response.
ID() ComponentID
// Type returns the type of the component in response.
Type() ComponentType
resp()
}
func (*ComponentInteractionData) Type() InteractionType {
return ComponentInteraction
// SelectInteraction is a select component's response.
type SelectInteraction struct {
CustomID ComponentID `json:"custom_id"`
Values []string `json:"values"`
}
type CommandInteractionData struct {
ID CommandID `json:"id"`
Name string `json:"name"`
Options []InteractionOption `json:"options"`
// ID implements ComponentInteraction.
func (s *SelectInteraction) ID() ComponentID { return s.CustomID }
// Type implements ComponentInteraction.
func (s *SelectInteraction) Type() ComponentType { return SelectComponentType }
// InteractionType implements InteractionData.
func (s *SelectInteraction) InteractionType() InteractionDataType {
return ComponentInteractionType
}
func (*CommandInteractionData) Type() InteractionType {
return CommandInteraction
func (s *SelectInteraction) resp() {}
func (s *SelectInteraction) data() {}
// ButtonInteraction is a button component's response. It is the custom ID of
// the button within the component tree.
type ButtonInteraction struct {
CustomID ComponentID `json:"custom_id"`
}
type UnknownInteractionData struct {
json.Raw
typ InteractionType
// ID implements ComponentInteraction.
func (b *ButtonInteraction) ID() ComponentID { return b.CustomID }
// Type implements ComponentInteraction.
func (b *ButtonInteraction) Type() ComponentType { return ButtonComponentType }
// InteractionType implements InteractionData.
func (b *ButtonInteraction) InteractionType() InteractionDataType {
return ComponentInteractionType
}
func (u *UnknownInteractionData) Type() InteractionType {
return u.typ
func (b *ButtonInteraction) data() {}
func (b *ButtonInteraction) resp() {}
// ParseComponentInteraction parses the given bytes as a component response.
func ParseComponentInteraction(b []byte) (ComponentInteraction, error) {
var t struct {
Type ComponentType `json:"type"`
CustomID ComponentID `json:"custom_id"`
}
if err := json.Unmarshal(b, &t); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal component interaction header")
}
var d ComponentInteraction
switch t.Type {
case ButtonComponentType:
d = &ButtonInteraction{CustomID: t.CustomID}
case SelectComponentType:
d = &SelectInteraction{CustomID: t.CustomID}
default:
d = &UnknownComponent{
Raw: append(json.Raw(nil), b...),
id: t.CustomID,
typ: t.Type,
}
}
if err := json.Unmarshal(b, d); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal component interaction data")
}
return d, nil
}
type InteractionOption struct {
Name string `json:"name"`
Value json.Raw `json:"value"`
Options []InteractionOption `json:"options"`
// CommandInteraction is a command interaction that Discord sends to us.
type CommandInteraction struct {
ID CommandID `json:"id"`
Name string `json:"name"`
Options []CommandInteractionOption `json:"options"`
}
// InteractionType implements InteractionData.
func (*CommandInteraction) InteractionType() InteractionDataType {
return CommandInteractionType
}
func (*CommandInteraction) data() {}
// CommandInteractionOption is an option for a Command interaction response.
type CommandInteractionOption struct {
Name string `json:"name"`
Value json.Raw `json:"value"`
Options []CommandInteractionOption `json:"options"`
}
// String will return the value if the option's value is a valid string.
// Otherwise, it will return the raw JSON value of the other type.
func (o InteractionOption) String() string {
func (o CommandInteractionOption) String() string {
val := string(o.Value)
s, err := strconv.Unquote(val)
if err != nil {
return val
}
return s
}
func (o InteractionOption) Int() (int64, error) {
// IntValue reads the option's value as an int.
func (o CommandInteractionOption) IntValue() (int64, error) {
var i int64
err := o.Value.UnmarshalTo(&i)
return i, err
}
func (o InteractionOption) Bool() (bool, error) {
// BoolValue reads the option's value as a bool.
func (o CommandInteractionOption) BoolValue() (bool, error) {
var b bool
err := o.Value.UnmarshalTo(&b)
return b, err
}
func (o InteractionOption) Snowflake() (Snowflake, error) {
// SnowflakeValue reads the option's value as a snowflake.
func (o CommandInteractionOption) SnowflakeValue() (Snowflake, error) {
var id Snowflake
err := o.Value.UnmarshalTo(&id)
return id, err
}
func (o InteractionOption) Float() (float64, error) {
// FloatValue reads the option's value as a float64.
func (o CommandInteractionOption) FloatValue() (float64, error) {
var f float64
err := o.Value.UnmarshalTo(&f)
return f, err
}
// UnknownInteractionData describes an Interaction response with an unknown
// type.
type UnknownInteractionData struct {
json.Raw
typ InteractionDataType
}
// InteractionType implements InteractionData.
func (u *UnknownInteractionData) InteractionType() InteractionDataType {
return u.typ
}
func (u *UnknownInteractionData) data() {}

View File

@ -75,7 +75,7 @@ type Message struct {
// Reactions contains any reactions to the message.
Reactions []Reaction `json:"reactions,omitempty"`
// Components contains any attached components.
Components []ComponentWrap `json:"components,omitempty"`
Components ContainerComponents `json:"components,omitempty"`
// Used for validating a message was sent
Nonce string `json:"nonce,omitempty"`

View File

@ -15,16 +15,31 @@ func DurationSinceEpoch(t time.Time) time.Duration {
return time.Duration(t.UnixNano()) - Epoch
}
//go:generate go run ../utils/gensnowflake -o snowflake_types.go AppID AttachmentID AuditLogEntryID ChannelID CommandID EmojiID GuildID IntegrationID InteractionID MessageID RoleID StageID StickerID StickerPackID TeamID UserID WebhookID
// Mention generates the mention syntax for this channel ID.
func (s ChannelID) Mention() string { return "<#" + s.String() + ">" }
// Mention generates the mention syntax for this role ID.
func (s RoleID) Mention() string { return "<@&" + s.String() + ">" }
// Mention generates the mention syntax for this user ID.
func (s UserID) Mention() string { return "<@" + s.String() + ">" }
// Snowflake is the format of Discord's ID type. It is a format that can be
// sorted chronologically.
type Snowflake uint64
// NullSnowflake gets encoded into a null. This is used for
// optional and nullable snowflake fields.
const NullSnowflake = ^Snowflake(0)
// NewSnowflake creates a new snowflake from the given time.
func NewSnowflake(t time.Time) Snowflake {
return Snowflake((DurationSinceEpoch(t) / time.Millisecond) << 22)
}
// ParseSnowflake parses a snowflake.
func ParseSnowflake(sf string) (Snowflake, error) {
if sf == "null" {
return NullSnowflake, nil
@ -93,244 +108,3 @@ func (s Snowflake) PID() uint8 {
func (s Snowflake) Increment() uint16 {
return uint16(s & 0xFFF)
}
type AppID Snowflake
const NullAppID = AppID(NullSnowflake)
func (s AppID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *AppID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
func (s AppID) String() string { return Snowflake(s).String() }
func (s AppID) IsValid() bool { return Snowflake(s).IsValid() }
func (s AppID) IsNull() bool { return Snowflake(s).IsNull() }
func (s AppID) Time() time.Time { return Snowflake(s).Time() }
func (s AppID) Worker() uint8 { return Snowflake(s).Worker() }
func (s AppID) PID() uint8 { return Snowflake(s).PID() }
func (s AppID) Increment() uint16 { return Snowflake(s).Increment() }
type TeamID Snowflake
const NullTeamID = AppID(NullSnowflake)
func (s TeamID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *TeamID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
func (s TeamID) String() string { return Snowflake(s).String() }
func (s TeamID) IsValid() bool { return Snowflake(s).IsValid() }
func (s TeamID) IsNull() bool { return Snowflake(s).IsNull() }
func (s TeamID) Time() time.Time { return Snowflake(s).Time() }
func (s TeamID) Worker() uint8 { return Snowflake(s).Worker() }
func (s TeamID) PID() uint8 { return Snowflake(s).PID() }
func (s TeamID) Increment() uint16 { return Snowflake(s).Increment() }
type AttachmentID Snowflake
const NullAttachmentID = AttachmentID(NullSnowflake)
func (s AttachmentID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *AttachmentID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
func (s AttachmentID) String() string { return Snowflake(s).String() }
func (s AttachmentID) IsValid() bool { return Snowflake(s).IsValid() }
func (s AttachmentID) IsNull() bool { return Snowflake(s).IsNull() }
func (s AttachmentID) Time() time.Time { return Snowflake(s).Time() }
func (s AttachmentID) Worker() uint8 { return Snowflake(s).Worker() }
func (s AttachmentID) PID() uint8 { return Snowflake(s).PID() }
func (s AttachmentID) Increment() uint16 { return Snowflake(s).Increment() }
type AuditLogEntryID Snowflake
const NullAuditLogEntryID = AuditLogEntryID(NullSnowflake)
func (s AuditLogEntryID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *AuditLogEntryID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
func (s AuditLogEntryID) String() string { return Snowflake(s).String() }
func (s AuditLogEntryID) IsValid() bool { return Snowflake(s).IsValid() }
func (s AuditLogEntryID) IsNull() bool { return Snowflake(s).IsNull() }
func (s AuditLogEntryID) Time() time.Time { return Snowflake(s).Time() }
func (s AuditLogEntryID) Worker() uint8 { return Snowflake(s).Worker() }
func (s AuditLogEntryID) PID() uint8 { return Snowflake(s).PID() }
func (s AuditLogEntryID) Increment() uint16 { return Snowflake(s).Increment() }
type ChannelID Snowflake
const NullChannelID = ChannelID(NullSnowflake)
func (s ChannelID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *ChannelID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
func (s ChannelID) String() string { return Snowflake(s).String() }
func (s ChannelID) IsValid() bool { return Snowflake(s).IsValid() }
func (s ChannelID) IsNull() bool { return Snowflake(s).IsNull() }
func (s ChannelID) Time() time.Time { return Snowflake(s).Time() }
func (s ChannelID) Worker() uint8 { return Snowflake(s).Worker() }
func (s ChannelID) PID() uint8 { return Snowflake(s).PID() }
func (s ChannelID) Increment() uint16 { return Snowflake(s).Increment() }
func (s ChannelID) Mention() string { return "<#" + s.String() + ">" }
type CommandID Snowflake
const NullCommandID = CommandID(NullSnowflake)
func (s CommandID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *CommandID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
func (s CommandID) String() string { return Snowflake(s).String() }
func (s CommandID) IsValid() bool { return Snowflake(s).IsValid() }
func (s CommandID) IsNull() bool { return Snowflake(s).IsNull() }
func (s CommandID) Time() time.Time { return Snowflake(s).Time() }
func (s CommandID) Worker() uint8 { return Snowflake(s).Worker() }
func (s CommandID) PID() uint8 { return Snowflake(s).PID() }
func (s CommandID) Increment() uint16 { return Snowflake(s).Increment() }
type EmojiID Snowflake
const NullEmojiID = EmojiID(NullSnowflake)
func (s EmojiID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *EmojiID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
func (s EmojiID) String() string { return Snowflake(s).String() }
func (s EmojiID) IsValid() bool { return Snowflake(s).IsValid() }
func (s EmojiID) IsNull() bool { return Snowflake(s).IsNull() }
func (s EmojiID) Time() time.Time { return Snowflake(s).Time() }
func (s EmojiID) Worker() uint8 { return Snowflake(s).Worker() }
func (s EmojiID) PID() uint8 { return Snowflake(s).PID() }
func (s EmojiID) Increment() uint16 { return Snowflake(s).Increment() }
type IntegrationID Snowflake
const NullIntegrationID = IntegrationID(NullSnowflake)
func (s IntegrationID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *IntegrationID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
func (s IntegrationID) String() string { return Snowflake(s).String() }
func (s IntegrationID) IsValid() bool { return Snowflake(s).IsValid() }
func (s IntegrationID) IsNull() bool { return Snowflake(s).IsNull() }
func (s IntegrationID) Time() time.Time { return Snowflake(s).Time() }
func (s IntegrationID) Worker() uint8 { return Snowflake(s).Worker() }
func (s IntegrationID) PID() uint8 { return Snowflake(s).PID() }
func (s IntegrationID) Increment() uint16 { return Snowflake(s).Increment() }
type InteractionID Snowflake
const NullInteractionID = InteractionID(NullSnowflake)
func (s InteractionID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *InteractionID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
func (s InteractionID) String() string { return Snowflake(s).String() }
func (s InteractionID) IsValid() bool { return Snowflake(s).IsValid() }
func (s InteractionID) IsNull() bool { return Snowflake(s).IsNull() }
func (s InteractionID) Time() time.Time { return Snowflake(s).Time() }
func (s InteractionID) Worker() uint8 { return Snowflake(s).Worker() }
func (s InteractionID) PID() uint8 { return Snowflake(s).PID() }
func (s InteractionID) Increment() uint16 { return Snowflake(s).Increment() }
type GuildID Snowflake
const NullGuildID = GuildID(NullSnowflake)
func (s GuildID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *GuildID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
func (s GuildID) String() string { return Snowflake(s).String() }
func (s GuildID) IsValid() bool { return Snowflake(s).IsValid() }
func (s GuildID) IsNull() bool { return Snowflake(s).IsNull() }
func (s GuildID) Time() time.Time { return Snowflake(s).Time() }
func (s GuildID) Worker() uint8 { return Snowflake(s).Worker() }
func (s GuildID) PID() uint8 { return Snowflake(s).PID() }
func (s GuildID) Increment() uint16 { return Snowflake(s).Increment() }
type MessageID Snowflake
const NullMessageID = MessageID(NullSnowflake)
func (s MessageID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *MessageID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
func (s MessageID) String() string { return Snowflake(s).String() }
func (s MessageID) IsValid() bool { return Snowflake(s).IsValid() }
func (s MessageID) IsNull() bool { return Snowflake(s).IsNull() }
func (s MessageID) Time() time.Time { return Snowflake(s).Time() }
func (s MessageID) Worker() uint8 { return Snowflake(s).Worker() }
func (s MessageID) PID() uint8 { return Snowflake(s).PID() }
func (s MessageID) Increment() uint16 { return Snowflake(s).Increment() }
type RoleID Snowflake
const NullRoleID = RoleID(NullSnowflake)
func (s RoleID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *RoleID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
func (s RoleID) String() string { return Snowflake(s).String() }
func (s RoleID) IsValid() bool { return Snowflake(s).IsValid() }
func (s RoleID) IsNull() bool { return Snowflake(s).IsNull() }
func (s RoleID) Time() time.Time { return Snowflake(s).Time() }
func (s RoleID) Worker() uint8 { return Snowflake(s).Worker() }
func (s RoleID) PID() uint8 { return Snowflake(s).PID() }
func (s RoleID) Increment() uint16 { return Snowflake(s).Increment() }
func (s RoleID) Mention() string { return "<@&" + s.String() + ">" }
type StageID Snowflake
const NullStageID = StageID(NullSnowflake)
func (s StageID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *StageID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
func (s StageID) String() string { return Snowflake(s).String() }
func (s StageID) IsValid() bool { return Snowflake(s).IsValid() }
func (s StageID) IsNull() bool { return Snowflake(s).IsNull() }
func (s StageID) Time() time.Time { return Snowflake(s).Time() }
func (s StageID) Worker() uint8 { return Snowflake(s).Worker() }
func (s StageID) PID() uint8 { return Snowflake(s).PID() }
func (s StageID) Increment() uint16 { return Snowflake(s).Increment() }
type StickerID Snowflake
const NullStickerID = StickerID(NullSnowflake)
func (s StickerID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *StickerID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
func (s StickerID) String() string { return Snowflake(s).String() }
func (s StickerID) IsValid() bool { return Snowflake(s).IsValid() }
func (s StickerID) IsNull() bool { return Snowflake(s).IsNull() }
func (s StickerID) Time() time.Time { return Snowflake(s).Time() }
func (s StickerID) Worker() uint8 { return Snowflake(s).Worker() }
func (s StickerID) PID() uint8 { return Snowflake(s).PID() }
func (s StickerID) Increment() uint16 { return Snowflake(s).Increment() }
type StickerPackID Snowflake
const NullStickerPackID = StickerPackID(NullSnowflake)
func (s StickerPackID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *StickerPackID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
func (s StickerPackID) String() string { return Snowflake(s).String() }
func (s StickerPackID) IsValid() bool { return Snowflake(s).IsValid() }
func (s StickerPackID) IsNull() bool { return Snowflake(s).IsNull() }
func (s StickerPackID) Time() time.Time { return Snowflake(s).Time() }
func (s StickerPackID) Worker() uint8 { return Snowflake(s).Worker() }
func (s StickerPackID) PID() uint8 { return Snowflake(s).PID() }
func (s StickerPackID) Increment() uint16 { return Snowflake(s).Increment() }
type UserID Snowflake
const NullUserID = UserID(NullSnowflake)
func (s UserID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *UserID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
func (s UserID) String() string { return Snowflake(s).String() }
func (s UserID) IsValid() bool { return Snowflake(s).IsValid() }
func (s UserID) IsNull() bool { return Snowflake(s).IsNull() }
func (s UserID) Time() time.Time { return Snowflake(s).Time() }
func (s UserID) Worker() uint8 { return Snowflake(s).Worker() }
func (s UserID) PID() uint8 { return Snowflake(s).PID() }
func (s UserID) Increment() uint16 { return Snowflake(s).Increment() }
func (s UserID) Mention() string { return "<@" + s.String() + ">" }
type WebhookID Snowflake
const NullWebhookID = WebhookID(NullSnowflake)
func (s WebhookID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *WebhookID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
func (s WebhookID) String() string { return Snowflake(s).String() }
func (s WebhookID) IsValid() bool { return Snowflake(s).IsValid() }
func (s WebhookID) IsNull() bool { return Snowflake(s).IsNull() }
func (s WebhookID) Time() time.Time { return Snowflake(s).Time() }
func (s WebhookID) Worker() uint8 { return Snowflake(s).Worker() }
func (s WebhookID) PID() uint8 { return Snowflake(s).PID() }
func (s WebhookID) Increment() uint16 { return Snowflake(s).Increment() }

396
discord/snowflake_types.go Normal file
View File

@ -0,0 +1,396 @@
// Code generated by gensnowflake. DO NOT EDIT.
package discord
import "time"
// AppID is the snowflake type for a AppID.
type AppID Snowflake
// NullAppID gets encoded into a null. This is used for optional and nullable snowflake fields.
const NullAppID = AppID(NullSnowflake)
func (s AppID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *AppID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
// String returns the ID, or nothing if the snowflake isn't valid.
func (s AppID) String() string { return Snowflake(s).String() }
// IsValid returns whether or not the snowflake is valid.
func (s AppID) IsValid() bool { return Snowflake(s).IsValid() }
// IsNull returns whether or not the snowflake is null.
func (s AppID) IsNull() bool { return Snowflake(s).IsNull() }
func (s AppID) Time() time.Time { return Snowflake(s).Time() }
func (s AppID) Worker() uint8 { return Snowflake(s).Worker() }
func (s AppID) PID() uint8 { return Snowflake(s).PID() }
func (s AppID) Increment() uint16 { return Snowflake(s).Increment() }
// AttachmentID is the snowflake type for a AttachmentID.
type AttachmentID Snowflake
// NullAttachmentID gets encoded into a null. This is used for optional and nullable snowflake fields.
const NullAttachmentID = AttachmentID(NullSnowflake)
func (s AttachmentID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *AttachmentID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
// String returns the ID, or nothing if the snowflake isn't valid.
func (s AttachmentID) String() string { return Snowflake(s).String() }
// IsValid returns whether or not the snowflake is valid.
func (s AttachmentID) IsValid() bool { return Snowflake(s).IsValid() }
// IsNull returns whether or not the snowflake is null.
func (s AttachmentID) IsNull() bool { return Snowflake(s).IsNull() }
func (s AttachmentID) Time() time.Time { return Snowflake(s).Time() }
func (s AttachmentID) Worker() uint8 { return Snowflake(s).Worker() }
func (s AttachmentID) PID() uint8 { return Snowflake(s).PID() }
func (s AttachmentID) Increment() uint16 { return Snowflake(s).Increment() }
// AuditLogEntryID is the snowflake type for a AuditLogEntryID.
type AuditLogEntryID Snowflake
// NullAuditLogEntryID gets encoded into a null. This is used for optional and nullable snowflake fields.
const NullAuditLogEntryID = AuditLogEntryID(NullSnowflake)
func (s AuditLogEntryID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *AuditLogEntryID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
// String returns the ID, or nothing if the snowflake isn't valid.
func (s AuditLogEntryID) String() string { return Snowflake(s).String() }
// IsValid returns whether or not the snowflake is valid.
func (s AuditLogEntryID) IsValid() bool { return Snowflake(s).IsValid() }
// IsNull returns whether or not the snowflake is null.
func (s AuditLogEntryID) IsNull() bool { return Snowflake(s).IsNull() }
func (s AuditLogEntryID) Time() time.Time { return Snowflake(s).Time() }
func (s AuditLogEntryID) Worker() uint8 { return Snowflake(s).Worker() }
func (s AuditLogEntryID) PID() uint8 { return Snowflake(s).PID() }
func (s AuditLogEntryID) Increment() uint16 { return Snowflake(s).Increment() }
// ChannelID is the snowflake type for a ChannelID.
type ChannelID Snowflake
// NullChannelID gets encoded into a null. This is used for optional and nullable snowflake fields.
const NullChannelID = ChannelID(NullSnowflake)
func (s ChannelID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *ChannelID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
// String returns the ID, or nothing if the snowflake isn't valid.
func (s ChannelID) String() string { return Snowflake(s).String() }
// IsValid returns whether or not the snowflake is valid.
func (s ChannelID) IsValid() bool { return Snowflake(s).IsValid() }
// IsNull returns whether or not the snowflake is null.
func (s ChannelID) IsNull() bool { return Snowflake(s).IsNull() }
func (s ChannelID) Time() time.Time { return Snowflake(s).Time() }
func (s ChannelID) Worker() uint8 { return Snowflake(s).Worker() }
func (s ChannelID) PID() uint8 { return Snowflake(s).PID() }
func (s ChannelID) Increment() uint16 { return Snowflake(s).Increment() }
// CommandID is the snowflake type for a CommandID.
type CommandID Snowflake
// NullCommandID gets encoded into a null. This is used for optional and nullable snowflake fields.
const NullCommandID = CommandID(NullSnowflake)
func (s CommandID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *CommandID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
// String returns the ID, or nothing if the snowflake isn't valid.
func (s CommandID) String() string { return Snowflake(s).String() }
// IsValid returns whether or not the snowflake is valid.
func (s CommandID) IsValid() bool { return Snowflake(s).IsValid() }
// IsNull returns whether or not the snowflake is null.
func (s CommandID) IsNull() bool { return Snowflake(s).IsNull() }
func (s CommandID) Time() time.Time { return Snowflake(s).Time() }
func (s CommandID) Worker() uint8 { return Snowflake(s).Worker() }
func (s CommandID) PID() uint8 { return Snowflake(s).PID() }
func (s CommandID) Increment() uint16 { return Snowflake(s).Increment() }
// EmojiID is the snowflake type for a EmojiID.
type EmojiID Snowflake
// NullEmojiID gets encoded into a null. This is used for optional and nullable snowflake fields.
const NullEmojiID = EmojiID(NullSnowflake)
func (s EmojiID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *EmojiID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
// String returns the ID, or nothing if the snowflake isn't valid.
func (s EmojiID) String() string { return Snowflake(s).String() }
// IsValid returns whether or not the snowflake is valid.
func (s EmojiID) IsValid() bool { return Snowflake(s).IsValid() }
// IsNull returns whether or not the snowflake is null.
func (s EmojiID) IsNull() bool { return Snowflake(s).IsNull() }
func (s EmojiID) Time() time.Time { return Snowflake(s).Time() }
func (s EmojiID) Worker() uint8 { return Snowflake(s).Worker() }
func (s EmojiID) PID() uint8 { return Snowflake(s).PID() }
func (s EmojiID) Increment() uint16 { return Snowflake(s).Increment() }
// GuildID is the snowflake type for a GuildID.
type GuildID Snowflake
// NullGuildID gets encoded into a null. This is used for optional and nullable snowflake fields.
const NullGuildID = GuildID(NullSnowflake)
func (s GuildID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *GuildID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
// String returns the ID, or nothing if the snowflake isn't valid.
func (s GuildID) String() string { return Snowflake(s).String() }
// IsValid returns whether or not the snowflake is valid.
func (s GuildID) IsValid() bool { return Snowflake(s).IsValid() }
// IsNull returns whether or not the snowflake is null.
func (s GuildID) IsNull() bool { return Snowflake(s).IsNull() }
func (s GuildID) Time() time.Time { return Snowflake(s).Time() }
func (s GuildID) Worker() uint8 { return Snowflake(s).Worker() }
func (s GuildID) PID() uint8 { return Snowflake(s).PID() }
func (s GuildID) Increment() uint16 { return Snowflake(s).Increment() }
// IntegrationID is the snowflake type for a IntegrationID.
type IntegrationID Snowflake
// NullIntegrationID gets encoded into a null. This is used for optional and nullable snowflake fields.
const NullIntegrationID = IntegrationID(NullSnowflake)
func (s IntegrationID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *IntegrationID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
// String returns the ID, or nothing if the snowflake isn't valid.
func (s IntegrationID) String() string { return Snowflake(s).String() }
// IsValid returns whether or not the snowflake is valid.
func (s IntegrationID) IsValid() bool { return Snowflake(s).IsValid() }
// IsNull returns whether or not the snowflake is null.
func (s IntegrationID) IsNull() bool { return Snowflake(s).IsNull() }
func (s IntegrationID) Time() time.Time { return Snowflake(s).Time() }
func (s IntegrationID) Worker() uint8 { return Snowflake(s).Worker() }
func (s IntegrationID) PID() uint8 { return Snowflake(s).PID() }
func (s IntegrationID) Increment() uint16 { return Snowflake(s).Increment() }
// InteractionID is the snowflake type for a InteractionID.
type InteractionID Snowflake
// NullInteractionID gets encoded into a null. This is used for optional and nullable snowflake fields.
const NullInteractionID = InteractionID(NullSnowflake)
func (s InteractionID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *InteractionID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
// String returns the ID, or nothing if the snowflake isn't valid.
func (s InteractionID) String() string { return Snowflake(s).String() }
// IsValid returns whether or not the snowflake is valid.
func (s InteractionID) IsValid() bool { return Snowflake(s).IsValid() }
// IsNull returns whether or not the snowflake is null.
func (s InteractionID) IsNull() bool { return Snowflake(s).IsNull() }
func (s InteractionID) Time() time.Time { return Snowflake(s).Time() }
func (s InteractionID) Worker() uint8 { return Snowflake(s).Worker() }
func (s InteractionID) PID() uint8 { return Snowflake(s).PID() }
func (s InteractionID) Increment() uint16 { return Snowflake(s).Increment() }
// MessageID is the snowflake type for a MessageID.
type MessageID Snowflake
// NullMessageID gets encoded into a null. This is used for optional and nullable snowflake fields.
const NullMessageID = MessageID(NullSnowflake)
func (s MessageID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *MessageID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
// String returns the ID, or nothing if the snowflake isn't valid.
func (s MessageID) String() string { return Snowflake(s).String() }
// IsValid returns whether or not the snowflake is valid.
func (s MessageID) IsValid() bool { return Snowflake(s).IsValid() }
// IsNull returns whether or not the snowflake is null.
func (s MessageID) IsNull() bool { return Snowflake(s).IsNull() }
func (s MessageID) Time() time.Time { return Snowflake(s).Time() }
func (s MessageID) Worker() uint8 { return Snowflake(s).Worker() }
func (s MessageID) PID() uint8 { return Snowflake(s).PID() }
func (s MessageID) Increment() uint16 { return Snowflake(s).Increment() }
// RoleID is the snowflake type for a RoleID.
type RoleID Snowflake
// NullRoleID gets encoded into a null. This is used for optional and nullable snowflake fields.
const NullRoleID = RoleID(NullSnowflake)
func (s RoleID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *RoleID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
// String returns the ID, or nothing if the snowflake isn't valid.
func (s RoleID) String() string { return Snowflake(s).String() }
// IsValid returns whether or not the snowflake is valid.
func (s RoleID) IsValid() bool { return Snowflake(s).IsValid() }
// IsNull returns whether or not the snowflake is null.
func (s RoleID) IsNull() bool { return Snowflake(s).IsNull() }
func (s RoleID) Time() time.Time { return Snowflake(s).Time() }
func (s RoleID) Worker() uint8 { return Snowflake(s).Worker() }
func (s RoleID) PID() uint8 { return Snowflake(s).PID() }
func (s RoleID) Increment() uint16 { return Snowflake(s).Increment() }
// StageID is the snowflake type for a StageID.
type StageID Snowflake
// NullStageID gets encoded into a null. This is used for optional and nullable snowflake fields.
const NullStageID = StageID(NullSnowflake)
func (s StageID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *StageID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
// String returns the ID, or nothing if the snowflake isn't valid.
func (s StageID) String() string { return Snowflake(s).String() }
// IsValid returns whether or not the snowflake is valid.
func (s StageID) IsValid() bool { return Snowflake(s).IsValid() }
// IsNull returns whether or not the snowflake is null.
func (s StageID) IsNull() bool { return Snowflake(s).IsNull() }
func (s StageID) Time() time.Time { return Snowflake(s).Time() }
func (s StageID) Worker() uint8 { return Snowflake(s).Worker() }
func (s StageID) PID() uint8 { return Snowflake(s).PID() }
func (s StageID) Increment() uint16 { return Snowflake(s).Increment() }
// StickerID is the snowflake type for a StickerID.
type StickerID Snowflake
// NullStickerID gets encoded into a null. This is used for optional and nullable snowflake fields.
const NullStickerID = StickerID(NullSnowflake)
func (s StickerID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *StickerID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
// String returns the ID, or nothing if the snowflake isn't valid.
func (s StickerID) String() string { return Snowflake(s).String() }
// IsValid returns whether or not the snowflake is valid.
func (s StickerID) IsValid() bool { return Snowflake(s).IsValid() }
// IsNull returns whether or not the snowflake is null.
func (s StickerID) IsNull() bool { return Snowflake(s).IsNull() }
func (s StickerID) Time() time.Time { return Snowflake(s).Time() }
func (s StickerID) Worker() uint8 { return Snowflake(s).Worker() }
func (s StickerID) PID() uint8 { return Snowflake(s).PID() }
func (s StickerID) Increment() uint16 { return Snowflake(s).Increment() }
// StickerPackID is the snowflake type for a StickerPackID.
type StickerPackID Snowflake
// NullStickerPackID gets encoded into a null. This is used for optional and nullable snowflake fields.
const NullStickerPackID = StickerPackID(NullSnowflake)
func (s StickerPackID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *StickerPackID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
// String returns the ID, or nothing if the snowflake isn't valid.
func (s StickerPackID) String() string { return Snowflake(s).String() }
// IsValid returns whether or not the snowflake is valid.
func (s StickerPackID) IsValid() bool { return Snowflake(s).IsValid() }
// IsNull returns whether or not the snowflake is null.
func (s StickerPackID) IsNull() bool { return Snowflake(s).IsNull() }
func (s StickerPackID) Time() time.Time { return Snowflake(s).Time() }
func (s StickerPackID) Worker() uint8 { return Snowflake(s).Worker() }
func (s StickerPackID) PID() uint8 { return Snowflake(s).PID() }
func (s StickerPackID) Increment() uint16 { return Snowflake(s).Increment() }
// TeamID is the snowflake type for a TeamID.
type TeamID Snowflake
// NullTeamID gets encoded into a null. This is used for optional and nullable snowflake fields.
const NullTeamID = TeamID(NullSnowflake)
func (s TeamID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *TeamID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
// String returns the ID, or nothing if the snowflake isn't valid.
func (s TeamID) String() string { return Snowflake(s).String() }
// IsValid returns whether or not the snowflake is valid.
func (s TeamID) IsValid() bool { return Snowflake(s).IsValid() }
// IsNull returns whether or not the snowflake is null.
func (s TeamID) IsNull() bool { return Snowflake(s).IsNull() }
func (s TeamID) Time() time.Time { return Snowflake(s).Time() }
func (s TeamID) Worker() uint8 { return Snowflake(s).Worker() }
func (s TeamID) PID() uint8 { return Snowflake(s).PID() }
func (s TeamID) Increment() uint16 { return Snowflake(s).Increment() }
// UserID is the snowflake type for a UserID.
type UserID Snowflake
// NullUserID gets encoded into a null. This is used for optional and nullable snowflake fields.
const NullUserID = UserID(NullSnowflake)
func (s UserID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *UserID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
// String returns the ID, or nothing if the snowflake isn't valid.
func (s UserID) String() string { return Snowflake(s).String() }
// IsValid returns whether or not the snowflake is valid.
func (s UserID) IsValid() bool { return Snowflake(s).IsValid() }
// IsNull returns whether or not the snowflake is null.
func (s UserID) IsNull() bool { return Snowflake(s).IsNull() }
func (s UserID) Time() time.Time { return Snowflake(s).Time() }
func (s UserID) Worker() uint8 { return Snowflake(s).Worker() }
func (s UserID) PID() uint8 { return Snowflake(s).PID() }
func (s UserID) Increment() uint16 { return Snowflake(s).Increment() }
// WebhookID is the snowflake type for a WebhookID.
type WebhookID Snowflake
// NullWebhookID gets encoded into a null. This is used for optional and nullable snowflake fields.
const NullWebhookID = WebhookID(NullSnowflake)
func (s WebhookID) MarshalJSON() ([]byte, error) { return Snowflake(s).MarshalJSON() }
func (s *WebhookID) UnmarshalJSON(v []byte) error { return (*Snowflake)(s).UnmarshalJSON(v) }
// String returns the ID, or nothing if the snowflake isn't valid.
func (s WebhookID) String() string { return Snowflake(s).String() }
// IsValid returns whether or not the snowflake is valid.
func (s WebhookID) IsValid() bool { return Snowflake(s).IsValid() }
// IsNull returns whether or not the snowflake is null.
func (s WebhookID) IsNull() bool { return Snowflake(s).IsNull() }
func (s WebhookID) Time() time.Time { return Snowflake(s).Time() }
func (s WebhookID) Worker() uint8 { return Snowflake(s).Worker() }
func (s WebhookID) PID() uint8 { return Snowflake(s).PID() }
func (s WebhookID) Increment() uint16 { return Snowflake(s).Increment() }

View File

@ -397,7 +397,7 @@ type (
)
type InteractionCreateEvent struct {
discord.Interaction
discord.InteractionEvent
}
// Undocumented

View File

@ -193,6 +193,7 @@ func NewCustomIdentifiedGateway(gatewayURL string, id *Identifier) *Gateway {
ErrorLog: wsutil.WSError,
AfterClose: func(error) {},
PacerLoop: wsutil.PacemakerLoop{ErrorLog: wsutil.WSError},
}
}

2
go.mod
View File

@ -1,6 +1,6 @@
module github.com/diamondburned/arikawa/v3
go 1.13
go 1.16
require (
github.com/gorilla/schema v1.2.0

View File

@ -0,0 +1,86 @@
package main
import (
"bytes"
"flag"
"go/format"
"log"
"os"
"path/filepath"
"text/template"
_ "embed"
)
type data struct {
Package string
ImportDiscord bool
Snowflakes []snowflakeType
}
type snowflakeType struct {
TypeName string
}
//go:embed template.tmpl
var packageTmpl string
var tmpl = template.Must(template.New("").Parse(packageTmpl))
func main() {
var pkg string
var out string
log.SetFlags(0)
flag.Usage = func() {
log.Printf("usage: %s [-p package] <type names...>", filepath.Base(os.Args[0]))
flag.PrintDefaults()
}
flag.StringVar(&out, "o", "", "output, empty for stdout")
flag.StringVar(&pkg, "p", "discord", "package name")
flag.Parse()
if len(flag.Args()) == 0 {
flag.Usage()
os.Exit(1)
}
d := data{
Package: pkg,
ImportDiscord: pkg != "discord",
}
for _, arg := range flag.Args() {
d.Snowflakes = append(d.Snowflakes, snowflakeType{
TypeName: arg,
})
}
buf := bytes.Buffer{}
if err := tmpl.Execute(&buf, d); err != nil {
log.Fatalln("failed to execute template:", err)
}
b, err := format.Source(buf.Bytes())
if err != nil {
log.Fatalln("failed to fmt:", err)
}
outFile := os.Stdout
if out != "" {
f, err := os.Create(out)
if err != nil {
log.Fatalln("failed to create output file:", err)
}
defer f.Close()
outFile = f
}
if _, err := outFile.Write(b); err != nil {
log.Fatalln("failed to write to file:", err)
}
}

View File

@ -0,0 +1,38 @@
// Code generated by gensnowflake. DO NOT EDIT.
package {{ .Package }}
{{ $dot := "" }}
{{ if .ImportDiscord }}
{{ $dot = "discord." }}
import "github.com/diamondburned/arikawa/v3/discord"
{{ end }}
import "time"
{{ range .Snowflakes }}
// {{ .TypeName }} is the snowflake type for a {{ .TypeName }}.
type {{.TypeName}} {{$dot}}Snowflake
// Null{{.TypeName}} gets encoded into a null. This is used for optional and nullable snowflake fields.
const Null{{.TypeName}} = {{.TypeName}}({{$dot}}NullSnowflake)
func (s {{.TypeName}}) MarshalJSON() ([]byte, error) { return {{$dot}}Snowflake(s).MarshalJSON() }
func (s *{{.TypeName}}) UnmarshalJSON(v []byte) error { return (*{{$dot}}Snowflake)(s).UnmarshalJSON(v) }
// String returns the ID, or nothing if the snowflake isn't valid.
func (s {{.TypeName}}) String() string { return {{$dot}}Snowflake(s).String() }
// IsValid returns whether or not the snowflake is valid.
func (s {{.TypeName}}) IsValid() bool { return {{$dot}}Snowflake(s).IsValid() }
// IsNull returns whether or not the snowflake is null.
func (s {{.TypeName}}) IsNull() bool { return {{$dot}}Snowflake(s).IsNull() }
func (s {{.TypeName}}) Time() time.Time { return {{$dot}}Snowflake(s).Time() }
func (s {{.TypeName}}) Worker() uint8 { return {{$dot}}Snowflake(s).Worker() }
func (s {{.TypeName}}) PID() uint8 { return {{$dot}}Snowflake(s).PID() }
func (s {{.TypeName}}) Increment() uint16 { return {{$dot}}Snowflake(s).Increment() }
{{ end }}

View File

@ -267,7 +267,7 @@ func (c *Client) request(
}
// Optionally unmarshal the error.
json.Unmarshal(httpErr.Body, &httpErr)
json.Unmarshal(httpErr.Body, httpErr)
doErr = httpErr
}