diff --git a/_example/buttons/main.go b/_example/buttons/main.go index 4746579..0ccbb79 100644 --- a/_example/buttons/main.go +++ b/_example/buttons/main.go @@ -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 {} } diff --git a/_example/commands/main.go b/_example/commands/main.go index 080ffb6..e9b59d7 100644 --- a/_example/commands/main.go +++ b/_example/commands/main.go @@ -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) } diff --git a/api/application.go b/api/application.go index 8c48627..e987c55 100644 --- a/api/application.go +++ b/api/application.go @@ -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) { diff --git a/api/interaction.go b/api/interaction.go index 54442f3..440d06a 100644 --- a/api/interaction.go +++ b/api/interaction.go @@ -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. diff --git a/api/message.go b/api/message.go index c5ce328..7d58396 100644 --- a/api/message.go +++ b/api/message.go @@ -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 diff --git a/api/send.go b/api/send.go index 02d1d50..dd335f3 100644 --- a/api/send.go +++ b/api/send.go @@ -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"` diff --git a/api/webhook/webhook.go b/api/webhook/webhook.go index f74f0e1..dff7f72 100644 --- a/api/webhook/webhook.go +++ b/api/webhook/webhook.go @@ -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 diff --git a/discord/application.go b/discord/application.go index 25f8ab5..2512cb2 100644 --- a/discord/application.go +++ b/discord/application.go @@ -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 (>NoNo 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"` diff --git a/discord/channel.go b/discord/channel.go index 90929de..0990ba8 100644 --- a/discord/channel.go +++ b/discord/channel.go @@ -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 diff --git a/discord/command.go b/discord/command.go new file mode 100644 index 0000000..a612903 --- /dev/null +++ b/discord/command.go @@ -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 (>NoNo 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() {} diff --git a/discord/interaction.go b/discord/interaction.go index 8e999ed..5e9ff41 100644 --- a/discord/interaction.go +++ b/discord/interaction.go @@ -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() {} diff --git a/discord/message.go b/discord/message.go index b79063c..79bcbab 100644 --- a/discord/message.go +++ b/discord/message.go @@ -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"` diff --git a/discord/snowflake.go b/discord/snowflake.go index 1f518d5..aacee7a 100644 --- a/discord/snowflake.go +++ b/discord/snowflake.go @@ -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() } diff --git a/discord/snowflake_types.go b/discord/snowflake_types.go new file mode 100644 index 0000000..f1da24c --- /dev/null +++ b/discord/snowflake_types.go @@ -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() } diff --git a/gateway/events.go b/gateway/events.go index ef0bd01..f6a7825 100644 --- a/gateway/events.go +++ b/gateway/events.go @@ -397,7 +397,7 @@ type ( ) type InteractionCreateEvent struct { - discord.Interaction + discord.InteractionEvent } // Undocumented diff --git a/gateway/gateway.go b/gateway/gateway.go index ad6e198..877b79e 100644 --- a/gateway/gateway.go +++ b/gateway/gateway.go @@ -193,6 +193,7 @@ func NewCustomIdentifiedGateway(gatewayURL string, id *Identifier) *Gateway { ErrorLog: wsutil.WSError, AfterClose: func(error) {}, + PacerLoop: wsutil.PacemakerLoop{ErrorLog: wsutil.WSError}, } } diff --git a/go.mod b/go.mod index 6d94969..28f8514 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/diamondburned/arikawa/v3 -go 1.13 +go 1.16 require ( github.com/gorilla/schema v1.2.0 diff --git a/utils/gensnowflake/main.go b/utils/gensnowflake/main.go new file mode 100644 index 0000000..c7cfcd2 --- /dev/null +++ b/utils/gensnowflake/main.go @@ -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] ", 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) + } +} diff --git a/utils/gensnowflake/template.tmpl b/utils/gensnowflake/template.tmpl new file mode 100644 index 0000000..00dca92 --- /dev/null +++ b/utils/gensnowflake/template.tmpl @@ -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 }} diff --git a/utils/httputil/client.go b/utils/httputil/client.go index b9674ee..e3167b2 100644 --- a/utils/httputil/client.go +++ b/utils/httputil/client.go @@ -267,7 +267,7 @@ func (c *Client) request( } // Optionally unmarshal the error. - json.Unmarshal(httpErr.Body, &httpErr) + json.Unmarshal(httpErr.Body, httpErr) doErr = httpErr }