From 331ec59dec98b70a95d99d26644e0e82289c64df Mon Sep 17 00:00:00 2001 From: diamondburned Date: Sun, 10 Oct 2021 15:44:31 -0700 Subject: [PATCH] discord: Refactor interactions and components This commit gets rid of contain-it-all structs and instead opt for interface union types containing underlying concrete types with no overloading. The code is much more verbose by doing this, but the API is much nicer to use. The only disadvantage in that regard is the interface assertion being too verbose and risky for users at times. --- 0-examples/buttons/main.go | 107 ++--- 0-examples/commands/main.go | 9 +- api/application.go | 10 +- api/interaction.go | 4 +- api/message.go | 2 +- api/send.go | 2 +- api/webhook/webhook.go | 4 +- discord/application.go | 166 +------- discord/channel.go | 6 +- discord/command.go | 580 ++++++++++++++++++++++++++++ discord/component.go | 493 ++++++++++++++++------- discord/interaction.go | 317 +++++++++++---- discord/message.go | 2 +- discord/snowflake.go | 256 +----------- discord/snowflake_types.go | 396 +++++++++++++++++++ gateway/events.go | 2 +- gateway/gateway.go | 1 + go.mod | 2 +- utils/generate-option-marshalers.sh | 42 ++ utils/gensnowflake/main.go | 86 +++++ utils/gensnowflake/template.tmpl | 38 ++ utils/httputil/client.go | 2 +- 22 files changed, 1838 insertions(+), 689 deletions(-) create mode 100644 discord/command.go create mode 100644 discord/snowflake_types.go create mode 100755 utils/generate-option-marshalers.sh create mode 100644 utils/gensnowflake/main.go create mode 100644 utils/gensnowflake/template.tmpl diff --git a/0-examples/buttons/main.go b/0-examples/buttons/main.go index 47465794..7847044b 100644 --- a/0-examples/buttons/main.go +++ b/0-examples/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{ + Components: discord.ComponentsPtr( &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, - }, + &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/0-examples/commands/main.go b/0-examples/commands/main.go index 080ffb65..e9b59d72 100644 --- a/0-examples/commands/main.go +++ b/0-examples/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 8c48627d..e987c556 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 d8d8bd61..ed1a0078 100644 --- a/api/interaction.go +++ b/api/interaction.go @@ -64,7 +64,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. @@ -162,7 +162,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 c5ce3280..7d583969 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 02d1d50d..dd335f34 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 f74f0e1e..dff7f727 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 95453bef..2512cb20 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,161 +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 90929de5..0990ba8b 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 00000000..15e33693 --- /dev/null +++ b/discord/command.go @@ -0,0 +1,580 @@ +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 } -// SelectComponent is a clickable button that may be added to an interaction +// Select 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"` + // 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"` } -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"` +// 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 (*SelectComponent) Type() ComponentType { +func (s *SelectComponent) Type() ComponentType { return SelectComponentType } +func (s *SelectComponent) _cmp() {} +func (s *SelectComponent) _icp() {} + // MarshalJSON marshals the select in the format Discord expects. -func (s SelectComponent) MarshalJSON() ([]byte, error) { - type selectComponent SelectComponent +func (s *SelectComponent) MarshalJSON() ([]byte, error) { + type sel SelectComponent - return json.Marshal(struct { - 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 e85b02c4..bc7de837 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,143 +23,315 @@ 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{} - case AutocompleteInteraction: - i.Data = &AutocompleteInteractionData{} + case AutocompleteInteractionType: + e.Data = &AutocompleteInteraction{} 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 - AutocompleteInteraction + PingInteractionType InteractionDataType = iota + 1 + CommandInteractionType + ComponentInteractionType + AutocompleteInteractionType ) -// 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() {} + +// AutocompleteInteraction is an autocompletion Interaction response. +type AutocompleteInteraction struct { + CommandID CommandID `json:"id"` + + // Name of command autocomplete is triggered for. + Name string `json:"name"` + CommandType CommandType `json:"type"` + Version string `json:"version"` + Options []AutocompleteOption `json:"options"` +} + +// AutocompleteOption is an autocompletion option in an AutocompleteInteraction. +type AutocompleteOption struct { + Type CommandOptionType `json:"type"` + Name string `json:"name"` + Value string `json:"value"` + Focused bool `json:"focused"` } -func (*ComponentInteractionData) Type() InteractionType { - return ComponentInteraction +// Type implements ComponentInteraction. +func (*AutocompleteInteraction) InteractionType() InteractionDataType { + return AutocompleteInteractionType } +func (*AutocompleteInteraction) data() {} -type CommandInteractionData struct { - ID CommandID `json:"id"` - Name string `json:"name"` - Options []InteractionOption `json:"options"` +// 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. Not all component + // interactions will have a component ID. + ID() ComponentID + // Type returns the type of the component in response. + Type() ComponentType + resp() } -func (*CommandInteractionData) Type() InteractionType { - return CommandInteraction +// SelectInteraction is a select component's response. +type SelectInteraction struct { + CustomID ComponentID `json:"custom_id"` + Values []string `json:"values"` } -type AutocompleteInteractionData struct { - ID CommandID `json:"id"` +// ID implements ComponentInteraction. +func (s *SelectInteraction) ID() ComponentID { return s.CustomID } - // Name of command autocomplete is triggered for. - Name string `json:"name"` - CommandType CommandType `json:"type"` - Version string `json:"version"` - Options []AutocompleteCommandOption `json:"options"` +// Type implements ComponentInteraction. +func (s *SelectInteraction) Type() ComponentType { return SelectComponentType } + +// InteractionType implements InteractionData. +func (s *SelectInteraction) InteractionType() InteractionDataType { + return ComponentInteractionType } -type AutocompleteCommandOption struct { - Type CommandOptionType `json:"type"` - Name string `json:"name"` - Value string `json:"value"` - Focused bool `json:"focused"` +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"` } -func (*AutocompleteInteractionData) Type() InteractionType { - return AutocompleteInteraction +// 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 } -type UnknownInteractionData struct { - json.Raw - typ InteractionType +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:"component_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, + } + return d, nil + } + + if err := json.Unmarshal(b, d); err != nil { + return nil, errors.Wrap(err, "failed to unmarshal component interaction data") + } + + return d, nil } -func (u *UnknownInteractionData) Type() InteractionType { - return u.typ +// 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"` } -type InteractionOption struct { - Name string `json:"name"` - Value json.Raw `json:"value"` - Options []InteractionOption `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 b79063cb..79bcbaba 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 1f518d5e..aacee7aa 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 00000000..f1da24cd --- /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 ef0bd01a..f6a78259 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 ad6e1982..877b79ed 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 ef567492..d1894573 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/generate-option-marshalers.sh b/utils/generate-option-marshalers.sh new file mode 100755 index 00000000..97166dcd --- /dev/null +++ b/utils/generate-option-marshalers.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +types=( + SubcommandOption + SubcommandGroupOption + StringOption + IntegerOption + BooleanOption + UserOption + ChannelOption + RoleOption + MentionableOption +) + +recvs=( + s + s + s + i + b + u + c + r + m +) + +for ((i = 0; i < 6; i++)); { + cat<", 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 00000000..00dca925 --- /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 b9674eee..e3167b25 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 }