diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 01229092..eacd49ce 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -12,56 +12,56 @@ name: "CodeQL" on: - push: - branches: [ master ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ master ] - schedule: - - cron: '00 12 * * *' + push: + branches: [ master, development ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master, development ] + schedule: + - cron: '0 0 1 * *' jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest + analyze: + name: Analyze + runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - language: [ 'go' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] - # Learn more: - # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed - steps: - - name: Checkout repository - uses: actions/checkout@v2 + steps: + - name: Checkout repository + uses: actions/checkout@v2 - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 - # ℹī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl - # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language + # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language - #- run: | - # make bootstrap - # make release + #- run: | + # make bootstrap + # make release - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 89635783..c7a74648 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -2,9 +2,9 @@ name: Go on: push: - branches: [ master ] + branches: [ master, development ] pull_request: - branches: [ master ] + branches: [ master, development ] jobs: diff --git a/api/activity.go b/api/activity.go index c13c6e67..dba05130 100644 --- a/api/activity.go +++ b/api/activity.go @@ -2,7 +2,7 @@ package api import "time" -// ActivityType represents the status of a user, one of Game, Streaming, Listening, Custom or Competing +// ActivityType represents the status of a user, one of Game, Streaming, Listening, Watching, Custom or Competing type ActivityType int // Constants for activities @@ -10,7 +10,7 @@ const ( Game ActivityType = iota Streaming Listening - _ + Watching Custom Competing ) diff --git a/api/audio_controller.go b/api/audio_controller.go new file mode 100644 index 00000000..4819ca7a --- /dev/null +++ b/api/audio_controller.go @@ -0,0 +1,16 @@ +package api + +import "errors" + +// errors returned when no gateway or ws conn exists +var ( + ErrNoGateway = errors.New("no gateway initialized") + ErrNoGatewayConn = errors.New("no active gateway connection found") +) + +// AudioController lets you Connect / Disconnect from a VoiceChannel +type AudioController interface { + Disgo() Disgo + Connect(guildID Snowflake, channelID Snowflake) error + Disconnect(guildID Snowflake) error +} diff --git a/api/cache.go b/api/cache.go index 06aa3aff..16241425 100644 --- a/api/cache.go +++ b/api/cache.go @@ -2,34 +2,43 @@ package api // Cache allows you to access the objects that are stored in-memory by Discord type Cache interface { + Disgo() Disgo Close() DoCleanup() + CacheFlags() CacheFlags - Guild(Snowflake) *Guild - GuildsByName(string, bool) []*Guild - Guilds() []*Guild - GuildCache() map[Snowflake]*Guild - CacheGuild(*Guild) - UncacheGuild(Snowflake) - - /*Message(Snowflake) *Message - Messages(Snowflake) []*Message - AllMessages() []*Message - MessageCache(Snowflake) map[Snowflake]*Message - AllMessageCache() map[Snowflake]map[Snowflake]*Message - CacheMessage(*Message) - UncacheMessage(Snowflake)*/ + Command(commandID Snowflake) *Command + GuildCommandCache(guildID Snowflake) map[Snowflake]*Command + AllGuildCommandCache() map[Snowflake]map[Snowflake]*Command + GlobalCommandCache() map[Snowflake]*Command + CacheGlobalCommand(command *Command) *Command + CacheGuildCommand(command *Command) *Command + UncacheCommand(commandID Snowflake) User(Snowflake) *User UserByTag(string) *User UsersByName(string, bool) []*User Users() []*User UserCache() map[Snowflake]*User - CacheUser(*User) + CacheUser(*User) *User UncacheUser(Snowflake) FindUser(func(*User) bool) *User FindUsers(func(*User) bool) []*User + Guild(Snowflake) *Guild + GuildsByName(string, bool) []*Guild + Guilds() []*Guild + GuildCache() map[Snowflake]*Guild + CacheGuild(*Guild) *Guild + UncacheGuild(Snowflake) + + Message(channelID Snowflake, messageID Snowflake) *Message + Messages(channelID Snowflake) []*Message + MessageCache(channelID Snowflake) map[Snowflake]*Message + AllMessageCache() map[Snowflake]map[Snowflake]*Message + CacheMessage(message *Message) *Message + UncacheMessage(channelID Snowflake, messageID Snowflake) + Member(Snowflake, Snowflake) *Member MemberByTag(Snowflake, string) *Member MembersByName(Snowflake, string, bool) []*Member @@ -37,18 +46,24 @@ type Cache interface { AllMembers() []*Member MemberCache(Snowflake) map[Snowflake]*Member AllMemberCache() map[Snowflake]map[Snowflake]*Member - CacheMember(*Member) + CacheMember(member *Member) *Member UncacheMember(Snowflake, Snowflake) FindMember(Snowflake, func(*Member) bool) *Member FindMembers(Snowflake, func(*Member) bool) []*Member - Role(Snowflake, Snowflake) *Role + VoiceState(guildID Snowflake, userID Snowflake) *VoiceState + VoiceStates(guildID Snowflake) []*VoiceState + VoiceStateCache(guildID Snowflake) map[Snowflake]*VoiceState + CacheVoiceState(voiceState *VoiceState) *VoiceState + UncacheVoiceState(guildID Snowflake, userID Snowflake) + + Role(roleID Snowflake) *Role RolesByName(Snowflake, string, bool) []*Role Roles(Snowflake) []*Role AllRoles() []*Role RoleCache(Snowflake) map[Snowflake]*Role AllRoleCache() map[Snowflake]map[Snowflake]*Role - CacheRole(*Role) + CacheRole(*Role) *Role UncacheRole(Snowflake, Snowflake) FindRole(Snowflake, func(*Role) bool) *Role FindRoles(Snowflake, func(*Role) bool) []*Role @@ -56,8 +71,8 @@ type Cache interface { DMChannel(Snowflake) *DMChannel DMChannels() []*DMChannel DMChannelCache() map[Snowflake]*DMChannel - CacheDMChannel(*DMChannel) - UncacheDMChannel(Snowflake) + CacheDMChannel(*DMChannel) *DMChannel + UncacheDMChannel(dmChannelID Snowflake) FindDMChannel(func(*DMChannel) bool) *DMChannel FindDMChannels(func(*DMChannel) bool) []*DMChannel @@ -69,7 +84,7 @@ type Cache interface { TextChannelsByName(Snowflake, string, bool) []*TextChannel TextChannels(Snowflake) []*TextChannel TextChannelCache(Snowflake) map[Snowflake]*TextChannel - CacheTextChannel(*TextChannel) + CacheTextChannel(*TextChannel) *TextChannel UncacheTextChannel(Snowflake, Snowflake) FindTextChannel(Snowflake, func(*TextChannel) bool) *TextChannel FindTextChannels(Snowflake, func(*TextChannel) bool) []*TextChannel @@ -78,7 +93,7 @@ type Cache interface { StoreChannelsByName(Snowflake, string, bool) []*StoreChannel StoreChannels(Snowflake) []*StoreChannel StoreChannelCache(Snowflake) map[Snowflake]*StoreChannel - CacheStoreChannel(*StoreChannel) + CacheStoreChannel(*StoreChannel) *StoreChannel UncacheStoreChannel(Snowflake, Snowflake) FindStoreChannel(Snowflake, func(*StoreChannel) bool) *StoreChannel FindStoreChannels(Snowflake, func(*StoreChannel) bool) []*StoreChannel @@ -87,7 +102,7 @@ type Cache interface { VoiceChannelsByName(Snowflake, string, bool) []*VoiceChannel VoiceChannels(Snowflake) []*VoiceChannel VoiceChannelCache(Snowflake) map[Snowflake]*VoiceChannel - CacheVoiceChannel(*VoiceChannel) + CacheVoiceChannel(*VoiceChannel) *VoiceChannel UncacheVoiceChannel(Snowflake, Snowflake) FindVoiceChannel(Snowflake, func(*VoiceChannel) bool) *VoiceChannel FindVoiceChannels(Snowflake, func(*VoiceChannel) bool) []*VoiceChannel @@ -98,15 +113,16 @@ type Cache interface { AllCategories() []*Category CategoryCache(Snowflake) map[Snowflake]*Category AllCategoryCache() map[Snowflake]map[Snowflake]*Category - CacheCategory(*Category) + CacheCategory(*Category) *Category UncacheCategory(Snowflake, Snowflake) FindCategory(Snowflake, func(*Category) bool) *Category FindCategories(Snowflake, func(*Category) bool) []*Category - /*Emote(Snowflake) *Emote - EmotesByName(string, bool) []*Emote - Emotes() []*Emote - EmoteCache() map[Snowflake]*Emote - CacheEmote(*Emote) - UncacheEmote(Snowflake)*/ + Emote(emoteID Snowflake) *Emote + EmotesByName(guildID Snowflake, name string, ignoreCase bool) []*Emote + Emotes(guildID Snowflake) []*Emote + EmoteCache(guildID Snowflake) map[Snowflake]*Emote + AllEmoteCache() map[Snowflake]map[Snowflake]*Emote + CacheEmote(*Emote) *Emote + UncacheEmote(guildID Snowflake, emoteID Snowflake) } diff --git a/api/cache_flags.go b/api/cache_flags.go new file mode 100644 index 00000000..324967b6 --- /dev/null +++ b/api/cache_flags.go @@ -0,0 +1,77 @@ +package api + +// CacheFlags are used to enable/disable certain internal caches +type CacheFlags int + +// values for CacheFlags +const ( + CacheFlagsNone CacheFlags = 0 + CacheFlagDMChannels CacheFlags = 1 << iota + CacheFlagCategories + CacheFlagTextChannels + CacheFlagVoiceChannels + CacheFlagStoreChannels + CacheFlagRoles + CacheFlagEmotes + CacheFlagVoiceState + CacheFlagCommands + CacheFlagCommandPermissions + + CacheFlagsDefault = CacheFlagDMChannels | + CacheFlagCategories | + CacheFlagTextChannels | + CacheFlagVoiceChannels | + CacheFlagStoreChannels | + CacheFlagRoles | + CacheFlagEmotes +) + +// Add allows you to add multiple bits together, producing a new bit +func (c CacheFlags) Add(bits ...Bit) Bit { + total := CacheFlags(0) + for _, bit := range bits { + total |= bit.(CacheFlags) + } + c |= total + return c +} + +// Remove allows you to subtract multiple bits from the first, producing a new bit +func (c CacheFlags) Remove(bits ...Bit) Bit { + total := CacheFlags(0) + for _, bit := range bits { + total |= bit.(CacheFlags) + } + c &^= total + return c +} + +// HasAll will ensure that the bit includes all of the bits entered +func (c CacheFlags) HasAll(bits ...Bit) bool { + for _, bit := range bits { + if !c.Has(bit) { + return false + } + } + return true +} + +// Has will check whether the Bit contains another bit +func (c CacheFlags) Has(bit Bit) bool { + return (c & bit.(CacheFlags)) == bit +} + +// MissingAny will check whether the bit is missing any one of the bits +func (c CacheFlags) MissingAny(bits ...Bit) bool { + for _, bit := range bits { + if !c.Has(bit) { + return true + } + } + return false +} + +// Missing will do the inverse of Bit.Has +func (c CacheFlags) Missing(bit Bit) bool { + return !c.Has(bit) +} diff --git a/api/channels.go b/api/channels.go index 529c2383..29e473ce 100644 --- a/api/channels.go +++ b/api/channels.go @@ -1,5 +1,7 @@ package api +import "errors" + // ChannelType for interacting with discord's channels type ChannelType int @@ -37,32 +39,56 @@ type Channel struct { //LastPinTimestamp *time.Time `json:"last_pin_timestamp,omitempty"` } -// MessageChannel is used for sending messages to user +// MessageChannel is used for sending Message(s) to User(s) type MessageChannel struct { Channel } -// SendMessage a Message to a TextChannel +// SendMessage sends a Message to a TextChannel func (c MessageChannel) SendMessage(message MessageCreate) (*Message, error) { - // Todo: embeds, attachments etc. + // Todo: attachments return c.Disgo.RestClient().SendMessage(c.ID, message) } -// DMChannel is used for interacting in private messages with users +// EditMessage edits a Message in this TextChannel +func (c MessageChannel) EditMessage(messageID Snowflake, message MessageUpdate) (*Message, error) { + return c.Disgo.RestClient().EditMessage(c.ID, messageID, message) +} + +// DeleteMessage allows you to edit an existing Message sent by you +func (c MessageChannel) DeleteMessage(messageID Snowflake) error { + return c.Disgo.RestClient().DeleteMessage(c.ID, messageID) +} + +// BulkDeleteMessages allows you bulk delete Message(s) +func (c MessageChannel) BulkDeleteMessages(messageIDs ...Snowflake) error { + return c.Disgo.RestClient().BulkDeleteMessages(c.ID, messageIDs...) +} + +// CrosspostMessage crossposts an existing Message +func (c MessageChannel) CrosspostMessage(messageID Snowflake) (*Message, error) { + if c.Type != ChannelTypeNews { + return nil, errors.New("channel type is not NEWS") + } + return c.Disgo.RestClient().CrosspostMessage(c.ID, messageID) +} + +// DMChannel is used for interacting in private Message(s) with users type DMChannel struct { MessageChannel - Users []User `json:"recipients"` } // GuildChannel is a generic type for all server channels type GuildChannel struct { Channel - GuildID Snowflake `json:"guild_id"` } // Guild returns the channel's Guild func (c GuildChannel) Guild() *Guild { - return c.Disgo.Cache().Guild(c.GuildID) + if c.GuildID == nil { + return nil + } + return c.Disgo.Cache().Guild(*c.GuildID) } // Category groups text & voice channels in servers together @@ -75,6 +101,11 @@ type VoiceChannel struct { GuildChannel } +// Connect sends a api.GatewayCommand to connect to this VoiceChannel +func (c *VoiceChannel) Connect() error { + return c.Disgo.AudioController().Connect(*c.GuildID, c.ID) +} + // TextChannel allows you to interact with discord's text channels type TextChannel struct { GuildChannel diff --git a/api/color.go b/api/color.go index 42a73b06..f5012239 100644 --- a/api/color.go +++ b/api/color.go @@ -1,4 +1,4 @@ package api -// Color is used for specifying colors in an Embed +// Color is used for specifying colors in an Embed / Role type Color int diff --git a/api/command.go b/api/command.go new file mode 100644 index 00000000..7af7885c --- /dev/null +++ b/api/command.go @@ -0,0 +1,201 @@ +package api + +import "errors" + +var errNoDisgoInstance = errors.New("no disgo instance injected") + +// Command is the base "command" model that belongs to an application. +type Command struct { + Disgo Disgo + GuildPermissions map[Snowflake]*GuildCommandPermissions + GuildID *Snowflake `json:"guild_id"` + ID Snowflake `json:"id,omitempty"` + ApplicationID Snowflake `json:"application_id,omitempty"` + Name string `json:"name"` + Description string `json:"description"` + DefaultPermission bool `json:"default_permission"` + Options []*CommandOption `json:"options,omitempty"` +} + +// Guild returns the Guild the Command is from from the Cache or nil if it is a global Command +func (c Command) Guild() *Guild { + if c.GuildID == nil { + return nil + } + return c.Disgo.Cache().Guild(*c.GuildID) +} + +// FromGuild returns true if this is a guild Command else false +func (c Command) FromGuild() bool { + return c.GuildID == nil +} + +// Create creates the Command either as global or guild Command depending on if GuildID is set +func (c *Command) Create() error { + if c.Disgo == nil { + return errNoDisgoInstance + } + var rC *Command + var err error + if c.GuildID == nil { + rC, err = c.Disgo.RestClient().CreateGlobalCommand(c.Disgo.ApplicationID(), *c) + + } else { + rC, err = c.Disgo.RestClient().CreateGuildCommand(c.Disgo.ApplicationID(), *c.GuildID, *c) + } + if err != nil { + return err + } + *c = *rC + return nil +} + +// Fetch updates/fetches the current Command from discord +func (c *Command) Fetch() error { + if c.Disgo == nil { + return errNoDisgoInstance + } + var rC *Command + var err error + if c.GuildID == nil { + rC, err = c.Disgo.RestClient().GetGlobalCommand(c.Disgo.ApplicationID(), c.ID) + + } else { + rC, err = c.Disgo.RestClient().GetGuildCommand(c.Disgo.ApplicationID(), *c.GuildID, c.ID) + } + if err != nil { + return err + } + *c = *rC + return nil +} + +// Update updates the current Command with the given fields +func (c *Command) Update(command UpdateCommand) error { + if c.Disgo == nil { + return errNoDisgoInstance + } + var rC *Command + var err error + if c.GuildID == nil { + rC, err = c.Disgo.RestClient().EditGlobalCommand(c.Disgo.ApplicationID(), c.ID, command) + + } else { + rC, err = c.Disgo.RestClient().EditGuildCommand(c.Disgo.ApplicationID(), *c.GuildID, c.ID, command) + } + if err != nil { + return err + } + *c = *rC + return nil +} + +// SetPermissions sets the GuildCommandPermissions for a specific Guild. this overrides all existing CommandPermission(s). thx discord for that +func (c *Command) SetPermissions(guildID Snowflake, permissions ...CommandPermission) error { + _, err := c.Disgo.RestClient().SetGuildCommandPermissions(c.Disgo.ApplicationID(), guildID, c.ID, SetGuildCommandPermissions{Permissions: permissions}) + if err != nil { + return err + } + return nil +} + +// GetPermissions returns the GuildCommandPermissions for the specific Guild from the Cache +func (c Command) GetPermissions(guildID Snowflake) *GuildCommandPermissions { + return c.GuildPermissions[guildID] +} + +// FetchPermissions fetched the GuildCommandPermissions for a specific Guild from discord +func (c *Command) FetchPermissions(guildID Snowflake) (*GuildCommandPermissions, error) { + perms, err := c.Disgo.RestClient().GetGuildCommandPermissions(c.Disgo.ApplicationID(), guildID, c.ID) + if err != nil { + return nil, err + } + return perms, nil +} + +// Delete deletes the Command from discord +func (c Command) Delete() error { + if c.Disgo == nil { + return errNoDisgoInstance + } + if c.GuildID == nil { + return c.Disgo.RestClient().DeleteGlobalCommand(c.Disgo.ApplicationID(), c.ID) + + } + return c.Disgo.RestClient().DeleteGuildCommand(c.Disgo.ApplicationID(), *c.GuildID, c.ID) +} + +// CommandOptionType specifies the type of the arguments used in Command.Options +type CommandOptionType int + +// Constants for each slash command option type +const ( + CommandOptionTypeSubCommand CommandOptionType = iota + 1 + CommandOptionTypeSubCommandGroup + CommandOptionTypeString + CommandOptionTypeInteger + CommandOptionTypeBoolean + CommandOptionTypeUser + CommandOptionTypeChannel + CommandOptionTypeRole +) + +// CommandOption are the arguments used in Command.Options +type CommandOption struct { + Type CommandOptionType `json:"type"` + Name string `json:"name"` + Description string `json:"description"` + Required bool `json:"required,omitempty"` + Choices []OptionChoice `json:"choices,omitempty"` + Options []CommandOption `json:"options,omitempty"` +} + +// OptionChoice contains the data for a user using your command +type OptionChoice struct { + Name string `json:"name"` + Value interface{} `json:"value"` +} + +// GuildCommandPermissions holds all permissions for a Command +type GuildCommandPermissions struct { + Disgo Disgo + ID Snowflake `json:"id"` + ApplicationID Snowflake `json:"application_id"` + GuildID Snowflake `json:"guild_id"` + Permissions []CommandPermission `json:"permissions"` +} + +// TODO: add methods to update those + +// CommandPermissionType is the type of the CommandPermission +type CommandPermissionType int + +// types of CommandPermissionType +const ( + CommandPermissionTypeRole = iota + 1 + CommandPermissionTypeUser +) + +// CommandPermission holds a User or Role and if they are allowed to use the Command +type CommandPermission struct { + ID Snowflake `json:"id"` + Type CommandPermissionType `json:"type"` + Permission bool `json:"permission"` +} + +// SetGuildCommandsPermissions holds a slice of SetGuildCommandPermissions +type SetGuildCommandsPermissions []SetGuildCommandPermissions + +// SetGuildCommandPermissions is used to update CommandPermission ID should be omitted fro bulk update +type SetGuildCommandPermissions struct { + ID Snowflake `json:"id,omitempty"` + Permissions []CommandPermission `json:"permissions"` +} + +// UpdateCommand is used to update an existing Command. all fields are optional +type UpdateCommand struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + DefaultPermission *bool `json:"default_permission,omitempty"` + Options []*CommandOption `json:"options,omitempty"` +} diff --git a/api/command_thread.go b/api/command_thread.go deleted file mode 100644 index 6df7c14e..00000000 --- a/api/command_thread.go +++ /dev/null @@ -1,17 +0,0 @@ -package api - -/*import ( - "github.com/chebyrash/promise" - - "github.com/DisgoOrg/disgo/internal/events" -) - -// CommandThread allows you to follow up Interactions (https://discord.com/developers/docs/interactions/slash-commands) -type CommandThread interface { - Disgo() Disgo - Event() events.SlashCommandEvent - Ephemeral(ephemeral bool) CommandThread - SendMessage() *promise.Promise - EditOriginal(message_events string) *promise.Promise - DeleteOriginal() *promise.Promise -}*/ diff --git a/api/disgo.go b/api/disgo.go index ad149a78..90112a94 100644 --- a/api/disgo.go +++ b/api/disgo.go @@ -5,61 +5,79 @@ import ( "runtime" "strings" "time" + + "github.com/DisgoOrg/disgo/api/endpoints" + "github.com/DisgoOrg/log" ) // Disgo is the main discord interface type Disgo interface { + Logger() log.Logger Connect() error - Start() error + Start() Close() - Token() string + Token() endpoints.Token Gateway() Gateway RestClient() RestClient WebhookServer() WebhookServer Cache() Cache Intents() Intents + RawGatewayEventsEnabled() bool ApplicationID() Snowflake SelfUser() *User - SetSelfUser(*User) + EntityBuilder() EntityBuilder EventManager() EventManager + VoiceDispatchInterceptor() VoiceDispatchInterceptor + SetVoiceDispatchInterceptor(voiceInterceptor VoiceDispatchInterceptor) + AudioController() AudioController HeartbeatLatency() time.Duration + LargeThreshold() int + HasGateway() bool - GetCommand(commandID Snowflake) (*SlashCommand, error) - GetCommands() ([]*SlashCommand, error) - CreateCommand(command SlashCommand) (*SlashCommand, error) - EditCommand(commandID Snowflake, command SlashCommand) (*SlashCommand, error) - DeleteCommand(command SlashCommand) (*SlashCommand, error) - SetCommands(commands ...SlashCommand) ([]*SlashCommand, error) + GetCommand(commandID Snowflake) (*Command, error) + GetCommands() ([]*Command, error) + CreateCommand(command Command) (*Command, error) + EditCommand(commandID Snowflake, command UpdateCommand) (*Command, error) + DeleteCommand(command Command) (*Command, error) + SetCommands(commands ...Command) ([]*Command, error) } // EventHandler provides info about the EventHandler type EventHandler interface { - Name() string + Event() GatewayEventType New() interface{} } // GatewayEventHandler is used to handle raw gateway events type GatewayEventHandler interface { EventHandler - Handle(Disgo, EventManager, interface{}) + HandleGatewayEvent(disgo Disgo, eventManager EventManager, sequenceNumber int, payload interface{}) } // WebhookEventHandler is used to handle raw webhook events type WebhookEventHandler interface { EventHandler - Handle(Disgo, EventManager, chan interface{}, interface{}) + HandleWebhookEvent(disgo Disgo, eventManager EventManager, replyChannel chan interface{}, payload interface{}) } // EventListener is used to create new EventListener to listen to events type EventListener interface { - OnEvent(interface{}) + OnEvent(event interface{}) +} + +// Event the basic interface each event implement +type Event interface { + Disgo() Disgo + SequenceNumber() int } // EventManager lets you listen for specific events triggered by raw gateway events type EventManager interface { - AddEventListeners(...EventListener) - Handle(string, json.RawMessage, chan interface{}) - Dispatch(GenericEvent) + Disgo() Disgo + Close() + AddEventListeners(eventListeners ...EventListener) + Handle(eventType GatewayEventType, replyChannel chan interface{}, sequenceNumber int, payload json.RawMessage) + Dispatch(event Event) } // GetOS returns the simplified version of the operating system for sending to Discord in the IdentifyCommandDataProperties.OS payload diff --git a/api/disgo_builder.go b/api/disgo_builder.go index d2b86cfb..c38245a0 100644 --- a/api/disgo_builder.go +++ b/api/disgo_builder.go @@ -1,12 +1,19 @@ package api -import log "github.com/sirupsen/logrus" +import ( + "github.com/DisgoOrg/log" + + "github.com/DisgoOrg/disgo/api/endpoints" +) // DisgoBuilder allows you to create a Disgo client through a series of methods type DisgoBuilder interface { - SetLogLevel(level log.Level) DisgoBuilder - SetToken(token string) DisgoBuilder + SetLogger(level log.Logger) DisgoBuilder + SetToken(token endpoints.Token) DisgoBuilder SetIntents(intents Intents) DisgoBuilder + SetRawGatewayEventsEnabled(enabled bool) DisgoBuilder + SetVoiceDispatchInterceptor(voiceDispatchInterceptor VoiceDispatchInterceptor) DisgoBuilder + SetEntityBuilder(entityBuilder EntityBuilder) DisgoBuilder SetEventManager(eventManager EventManager) DisgoBuilder AddEventListeners(eventsListeners ...EventListener) DisgoBuilder SetWebhookServer(webhookServer WebhookServer) DisgoBuilder @@ -14,6 +21,8 @@ type DisgoBuilder interface { SetRestClient(restClient RestClient) DisgoBuilder SetCache(cache Cache) DisgoBuilder SetMemberCachePolicy(memberCachePolicy MemberCachePolicy) DisgoBuilder + SetMessageCachePolicy(messageCachePolicy MessageCachePolicy) DisgoBuilder + SetCacheFlags(cacheFlags CacheFlags) DisgoBuilder SetGateway(gateway Gateway) DisgoBuilder Build() (Disgo, error) } diff --git a/api/embed.go b/api/embed.go index c6d7aa17..d1238ff9 100644 --- a/api/embed.go +++ b/api/embed.go @@ -82,8 +82,8 @@ type EmbedBuilder struct { } // SetTitle sets the title of the EmbedBuilder -func (b *EmbedBuilder) SetTitle(title *string) *EmbedBuilder { - b.Title = title +func (b *EmbedBuilder) SetTitle(title string) *EmbedBuilder { + b.Title = &title return b } @@ -100,12 +100,31 @@ func (b *EmbedBuilder) SetDescriptionf(description string, a ...interface{}) *Em return b } -// SetAuthor sets the author of the EmbedBuilder -func (b *EmbedBuilder) SetAuthor(author *EmbedAuthor) *EmbedBuilder { +// SetEmbedAuthor sets the author of the EmbedBuilder using an EmbedAuthor struct +func (b *EmbedBuilder) SetEmbedAuthor(author *EmbedAuthor) *EmbedBuilder { b.Author = author return b } +// SetAuthor sets the author of the EmbedBuilder without an Icon URL +func (b *EmbedBuilder) SetAuthor(name string, url string) *EmbedBuilder { + b.Author = &EmbedAuthor{ + Name: &name, + URL: &url, + } + return b +} + +// SetAuthorI sets the author of the EmbedBuilder with all properties +func (b *EmbedBuilder) SetAuthorI(name string, url string, iconURL string) *EmbedBuilder { + b.Author = &EmbedAuthor{ + Name: &name, + URL: &url, + IconURL: &iconURL, + } + return b +} + // SetColor sets the color of the EmbedBuilder func (b *EmbedBuilder) SetColor(color int) *EmbedBuilder { b.Color = &color @@ -144,8 +163,8 @@ func (b *EmbedBuilder) SetThumbnail(i *string) *EmbedBuilder { } // SetURL sets the URL of the EmbedBuilder -func (b *EmbedBuilder) SetURL(u *string) *EmbedBuilder { - b.URL = u +func (b *EmbedBuilder) SetURL(url string) *EmbedBuilder { + b.URL = &url return b } @@ -155,6 +174,14 @@ func (b *EmbedBuilder) AddField(name string, value string, inline bool) *EmbedBu return b } +// SetField sets a field to the EmbedBuilder by name and value +func (b *EmbedBuilder) SetField(index int, name string, value string, inline bool) *EmbedBuilder { + if len(b.Fields) > index { + b.Fields[index] = &EmbedField{name, value, &inline} + } + return b +} + // AddFields adds multiple fields to the EmbedBuilder func (b *EmbedBuilder) AddFields(f *EmbedField, fs ...*EmbedField) *EmbedBuilder { b.Fields = append(b.Fields, f) diff --git a/api/emote.go b/api/emote.go index f2e84ff9..5df8157f 100644 --- a/api/emote.go +++ b/api/emote.go @@ -2,11 +2,18 @@ package api // An Emote allows you to interact with custom emojis in discord. type Emote struct { + Disgo Disgo ID Snowflake + GuildID Snowflake Name string Animated bool } +// Guild returns the Guild of the Emote from the Cache +func (e Emote) Guild() *Guild { + return e.Disgo.Cache().Guild(e.GuildID) +} + // Mention returns the string used to send the emoji func (e Emote) Mention() string { start := "<:" @@ -16,6 +23,7 @@ func (e Emote) Mention() string { return start + e.Name + ":" + e.ID.String() + ">" } +// String formats the Emote as string func (e Emote) String() string { return e.Mention() } diff --git a/api/endpoints/api_route.go b/api/endpoints/api_route.go index d744c989..887d0d13 100644 --- a/api/endpoints/api_route.go +++ b/api/endpoints/api_route.go @@ -7,11 +7,15 @@ type APIRoute struct { } // Compile returns a CompiledAPIRoute -func (r APIRoute) Compile(args ...interface{}) CompiledAPIRoute { - return CompiledAPIRoute{ - CompiledRoute: r.Route.Compile(args...), - method: r.method, +func (r APIRoute) Compile(args ...interface{}) (*CompiledAPIRoute, error) { + compiledRoute, err := r.Route.Compile(args...) + if err != nil { + return nil, err } + return &CompiledAPIRoute{ + CompiledRoute: compiledRoute, + method: r.method, + }, nil } // Method returns the request method used by the route @@ -33,7 +37,7 @@ func NewAPIRoute(method Method, url string) APIRoute { // CompiledAPIRoute is APIRoute compiled with all URL args type CompiledAPIRoute struct { - CompiledRoute + *CompiledRoute method Method } diff --git a/api/endpoints/cdn_route.go b/api/endpoints/cdn_route.go index ba78eaca..7df913d3 100644 --- a/api/endpoints/cdn_route.go +++ b/api/endpoints/cdn_route.go @@ -1,16 +1,19 @@ package endpoints -import log "github.com/sirupsen/logrus" +import ( + "errors" +) // FileExtension is the type of an image on Discord's CDN type FileExtension string // The available FileExtension(s) const ( - PNG FileExtension = "png" - JPEG FileExtension = "jpg" - WEBP FileExtension = "webp" - GIF FileExtension = "gif" + PNG FileExtension = "png" + JPEG FileExtension = "jpg" + WEBP FileExtension = "webp" + GIF FileExtension = "gif" + BLANK FileExtension = "" ) func (f FileExtension) String() string { @@ -36,7 +39,7 @@ func NewCDNRoute(url string, supportedFileExtensions ...FileExtension) CDNRoute } // Compile builds a full request URL based on provided arguments -func (r CDNRoute) Compile(fileExtension FileExtension, args ...interface{}) CompiledCDNRoute { +func (r CDNRoute) Compile(fileExtension FileExtension, args ...interface{}) (*CompiledCDNRoute, error) { supported := false for _, supportedFileExtension := range r.supportedFileExtensions { if supportedFileExtension == fileExtension { @@ -44,16 +47,20 @@ func (r CDNRoute) Compile(fileExtension FileExtension, args ...interface{}) Comp } } if !supported { - log.Infof("provided file extension: %s is not supported by discord on this endpoint!", fileExtension) + return nil, errors.New("provided file extension: " + fileExtension.String() + " is not supported by discord on this endpoint!") + } + compiledRoute, err := r.Route.Compile(args...) + if err != nil { + return nil, err } - compiledRoute := CompiledCDNRoute{ - CompiledRoute: r.Route.Compile(args...), + compiledRoute.route += fileExtension.String() + compiledCDNRoute := &CompiledCDNRoute{ + CompiledRoute: compiledRoute, } - compiledRoute.CompiledRoute.route += fileExtension.String() - return compiledRoute + return compiledCDNRoute, nil } // CompiledCDNRoute is CDNRoute compiled with all URL args type CompiledCDNRoute struct { - CompiledRoute + *CompiledRoute } diff --git a/api/endpoints/custom_route.go b/api/endpoints/custom_route.go index 2e0f26ab..64b26672 100644 --- a/api/endpoints/custom_route.go +++ b/api/endpoints/custom_route.go @@ -6,11 +6,15 @@ type CustomRoute struct { } // Compile returns a CompiledAPIRoute -func (r CustomRoute) Compile(args ...interface{}) CompiledAPIRoute { - return CompiledAPIRoute{ - CompiledRoute: r.Route.Compile(args...), - method: r.method, +func (r CustomRoute) Compile(args ...interface{}) (*CompiledAPIRoute, error) { + compiledRoute, err := r.Route.Compile(args...) + if err != nil { + return nil, err } + return &CompiledAPIRoute{ + CompiledRoute: compiledRoute, + method: r.method, + }, nil } // NewCustomRoute generates a new custom route struct diff --git a/api/endpoints/endpoints.go b/api/endpoints/endpoints.go index ac61ca2c..d335c1f5 100644 --- a/api/endpoints/endpoints.go +++ b/api/endpoints/endpoints.go @@ -10,7 +10,6 @@ const ( // Misc var ( - GetVoiceReagions = NewAPIRoute(GET, "/voice/regions") GetGateway = NewAPIRoute(GET, "/gateway") GetGatewayBot = NewAPIRoute(GET, "/gateway/bot") GetBotApplication = NewAPIRoute(GET, "/oauth2/applications/@me") @@ -32,6 +31,11 @@ var ( EditGuildCommand = NewAPIRoute(PATCH, "/applications/{application.id}/guilds/{guild.id}/commands/{command.id}") DeleteGuildCommand = NewAPIRoute(DELETE, "/applications/{application.id}/guilds/{guild.id}/commands") + GetGuildCommandPermissions = NewAPIRoute(GET, "/applications/{application.id}/guilds/{guild.id}/commands/permissions") + GetGuildCommandPermission = NewAPIRoute(GET, "/applications/{application.id}/guilds/{guild.id}/commands/{command.id}/permissions") + SetGuildCommandsPermissions = NewAPIRoute(PUT, "/applications/{application.id}/guilds/{guild.id}/commands/permissions") + SetGuildCommandPermissions = NewAPIRoute(PUT, "/applications/{application.id}/guilds/{guild.id}/commands/{command.id}/permissions") + CreateInteractionResponse = NewAPIRoute(POST, "/interactions/{interaction.id}/{interaction.token}/callback") EditInteractionResponse = NewAPIRoute(PATCH, "/webhooks/{application.id}/{interaction.token}/messages/@original") DeleteInteractionResponse = NewAPIRoute(DELETE, "/webhooks/{application.id}/{interaction.token}/messages/@original") @@ -46,7 +50,7 @@ var ( GetUser = NewAPIRoute(GET, "/users/{user.id}") GetSelfUser = NewAPIRoute(GET, "/users/@me") UpdateSelfUser = NewAPIRoute(PATCH, "/users/@me") - GetGuilds = NewAPIRoute(GET, "/users/@me/guilds/{guild.id}") + GetGuilds = NewAPIRoute(GET, "/users/@me/guilds") LeaveGuild = NewAPIRoute(DELETE, "/users/@me/guilds/{guild.id}") GetDMChannels = NewAPIRoute(GET, "/users/@me/channels") CreateDMChannel = NewAPIRoute(POST, "/users/@me/channels") @@ -85,9 +89,9 @@ var ( GetGuildWebhooks = NewAPIRoute(GET, "/guilds/{guild.id}/webhooks") - GetAudiotLogs = NewAPIRoute(GET, "/guilds/{guild.id}/audit-logs") + GetAuditLogs = NewAPIRoute(GET, "/guilds/{guild.id}/audit-logs") - GetVoiceRegions = NewAPIRoute(GET, "guilds/{guild.id}/regions") + GetVoiceRegions = NewAPIRoute(GET, "/guilds/{guild.id}/regions") GetIntegrations = NewAPIRoute(GET, "/guilds/{guild.id}/integrations") CreateIntegration = NewAPIRoute(POST, "/guilds/{guild.id}/integrations") @@ -196,4 +200,10 @@ var ( ApplicationAsset = NewCDNRoute("/app-assets/{application.id}/{asset.id}.", PNG, JPEG, WEBP) AchievementIcon = NewCDNRoute("/app-assets/{application.id}/achievements/{achievement.id}/icons/{icon.hash}.", PNG, JPEG, WEBP) TeamIcon = NewCDNRoute("/team-icons/{team.id}/team.icon.", PNG, JPEG, WEBP) + Attachments = NewCDNRoute("/attachments/{channel.id}/{attachment.id}/{file.name}", BLANK) +) + +// Other +var ( + InviteURL = NewRoute("https://discord.gg/{code}") ) diff --git a/api/endpoints/route.go b/api/endpoints/route.go index c68d6538..eb5b661d 100644 --- a/api/endpoints/route.go +++ b/api/endpoints/route.go @@ -1,10 +1,10 @@ package endpoints import ( + "errors" "fmt" + "strconv" "strings" - - log "github.com/sirupsen/logrus" ) // Route the base struct for routes used in disgo @@ -15,27 +15,39 @@ type Route struct { } // Compile builds a full request URL based on provided arguments -func (r Route) Compile(args ...interface{}) CompiledRoute { +func (r Route) Compile(args ...interface{}) (*CompiledRoute, error) { if len(args) != r.paramCount { - log.Errorf("invalid amount of arguments received. expected: %d, received: %d", r.paramCount, len(args)) + return nil, errors.New("invalid amount of arguments received. expected: " + strconv.Itoa(len(args)) + ", received: " + strconv.Itoa(r.paramCount)) } route := r.route if len(args) > 0 { for _, arg := range args { start := strings.Index(route, "{") end := strings.Index(route, "}") - route = route[:start] + fmt.Sprint(arg) + route[end+1:] + var value string + if t, ok := arg.(Token); ok { + value = string(t) + } else { + value = fmt.Sprint(arg) + } + route = route[:start] + value + route[end+1:] } } - return CompiledRoute{route: r.baseRoute + route} + return &CompiledRoute{route: r.baseRoute + route}, nil +} + +// NewRoute generates a Route when given a URL +func NewRoute(url string) Route { + return Route{ + baseRoute: "", + route: url, + paramCount: countParams(url), + } } func countParams(url string) int { paramCount := strings.Count(url, "{") - if paramCount != strings.Count(url, "}") { - log.Errorf("invalid format for route provided: %s", url) - } return paramCount } diff --git a/api/endpoints/route_test.go b/api/endpoints/route_test.go index 62c91b59..052238ff 100644 --- a/api/endpoints/route_test.go +++ b/api/endpoints/route_test.go @@ -7,16 +7,25 @@ import ( ) func TestAPIRoute_Compile(t *testing.T) { - assert.Equal(t, API+"/channels/test1/messages/test2/reactions/test3/@me", AddReaction.Compile("test1", "test2", "test3").Route()) + compiledRoute, err := AddReaction.Compile("test1", "test2", "test3") + assert.NoError(t, err) + assert.Equal(t, API+"/channels/test1/messages/test2/reactions/test3/@me", compiledRoute.Route()) } func TestCDNRoute_Compile(t *testing.T) { - assert.Equal(t, CDN+"/emojis/test1.png", Emote.Compile(PNG, "test1").Route()) - assert.Equal(t, CDN+"/emojis/test1.gif", Emote.Compile(GIF, "test1").Route()) + compiledRoute, err := Emote.Compile(PNG, "test1") + assert.NoError(t, err) + assert.Equal(t, CDN+"/emojis/test1.png", compiledRoute.Route()) + + compiledRoute, err = Emote.Compile(GIF, "test1") + assert.NoError(t, err) + assert.Equal(t, CDN+"/emojis/test1.gif", compiledRoute.Route()) } func TestCustomRoute_Compile(t *testing.T) { testAPI := NewCustomRoute(GET, "https://test.de/{test}") - assert.Equal(t, "https://test.de/test", testAPI.Compile("test").Route()) -} \ No newline at end of file + compiledRoute, err := testAPI.Compile("test") + assert.NoError(t, err) + assert.Equal(t, "https://test.de/test", compiledRoute.Route()) +} diff --git a/api/endpoints/token.go b/api/endpoints/token.go new file mode 100644 index 00000000..29e23dc9 --- /dev/null +++ b/api/endpoints/token.go @@ -0,0 +1,29 @@ +package endpoints + +import ( + "strings" +) + +// Token holds a discord token and is used to keep your logs clean from critical information +type Token string + +// MarshalJSON makes sure we don#t send ********* to discords as tokens +func (t Token) MarshalJSON() ([]byte, error) { + return []byte("\"" + t + "\""), nil +} + +// UnmarshalJSON makes sure we parse tokens from discord correctly +func (t *Token) UnmarshalJSON(raw []byte) error { + *t = Token(strings.ReplaceAll(string(raw), "\"", "")) + return nil +} + +// String masks the token +func (t Token) String() string { + return strings.Repeat("*", len(t)) +} + +// GoString masks the token +func (t Token) GoString() string { + return t.String() +} diff --git a/api/entity_builder.go b/api/entity_builder.go new file mode 100644 index 00000000..7cded026 --- /dev/null +++ b/api/entity_builder.go @@ -0,0 +1,38 @@ +package api + +// CacheStrategy is used to determine whether something should be cached when making an api request. When using the +// gateway, you'll receive the event shortly afterwards if you have the correct intents. +type CacheStrategy func(disgo Disgo) bool + +// Default cache strategy choices +var ( + CacheStrategyYes CacheStrategy = func(disgo Disgo) bool { return true } + CacheStrategyNo CacheStrategy = func(disgo Disgo) bool { return true } + CacheStrategyNoWs CacheStrategy = func(disgo Disgo) bool { return disgo.HasGateway() } +) + +// EntityBuilder is used to create structs for disgo's cache +type EntityBuilder interface { + Disgo() Disgo + + CreateGlobalCommand(command *Command, updateCache CacheStrategy) *Command + + CreateUser(user *User, updateCache CacheStrategy) *User + + CreateMessage(message *Message, updateCache CacheStrategy) *Message + + CreateGuild(guild *Guild, updateCache CacheStrategy) *Guild + CreateMember(guildID Snowflake, member *Member, updateCache CacheStrategy) *Member + CreateGuildCommand(guildID Snowflake, command *Command, updateCache CacheStrategy) *Command + CreateGuildCommandPermissions(guildCommandPermissions *GuildCommandPermissions, updateCache CacheStrategy) *GuildCommandPermissions + CreateRole(guildID Snowflake, role *Role, updateCache CacheStrategy) *Role + CreateVoiceState(role *VoiceState, updateCache CacheStrategy) *VoiceState + + CreateTextChannel(channel *Channel, updateCache CacheStrategy) *TextChannel + CreateVoiceChannel(channel *Channel, updateCache CacheStrategy) *VoiceChannel + CreateStoreChannel(channel *Channel, updateCache CacheStrategy) *StoreChannel + CreateCategory(channel *Channel, updateCache CacheStrategy) *Category + CreateDMChannel(channel *Channel, updateCache CacheStrategy) *DMChannel + + CreateEmote(guildID Snowflake, emote *Emote, updateCache CacheStrategy) *Emote +} diff --git a/api/events/application_command_events.go b/api/events/application_command_events.go new file mode 100644 index 00000000..3b427c71 --- /dev/null +++ b/api/events/application_command_events.go @@ -0,0 +1,37 @@ +package events + +import ( + "github.com/DisgoOrg/disgo/api" +) + +// GenericApplicationCommandEvent is called upon receiving either ApplicationCommandCreateEvent, ApplicationCommandUpdateEvent or ApplicationCommandDeleteEvent +type GenericApplicationCommandEvent struct { + GenericEvent + CommandID api.Snowflake + Command *api.Command + GuildID *api.Snowflake +} + +// Guild returns the api.Guild the api.Event got called or nil for global api.Command(s) +func (e GenericApplicationCommandEvent) Guild() *api.Guild { + if e.GuildID == nil { + return nil + } + return e.Disgo().Cache().Guild(*e.GuildID) +} + +// ApplicationCommandCreateEvent indicates that a new api.Command got created(this can come from any bot!) +type ApplicationCommandCreateEvent struct { + GenericApplicationCommandEvent +} + +// ApplicationCommandUpdateEvent indicates that a api.Command got updated(this can come from any bot!) +type ApplicationCommandUpdateEvent struct { + GenericApplicationCommandEvent + OldCommand *api.Command +} + +// ApplicationCommandDeleteEvent indicates that a api.Command got deleted(this can come from any bot!) +type ApplicationCommandDeleteEvent struct { + GenericApplicationCommandEvent +} diff --git a/api/events/category_events.go b/api/events/category_events.go new file mode 100644 index 00000000..722cb544 --- /dev/null +++ b/api/events/category_events.go @@ -0,0 +1,27 @@ +package events + +import ( + "github.com/DisgoOrg/disgo/api" +) + +// GenericCategoryEvent is called upon receiving CategoryCreateEvent, CategoryUpdateEvent or CategoryDeleteEvent +type GenericCategoryEvent struct { + GenericChannelEvent + Category *api.Category +} + +// CategoryCreateEvent indicates that a new api.Category got created in a api.Guild +type CategoryCreateEvent struct { + GenericCategoryEvent +} + +// CategoryUpdateEvent indicates that a api.Category got updated in a api.Guild +type CategoryUpdateEvent struct { + GenericCategoryEvent + OldCategory *api.Category +} + +// CategoryDeleteEvent indicates that a api.Category got deleted in a api.Guild +type CategoryDeleteEvent struct { + GenericCategoryEvent +} diff --git a/api/events/channel_events.go b/api/events/channel_events.go index 1c0279c7..949a1523 100644 --- a/api/events/channel_events.go +++ b/api/events/channel_events.go @@ -1,74 +1,16 @@ package events -import "github.com/DisgoOrg/disgo/api" +import ( + "github.com/DisgoOrg/disgo/api" +) -// GenericChannelEvent is called upon receiving an event in a api.Channel +// GenericChannelEvent is called upon receiving any api.Channel api.Event type GenericChannelEvent struct { - api.Event + GenericEvent ChannelID api.Snowflake } -// Channel returns the api.Channel from the api.Cache +// Channel returns the api.Channel from the api.Cache if cached func (e GenericChannelEvent) Channel() *api.Channel { - return e.Disgo.Cache().Channel(e.ChannelID) -} - -// GenericDMChannelEvent is called upon receiving an event in a api.DMChannel -type GenericDMChannelEvent struct { - GenericChannelEvent -} - -// DMChannel returns the api.DMChannel from the api.Cache -func (e GenericDMChannelEvent) DMChannel() *api.DMChannel { - return e.Disgo.Cache().DMChannel(e.ChannelID) -} - -// GenericMessageChannelEvent is called upon receiving an event in a api.MessageChannel -type GenericMessageChannelEvent struct { - GenericChannelEvent -} - -// MessageChannel returns the api.MessageChannel from the api.Cache -func (e GenericMessageChannelEvent) MessageChannel() *api.MessageChannel { - return e.Disgo.Cache().MessageChannel(e.ChannelID) -} - -// GenericTextChannelEvent is called upon receiving an event in a api.TextChannel -type GenericTextChannelEvent struct { - GenericChannelEvent -} - -// TextChannel returns the api.TextChannel from the api.Cache -func (e GenericTextChannelEvent) TextChannel() *api.TextChannel { - return e.Disgo.Cache().TextChannel(e.ChannelID) -} - -// GenericVoiceChannelEvent is called upon receiving an event in a api.VoiceChannel -type GenericVoiceChannelEvent struct { - GenericChannelEvent -} - -// VoiceChannel returns the api.VoiceChannel from the api.Cache -func (e GenericVoiceChannelEvent) VoiceChannel() *api.VoiceChannel { - return e.Disgo.Cache().VoiceChannel(e.ChannelID) -} - -// GenericCategoryEvent is called upon receiving an event in a api.Category -type GenericCategoryEvent struct { - GenericChannelEvent -} - -// Category returns the api.Category from the api.Cache -func (e GenericCategoryEvent) Category() *api.Category { - return e.Disgo.Cache().Category(e.ChannelID) -} - -// GenericStoreChannelEvent is called upon receiving an event in a api.StoreChannel -type GenericStoreChannelEvent struct { - GenericChannelEvent -} - -// StoreChannel returns the api.StoreChannel from the api.Cache -func (e GenericStoreChannelEvent) StoreChannel() *api.StoreChannel { - return e.Disgo.Cache().StoreChannel(e.ChannelID) + return e.Disgo().Cache().Channel(e.ChannelID) } diff --git a/api/events/dm_event.go b/api/events/dm_event.go deleted file mode 100644 index 6e84ef32..00000000 --- a/api/events/dm_event.go +++ /dev/null @@ -1,20 +0,0 @@ -package events - -import "github.com/DisgoOrg/disgo/api" - -// GenericDMEvent is a generic dm channel event -type GenericDMEvent struct { - api.Event - UserID api.Snowflake - DMChannelID api.Snowflake -} - -// User gets the user from the api.Cache -func (e GenericDMEvent) User() *api.DMChannel { - return e.Disgo.Cache().DMChannel(e.DMChannelID) -} - -// DMChannel returns the api.DMChannel from the api.Cache -func (e GenericDMEvent) DMChannel() *api.DMChannel { - return e.Disgo.Cache().DMChannel(e.DMChannelID) -} diff --git a/api/events/dm_events.go b/api/events/dm_events.go new file mode 100644 index 00000000..6f271d81 --- /dev/null +++ b/api/events/dm_events.go @@ -0,0 +1,32 @@ +package events + +import ( + "github.com/DisgoOrg/disgo/api" +) + +// GenericDMChannelEvent is called upon receiving DMChannelCreateEvent, DMChannelUpdateEvent, DMChannelDeleteEvent or DMUserTypingEvent +type GenericDMChannelEvent struct { + GenericChannelEvent + DMChannel *api.DMChannel +} + +// DMChannelCreateEvent indicates that a new api.DMChannel got created +type DMChannelCreateEvent struct { + GenericDMChannelEvent +} + +// DMChannelUpdateEvent indicates that a api.DMChannel got updated +type DMChannelUpdateEvent struct { + GenericDMChannelEvent + OldDMChannel *api.DMChannel +} + +// DMChannelDeleteEvent indicates that a api.DMChannel got deleted +type DMChannelDeleteEvent struct { + GenericDMChannelEvent +} + +// DMUserTypingEvent indicates that a api.User started typing in a api.DMChannel(requires api.IntentsDirectMessageTyping) +type DMUserTypingEvent struct { + GenericDMChannelEvent +} diff --git a/api/events/dm_message_event.go b/api/events/dm_message_event.go index e1a325b4..39b012be 100644 --- a/api/events/dm_message_event.go +++ b/api/events/dm_message_event.go @@ -4,25 +4,28 @@ import ( "github.com/DisgoOrg/disgo/api" ) -// GenericDMMessageEvent generic api.DMChannel api.Message api.Event +// GenericDMMessageEvent is called upon receiving DMMessageCreateEvent, DMMessageUpdateEvent, DMMessageDeleteEvent, GenericDMMessageReactionEvent, DMMessageReactionAddEvent, DMMessageReactionRemoveEvent, DMMessageReactionRemoveEmoteEvent or DMMessageReactionRemoveAllEvent(requires api.IntentsDirectMessages) type GenericDMMessageEvent struct { - GenericDMEvent GenericMessageEvent } -// DMMessageReceivedEvent called upon receiving a api.Message in a api.DMChannel -type DMMessageReceivedEvent struct { +// DMChannel returns the api.DMChannel where the GenericDMMessageEvent happened +func (e GenericDMMessageEvent) DMChannel() *api.DMChannel { + return e.Disgo().Cache().DMChannel(e.ChannelID) +} + +// DMMessageCreateEvent is called upon receiving a api.Message in a api.DMChannel(requires api.IntentsDirectMessages) +type DMMessageCreateEvent struct { GenericDMMessageEvent - Message *api.Message } -// DMMessageUpdateEvent called upon editing a api.Message in a api.DMChannel +// DMMessageUpdateEvent is called upon editing a api.Message in a api.DMChannel(requires api.IntentsDirectMessages) type DMMessageUpdateEvent struct { GenericDMMessageEvent - Message *api.Message + OldMessage *api.Message } -// DMMessageDeleteEvent called upon deleting a api.Message in a api.DMChannel +// DMMessageDeleteEvent is called upon deleting a api.Message in a api.DMChannel(requires api.IntentsDirectMessages) type DMMessageDeleteEvent struct { GenericDMMessageEvent } diff --git a/api/events/dm_message_reaction_events.go b/api/events/dm_message_reaction_events.go new file mode 100644 index 00000000..40027fe9 --- /dev/null +++ b/api/events/dm_message_reaction_events.go @@ -0,0 +1,32 @@ +package events + +import "github.com/DisgoOrg/disgo/api" + +// GenericDMMessageReactionEvent is called upon receiving DMMessageReactionAddEvent or DMMessageReactionRemoveEvent(requires the api.IntentsDirectMessageReactions) +type GenericDMMessageReactionEvent struct { + GenericGuildMessageEvent + UserID api.Snowflake + User *api.User + MessageReaction api.MessageReaction +} + +// DMMessageReactionAddEvent indicates that a api.User added a api.MessageReaction to a api.Message in a api.DMChannel(requires the api.IntentsDirectMessageReactions) +type DMMessageReactionAddEvent struct { + GenericDMMessageReactionEvent +} + +// DMMessageReactionRemoveEvent indicates that a api.User removed a api.MessageReaction from a api.Message in a api.DMChannel(requires the api.IntentsDirectMessageReactions) +type DMMessageReactionRemoveEvent struct { + GenericDMMessageReactionEvent +} + +// DMMessageReactionRemoveEmoteEvent indicates someone removed all api.MessageReaction of a specific api.Emote from a api.Message in a api.DMChannel(requires the api.IntentsDirectMessageReactions) +type DMMessageReactionRemoveEmoteEvent struct { + GenericDMMessageEvent + MessageReaction api.MessageReaction +} + +// DMMessageReactionRemoveAllEvent indicates someone removed all api.MessageReaction(s) from a api.Message in a api.DMChannel(requires the api.IntentsDirectMessageReactions) +type DMMessageReactionRemoveAllEvent struct { + GenericDMMessageEvent +} diff --git a/api/events/emote_events.go b/api/events/emote_events.go new file mode 100644 index 00000000..7e6f6876 --- /dev/null +++ b/api/events/emote_events.go @@ -0,0 +1,27 @@ +package events + +import ( + "github.com/DisgoOrg/disgo/api" +) + +// GenericEmoteEvent is called upon receiving EmoteCreateEvent, EmoteUpdateEvent or EmoteDeleteEvent(requires api.IntentsGuildEmojis) +type GenericEmoteEvent struct { + GenericGuildEvent + Emote *api.Emote +} + +// EmoteCreateEvent indicates that a new api.Emote got created in a api.Guild(requires api.IntentsGuildEmojis) +type EmoteCreateEvent struct { + GenericEmoteEvent +} + +// EmoteUpdateEvent indicates that a api.Emote got updated in a api.Guild(requires api.IntentsGuildEmojis) +type EmoteUpdateEvent struct { + GenericEmoteEvent + OldEmote *api.Emote +} + +// EmoteDeleteEvent indicates that a api.Emote got deleted in a api.Guild(requires api.IntentsGuildEmojis) +type EmoteDeleteEvent struct { + GenericEmoteEvent +} diff --git a/api/events/gateway_status_event.go b/api/events/gateway_status_event.go new file mode 100644 index 00000000..17ad0f5e --- /dev/null +++ b/api/events/gateway_status_event.go @@ -0,0 +1,31 @@ +package events + +import ( + "github.com/DisgoOrg/disgo/api" +) + +// GenericGatewayStatusEvent is called upon receiving ConnectedEvent, ReconnectedEvent, ResumedEvent, DisconnectedEvent or ShutdownEvent +type GenericGatewayStatusEvent struct { + GenericEvent + Status api.GatewayStatus +} + +// ConnectedEvent indicates disgo connected to the api.Gateway +type ConnectedEvent struct { + GenericGatewayStatusEvent +} + +// ReconnectedEvent indicates disgo reconnected to the api.Gateway +type ReconnectedEvent struct { + GenericGatewayStatusEvent +} + +// ResumedEvent indicates disgo resumed to the api.Gateway +type ResumedEvent struct { + GenericGatewayStatusEvent +} + +// DisconnectedEvent indicates disgo disconnected to the api.Gateway +type DisconnectedEvent struct { + GenericGatewayStatusEvent +} diff --git a/api/events/generic_event.go b/api/events/generic_event.go new file mode 100644 index 00000000..0561e8e8 --- /dev/null +++ b/api/events/generic_event.go @@ -0,0 +1,26 @@ +package events + +import "github.com/DisgoOrg/disgo/api" + +// NewEvent constructs a new GenericEvent with the provided Disgo instance +func NewEvent(disgo api.Disgo, sequenceNumber int) GenericEvent { + event := GenericEvent{disgo: disgo, sequenceNumber: sequenceNumber} + disgo.EventManager().Dispatch(event) + return event +} + +// GenericEvent the base event structure +type GenericEvent struct { + disgo api.Disgo + sequenceNumber int +} + +// Disgo returns the Disgo instance for this event +func (d GenericEvent) Disgo() api.Disgo { + return d.disgo +} + +// SequenceNumber returns the sequence number of the gateway event +func (d GenericEvent) SequenceNumber() int { + return d.sequenceNumber +} diff --git a/api/events/guild_events.go b/api/events/guild_events.go index 1939b943..aa214bae 100644 --- a/api/events/guild_events.go +++ b/api/events/guild_events.go @@ -4,43 +4,51 @@ import ( "github.com/DisgoOrg/disgo/api" ) -// GenericGuildEvent generic api.Guild api.Event +// GenericGuildEvent is called upon receiving GuildUpdateEvent, GuildAvailableEvent, GuildUnavailableEvent, GuildJoinEvent, GuildLeaveEvent, GuildReadyEvent, GuildBanEvent, GuildUnbanEvent type GenericGuildEvent struct { - api.Event - GuildID api.Snowflake -} - -// Guild returns the api.Guild from the api.Cache -func (e GenericGuildEvent) Guild() *api.Guild { - return e.Disgo.Cache().Guild(e.GuildID) + GenericEvent + Guild *api.Guild } -// GuildUpdateEvent called upon receiving api.Guild updates +// GuildUpdateEvent is called upon receiving api.Guild updates type GuildUpdateEvent struct { GenericGuildEvent - Guild *api.Guild OldGuild *api.Guild } -// GuildAvailableEvent called when an unavailable api.Guild becomes available +// GuildAvailableEvent is called when an unavailable api.Guild becomes available type GuildAvailableEvent struct { GenericGuildEvent - Guild *api.Guild } -// GuildUnavailableEvent called when an available api.Guild becomes unavailable +// GuildUnavailableEvent is called when an available api.Guild becomes unavailable type GuildUnavailableEvent struct { GenericGuildEvent } -// GuildJoinEvent called when the bot joins a api.Guild +// GuildJoinEvent is called when the bot joins a api.Guild type GuildJoinEvent struct { GenericGuildEvent - Guild *api.Guild } -// GuildLeaveEvent called when the bot leaves a api.Guild +// GuildLeaveEvent is called when the bot leaves a api.Guild type GuildLeaveEvent struct { GenericGuildEvent - Guild *api.Guild +} + +// GuildReadyEvent is called when the loaded the api.Guild in login phase +type GuildReadyEvent struct { + GenericGuildEvent +} + +// GuildBanEvent is called when a api.Member/api.User is banned from the api.Guild +type GuildBanEvent struct { + GenericGuildEvent + User *api.User +} + +// GuildUnbanEvent is called when a api.Member/api.User is unbanned from the api.Guild +type GuildUnbanEvent struct { + GenericGuildEvent + User *api.User } diff --git a/api/events/guild_invite_events.go b/api/events/guild_invite_events.go new file mode 100644 index 00000000..0fa4c812 --- /dev/null +++ b/api/events/guild_invite_events.go @@ -0,0 +1,56 @@ +package events + +import ( + "github.com/DisgoOrg/disgo/api" +) + +// GenericGuildInviteEvent is called upon receiving GuildInviteCreateEvent or GuildInviteDeleteEvent(requires api.IntentsGuildInvites) +type GenericGuildInviteEvent struct { + GenericGuildEvent + Code string + ChannelID api.Snowflake +} + +// Channel returns the api.Channel the GenericGuildInviteEvent happened in(returns nil if the api.Channel is uncached or api.Cache is disabled) +func (e GenericGuildInviteEvent) Channel() *api.Channel { + return e.Disgo().Cache().Channel(e.ChannelID) +} + +// GuildChannel returns the api.GuildChannel the GenericGuildInviteEvent happened in(returns nil if the api.GuildChannel is uncached or api.Cache is disabled) +func (e GenericGuildInviteEvent) GuildChannel() *api.GuildChannel { + return e.Disgo().Cache().GuildChannel(e.ChannelID) +} + +// MessageChannel returns the api.MessageChannel the GenericGuildInviteEvent happened in(returns nil if the api.MessageChannel is uncached or api.Cache is disabled) +func (e GenericGuildInviteEvent) MessageChannel() *api.TextChannel { + return e.Disgo().Cache().TextChannel(e.ChannelID) +} + +// TextChannel returns the api.TextChannel the GenericGuildInviteEvent happened in(returns nil if the api.TextChannel is uncached or api.CacheFlagTextChannels is disabled) +func (e GenericGuildInviteEvent) TextChannel() *api.TextChannel { + return e.Disgo().Cache().TextChannel(e.ChannelID) +} + +// VoiceChannel returns the api.VoiceChannel the GenericGuildInviteEvent happened in(returns nil if the api.VoiceChannel is uncached or api.CacheFlagVoiceChannels is disabled) +func (e GenericGuildInviteEvent) VoiceChannel() *api.VoiceChannel { + return e.Disgo().Cache().VoiceChannel(e.ChannelID) +} +// StoreChannel returns the api.StoreChannel the GenericGuildInviteEvent happened in(returns nil if the api.StoreChannel is uncached or api.CacheFlagStoreChannels is disabled) +func (e GenericGuildInviteEvent) StoreChannel() *api.StoreChannel { + return e.Disgo().Cache().StoreChannel(e.ChannelID) +} +// Category returns the api.Category the GenericGuildInviteEvent happened in(returns nil if the api.Category is uncached or api.CacheFlagCategories is disabled) +func (e GenericGuildInviteEvent) Category() *api.Category { + return e.Disgo().Cache().Category(e.ChannelID) +} + +// GuildInviteCreateEvent is called upon creation of a new api.Invite in a api.Guild(requires api.IntentsGuildInvites) +type GuildInviteCreateEvent struct { + GenericGuildInviteEvent + Invite *api.Invite +} + +// GuildInviteDeleteEvent is called upon deletion of a new api.Invite in a api.Guild(requires api.IntentsGuildInvites) +type GuildInviteDeleteEvent struct { + GenericGuildInviteEvent +} diff --git a/api/events/guild_member_events.go b/api/events/guild_member_events.go index 862c56a0..32ce9a55 100644 --- a/api/events/guild_member_events.go +++ b/api/events/guild_member_events.go @@ -1,33 +1,47 @@ package events -import "github.com/DisgoOrg/disgo/api" +import ( + "github.com/DisgoOrg/disgo/api" +) // GenericGuildMemberEvent generic api.Member event type GenericGuildMemberEvent struct { GenericGuildEvent - UserID api.Snowflake + Member *api.Member } // User gets the api.User form the api.Cache func (e GenericGuildMemberEvent) User() *api.User { - return e.Disgo.Cache().User(e.UserID) + if e.Member == nil { + return nil + } + return e.Disgo().Cache().User(e.Member.User.ID) } // GuildMemberJoinEvent indicates that a api.Member joined the api.Guild type GuildMemberJoinEvent struct { GenericGuildMemberEvent - Member *api.Member } // GuildMemberUpdateEvent indicates that a api.Member updated type GuildMemberUpdateEvent struct { GenericGuildMemberEvent OldMember *api.Member - NewMember *api.Member } // GuildMemberLeaveEvent indicates that a api.Member left the api.Guild type GuildMemberLeaveEvent struct { GenericGuildMemberEvent - Member *api.Member + User *api.User +} + +// GuildMemberTypingEvent indicates that a api.Member started typing in a api.TextChannel(requires api.IntentsGuildMessageTyping) +type GuildMemberTypingEvent struct { + GenericGuildMemberEvent + ChannelID api.Snowflake +} + +// TextChannel returns the api.TextChannel the GuildMemberTypingEvent happened in +func (e GuildMemberTypingEvent) TextChannel() *api.TextChannel { + return e.Disgo().Cache().TextChannel(e.ChannelID) } diff --git a/api/events/guild_message_events.go b/api/events/guild_message_events.go index de4772d7..15407c44 100644 --- a/api/events/guild_message_events.go +++ b/api/events/guild_message_events.go @@ -4,26 +4,34 @@ import ( "github.com/DisgoOrg/disgo/api" ) -// GenericGuildMessageEvent indicates that we received a api.Message api.Event in a api.Guild +// GenericGuildMessageEvent is called upon receiving GuildMessageCreateEvent, GuildMessageUpdateEvent or GuildMessageDeleteEvent type GenericGuildMessageEvent struct { - GenericGuildEvent GenericMessageEvent + GuildID api.Snowflake } -// GuildMessageReceivedEvent indicates that we received a api.Message in a api.Guild -type GuildMessageReceivedEvent struct { +// Guild returns the api.Guild the GenericGuildMessageEvent happened in +func (e GenericGuildMessageEvent) Guild() *api.Guild { + return e.Disgo().Cache().Guild(e.GuildID) +} + +// TextChannel returns the api.TextChannel from the api.Cache +func (e GenericGuildMessageEvent) TextChannel() *api.TextChannel { + return e.Disgo().Cache().TextChannel(e.ChannelID) +} + +// GuildMessageCreateEvent is called upon receiving a api.Message in a api.DMChannel +type GuildMessageCreateEvent struct { GenericGuildMessageEvent - Message api.Message } -// GuildMessageUpdateEvent indicates that a api.Message was updated in a api.Guild +// GuildMessageUpdateEvent is called upon editing a api.Message in a api.DMChannel type GuildMessageUpdateEvent struct { GenericGuildMessageEvent - Message api.Message + OldMessage *api.Message } -// GuildMessageDeleteEvent indicates that a api.Message was deleted in a api.Guild +// GuildMessageDeleteEvent is called upon deleting a api.Message in a api.DMChannel type GuildMessageDeleteEvent struct { GenericGuildMessageEvent - Message *api.Message } diff --git a/api/events/guild_message_reaction_events.go b/api/events/guild_message_reaction_events.go new file mode 100644 index 00000000..77f0f7ed --- /dev/null +++ b/api/events/guild_message_reaction_events.go @@ -0,0 +1,30 @@ +package events + +import "github.com/DisgoOrg/disgo/api" +// GenericGuildMessageReactionEvent is called upon receiving DMMessageReactionAddEvent or DMMessageReactionRemoveEvent +type GenericGuildMessageReactionEvent struct { + GenericGuildMessageEvent + UserID api.Snowflake + Member *api.Member + MessageReaction api.MessageReaction +} +// GuildMessageReactionAddEvent indicates that a api.Member added a api.MessageReaction to a api.Message in a api.TextChannel(requires the api.IntentsGuildMessageReactions) +type GuildMessageReactionAddEvent struct { + GenericGuildMessageReactionEvent +} + +// GuildMessageReactionRemoveEvent indicates that a api.Member removed a api.MessageReaction from a api.Message in a api.TextChannel(requires the api.IntentsGuildMessageReactions) +type GuildMessageReactionRemoveEvent struct { + GenericGuildMessageReactionEvent +} + +// GuildMessageReactionRemoveEmoteEvent indicates someone removed all api.MessageReaction of a specific api.Emote from a api.Message in a api.TextChannel(requires the api.IntentsGuildMessageReactions) +type GuildMessageReactionRemoveEmoteEvent struct { + GenericGuildMessageEvent + MessageReaction api.MessageReaction +} + +// GuildMessageReactionRemoveAllEvent indicates someone removed all api.MessageReaction(s) from a api.Message in a api.TextChannel(requires the api.IntentsGuildMessageReactions) +type GuildMessageReactionRemoveAllEvent struct { + GenericGuildMessageEvent +} diff --git a/api/events/guild_role_events.go b/api/events/guild_role_events.go deleted file mode 100644 index db36893e..00000000 --- a/api/events/guild_role_events.go +++ /dev/null @@ -1,26 +0,0 @@ -package events - -import "github.com/DisgoOrg/disgo/api" - -// GenericGuildRoleEvent generic api.Role event -type GenericGuildRoleEvent struct { - GenericGuildEvent - Role *api.Role - RoleID api.Snowflake -} - -// GuildRoleCreateEvent indicates that a api.Role got created -type GuildRoleCreateEvent struct { - GenericGuildEvent -} - -// GuildRoleDeleteEvent indicates that a api.Role got deleted -type GuildRoleDeleteEvent struct { - GenericGuildEvent -} - -// GuildRoleUpdateEvent indicates that a api.Role got updated -type GuildRoleUpdateEvent struct { - GenericGuildEvent - OldRole *api.Role -} diff --git a/api/events/guild_voice_events.go b/api/events/guild_voice_events.go new file mode 100644 index 00000000..60d14088 --- /dev/null +++ b/api/events/guild_voice_events.go @@ -0,0 +1,27 @@ +package events + +import ( + "github.com/DisgoOrg/disgo/api" +) + +// GenericGuildVoiceEvent is called upon receiving GuildVoiceJoinEvent, GuildVoiceUpdateEvent, GuildVoiceLeaveEvent +type GenericGuildVoiceEvent struct { + GenericGuildMemberEvent + VoiceState *api.VoiceState +} + +// GuildVoiceJoinEvent indicates that a api.Member joined a api.VoiceChannel(requires api.IntentsGuildVoiceStates) +type GuildVoiceJoinEvent struct { + GenericGuildVoiceEvent +} + +// GuildVoiceUpdateEvent indicates that a api.Member moved a api.VoiceChannel(requires api.IntentsGuildVoiceStates) +type GuildVoiceUpdateEvent struct { + GenericGuildVoiceEvent + OldVoiceState *api.VoiceState +} + +// GuildVoiceLeaveEvent indicates that a api.Member left a api.VoiceChannel(requires api.IntentsGuildVoiceStates) +type GuildVoiceLeaveEvent struct { + GenericGuildVoiceEvent +} diff --git a/api/events/heartbeat_event.go b/api/events/heartbeat_event.go new file mode 100644 index 00000000..7ab07a74 --- /dev/null +++ b/api/events/heartbeat_event.go @@ -0,0 +1,10 @@ +package events + +import "time" + +// HeartbeatEvent is called upon sending a heartbeat to the api.Gateway +type HeartbeatEvent struct { + GenericEvent + NewPing time.Duration + OldPing time.Duration +} diff --git a/api/events/http_request_events.go b/api/events/http_request_events.go new file mode 100644 index 00000000..29185042 --- /dev/null +++ b/api/events/http_request_events.go @@ -0,0 +1,12 @@ +package events + +import ( + "net/http" +) + +// HTTPRequestEvent indicates a new http.Request was made and can be used to collect data of StatusCodes as an example +type HTTPRequestEvent struct { + GenericEvent + Request *http.Request + Response *http.Response +} diff --git a/api/events/interaction_events.go b/api/events/interaction_events.go index 87fdf2cd..fe691428 100644 --- a/api/events/interaction_events.go +++ b/api/events/interaction_events.go @@ -8,8 +8,8 @@ import ( // GenericInteractionEvent generic api.Interaction event type GenericInteractionEvent struct { - api.Event - Interaction api.Interaction + GenericEvent + *api.Interaction } // Guild returns the api.Guild from the api.Cache @@ -17,7 +17,7 @@ func (e GenericInteractionEvent) Guild() *api.Guild { if e.Interaction.GuildID == nil { return nil } - return e.Disgo.Cache().Guild(*e.Interaction.GuildID) + return e.Disgo().Cache().Guild(*e.Interaction.GuildID) } // DMChannel returns the api.DMChannel from the api.Cache @@ -25,7 +25,7 @@ func (e GenericInteractionEvent) DMChannel() *api.DMChannel { if e.Interaction.ChannelID == nil { return nil } - return e.Disgo.Cache().DMChannel(*e.Interaction.ChannelID) + return e.Disgo().Cache().DMChannel(*e.Interaction.ChannelID) } // MessageChannel returns the api.MessageChannel from the api.Cache @@ -33,7 +33,7 @@ func (e GenericInteractionEvent) MessageChannel() *api.MessageChannel { if e.Interaction.ChannelID == nil { return nil } - return e.Disgo.Cache().MessageChannel(*e.Interaction.ChannelID) + return e.Disgo().Cache().MessageChannel(*e.Interaction.ChannelID) } // TextChannel returns the api.TextChannel from the api.Cache @@ -41,7 +41,7 @@ func (e GenericInteractionEvent) TextChannel() *api.TextChannel { if e.Interaction.ChannelID == nil { return nil } - return e.Disgo.Cache().TextChannel(*e.Interaction.ChannelID) + return e.Disgo().Cache().TextChannel(*e.Interaction.ChannelID) } // GuildChannel returns the api.GuildChannel from the api.Cache @@ -49,143 +49,46 @@ func (e GenericInteractionEvent) GuildChannel() *api.GuildChannel { if e.Interaction.ChannelID == nil { return nil } - return e.Disgo.Cache().GuildChannel(*e.Interaction.ChannelID) + return e.Disgo().Cache().GuildChannel(*e.Interaction.ChannelID) } -// SlashCommandEvent indicates a slash api.SlashCommand was ran in a api.Guild +// SlashCommandEvent indicates that a slash api.Command was ran in a api.Guild type SlashCommandEvent struct { GenericInteractionEvent - ResponseChannel chan interface{} - FromWebhook bool - CommandID api.Snowflake - Name string - SubCommandName *string - SubCommandGroup *string - Options []*Option - Replied bool + ResponseChannel chan interface{} + FromWebhook bool + CommandID api.Snowflake + CommandName string + SubCommandName *string + SubCommandGroupName *string + Options []*api.Option + Replied bool } -// Option holds info about an Option.Value -type Option struct { - Resolved *api.Resolved - Name string - Type api.SlashCommandOptionType - Value interface{} -} - -// String returns the Option.Value as string -func (o Option) String() string { - return o.Value.(string) -} - -// Bool returns the Option.Value as bool -func (o Option) Bool() bool { - return o.Value.(bool) -} - -// Snowflake returns the Option.Value as api.Snowflake -func (o Option) Snowflake() api.Snowflake { - return api.Snowflake(o.String()) -} - -// User returns the Option.Value as api.User -func (o Option) User() *api.User { - return o.Resolved.Users[o.Snowflake()] -} - -// Member returns the Option.Value as api.Member -func (o Option) Member() *api.Member { - return o.Resolved.Members[o.Snowflake()] -} - -// Role returns the Option.Value as api.Role -func (o Option) Role() *api.Role { - return o.Resolved.Roles[o.Snowflake()] -} - -// Channel returns the Option.Value as api.Channel -func (o Option) Channel() *api.Channel { - return o.Resolved.Channels[o.Snowflake()] -} - -// MessageChannel returns the Option.Value as api.MessageChannel -func (o Option) MessageChannel() *api.MessageChannel { - channel := o.Channel() - if channel == nil || (channel.Type != api.ChannelTypeText && channel.Type != api.ChannelTypeNews) { - return nil - } - return &api.MessageChannel{Channel: *channel} -} - -// GuildChannel returns the Option.Value as api.GuildChannel -func (o Option) GuildChannel() *api.GuildChannel { - channel := o.Channel() - if channel == nil || (channel.Type != api.ChannelTypeText && channel.Type != api.ChannelTypeNews && channel.Type != api.ChannelTypeCategory && channel.Type != api.ChannelTypeStore && channel.Type != api.ChannelTypeVoice) { - return nil - } - return &api.GuildChannel{Channel: *channel} -} - -// VoiceChannel returns the Option.Value as api.VoiceChannel -func (o Option) VoiceChannel() *api.VoiceChannel { - channel := o.Channel() - if channel == nil || channel.Type != api.ChannelTypeVoice { - return nil - } - return &api.VoiceChannel{GuildChannel: api.GuildChannel{Channel: *channel}} -} - -// TextChannel returns the Option.Value as api.TextChannel -func (o Option) TextChannel() *api.TextChannel { - channel := o.Channel() - if channel == nil || (channel.Type != api.ChannelTypeText && channel.Type != api.ChannelTypeNews) { - return nil - } - return &api.TextChannel{GuildChannel: api.GuildChannel{Channel: *channel}, MessageChannel: api.MessageChannel{Channel: *channel}} -} - -// Category returns the Option.Value as api.Category -func (o Option) Category() *api.Category { - channel := o.Channel() - if channel == nil || channel.Type != api.ChannelTypeCategory { - return nil - } - return &api.Category{GuildChannel: api.GuildChannel{Channel: *channel}} -} - -// StoreChannel returns the Option.Value as api.StoreChannel -func (o Option) StoreChannel() *api.StoreChannel { - channel := o.Channel() - if channel == nil || channel.Type != api.ChannelTypeStore { - return nil - } - return &api.StoreChannel{GuildChannel: api.GuildChannel{Channel: *channel}} -} - -// CommandPath returns the api.SlashCommand path +// CommandPath returns the api.Command path func (e SlashCommandEvent) CommandPath() string { - path := e.Name + path := e.CommandName if e.SubCommandName != nil { path += "/" + *e.SubCommandName } - if e.SubCommandGroup != nil { - path += "/" + *e.SubCommandGroup + if e.SubCommandGroupName != nil { + path += "/" + *e.SubCommandGroupName } return path } -// OptionByName returns an Option by name -func (e SlashCommandEvent) OptionByName(name string) *Option { - options := e.OptionsByName(name) +// Option returns an Option by name +func (e SlashCommandEvent) Option(name string) *api.Option { + options := e.OptionN(name) if len(options) == 0 { return nil } return options[0] } -// OptionsByName returns Option(s) by name -func (e SlashCommandEvent) OptionsByName(name string) []*Option { - options := make([]*Option, 0) +// OptionN returns Option(s) by name +func (e SlashCommandEvent) OptionN(name string) []*api.Option { + options := make([]*api.Option, 0) for _, option := range e.Options { if option.Name == name { options = append(options, option) @@ -194,9 +97,9 @@ func (e SlashCommandEvent) OptionsByName(name string) []*Option { return options } -// OptionsByType returns Option(s) by api.SlashCommandOptionType -func (e SlashCommandEvent) OptionsByType(optionType api.SlashCommandOptionType) []*Option { - options := make([]*Option, 0) +// OptionsT returns Option(s) by api.CommandOptionType +func (e SlashCommandEvent) OptionsT(optionType api.CommandOptionType) []*api.Option { + options := make([]*api.Option, 0) for _, option := range e.Options { if option.Type == optionType { options = append(options, option) @@ -205,6 +108,11 @@ func (e SlashCommandEvent) OptionsByType(optionType api.SlashCommandOptionType) return options } +// Acknowledge replies to the api.Interaction with api.InteractionResponseTypeDeferredChannelMessageWithSource +func (e *SlashCommandEvent) Acknowledge() error { + return e.Reply(api.NewInteractionResponseBuilder().SetType(api.InteractionResponseTypeDeferredChannelMessageWithSource).Build()) +} + // Reply replies to the api.Interaction with the provided api.InteractionResponse func (e *SlashCommandEvent) Reply(response api.InteractionResponse) error { if e.Replied { @@ -217,5 +125,30 @@ func (e *SlashCommandEvent) Reply(response api.InteractionResponse) error { return nil } - return e.Disgo.RestClient().SendInteractionResponse(e.Interaction.ID, e.Interaction.Token, response) + return e.Disgo().RestClient().SendInteractionResponse(e.Interaction.ID, e.Interaction.Token, response) +} + +// EditOriginal edits the original api.InteractionResponse +func (e *SlashCommandEvent) EditOriginal(followupMessage api.FollowupMessage) (*api.Message, error) { + return e.Disgo().RestClient().EditInteractionResponse(e.Disgo().ApplicationID(), e.Interaction.Token, followupMessage) +} + +// DeleteOriginal deletes the original api.InteractionResponse +func (e *SlashCommandEvent) DeleteOriginal() error { + return e.Disgo().RestClient().DeleteInteractionResponse(e.Disgo().ApplicationID(), e.Interaction.Token) +} + +// SendFollowup used to send a api.FollowupMessage to an api.Interaction +func (e *SlashCommandEvent) SendFollowup(followupMessage api.FollowupMessage) (*api.Message, error) { + return e.Disgo().RestClient().SendFollowupMessage(e.Disgo().ApplicationID(), e.Interaction.Token, followupMessage) +} + +// EditFollowup used to edit a api.FollowupMessage from an api.Interaction +func (e *SlashCommandEvent) EditFollowup(messageID api.Snowflake, followupMessage api.FollowupMessage) (*api.Message, error) { + return e.Disgo().RestClient().EditFollowupMessage(e.Disgo().ApplicationID(), e.Interaction.Token, messageID, followupMessage) +} + +// DeleteFollowup used to delete a api.FollowupMessage from an api.Interaction +func (e *SlashCommandEvent) DeleteFollowup(messageID api.Snowflake) error { + return e.Disgo().RestClient().DeleteFollowupMessage(e.Disgo().ApplicationID(), e.Interaction.Token, messageID) } diff --git a/api/events/listener_adapter.go b/api/events/listener_adapter.go index 71becb9c..7c0f6978 100644 --- a/api/events/listener_adapter.go +++ b/api/events/listener_adapter.go @@ -1,108 +1,605 @@ package events import ( + "reflect" + "github.com/DisgoOrg/disgo/api" ) // ListenerAdapter lets you override the handles for receiving events type ListenerAdapter struct { - OnGenericEvent func(*api.GenericEvent) - - // Guild Events - OnGenericGuildEvent func(*GenericGuildEvent) - OnGuildJoin func(*GuildJoinEvent) - OnGuildUpdate func(*GuildUpdateEvent) - OnGuildLeave func(*GuildLeaveEvent) - OnGuildAvailable func(*GuildAvailableEvent) - OnGuildUnavailable func(*GuildUnavailableEvent) - - // Guild Role Events - OnGenericGuildRole func(*GenericGuildRoleEvent) - OnGuildRoleCreate func(*GuildRoleCreateEvent) - OnGuildRoleUpdate func(*GuildRoleUpdateEvent) - OnGuildRoleDelete func(*GuildRoleDeleteEvent) - - // Message Events - OnMessageReceived func(*MessageReceivedEvent) - OnGuildMessageReceived func(*GuildMessageReceivedEvent) + // Other events + OnGenericEvent func(event *GenericEvent) + OnHeartbeat func(event *HeartbeatEvent) + OnHTTPRequest func(event *HTTPRequestEvent) + OnRawGateway func(event *RawGatewayEvent) + OnReadyEvent func(event *ReadyEvent) - // Interaction Events - OnGenericInteraction func(*GenericInteractionEvent) - OnSlashCommand func(*SlashCommandEvent) + // api.Command Events + OnGenericApplicationCommandEvent func(event *GenericApplicationCommandEvent) + OnApplicationCommandCreate func(event *ApplicationCommandCreateEvent) + OnApplicationCommandUpdate func(event *ApplicationCommandUpdateEvent) + OnApplicationCommandDelete func(event *ApplicationCommandDeleteEvent) + + // api.Channel Events + OnGenericChannelEvent func(event *GenericChannelEvent) + + // api.Category Events + OnGenericCategoryEvent func(event *GenericCategoryEvent) + OnCategoryCreate func(event *CategoryCreateEvent) + OnCategoryUpdate func(event *CategoryUpdateEvent) + OnCategoryDelete func(event *CategoryDeleteEvent) + + // api.DMChannel Events + OnGenericDMChannelEvent func(event *GenericDMChannelEvent) + OnDMChannelCreate func(event *DMChannelCreateEvent) + OnDMChannelUpdate func(event *DMChannelUpdateEvent) + OnDMChannelDelete func(event *DMChannelDeleteEvent) + + // api.DMChannel Reaction Events + OnGenericDMMessageReactionEventEvent func(event *GenericDMMessageReactionEvent) + OnDMMessageReactionAdd func(event *DMMessageReactionAddEvent) + OnDMMessageReactionRemove func(event *DMMessageReactionRemoveEvent) + OnDMMessageReactionRemoveEmote func(event *DMMessageReactionRemoveEmoteEvent) + OnDMMessageReactionRemoveAll func(event *DMMessageReactionRemoveAllEvent) + + // api.StoreChannel Events + OnGenericStoreChannelEvent func(event *GenericStoreChannelEvent) + OnStoreChannelCreate func(event *StoreChannelCreateEvent) + OnStoreChannelUpdate func(event *StoreChannelUpdateEvent) + OnStoreChannelDelete func(event *StoreChannelDeleteEvent) + + // api.TextChannel Events + OnGenericTextChannelEvent func(event *GenericTextChannelEvent) + OnTextChannelCreate func(event *TextChannelCreateEvent) + OnTextChannelUpdate func(event *TextChannelUpdateEvent) + OnTextChannelDelete func(event *TextChannelDeleteEvent) + + // api.VoiceChannel Events + OnGenericVoiceChannelEvent func(event *GenericVoiceChannelEvent) + OnVoiceChannelCreate func(event *VoiceChannelCreateEvent) + OnVoiceChannelUpdate func(event *VoiceChannelUpdateEvent) + OnVoiceChannelDelete func(event *VoiceChannelDeleteEvent) + + // api.Emote Events + OnGenericEmoteEvent func(event *GenericEmoteEvent) + OnEmoteCreate func(event *EmoteCreateEvent) + OnEmoteUpdate func(event *EmoteUpdateEvent) + OnEmoteDelete func(event *EmoteDeleteEvent) + + // api.GatewayStatus Events + OnGenericGatewayStatusEvent func(event *GenericGatewayStatusEvent) + OnConnected func(event *ConnectedEvent) + OnReconnected func(event *ReconnectedEvent) + OnResumed func(event *ResumedEvent) + OnDisconnected func(event *DisconnectedEvent) + + // api.Guild Events + OnGenericGuildEvent func(event *GenericGuildEvent) + OnGuildJoin func(event *GuildJoinEvent) + OnGuildUpdate func(event *GuildUpdateEvent) + OnGuildLeave func(event *GuildLeaveEvent) + OnGuildAvailable func(event *GuildAvailableEvent) + OnGuildUnavailable func(event *GuildUnavailableEvent) + OnGuildReady func(event *GuildReadyEvent) + OnGuildBan func(event *GuildBanEvent) + OnGuildUnban func(event *GuildUnbanEvent) + + // api.Guild api.Invite Events + OnGenericGuildInviteEvent func(event *GenericGuildInviteEvent) + OnGuildInviteCreate func(event *GuildInviteCreateEvent) + OnGuildInviteDelete func(event *GuildInviteDeleteEvent) + + // api.Guild api.Member Events + OnGenericGuildMemberEvent func(event *GenericGuildMemberEvent) + OnGuildMemberJoin func(event *GuildMemberJoinEvent) + OnGuildMemberUpdate func(event *GuildMemberUpdateEvent) + OnGuildMemberLeave func(event *GuildMemberLeaveEvent) + + // api.Guild api.Message Events + OnGenericGuildMessageEvent func(event *GenericGuildMessageEvent) + OnGuildMessageCreate func(event *GuildMessageCreateEvent) + OnGuildMessageUpdate func(event *GuildMessageUpdateEvent) + OnGuildMessageDelete func(event *GuildMessageDeleteEvent) + + // api.Guild api.Message Reaction Events + OnGenericGuildMessageReactionEvent func(event *GenericGuildMessageReactionEvent) + OnGuildMessageReactionAdd func(event *GuildMessageReactionAddEvent) + OnGuildMessageReactionRemove func(event *GuildMessageReactionRemoveEvent) + OnGuildMessageReactionRemoveEmote func(event *GuildMessageReactionRemoveEmoteEvent) + OnGuildMessageReactionRemoveAll func(event *GuildMessageReactionRemoveAllEvent) + + // api.Guild Voice Events + OnGenericGuildVoiceEvent func(event *GenericGuildVoiceEvent) + OnGuildVoiceUpdate func(event *GuildVoiceUpdateEvent) + OnGuildVoiceJoin func(event *GuildVoiceJoinEvent) + OnGuildVoiceLeave func(event *GuildVoiceLeaveEvent) + + // api.Guild api.Role Events + OnGenericRoleEvent func(event *GenericRoleEvent) + OnRoleCreate func(event *RoleCreateEvent) + OnRoleUpdate func(event *RoleUpdateEvent) + OnRoleDelete func(event *RoleDeleteEvent) + + // api.Interaction Events + OnGenericInteractionEvent func(event *GenericInteractionEvent) + OnSlashCommand func(event *SlashCommandEvent) + + // api.Message Events + OnGenericMessageEvent func(event *GenericMessageEvent) + OnMessageCreate func(event *MessageCreateEvent) + OnMessageUpdate func(event *MessageUpdateEvent) + OnMessageDelete func(event *MessageDeleteEvent) + + // api.Message Reaction Events + OnGenericReactionEvent func(event *GenericReactionEvents) + OnMessageReactionAdd func(event *MessageReactionAddEvent) + OnMessageReactionRemove func(event *MessageReactionRemoveEvent) + OnMessageReactionRemoveEmote func(event *MessageReactionRemoveEmoteEvent) + OnMessageReactionRemoveAll func(event *MessageReactionRemoveAllEvent) + + // Self Events + OnSelfUpdate func(event *SelfUpdateEvent) + + // api.User Events + OnGenericUserEvent func(event *GenericUserEvent) + OnUserUpdate func(event *UserUpdateEvent) + OnUserTyping func(event *UserTypingEvent) + OnGuildUserTyping func(event *GuildMemberTypingEvent) + OnDMUserTyping func(event *DMUserTypingEvent) + + // api.User api.Activity Events + OnGenericUserActivityEvent func(event *GenericUserActivityEvent) + OnUserActivityStart func(event *UserActivityStartEvent) + OnUserActivityUpdate func(event *UserActivityUpdateEvent) + OnUserActivityEnd func(event *UserActivityEndEvent) } // OnEvent is getting called everytime we receive an event func (l ListenerAdapter) OnEvent(event interface{}) { - if event, ok := event.(api.GenericEvent); ok { - if l.OnGenericEvent != nil { - l.OnGenericEvent(&event) - } - } switch e := event.(type) { - // Guild Events + case GenericEvent: + if listener := l.OnGenericEvent; listener != nil { + listener(&e) + } + case HeartbeatEvent: + if listener := l.OnHeartbeat; listener != nil { + listener(&e) + } + case HTTPRequestEvent: + if listener := l.OnHTTPRequest; listener != nil { + listener(&e) + } + case RawGatewayEvent: + if listener := l.OnRawGateway; listener != nil { + listener(&e) + } + case ReadyEvent: + if listener := l.OnReadyEvent; listener != nil { + listener(&e) + } + + // api.Command Events + case GenericApplicationCommandEvent: + if listener := l.OnGenericApplicationCommandEvent; listener != nil { + listener(&e) + } + case ApplicationCommandCreateEvent: + if listener := l.OnApplicationCommandCreate; listener != nil { + listener(&e) + } + case ApplicationCommandUpdateEvent: + if listener := l.OnApplicationCommandUpdate; listener != nil { + listener(&e) + } + case ApplicationCommandDeleteEvent: + if listener := l.OnApplicationCommandDelete; listener != nil { + listener(&e) + } + + // api.Channel Events + case GenericChannelEvent: + if listener := l.OnGenericChannelEvent; listener != nil { + listener(&e) + } + + // api.Category Events + case GenericCategoryEvent: + if listener := l.OnGenericCategoryEvent; listener != nil { + listener(&e) + } + case CategoryCreateEvent: + if listener := l.OnCategoryCreate; listener != nil { + listener(&e) + } + case CategoryUpdateEvent: + if listener := l.OnCategoryUpdate; listener != nil { + listener(&e) + } + case CategoryDeleteEvent: + if listener := l.OnCategoryDelete; listener != nil { + listener(&e) + } + + // api.DMChannel Events// api.Category Events + case GenericDMChannelEvent: + if listener := l.OnGenericDMChannelEvent; listener != nil { + listener(&e) + } + case DMChannelCreateEvent: + if listener := l.OnDMChannelCreate; listener != nil { + listener(&e) + } + case DMChannelUpdateEvent: + if listener := l.OnDMChannelUpdate; listener != nil { + listener(&e) + } + case DMChannelDeleteEvent: + if listener := l.OnDMChannelDelete; listener != nil { + listener(&e) + } + + // api.DMChannel Events// api.Category Events + case GenericDMMessageReactionEvent: + if listener := l.OnGenericDMMessageReactionEventEvent; listener != nil { + listener(&e) + } + case DMMessageReactionAddEvent: + if listener := l.OnDMMessageReactionAdd; listener != nil { + listener(&e) + } + case DMMessageReactionRemoveEvent: + if listener := l.OnDMMessageReactionRemove; listener != nil { + listener(&e) + } + case DMMessageReactionRemoveEmoteEvent: + if listener := l.OnDMMessageReactionRemoveEmote; listener != nil { + listener(&e) + } + case DMMessageReactionRemoveAllEvent: + if listener := l.OnDMMessageReactionRemoveAll; listener != nil { + listener(&e) + } + + // api.StoreChannel Events + case GenericStoreChannelEvent: + if listener := l.OnGenericStoreChannelEvent; listener != nil { + listener(&e) + } + case StoreChannelCreateEvent: + if listener := l.OnStoreChannelCreate; listener != nil { + listener(&e) + } + case StoreChannelUpdateEvent: + if listener := l.OnStoreChannelUpdate; listener != nil { + listener(&e) + } + case StoreChannelDeleteEvent: + if listener := l.OnStoreChannelDelete; listener != nil { + listener(&e) + } + + // api.TextChannel Events + case GenericTextChannelEvent: + if listener := l.OnGenericTextChannelEvent; listener != nil { + listener(&e) + } + case TextChannelCreateEvent: + if listener := l.OnTextChannelCreate; listener != nil { + listener(&e) + } + case TextChannelUpdateEvent: + if listener := l.OnTextChannelUpdate; listener != nil { + listener(&e) + } + case TextChannelDeleteEvent: + if listener := l.OnTextChannelDelete; listener != nil { + listener(&e) + } + + // api.VoiceChannel Events + case GenericVoiceChannelEvent: + if listener := l.OnGenericVoiceChannelEvent; listener != nil { + listener(&e) + } + case VoiceChannelCreateEvent: + if listener := l.OnVoiceChannelCreate; listener != nil { + listener(&e) + } + case VoiceChannelUpdateEvent: + if listener := l.OnVoiceChannelUpdate; listener != nil { + listener(&e) + } + case VoiceChannelDeleteEvent: + if listener := l.OnVoiceChannelDelete; listener != nil { + listener(&e) + } + + // api.emote Events + case GenericEmoteEvent: + if listener := l.OnGenericEmoteEvent; listener != nil { + listener(&e) + } + case EmoteCreateEvent: + if listener := l.OnEmoteCreate; listener != nil { + listener(&e) + } + case EmoteUpdateEvent: + if listener := l.OnEmoteUpdate; listener != nil { + listener(&e) + } + case EmoteDeleteEvent: + if listener := l.OnEmoteDelete; listener != nil { + listener(&e) + } + + // api.GatewayStatus Events + case GenericGatewayStatusEvent: + if listener := l.OnGenericGatewayStatusEvent; listener != nil { + listener(&e) + } + case ConnectedEvent: + if listener := l.OnConnected; listener != nil { + listener(&e) + } + case ReconnectedEvent: + if listener := l.OnReconnected; listener != nil { + listener(&e) + } + case ResumedEvent: + if listener := l.OnResumed; listener != nil { + listener(&e) + } + case DisconnectedEvent: + if listener := l.OnDisconnected; listener != nil { + listener(&e) + } + + // api.Guild Events case GenericGuildEvent: - if l.OnGenericGuildEvent != nil { - l.OnGenericGuildEvent(&e) + if listener := l.OnGenericGuildEvent; listener != nil { + listener(&e) } case GuildJoinEvent: - if l.OnGuildJoin != nil { - l.OnGuildJoin(&e) + if listener := l.OnGuildJoin; listener != nil { + listener(&e) } case GuildUpdateEvent: - if l.OnGuildUpdate != nil { - l.OnGuildUpdate(&e) + if listener := l.OnGuildUpdate; listener != nil { + listener(&e) } case GuildLeaveEvent: - if l.OnGuildLeave != nil { - l.OnGuildLeave(&e) + if listener := l.OnGuildLeave; listener != nil { + listener(&e) } case GuildAvailableEvent: - if l.OnGuildAvailable != nil { - l.OnGuildAvailable(&e) + if listener := l.OnGuildAvailable; listener != nil { + listener(&e) } case GuildUnavailableEvent: - if l.OnGuildUnavailable != nil { - l.OnGuildUnavailable(&e) + if listener := l.OnGuildUnavailable; listener != nil { + listener(&e) + } + case GuildReadyEvent: + if listener := l.OnGuildReady; listener != nil { + listener(&e) + } + case GuildBanEvent: + if listener := l.OnGuildBan; listener != nil { + listener(&e) + } + case GuildUnbanEvent: + if listener := l.OnGuildUnban; listener != nil { + listener(&e) + } + + // api.Guild api.Invite Events + case GenericGuildInviteEvent: + if listener := l.OnGenericGuildInviteEvent; listener != nil { + listener(&e) + } + case GuildInviteCreateEvent: + if listener := l.OnGuildInviteCreate; listener != nil { + listener(&e) + } + case GuildInviteDeleteEvent: + if listener := l.OnGuildInviteDelete; listener != nil { + listener(&e) + } + + // api.Member Events + case GenericGuildMemberEvent: + if listener := l.OnGenericGuildMemberEvent; listener != nil { + listener(&e) + } + case GuildMemberJoinEvent: + if listener := l.OnGuildMemberJoin; listener != nil { + listener(&e) + } + case GuildMemberUpdateEvent: + if listener := l.OnGuildMemberUpdate; listener != nil { + listener(&e) + } + case GuildMemberLeaveEvent: + if listener := l.OnGuildMemberLeave; listener != nil { + listener(&e) + } + + // api.Guild api.Message Events + case GenericGuildMessageEvent: + if listener := l.OnGenericGuildMessageEvent; listener != nil { + listener(&e) + } + case GuildMessageCreateEvent: + if listener := l.OnGuildMessageCreate; listener != nil { + listener(&e) + } + case GuildMessageUpdateEvent: + if listener := l.OnGuildMessageUpdate; listener != nil { + listener(&e) + } + case GuildMessageDeleteEvent: + if listener := l.OnGuildMessageDelete; listener != nil { + listener(&e) + } + + // api.Guild api.Message Reaction Events + case GenericGuildMessageReactionEvent: + if listener := l.OnGenericGuildMessageReactionEvent; listener != nil { + listener(&e) + } + case GuildMessageReactionAddEvent: + if listener := l.OnGuildMessageReactionAdd; listener != nil { + listener(&e) + } + case GuildMessageReactionRemoveEvent: + if listener := l.OnGuildMessageReactionRemove; listener != nil { + listener(&e) + } + case GuildMessageReactionRemoveEmoteEvent: + if listener := l.OnGuildMessageReactionRemoveEmote; listener != nil { + listener(&e) + } + case GuildMessageReactionRemoveAllEvent: + if listener := l.OnGuildMessageReactionRemoveAll; listener != nil { + listener(&e) } - // Guild Role Events - case GenericGuildRoleEvent: - if l.OnGenericGuildRole != nil { - l.OnGenericGuildRole(&e) + // api.Guild Voice Events + case GenericGuildVoiceEvent: + if listener := l.OnGenericGuildVoiceEvent; listener != nil { + listener(&e) } - case GuildRoleCreateEvent: - if l.OnGuildRoleCreate != nil { - l.OnGuildRoleCreate(&e) + case GuildVoiceUpdateEvent: + if listener := l.OnGuildVoiceUpdate; listener != nil { + listener(&e) } - case GuildRoleUpdateEvent: - if l.OnGuildRoleUpdate != nil { - l.OnGuildRoleUpdate(&e) + case GuildVoiceJoinEvent: + if listener := l.OnGuildVoiceJoin; listener != nil { + listener(&e) } - case GuildRoleDeleteEvent: - if l.OnGuildRoleDelete != nil { - l.OnGuildRoleDelete(&e) + case GuildVoiceLeaveEvent: + if listener := l.OnGuildVoiceLeave; listener != nil { + listener(&e) } - // Message Events - case MessageReceivedEvent: - if l.OnMessageReceived != nil { - l.OnMessageReceived(&e) + // api.Guild api.Role Events + case GenericRoleEvent: + if listener := l.OnGenericRoleEvent; listener != nil { + listener(&e) } - case GuildMessageReceivedEvent: - if l.OnGuildMessageReceived != nil { - l.OnGuildMessageReceived(&e) + case RoleCreateEvent: + if listener := l.OnRoleCreate; listener != nil { + listener(&e) + } + case RoleUpdateEvent: + if listener := l.OnRoleUpdate; listener != nil { + listener(&e) + } + case RoleDeleteEvent: + if listener := l.OnRoleDelete; listener != nil { + listener(&e) } // Interaction Events case GenericInteractionEvent: - if l.OnGenericInteraction != nil { - l.OnGenericInteraction(&e) + if listener := l.OnGenericInteractionEvent; listener != nil { + listener(&e) } case SlashCommandEvent: - if l.OnSlashCommand != nil { - l.OnSlashCommand(&e) + if listener := l.OnSlashCommand; listener != nil { + listener(&e) + } + + // api.Message Events + case GenericMessageEvent: + if listener := l.OnGenericMessageEvent; listener != nil { + listener(&e) + } + case MessageCreateEvent: + if listener := l.OnMessageCreate; listener != nil { + listener(&e) + } + case MessageUpdateEvent: + if listener := l.OnMessageUpdate; listener != nil { + listener(&e) + } + case MessageDeleteEvent: + if listener := l.OnMessageDelete; listener != nil { + listener(&e) + } + + // api.Message Reaction Events + case GenericReactionEvents: + if listener := l.OnGenericReactionEvent; listener != nil { + listener(&e) + } + case MessageReactionAddEvent: + if listener := l.OnMessageReactionAdd; listener != nil { + listener(&e) + } + case MessageReactionRemoveEvent: + if listener := l.OnMessageReactionRemove; listener != nil { + listener(&e) + } + case MessageReactionRemoveEmoteEvent: + if listener := l.OnMessageReactionRemoveEmote; listener != nil { + listener(&e) } + case MessageReactionRemoveAllEvent: + if listener := l.OnMessageReactionRemoveAll; listener != nil { + listener(&e) + } + + // Self Events + case SelfUpdateEvent: + if listener := l.OnSelfUpdate; listener != nil { + listener(&e) + } + + // api.User Events + case GenericUserEvent: + if listener := l.OnGenericUserEvent; listener != nil { + listener(&e) + } + case UserUpdateEvent: + if listener := l.OnUserUpdate; listener != nil { + listener(&e) + } + case UserTypingEvent: + if listener := l.OnUserTyping; listener != nil { + listener(&e) + } + case GuildMemberTypingEvent: + if listener := l.OnGuildUserTyping; listener != nil { + listener(&e) + } + case DMUserTypingEvent: + if listener := l.OnDMUserTyping; listener != nil { + listener(&e) + } + + // api.User api.Activity Events + case GenericUserActivityEvent: + if listener := l.OnGenericUserActivityEvent; listener != nil { + listener(&e) + } + case UserActivityStartEvent: + if listener := l.OnUserActivityStart; listener != nil { + listener(&e) + } + case UserActivityUpdateEvent: + if listener := l.OnUserActivityUpdate; listener != nil { + listener(&e) + } + case UserActivityEndEvent: + if listener := l.OnUserActivityEnd; listener != nil { + listener(&e) + } + default: - //log.Errorf("unexpected event received: %#e", event) + if e, ok := e.(api.Event); ok { + e.Disgo().Logger().Errorf("unexpected event received: \"%s\", event: \"%#e\"", reflect.TypeOf(event).Name(), event) + } } } diff --git a/api/events/message_event.go b/api/events/message_event.go deleted file mode 100644 index 39d50f4b..00000000 --- a/api/events/message_event.go +++ /dev/null @@ -1,36 +0,0 @@ -package events - -import "github.com/DisgoOrg/disgo/api" - -// GenericMessageEvent generic api.Message event -type GenericMessageEvent struct { - api.Event - MessageID api.Snowflake - MessageChannelID api.Snowflake -} - -// MessageChannel returns the api.MessageChannel where this api.message got received -func (e *GenericMessageEvent) MessageChannel() *api.MessageChannel { - return e. - Disgo. - Cache(). - MessageChannel(e.MessageChannelID) -} - -// MessageDeleteEvent indicates a api.Message got deleted -type MessageDeleteEvent struct { - GenericMessageEvent - Message api.Message -} - -// MessageReceivedEvent indicates a api.Message got received -type MessageReceivedEvent struct { - GenericMessageEvent - Message api.Message -} - -// MessageUpdateEvent indicates a api.Message got update -type MessageUpdateEvent struct { - GenericMessageEvent - Message api.Message -} diff --git a/api/events/message_events.go b/api/events/message_events.go new file mode 100644 index 00000000..e1e22c76 --- /dev/null +++ b/api/events/message_events.go @@ -0,0 +1,34 @@ +package events + +import ( + "github.com/DisgoOrg/disgo/api" +) + +// GenericMessageEvent generic api.Message event +type GenericMessageEvent struct { + GenericEvent + MessageID api.Snowflake + Message *api.Message + ChannelID api.Snowflake +} + +// MessageChannel returns the api.MessageChannel where the GenericMessageEvent happened +func (e *GenericMessageEvent) MessageChannel() *api.MessageChannel { + return e.Disgo().Cache().MessageChannel(e.ChannelID) +} + +// MessageDeleteEvent indicates that a api.Message got deleted +type MessageDeleteEvent struct { + GenericMessageEvent +} + +// MessageCreateEvent indicates that a api.Message got received +type MessageCreateEvent struct { + GenericMessageEvent +} + +// MessageUpdateEvent indicates that a api.Message got update +type MessageUpdateEvent struct { + GenericMessageEvent + OldMessage *api.Message +} diff --git a/api/events/message_reaction_events.go b/api/events/message_reaction_events.go new file mode 100644 index 00000000..fc4cf972 --- /dev/null +++ b/api/events/message_reaction_events.go @@ -0,0 +1,32 @@ +package events + +import "github.com/DisgoOrg/disgo/api" + +// GenericReactionEvents is called upon receiving MessageReactionAddEvent or MessageReactionRemoveEvent +type GenericReactionEvents struct { + GenericMessageEvent + UserID api.Snowflake + User *api.User + MessageReaction api.MessageReaction +} + +// MessageReactionAddEvent indicates that a api.User added a api.MessageReaction to a api.Message in a api.Channel(this+++ requires the api.IntentsGuildMessageReactions and/or api.IntentsDirectMessageReactions) +type MessageReactionAddEvent struct { + GenericReactionEvents +} + +// MessageReactionRemoveEvent indicates that a api.User removed a api.MessageReaction from a api.Message in a api.Channel(requires the api.IntentsGuildMessageReactions and/or api.IntentsDirectMessageReactions) +type MessageReactionRemoveEvent struct { + GenericReactionEvents +} + +// MessageReactionRemoveEmoteEvent indicates someone removed all api.MessageReaction of a specific api.Emote from a api.Message in a api.Channel(requires the api.IntentsGuildMessageReactions and/or api.IntentsDirectMessageReactions) +type MessageReactionRemoveEmoteEvent struct { + GenericMessageEvent + MessageReaction api.MessageReaction +} + +// MessageReactionRemoveAllEvent indicates someone removed all api.MessageReaction(s) from a api.Message in a api.Channel(requires the api.IntentsGuildMessageReactions and/or api.IntentsDirectMessageReactionss) +type MessageReactionRemoveAllEvent struct { + GenericMessageEvent +} diff --git a/api/events/raw_gateway_event.go b/api/events/raw_gateway_event.go new file mode 100644 index 00000000..bb632eea --- /dev/null +++ b/api/events/raw_gateway_event.go @@ -0,0 +1,15 @@ +package events + +import ( + "encoding/json" + + "github.com/DisgoOrg/disgo/api" +) + +// RawGatewayEvent is called for any api.GatewayEventType we receive if enabled in the api.DisgoBuilder/api.Options +type RawGatewayEvent struct { + GenericEvent + Type api.GatewayEventType + RawPayload json.RawMessage + Payload map[string]interface{} +} diff --git a/api/events/ready_events.go b/api/events/ready_event.go similarity index 80% rename from api/events/ready_events.go rename to api/events/ready_event.go index 5b04d979..77bcb301 100644 --- a/api/events/ready_events.go +++ b/api/events/ready_event.go @@ -4,6 +4,6 @@ import "github.com/DisgoOrg/disgo/api" // ReadyEvent indicates we received the ReadyEvent from the api.Gateway type ReadyEvent struct { - api.Event - api.ReadyEventData + GenericEvent + api.ReadyGatewayEvent } diff --git a/api/events/role_events.go b/api/events/role_events.go new file mode 100644 index 00000000..f245f3ef --- /dev/null +++ b/api/events/role_events.go @@ -0,0 +1,28 @@ +package events + +import ( + "github.com/DisgoOrg/disgo/api" +) + +// GenericRoleEvent generic api.Role event +type GenericRoleEvent struct { + GenericGuildEvent + RoleID api.Snowflake + Role *api.Role +} + +// RoleCreateEvent indicates that a api.Role got created +type RoleCreateEvent struct { + GenericGuildEvent +} + +// RoleUpdateEvent indicates that a api.Role got updated +type RoleUpdateEvent struct { + GenericGuildEvent + OldRole *api.Role +} + +// RoleDeleteEvent indicates that a api.Role got deleted +type RoleDeleteEvent struct { + GenericGuildEvent +} diff --git a/api/events/self_update_events.go b/api/events/self_update_events.go new file mode 100644 index 00000000..4a12a6c4 --- /dev/null +++ b/api/events/self_update_events.go @@ -0,0 +1,12 @@ +package events + +import ( + "github.com/DisgoOrg/disgo/api" +) + +// SelfUpdateEvent is called when something about this api.User updates +type SelfUpdateEvent struct { + GenericEvent + Self *api.User + OldSelf *api.User +} diff --git a/api/events/store_channel_events.go b/api/events/store_channel_events.go new file mode 100644 index 00000000..fffc1b5d --- /dev/null +++ b/api/events/store_channel_events.go @@ -0,0 +1,27 @@ +package events + +import ( + "github.com/DisgoOrg/disgo/api" +) + +// GenericStoreChannelEvent is called upon receiving StoreChannelCreateEvent, StoreChannelUpdateEvent or StoreChannelDeleteEvent +type GenericStoreChannelEvent struct { + GenericChannelEvent + StoreChannel *api.StoreChannel +} + +// StoreChannelCreateEvent indicates that a new api.StoreChannel got created in a api.Guild +type StoreChannelCreateEvent struct { + GenericStoreChannelEvent +} + +// StoreChannelUpdateEvent indicates that a api.StoreChannel got updated in a api.Guild +type StoreChannelUpdateEvent struct { + GenericStoreChannelEvent + OldStoreChannel *api.StoreChannel +} + +// StoreChannelDeleteEvent indicates that a api.StoreChannel got deleted in a api.Guild +type StoreChannelDeleteEvent struct { + GenericStoreChannelEvent +} diff --git a/api/events/text_channel_events.go b/api/events/text_channel_events.go new file mode 100644 index 00000000..7ddc08f0 --- /dev/null +++ b/api/events/text_channel_events.go @@ -0,0 +1,27 @@ +package events + +import ( + "github.com/DisgoOrg/disgo/api" +) + +// GenericTextChannelEvent is called upon receiving TextChannelCreateEvent, TextChannelUpdateEvent or TextChannelDeleteEvent +type GenericTextChannelEvent struct { + GenericChannelEvent + TextChannel *api.TextChannel +} + +// TextChannelCreateEvent indicates that a new api.TextChannel got created in a api.Guild +type TextChannelCreateEvent struct { + GenericTextChannelEvent +} + +// TextChannelUpdateEvent indicates that a api.TextChannel got updated in a api.Guild +type TextChannelUpdateEvent struct { + GenericTextChannelEvent + OldTextChannel *api.TextChannel +} + +// TextChannelDeleteEvent indicates that a api.TextChannel got deleted in a api.Guild +type TextChannelDeleteEvent struct { + GenericTextChannelEvent +} diff --git a/api/events/user_activity_events.go b/api/events/user_activity_events.go new file mode 100644 index 00000000..3aa5a942 --- /dev/null +++ b/api/events/user_activity_events.go @@ -0,0 +1,30 @@ +package events + +import ( + "github.com/DisgoOrg/disgo/api" +) + +// GenericUserActivityEvent is called upon receiving UserActivityStartEvent, UserActivityUpdateEvent or UserActivityEndEvent(requires the api.IntentsGuildPresences) +type GenericUserActivityEvent struct { + GenericGuildMemberEvent + Member *api.Member +} + +// UserActivityStartEvent indicates that a api.User started a new api.Activity(requires the api.IntentsGuildPresences) +type UserActivityStartEvent struct { + GenericUserActivityEvent + Activity *api.Activity +} + +// UserActivityUpdateEvent indicates that a api.User's api.Activity(s) updated(requires the api.IntentsGuildPresences) +type UserActivityUpdateEvent struct { + GenericUserActivityEvent + NewActivities *api.Activity + OldActivities *api.Activity +} + +// UserActivityEndEvent indicates that a api.User ended a api.Activity(requires the api.IntentsGuildPresences) +type UserActivityEndEvent struct { + GenericUserActivityEvent + Activity *api.Activity +} diff --git a/api/events/user_events.go b/api/events/user_events.go new file mode 100644 index 00000000..c961ccbf --- /dev/null +++ b/api/events/user_events.go @@ -0,0 +1,38 @@ +package events + +import ( + "github.com/DisgoOrg/disgo/api" +) + +// GenericUserEvent is called upon receiving UserUpdateEvent or UserTypingEvent +type GenericUserEvent struct { + GenericEvent + UserID api.Snowflake + User *api.User +} + +// UserUpdateEvent indicates that a api.User updated +type UserUpdateEvent struct { + GenericUserEvent + OldUser *api.User +} +// UserTypingEvent indicates that a api.User started typing in a api.DMChannel or api.TextChannel(requires the api.IntentsDirectMessageTyping and/or api.IntentsGuildMessageTyping) +type UserTypingEvent struct { + GenericUserEvent + ChannelID api.Snowflake +} + +// Channel returns the api.Channel the api.User started typing in +func (e UserTypingEvent) Channel() *api.Channel { + return e.Disgo().Cache().Channel(e.ChannelID) +} + +// DMChannel returns the api.DMChannel the api.User started typing in +func (e UserTypingEvent) DMChannel() *api.DMChannel { + return e.Disgo().Cache().DMChannel(e.ChannelID) +} + +// TextChannel returns the api.TextChannel the api.User started typing in +func (e UserTypingEvent) TextChannel() *api.TextChannel { + return e.Disgo().Cache().TextChannel(e.ChannelID) +} diff --git a/api/events/voice_channel_events.go b/api/events/voice_channel_events.go new file mode 100644 index 00000000..d2de4cb2 --- /dev/null +++ b/api/events/voice_channel_events.go @@ -0,0 +1,27 @@ +package events + +import ( + "github.com/DisgoOrg/disgo/api" +) + +// GenericVoiceChannelEvent is called upon receiving VoiceChannelCreateEvent, VoiceChannelUpdateEvent or VoiceChannelDeleteEvent +type GenericVoiceChannelEvent struct { + GenericChannelEvent + VoiceChannel *api.VoiceChannel +} + +// VoiceChannelCreateEvent indicates that a new api.VoiceChannel got created in a api.Guild +type VoiceChannelCreateEvent struct { + GenericVoiceChannelEvent +} + +// VoiceChannelUpdateEvent indicates that a api.VoiceChannel got updated in a api.Guild +type VoiceChannelUpdateEvent struct { + GenericVoiceChannelEvent + OldVoiceChannel *api.VoiceChannel +} + +// VoiceChannelDeleteEvent indicates that a api.VoiceChannel got deleted in a api.Guild +type VoiceChannelDeleteEvent struct { + GenericVoiceChannelEvent +} diff --git a/api/gateway.go b/api/gateway.go index e234732d..6b47acaa 100644 --- a/api/gateway.go +++ b/api/gateway.go @@ -1,13 +1,18 @@ package api -import "time" +import ( + "time" -// ConnectionStatus is the state that the client is currently in -type ConnectionStatus int + "github.com/gorilla/websocket" +) + +// GatewayStatus is the state that the client is currently in +type GatewayStatus int // Indicates how far along the client is to connecting const ( - Ready ConnectionStatus = iota + Ready GatewayStatus = iota + Unconnected Connecting Reconnecting WaitingForHello @@ -22,8 +27,9 @@ const ( type Gateway interface { Disgo() Disgo Open() error - Status() ConnectionStatus + Status() GatewayStatus Close() + Conn() *websocket.Conn Latency() time.Duration } @@ -46,48 +52,54 @@ const ( OpHeartbeatACK ) +// GatewayEventType wraps all GatewayEventType types +type GatewayEventType string + // Constants for the gateway events const ( - ChannelCreateGatewayEvent = "CHANNEL_CREATE" - ChannelDeleteGatewayEvent = "CHANNEL_DELETE" - ChannelPinsUpdateGatewayEvent = "CHANNEL_PINS_UPDATE" - ChannelUpdateGatewayEvent = "CHANNEL_UPDATE" - GuildBanAddGatewayEvent = "GUILD_BAN_ADD" - GuildBanRemoveGatewayEvent = "GUILD_BAN_REMOVE" - GuildCreateGatewayEvent = "GUILD_CREATE" - GuildDeleteGatewayEvent = "GUILD_DELETE" - GuildEmojisUpdateGatewayEvent = "GUILD_EMOJIS_UPDATE" - GuildIntegrationsUpdateGatewayEvent = "GUILD_INTEGRATIONS_UPDATE" - GuildMemberAddGatewayEvent = "GUILD_MEMBER_ADD" - GuildMemberRemoveGatewayEvent = "GUILD_MEMBER_REMOVE" - GuildMemberUpdateGatewayEvent = "GUILD_MEMBER_UPDATE" - GuildMembersChunkGatewayEvent = "GUILD_MEMBERS_CHUNK" - GuildRoleCreateGatewayEvent = "GUILD_ROLE_CREATE" - GuildRoleDeleteGatewayEvent = "GUILD_ROLE_DELETE" - GuildRoleUpdateGatewayEvent = "GUILD_ROLE_UPDATE" - GuildUpdateGatewayEvent = "GUILD_UPDATE" - InteractionCreateGatewayEvent = "INTERACTION_CREATE" - InteractionCreateWebhookEvent = "INTERACTION_WEBHOOK_CREATE" - MessageAckGatewayEvent = "MESSAGE_ACK" - MessageCreateGatewayEvent = "MESSAGE_CREATE" - MessageDeleteGatewayEvent = "MESSAGE_DELETE" - MessageDeleteBulkGatewayEvent = "MESSAGE_DELETE_BULK" - MessageReactionAddGatewayEvent = "MESSAGE_REACTION_ADD" - MessageReactionRemoveGatewayEvent = "MESSAGE_REACTION_REMOVE" - MessageReactionRemoveAllGatewayEvent = "MESSAGE_REACTION_REMOVE_ALL" - MessageUpdateGatewayEvent = "MESSAGE_UPDATE" - PresenceUpdateGatewayEvent = "PRESENCE_UPDATE" - PresencesReplaceGatewayEvent = "PRESENCES_REPLACE" - ReadyGatewayEvent = "READY" - ResumedGatewayEvent = "RESUMED" - TypingStartGatewayEvent = "TYPING_START" - UserGuildSettingsUpdateGatewayEvent = "USER_GUILD_SETTINGS_UPDATE" - UserNoteUpdateGatewayEvent = "USER_NOTE_UPDATE" - UserSettingsUpdateGatewayEvent = "USER_SETTINGS_UPDATE" - UserUpdateGatewayEvent = "USER_UPDATE" - VoiceServerUpdateGatewayEvent = "VOICE_SERVER_UPDATE" - VoiceStateUpdateGatewayEvent = "VOICE_STATE_UPDATE" - WebhooksUpdateGatewayEvent = "WEBHOOKS_UPDATE" + GatewayEventApplicationCommandCreate GatewayEventType = "APPLICATION_COMMAND_CREATE" + GatewayEventApplicationCommandUpdate GatewayEventType = "APPLICATION_COMMAND_UPDATE" + GatewayEventApplicationCommandDelete GatewayEventType = "APPLICATION_COMMAND_DELETE" + GatewayEventChannelCreate GatewayEventType = "CHANNEL_CREATE" + GatewayEventChannelDelete GatewayEventType = "CHANNEL_DELETE" + GatewayEventChannelPinsUpdate GatewayEventType = "CHANNEL_PINS_UPDATE" + GatewayEventChannelUpdate GatewayEventType = "CHANNEL_UPDATE" + GatewayEventGuildBanAdd GatewayEventType = "GUILD_BAN_ADD" + GatewayEventGuildBanRemove GatewayEventType = "GUILD_BAN_REMOVE" + GatewayEventGuildCreate GatewayEventType = "GUILD_CREATE" + GatewayEventGuildDelete GatewayEventType = "GUILD_DELETE" + GatewayEventGuildEmojisUpdate GatewayEventType = "GUILD_EMOJIS_UPDATE" + GatewayEventGuildIntegrationsUpdate GatewayEventType = "GUILD_INTEGRATIONS_UPDATE" + GatewayEventGuildMemberAdd GatewayEventType = "GUILD_MEMBER_ADD" + GatewayEventGuildMemberRemove GatewayEventType = "GUILD_MEMBER_REMOVE" + GatewayEventGuildMemberUpdate GatewayEventType = "GUILD_MEMBER_UPDATE" + GatewayEventGuildMembersChunk GatewayEventType = "GUILD_MEMBERS_CHUNK" + GatewayEventGuildRoleCreate GatewayEventType = "GUILD_ROLE_CREATE" + GatewayEventGuildRoleDelete GatewayEventType = "GUILD_ROLE_DELETE" + GatewayEventGuildRoleUpdate GatewayEventType = "GUILD_ROLE_UPDATE" + GatewayEventGuildUpdate GatewayEventType = "GUILD_UPDATE" + GatewayEventInteractionCreate GatewayEventType = "INTERACTION_CREATE" + WebhookEventInteractionCreate GatewayEventType = "INTERACTION_WEBHOOK_CREATE" + GatewayEventMessageAck GatewayEventType = "MESSAGE_ACK" + GatewayEventMessageCreate GatewayEventType = "MESSAGE_CREATE" + GatewayEventMessageDelete GatewayEventType = "MESSAGE_DELETE" + GatewayEventMessageDeleteBulk GatewayEventType = "MESSAGE_DELETE_BULK" + GatewayEventMessageReactionAdd GatewayEventType = "MESSAGE_REACTION_ADD" + GatewayEventMessageReactionRemove GatewayEventType = "MESSAGE_REACTION_REMOVE" + GatewayEventMessageReactionRemoveAll GatewayEventType = "MESSAGE_REACTION_REMOVE_ALL" + GatewayEventMessageUpdate GatewayEventType = "MESSAGE_UPDATE" + GatewayEventPresenceUpdate GatewayEventType = "PRESENCE_UPDATE" + GatewayEventPresencesReplace GatewayEventType = "PRESENCES_REPLACE" + GatewayEventReady GatewayEventType = "READY" + GatewayEventResumed GatewayEventType = "RESUMED" + GatewayEventTypingStart GatewayEventType = "TYPING_START" + GatewayEventUserGuildSettingsUpdate GatewayEventType = "USER_GUILD_SETTINGS_UPDATE" + GatewayEventUserNoteUpdate GatewayEventType = "USER_NOTE_UPDATE" + GatewayEventUserSettingsUpdate GatewayEventType = "USER_SETTINGS_UPDATE" + GatewayEventUserUpdate GatewayEventType = "USER_UPDATE" + GatewayEventVoiceServerUpdate GatewayEventType = "VOICE_SERVER_UPDATE" + GatewayEventVoiceStateUpdate GatewayEventType = "VOICE_STATE_UPDATE" + GatewayEventWebhooksUpdate GatewayEventType = "WEBHOOKS_UPDATE" ) // GatewayRs contains the response for GET /gateway diff --git a/api/gateway_commands.go b/api/gateway_commands.go index 4d337020..792c5486 100644 --- a/api/gateway_commands.go +++ b/api/gateway_commands.go @@ -1,37 +1,32 @@ package api -import ( - "encoding/json" - "time" -) +import "github.com/DisgoOrg/disgo/api/endpoints" -// GatewayCommand object is used when sending data to discord's websocket, it's recommended that you don't use these -type GatewayCommand struct { - Op GatewayOp `json:"op"` - S *int `json:"s,omitempty"` - T *string `json:"t,omitempty"` +// NewGatewayCommand returns a new GatewayCommand struct with the given payload +func NewGatewayCommand(op GatewayOp, d interface{}) GatewayCommand { + return GatewayCommand{ + GatewayPacket: GatewayPacket{ + Op: op, + S: nil, + T: nil, + }, + D: d, + } } -// RawGatewayCommand specifies the data for the GatewayCommand payload that is being sent -type RawGatewayCommand struct { - GatewayCommand - D json.RawMessage `json:"d"` +// GatewayCommand object is used when sending data to discord's websocket, it's recommended that you don't use these +type GatewayCommand struct { + GatewayPacket + D interface{} `json:"d"` } -// IdentifyCommand is used for Identifying to discord +// IdentifyCommand is the data used in IdentifyCommand type IdentifyCommand struct { - GatewayCommand - D IdentifyCommandData `json:"d"` -} - -// IdentifyCommandData is the data used in IdentifyCommand -type IdentifyCommandData struct { - Token string `json:"token"` - Properties IdentifyCommandDataProperties `json:"properties"` - Compress bool `json:"compress,omitempty"` - LargeThreshold int `json:"large_threshold,omitempty"` - GuildSubscriptions bool `json:"guild_subscriptions,omitempty"` // Deprecated, should not be specified when using intents - Intents Intents `json:"intents"` + Token endpoints.Token `json:"token"` + Properties IdentifyCommandDataProperties `json:"properties"` + Compress bool `json:"compress,omitempty"` + LargeThreshold int `json:"large_threshold,omitempty"` + Intents Intents `json:"intents"` // Todo: Add presence property here, need presence methods/struct // Todo: Add shard property here, need to discuss } @@ -47,29 +42,19 @@ type IdentifyCommandDataProperties struct { // ResumeCommand is used to resume a connection to discord in the case that you are disconnected. Is automatically // handled by the library and should rarely be used. type ResumeCommand struct { - GatewayCommand - D struct { - Token string `json:"token"` - SessionID string `json:"session_id"` - Seq int `json:"seq"` - } `json:"d"` + Token endpoints.Token `json:"token"` + SessionID string `json:"session_id"` + Seq int `json:"seq"` } // HeartbeatCommand is used to ensure the websocket connection remains open, and disconnect if not. type HeartbeatCommand struct { - GatewayCommand D *int `json:"d"` } // RequestGuildMembersCommand is used for fetching all of the members of a guild_events. It is recommended you have a strict // member caching policy when using this. type RequestGuildMembersCommand struct { - GatewayCommand - D RequestGuildMembersCommandData `json:"d"` -} - -// RequestGuildMembersCommandData is the RequestGuildMembersCommand.D payload -type RequestGuildMembersCommandData struct { GuildID Snowflake `json:"guild_id"` Query string `json:"query"` //If specified, user_ids must not be entered Limit int `json:"limit"` //Must be >=1 if query/user_ids is used, otherwise 0 @@ -80,34 +65,16 @@ type RequestGuildMembersCommandData struct { // UpdateVoiceStateCommand is used for updating the bots voice state in a guild_events type UpdateVoiceStateCommand struct { - GatewayCommand - D UpdateVoiceStateCommandData `json:"d"` -} - -// UpdateVoiceStateCommandData is the UpdateVoiceStateCommand.D payload -type UpdateVoiceStateCommandData struct { - GuildID Snowflake `json:"guild_id"` - ChannelID Snowflake `json:"channel_id"` - SelfMute bool `json:"self_mute"` - SelfDeaf bool `json:"self_deaf"` + GuildID Snowflake `json:"guild_id"` + ChannelID *Snowflake `json:"channel_id"` + SelfMute bool `json:"self_mute"` + SelfDeaf bool `json:"self_deaf"` } // UpdateStatusCommand is used for updating Disgo's presence type UpdateStatusCommand struct { - GatewayCommand - D UpdateStatusCommandData `json:"d"` -} - -// UpdateStatusCommandData is the UpdateStatusCommand.D payload -type UpdateStatusCommandData struct { Since *int `json:"since"` Activities []Activity `json:"activities"` Status bool `json:"status"` AFK bool `json:"afk"` } - -// HelloEvent is used when -type HelloEvent struct { - GatewayCommand - HeartbeatInterval time.Duration `json:"heartbeat_interval"` -} diff --git a/api/gateway_events.go b/api/gateway_events.go index 304a10df..85565b65 100644 --- a/api/gateway_events.go +++ b/api/gateway_events.go @@ -1,16 +1,33 @@ package api -// ReadyEvent is the event sent by discord when you successfully Identify -type ReadyEvent struct { - GatewayCommand - D ReadyEventData `json:"d"` +import ( + "encoding/json" + "time" +) + +// GatewayPacket raw GatewayEvent type +type GatewayPacket struct { + Op GatewayOp `json:"op"` + S *int `json:"s,omitempty"` + T *GatewayEventType `json:"t,omitempty"` +} + +// RawGatewayEvent specifies the data for the GatewayCommand payload that is being sent +type RawGatewayEvent struct { + GatewayPacket + D json.RawMessage `json:"d"` +} + +// ReadyGatewayEvent is the event sent by discord when you successfully Identify +type ReadyGatewayEvent struct { + Version int `json:"v"` + SelfUser User `json:"user"` + Guilds []*Guild `json:"guilds"` + SessionID string `json:"session_id"` + Shard *[2]int `json:"shard,omitempty"` } -// ReadyEventData is the ReadyEvent.D payload -type ReadyEventData struct { - User User `json:"user"` - PrivateChannels []DMChannel `json:"channel"` - Guilds []Guild `json:"guild_events"` - SessionID string `json:"session_id"` - Shard [2]int `json:"shard,omitempty"` +// HelloGatewayEventData is sent when we connect to the gateway +type HelloGatewayEventData struct { + HeartbeatInterval time.Duration `json:"heartbeat_interval"` } diff --git a/api/generic_event.go b/api/generic_event.go deleted file mode 100644 index e504aa1e..00000000 --- a/api/generic_event.go +++ /dev/null @@ -1,9 +0,0 @@ -package api - -// GenericEvent the basic interface each event implement -type GenericEvent interface{} - -// Event the base event structure -type Event struct { - Disgo Disgo -} diff --git a/api/guild.go b/api/guild.go index 9b35600e..3e607f9a 100644 --- a/api/guild.go +++ b/api/guild.go @@ -89,6 +89,46 @@ const ( GuildFeaturePreviewEnabled GuildFeature = "PREVIEW_ENABLED" ) +// GuildWelcomeScreen is the Welcome Screen of a Guild +type GuildWelcomeScreen struct { + Description *string `json:"description,omitempty"` + WelcomeChannels []*GuildWelcomeChannel `json:"welcome_channels"` +} + +// GuildWelcomeChannel is one of the channels in a GuildWelcomeScreen +type GuildWelcomeChannel struct { + ChannelID Snowflake `json:"channel_id"` + Description string `json:"description"` + EmojiID *Snowflake `json:"emoji_id,omitempty"` + EmojiName *string `json:"emoji_name,omitempty"` +} + +// GuildPreview is used for previewing public guilds before joining them +type GuildPreview struct { + Disgo Disgo + ID Snowflake `json:"id"` + Name string `json:"name"` + Icon *string `json:"icon"` + DiscoverySplash *string `json:"discovery_splash"` + Splash *string `json:"splash"` + Features []GuildFeature `json:"features"` + Description *string `json:"description"` + ApproximateMemberCount *int `json:"approximate_member_count"` + ApproximatePresenceCount *int `json:"approximate_presence_count"` + Emojis []*Emote `json:"emojis"` +} + +// FullGuild represents a Guild objects sent by discord with the GatewayEventGuildCreate +type FullGuild struct { + *Guild + Roles []*Role `json:"roles"` + Emotes []*Emote `json:"emojis"` + Members []*Member `json:"members"` + Channels []*Channel `json:"channels"` + VoiceStates []*VoiceState `json:"voice_states"` + //Presences []*Presence `json:"presences"` +} + // Guild represents a discord guild_events type Guild struct { Disgo Disgo @@ -106,13 +146,8 @@ type Guild struct { VerificationLevel VerificationLevel `json:"verification_level"` Large *bool `json:"large"` DefaultMessageNotifications MessageNotifications `json:"default_message_notifications"` - Roles []*Role `json:"roles"` - Emojis []*Emote `json:"emojis"` - Members []*Member `json:"members"` MaxPresences *int `json:"max_presences"` MaxMembers *int `json:"max_members"` - Channels []*GuildChannel `json:"channels"` - VoiceStates []*VoiceState `json:"voice_states"` Unavailable bool `json:"unavailable"` ExplicitContentFilter ExplicitContentFilterLevel `json:"explicit_content_filter"` Features []GuildFeature `json:"features"` @@ -133,21 +168,26 @@ type Guild struct { MaxVideoChannelUsers *int `json:"max_video_channel_users"` ApproximateMemberCount *int `json:"approximate_member_count"` ApproximatePresenceCount *int `json:"approximate_presence_count"` - //Presences []*Presence `json:"presences"` + WelcomeScreen *GuildWelcomeScreen `json:"welcome_screen"` +} + +// Disconnect sends a api.GatewayCommand to disconnect from this Guild +func (g *Guild) Disconnect() error { + return g.Disgo.AudioController().Disconnect(g.ID) } // CreateRole allows you to create a new Role -func (g Guild) CreateRole(role UpdateRole) (*Role, error) { +func (g *Guild) CreateRole(role UpdateRole) (*Role, error) { return g.Disgo.RestClient().CreateRole(g.ID, role) } // AddMember adds a member to the guild with the oauth2 access token -func (g Guild) AddMember(userID Snowflake, addGuildMemberData AddGuildMemberData) (*Member, error) { +func (g *Guild) AddMember(userID Snowflake, addGuildMemberData AddGuildMemberData) (*Member, error) { return g.Disgo.RestClient().AddMember(g.ID, userID, addGuildMemberData) } // IconURL returns the Icon of a guild_events -func (g Guild) IconURL() *string { +func (g *Guild) IconURL() *string { if g.Icon == nil { return nil } @@ -156,36 +196,60 @@ func (g Guild) IconURL() *string { if animated { format = endpoints.GIF } - u := endpoints.GuildIcon.Compile(format, g.ID.String(), *g.Icon).Route() + route, err := endpoints.GuildIcon.Compile(format, g.ID.String(), *g.Icon) + if err != nil { + return nil + } + u := route.Route() return &u } // GetCommand fetches a specific guild command -func (g Guild) GetCommand(commandID Snowflake) (*SlashCommand, error) { +func (g *Guild) GetCommand(commandID Snowflake) (*Command, error) { return g.Disgo.RestClient().GetGuildCommand(g.Disgo.ApplicationID(), g.ID, commandID) } // GetCommands fetches all guild commands -func (g Guild) GetCommands() ([]*SlashCommand, error) { +func (g *Guild) GetCommands() ([]*Command, error) { return g.Disgo.RestClient().GetGuildCommands(g.Disgo.ApplicationID(), g.ID) } // CreateCommand creates a new command for this guild -func (g Guild) CreateCommand(command SlashCommand) (*SlashCommand, error) { - return g.Disgo.RestClient().CreateGuildGuildCommand(g.Disgo.ApplicationID(), g.ID, command) +func (g *Guild) CreateCommand(command Command) (*Command, error) { + return g.Disgo.RestClient().CreateGuildCommand(g.Disgo.ApplicationID(), g.ID, command) } // EditCommand edits a specific guild command -func (g Guild) EditCommand(commandID Snowflake, command SlashCommand) (*SlashCommand, error) { +func (g *Guild) EditCommand(commandID Snowflake, command UpdateCommand) (*Command, error) { return g.Disgo.RestClient().EditGuildCommand(g.Disgo.ApplicationID(), g.ID, commandID, command) } // DeleteCommand creates a new command for this guild -func (g Guild) DeleteCommand(command SlashCommand) (*SlashCommand, error) { - return g.Disgo.RestClient().CreateGuildGuildCommand(g.Disgo.ApplicationID(), g.ID, command) +func (g *Guild) DeleteCommand(command Command) (*Command, error) { + return g.Disgo.RestClient().CreateGuildCommand(g.Disgo.ApplicationID(), g.ID, command) } // SetCommands overrides all commands for this guild -func (g Guild) SetCommands(commands ...SlashCommand) ([]*SlashCommand, error) { +func (g *Guild) SetCommands(commands ...Command) ([]*Command, error) { return g.Disgo.RestClient().SetGuildCommands(g.Disgo.ApplicationID(), g.ID, commands...) } + +// GetCommandsPermissions returns the GuildCommandPermissions for a all Command(s) in a guild +func (g *Guild) GetCommandsPermissions() ([]*GuildCommandPermissions, error) { + return g.Disgo.RestClient().GetGuildCommandsPermissions(g.Disgo.ApplicationID(), g.ID) +} + +// GetCommandPermissions returns the GuildCommandPermissions for a specific Command in a guild +func (g *Guild) GetCommandPermissions(commandID Snowflake) (*GuildCommandPermissions, error) { + return g.Disgo.RestClient().GetGuildCommandPermissions(g.Disgo.ApplicationID(), g.ID, commandID) +} + +// SetCommandsPermissions sets the GuildCommandPermissions for a all Command(s) +func (g *Guild) SetCommandsPermissions(commandPermissions ...SetGuildCommandPermissions) ([]*GuildCommandPermissions, error) { + return g.Disgo.RestClient().SetGuildCommandsPermissions(g.Disgo.ApplicationID(), g.ID, commandPermissions...) +} + +// SetCommandPermissions sets the GuildCommandPermissions for a specific Command +func (g *Guild) SetCommandPermissions(commandID Snowflake, permissions SetGuildCommandPermissions) (*GuildCommandPermissions, error) { + return g.Disgo.RestClient().SetGuildCommandPermissions(g.Disgo.ApplicationID(), g.ID, commandID, permissions) +} diff --git a/api/interaction.go b/api/interaction.go index 39fee1fc..689c17e3 100644 --- a/api/interaction.go +++ b/api/interaction.go @@ -1,5 +1,7 @@ package api +import "github.com/DisgoOrg/disgo/api/endpoints" + // InteractionType is the type of Interaction type InteractionType int @@ -18,7 +20,7 @@ type Interaction struct { ChannelID *Snowflake `json:"channel_id,omitempty"` Member *Member `json:"member,omitempty"` User *User `json:"User,omitempty"` - Token string `json:"token"` + Token endpoints.Token `json:"token"` Version int `json:"version"` } @@ -40,8 +42,105 @@ type Resolved struct { // OptionData is used for options or subcommands in your slash commands type OptionData struct { - Name string `json:"name"` - Type SlashCommandOptionType `json:"type"` - Value interface{} `json:"value,omitempty"` - Options []*OptionData `json:"options,omitempty"` + Name string `json:"name"` + Type CommandOptionType `json:"type"` + Value interface{} `json:"value,omitempty"` + Options []*OptionData `json:"options,omitempty"` +} + +// Option holds info about an Option.Value +type Option struct { + Resolved *Resolved + Name string + Type CommandOptionType + Value interface{} +} + +// String returns the Option.Value as string +func (o Option) String() string { + return o.Value.(string) +} + +// Bool returns the Option.Value as bool +func (o Option) Bool() bool { + return o.Value.(bool) +} + +// Snowflake returns the Option.Value as Snowflake +func (o Option) Snowflake() Snowflake { + return Snowflake(o.String()) +} + +// User returns the Option.Value as User +func (o Option) User() *User { + return o.Resolved.Users[o.Snowflake()] +} + +// Member returns the Option.Value as Member +func (o Option) Member() *Member { + return o.Resolved.Members[o.Snowflake()] +} + +// Role returns the Option.Value as Role +func (o Option) Role() *Role { + return o.Resolved.Roles[o.Snowflake()] +} + +// Channel returns the Option.Value as Channel +func (o Option) Channel() *Channel { + return o.Resolved.Channels[o.Snowflake()] +} + +// MessageChannel returns the Option.Value as MessageChannel +func (o Option) MessageChannel() *MessageChannel { + channel := o.Channel() + if channel == nil || (channel.Type != ChannelTypeText && channel.Type != ChannelTypeNews) { + return nil + } + return &MessageChannel{Channel: *channel} +} + +// GuildChannel returns the Option.Value as GuildChannel +func (o Option) GuildChannel() *GuildChannel { + channel := o.Channel() + if channel == nil || (channel.Type != ChannelTypeText && channel.Type != ChannelTypeNews && channel.Type != ChannelTypeCategory && channel.Type != ChannelTypeStore && channel.Type != ChannelTypeVoice) { + return nil + } + return &GuildChannel{Channel: *channel} +} + +// VoiceChannel returns the Option.Value as VoiceChannel +func (o Option) VoiceChannel() *VoiceChannel { + channel := o.Channel() + if channel == nil || channel.Type != ChannelTypeVoice { + return nil + } + return &VoiceChannel{GuildChannel: GuildChannel{Channel: *channel}} +} + +// TextChannel returns the Option.Value as TextChannel +func (o Option) TextChannel() *TextChannel { + channel := o.Channel() + if channel == nil || (channel.Type != ChannelTypeText && channel.Type != ChannelTypeNews) { + return nil + } + return &TextChannel{GuildChannel: GuildChannel{Channel: *channel}, MessageChannel: MessageChannel{Channel: *channel}} +} + +// Category returns the Option.Value as Category +func (o Option) Category() *Category { + channel := o.Channel() + if channel == nil || channel.Type != ChannelTypeCategory { + return nil + } + return &Category{GuildChannel: GuildChannel{Channel: *channel}} +} + +// StoreChannel returns the Option.Value as StoreChannel +func (o Option) StoreChannel() *StoreChannel { + channel := o.Channel() + if channel == nil || channel.Type != ChannelTypeStore { + return nil + } + return &StoreChannel{GuildChannel: GuildChannel{Channel: *channel}} } diff --git a/api/interaction_followup.go b/api/interaction_followup.go new file mode 100644 index 00000000..2cd7bb7f --- /dev/null +++ b/api/interaction_followup.go @@ -0,0 +1,102 @@ +package api + +// FollowupMessage is used to add additional messages to an Interaction after you've responded initially +type FollowupMessage struct { + Content string `json:"content,omitempty"` + Username string `json:"username,omitempty"` + AvatarURL string `json:"avatar_url,omitempty"` + TTS bool `json:"tts,omitempty"` + Embeds []Embed `json:"embeds,omitempty"` + AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"` + Flags MessageFlags `json:"flags,omitempty"` + //PayloadJSON string `json:"payload_json"` + //File FileContents `json:"file"` +} + +// FollowupMessageBuilder allows you to create an FollowupMessage with ease +type FollowupMessageBuilder struct { + FollowupMessage +} + +// NewFollowupMessageBuilder returns a new FollowupMessageBuilder +func NewFollowupMessageBuilder() *FollowupMessageBuilder { + return &FollowupMessageBuilder{ + FollowupMessage{}, + } +} + +// SetTTS sets if the FollowupMessage is a tts message +func (b *FollowupMessageBuilder) SetTTS(tts bool) *FollowupMessageBuilder { + b.TTS = tts + return b +} + +// SetContent sets the content of the FollowupMessage +func (b *FollowupMessageBuilder) SetContent(content string) *FollowupMessageBuilder { + b.Content = content + return b +} + +// SetEmbeds sets the embeds of the FollowupMessage +func (b *FollowupMessageBuilder) SetEmbeds(embeds ...Embed) *FollowupMessageBuilder { + b.Embeds = embeds + return b +} + +// AddEmbeds adds multiple embeds to the FollowupMessage +func (b *FollowupMessageBuilder) AddEmbeds(embeds ...Embed) *FollowupMessageBuilder { + b.Embeds = append(b.Embeds, embeds...) + return b +} + +// ClearEmbeds removes all of the embeds from the FollowupMessage +func (b *FollowupMessageBuilder) ClearEmbeds() *FollowupMessageBuilder { + if b.Embeds != nil { + b.Embeds = []Embed{} + } + return b +} + +// RemoveEmbed removes an embed from the FollowupMessage +func (b *FollowupMessageBuilder) RemoveEmbed(index int) *FollowupMessageBuilder { + if b != nil && len(b.Embeds) > index { + b.Embeds = append(b.Embeds[:index], b.Embeds[index+1:]...) + } + return b +} + +// SetAllowedMentions sets the allowed mentions of the FollowupMessage +func (b *FollowupMessageBuilder) SetAllowedMentions(allowedMentions *AllowedMentions) *FollowupMessageBuilder { + b.AllowedMentions = allowedMentions + return b +} + +// SetAllowedMentionsEmpty sets the allowed mentions of the FollowupMessage to nothing +func (b *FollowupMessageBuilder) SetAllowedMentionsEmpty() *FollowupMessageBuilder { + return b.SetAllowedMentions(&AllowedMentions{}) +} + +// SetFlags sets the message flags of the FollowupMessage +func (b *FollowupMessageBuilder) SetFlags(flags MessageFlags) *FollowupMessageBuilder { + b.Flags = flags + return b +} + +// SetEphemeral adds/removes MessageFlagEphemeral to the message flags +func (b *FollowupMessageBuilder) SetEphemeral(ephemeral bool) *FollowupMessageBuilder { + if ephemeral { + if !b.Flags.Has(MessageFlagEphemeral) { + b.Flags = b.Flags.Add(MessageFlagEphemeral) + } + } else { + if b.Flags.Has(MessageFlagEphemeral) { + b.Flags = b.Flags.Remove(MessageFlagEphemeral) + } + } + return b +} + +// Build returns your built FollowupMessage +func (b *FollowupMessageBuilder) Build() FollowupMessage { + return b.FollowupMessage +} diff --git a/api/interaction_response.go b/api/interaction_response.go index 3c9ab372..02546ac3 100644 --- a/api/interaction_response.go +++ b/api/interaction_response.go @@ -137,9 +137,13 @@ func (b *InteractionResponseBuilder) SetEphemeral(ephemeral bool) *InteractionRe b.Data = &InteractionResponseData{} } if ephemeral { - b.Data.Flags |= MessageFlagEphemeral + if !b.Data.Flags.Has(MessageFlagEphemeral) { + b.Data.Flags.Add(MessageFlagEphemeral) + } } else { - b.Data.Flags &^= MessageFlagEphemeral + if b.Data.Flags.Has(MessageFlagEphemeral) { + b.Data.Flags.Remove(MessageFlagEphemeral) + } } return b } @@ -148,8 +152,3 @@ func (b *InteractionResponseBuilder) SetEphemeral(ephemeral bool) *InteractionRe func (b *InteractionResponseBuilder) Build() InteractionResponse { return b.InteractionResponse } - -// FollowupMessage is used to add additional messages to an Interaction after you've responded initially -type FollowupMessage struct { - // Todo: fill this -} diff --git a/api/invite.go b/api/invite.go new file mode 100644 index 00000000..d12396b8 --- /dev/null +++ b/api/invite.go @@ -0,0 +1,76 @@ +package api + +import ( + "time" + + "github.com/DisgoOrg/disgo/api/endpoints" +) + +// ExpandedInvite is a full Invite struct +type ExpandedInvite struct { + Invite + Uses int `json:"uses"` + MaxUses int `json:"max_uses"` + MaxAge int `json:"max_age"` + Temporary bool `json:"temporary"` + CreatedAt time.Time `json:"created_at"` +} + +// Invite is a partial invite struct +type Invite struct { + Disgo Disgo + Code string `json:"code"` + Guild *InviteGuild `json:"guild"` + Channel InviteChannel `json:"channel"` + Inviter *User `json:"inviter"` + TargetUser *InviteUser `json:"target_user"` + TargetType *TargetType `json:"target_user_type"` + ApproximatePresenceCount *int `json:"approximate_presence_count"` + ApproximateMemberCount *int `json:"approximate_member_count"` +} + +// URL returns the invite url in format like https://discord.gg/{code} +func (i Invite) URL() string { + url, err := endpoints.InviteURL.Compile(i.Code) + if err != nil { + return "" + } + return url.Route() +} + +// TargetType is type of target an Invite uses +type TargetType int + +// Constants for TargetType +const ( + TargetTypeStream TargetType = iota + 1 + TargetTypeEmbeddedApplication +) + +// An InviteGuild is the Guild of an Invite +type InviteGuild struct { + ID Snowflake `json:"id"` + Name string `json:"name"` + Splash *string `json:"splash"` + Banner *string `json:"banner"` + Description *string `json:"description"` + Icon *string `json:"icon"` + Features []GuildFeature `json:"features"` + VerificationLevel VerificationLevel `json:"verification_level"` + VanityURLCode *string `json:"vanity_url_code"` +} + +// InviteChannel is the Channel of an invite +type InviteChannel struct { + ID string `json:"id"` + Name string `json:"name"` + Type ChannelType `json:"type"` +} + +// InviteUser is the user who created an invite +type InviteUser struct { + ID string `json:"id"` + Username string `json:"username"` + Avatar string `json:"avatar"` + Discriminator string `json:"discriminator"` +} diff --git a/api/cache_policies.go b/api/member_cache_policy.go similarity index 52% rename from api/cache_policies.go rename to api/member_cache_policy.go index 588a8624..cdd773c9 100644 --- a/api/cache_policies.go +++ b/api/member_cache_policy.go @@ -1,7 +1,5 @@ package api -import "time" - // MemberCachePolicy can be used to define your own policy for caching members type MemberCachePolicy func(*Member) bool @@ -45,49 +43,3 @@ func MemberCachePolicyAllOf(policy MemberCachePolicy, policies ...MemberCachePol } return policy } - -// MessageCachePolicy can be used to define your own policy for caching messages -type MessageCachePolicy func(*Message) bool - -// Default member cache policies -var ( - MessageCachePolicyNone MessageCachePolicy = func(_ *Message) bool { return false } - MessageCachePolicyDefault = MessageCachePolicyNone -) - -// Or allows you to combine that policy with another, meaning either needs to be true -func (p MessageCachePolicy) Or(policy MessageCachePolicy) MessageCachePolicy { - return func(message *Message) bool { - return p(message) || policy(message) - } -} - -// And allows you to require both policies to be true for the member to be cached -func (p MessageCachePolicy) And(policy MessageCachePolicy) MessageCachePolicy { - return func(message *Message) bool { - return p(message) && policy(message) - } -} - -// MessageCachePolicyDuration creates a new MessageCachePolicy which caches messages for the give duration -func MessageCachePolicyDuration(duration time.Duration) MessageCachePolicy { - return func(message *Message) bool { - return message.CreatedAt.Add(duration).After(time.Now()) - } -} - -// MessageCachePolicyAny is a shorthand for MessageCachePolicy.Or(MessageCachePolicy).Or(MessageCachePolicy) etc. -func MessageCachePolicyAny(policy MessageCachePolicy, policies ...MessageCachePolicy) MessageCachePolicy { - for _, p := range policies { - policy = policy.Or(p) - } - return policy -} - -// MessageCachePolicyAll is a shorthand for MessageCachePolicy.And(MessageCachePolicy).And(MessageCachePolicy) etc. -func MessageCachePolicyAll(policy MessageCachePolicy, policies ...MessageCachePolicy) MessageCachePolicy { - for _, p := range policies { - policy = policy.And(p) - } - return policy -} diff --git a/api/message.go b/api/message.go index 36cd9fcb..22bb05de 100644 --- a/api/message.go +++ b/api/message.go @@ -1,6 +1,7 @@ package api import ( + "errors" "time" ) @@ -25,8 +26,8 @@ const ( _ MessageTypeGuildDiscoveryDisqualified MessageTypeGuildDiscoveryRequalified - _ - _ + MessageTypeGuildDiscoveryInitialWarning + MessageTypeGuildDiscoveryFinalWarning _ MessageTypeReply MessageTypeApplicationCommand @@ -36,27 +37,27 @@ const ( type MessageFlags int64 // Add allows you to add multiple bits together, producing a new bit -func (f MessageFlags) Add(bits ...Bit) Bit { +func (f MessageFlags) Add(bits ...MessageFlags) MessageFlags { total := MessageFlags(0) for _, bit := range bits { - total |= bit.(MessageFlags) + total |= bit } f |= total return f } // Remove allows you to subtract multiple bits from the first, producing a new bit -func (f MessageFlags) Remove(bits ...Bit) Bit { +func (f MessageFlags) Remove(bits ...MessageFlags) MessageFlags { total := MessageFlags(0) for _, bit := range bits { - total |= bit.(MessageFlags) + total |= bit } f &^= total return f } // HasAll will ensure that the bit includes all of the bits entered -func (f MessageFlags) HasAll(bits ...Bit) bool { +func (f MessageFlags) HasAll(bits ...MessageFlags) bool { for _, bit := range bits { if !f.Has(bit) { return false @@ -66,12 +67,12 @@ func (f MessageFlags) HasAll(bits ...Bit) bool { } // Has will check whether the Bit contains another bit -func (f MessageFlags) Has(bit Bit) bool { - return (f & bit.(MessageFlags)) == bit +func (f MessageFlags) Has(bit MessageFlags) bool { + return (f & bit) == bit } // MissingAny will check whether the bit is missing any one of the bits -func (f MessageFlags) MissingAny(bits ...Bit) bool { +func (f MessageFlags) MissingAny(bits ...MessageFlags) bool { for _, bit := range bits { if !f.Has(bit) { return true @@ -81,43 +82,111 @@ func (f MessageFlags) MissingAny(bits ...Bit) bool { } // Missing will do the inverse of Bit.Has -func (f MessageFlags) Missing(bit Bit) bool { +func (f MessageFlags) Missing(bit MessageFlags) bool { return !f.Has(bit) } // Constants for MessageFlags const ( MessageFlagNone MessageFlags = 0 - MessageFlagCrossposted MessageFlags = 1 << iota + MessageFlagCrossposted MessageFlags = 1 << (iota - 1) MessageFlagIsCrosspost MessageFlagSuppressEmbeds MessageFlagSourceMessageDeleted MessageFlagUrgent _ MessageFlagEphemeral + MessageFlagLoading // Message is an interaction of type 5, awaiting further response +) + +//MessageAttachment is used for files sent in a Message +type MessageAttachment struct { + ID Snowflake `json:"id,omitempty"` + Filename string `json:"filename"` + Size int `json:"size"` + URL string `json:"url"` + ProxyURL string `json:"proxy_url"` + Height *int `json:"height"` + Width *int `json:"width"` +} + +// MessageActivityType is the type of MessageActivity +type MessageActivityType int + +//Constants for MessageActivityType +const ( + MessageActivityTypeJoin MessageActivityType = iota + 1 + MessageActivityTypeSpectate + MessageActivityTypeListen + _ + MessageActivityTypeJoinRequest +) + +//MessageActivity is used for rich presence-related chat embeds in a Message +type MessageActivity struct { + Type MessageActivityType `json:"type"` + PartyID *string `json:"party_id,omitempty"` +} + +//MessageApplication is used for rich presence-related chat embeds in a Message +type MessageApplication struct { + ID Snowflake `json:"id"` + CoverImage *string `json:"cover_image,omitempty"` + Description string `json:"description"` + Icon *string `json:"icon,omitempty"` + Name string `json:"name"` +} + +// MessageStickerFormatType is the Format Type of a MessageSticker +type MessageStickerFormatType int + +// Constants for MessageStickerFormatType +const ( + MessageStickerFormatPNG MessageStickerFormatType = iota + 1 + MessageStickerFormatAPNG + MessageStickerFormatLottie ) +// MessageSticker is a sticker sent with a Message +type MessageSticker struct { + ID Snowflake `json:"id"` + PackID Snowflake `json:"pack_id"` + Name string `json:"name"` + Description string `json:"description"` + Tags *string `json:"tags"` + FormatType MessageStickerFormatType `json:"format_type"` +} + // Message is a struct for messages sent in discord text-based channels type Message struct { - Disgo Disgo - ID Snowflake `json:"id"` - GuildID *Snowflake `json:"guild_id"` - Reactions []Reactions `json:"reactions"` - Attachments []interface{} `json:"attachments"` - Tts bool `json:"tts"` - Embeds []*Embed `json:"embeds,omitempty"` - CreatedAt time.Time `json:"timestamp"` - MentionEveryone bool `json:"mention_everyone"` - Pinned bool `json:"pinned"` - EditedTimestamp interface{} `json:"edited_timestamp"` - Author User `json:"author"` - MentionRoles []interface{} `json:"mention_roles"` - Content *string `json:"content,omitempty"` - ChannelID Snowflake `json:"channel_id"` - Mentions []interface{} `json:"mentions"` - MessageType MessageType `json:"type"` - MessageReference *MessageReference `json:"message_reference,omitempty"` - LastUpdated *time.Time + Disgo Disgo + ID Snowflake `json:"id"` + GuildID *Snowflake `json:"guild_id"` + Reactions []*MessageReaction `json:"reactions"` + Attachments []*MessageAttachment `json:"attachments"` + TTS bool `json:"tts"` + Embeds []*Embed `json:"embeds,omitempty"` + CreatedAt time.Time `json:"timestamp"` + Mentions []interface{} `json:"mentions"` + MentionEveryone bool `json:"mention_everyone"` + MentionRoles []*Role `json:"mention_roles"` + MentionChannels []*Channel `json:"mention_channels"` + Pinned bool `json:"pinned"` + EditedTimestamp *time.Time `json:"edited_timestamp"` + Author *User `json:"author"` + Member *Member `json:"member"` + Content *string `json:"content,omitempty"` + ChannelID Snowflake `json:"channel_id"` + Type MessageType `json:"type"` + Flags *MessageFlags `json:"flags"` + MessageReference *MessageReference `json:"message_reference,omitempty"` + Interaction *MessageInteraction `json:"message_interaction,omitempty"` + WebhookID *Snowflake `json:"webhook_id,omitempty"` + Activity *MessageActivity `json:"activity,omitempty"` + Application *MessageApplication `json:"application,omitempty"` + Stickers []*MessageSticker `json:"stickers,omitempty"` + ReferencedMessage *Message `json:"referenced_message,omitempty"` + LastUpdated *time.Time } // MessageReference is a reference to another message @@ -136,10 +205,6 @@ type MessageInteraction struct { User User `json:"user"` } -// missing Member, mention channels, nonce, webhook id, type, activity, application, message_reference, flags, stickers -// referenced_message, interaction -// https://discord.com/developers/docs/resources/channel#message-object - // Guild gets the guild_events the message_events was sent in func (m Message) Guild() *Guild { if m.GuildID == nil { @@ -163,25 +228,58 @@ func (m Message) AddReaction(emoji string) error { return m.Disgo.RestClient().AddReaction(m.ChannelID, m.ID, emoji) } +// Edit allows you to edit an existing Message sent by you +func (m Message) Edit(message MessageUpdate) (*Message, error) { + return m.Disgo.RestClient().EditMessage(m.ChannelID, m.ID, message) +} + +// Delete allows you to edit an existing Message sent by you +func (m Message) Delete() error { + return m.Disgo.RestClient().DeleteMessage(m.ChannelID, m.ID) +} + +// Crosspost crossposts an existing message +func (m Message) Crosspost() (*Message, error) { + channel := m.Channel() + if channel != nil && channel.Type != ChannelTypeNews { + return nil, errors.New("channel type is not NEWS") + } + return m.Disgo.RestClient().CrosspostMessage(m.ChannelID, m.ID) +} + // Reply allows you to reply to an existing Message func (m Message) Reply(message MessageCreate) (*Message, error) { message.MessageReference = &MessageReference{ MessageID: &m.ID, } - return m.Channel().SendMessage(message) + return m.Disgo.RestClient().SendMessage(m.ChannelID, message) } -// Reactions contains information about the reactions of a message_events -type Reactions struct { +// MessageReaction contains information about the reactions of a message_events +type MessageReaction struct { Count int `json:"count"` Me bool `json:"me"` Emoji Emote `json:"emoji"` } -// UpdateMessage is used to edit a message -type UpdateMessage struct { - Content *string `json:"content,omitempty"` +// MessageUpdate is used to edit a Message +type MessageUpdate struct { + Content string `json:"content,omitempty"` Embed *Embed `json:"embed,omitempty"` - Flags *MessageFlags `json:"flags,omitempty"` + Flags MessageFlags `json:"flags,omitempty"` AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"` } + +// MessageCreate is the struct to create a new Message with +type MessageCreate struct { + Content string `json:"content,omitempty"` + TTS bool `json:"tts,omitempty"` + Embed *Embed `json:"embed,omitempty"` + AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"` + MessageReference *MessageReference `json:"message_reference,omitempty"` +} + +// MessageBulkDelete is used to bulk delete Message(s) +type MessageBulkDelete struct { + Messages []Snowflake `json:"messages"` +} diff --git a/api/message_create.go b/api/message_builder.go similarity index 81% rename from api/message_create.go rename to api/message_builder.go index 217b1754..4a5616d7 100644 --- a/api/message_create.go +++ b/api/message_builder.go @@ -1,14 +1,5 @@ package api -// MessageCreate is the struct to create a new Message with -type MessageCreate struct { - Content string `json:"content,omitempty"` - TTS bool `json:"tts,omitempty"` - Embed *Embed `json:"embed,omitempty"` - AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"` - MessageReference *MessageReference `json:"message_reference,omitempty"` -} - // MessageBuilder helper to build Message(s) easier type MessageBuilder struct { MessageCreate diff --git a/api/message_cache_policy.go b/api/message_cache_policy.go new file mode 100644 index 00000000..17b4c72f --- /dev/null +++ b/api/message_cache_policy.go @@ -0,0 +1,49 @@ +package api + +import "time" + +// MessageCachePolicy can be used to define your own policy for caching messages +type MessageCachePolicy func(*Message) bool + +// Default member cache policies +var ( + MessageCachePolicyNone MessageCachePolicy = func(_ *Message) bool { return false } + MessageCachePolicyDefault = MessageCachePolicyNone +) + +// Or allows you to combine that policy with another, meaning either needs to be true +func (p MessageCachePolicy) Or(policy MessageCachePolicy) MessageCachePolicy { + return func(message *Message) bool { + return p(message) || policy(message) + } +} + +// And allows you to require both policies to be true for the member to be cached +func (p MessageCachePolicy) And(policy MessageCachePolicy) MessageCachePolicy { + return func(message *Message) bool { + return p(message) && policy(message) + } +} + +// MessageCachePolicyDuration creates a new MessageCachePolicy which caches messages for the give duration +func MessageCachePolicyDuration(duration time.Duration) MessageCachePolicy { + return func(message *Message) bool { + return message.CreatedAt.Add(duration).After(time.Now()) + } +} + +// MessageCachePolicyAny is a shorthand for MessageCachePolicy.Or(MessageCachePolicy).Or(MessageCachePolicy) etc. +func MessageCachePolicyAny(policy MessageCachePolicy, policies ...MessageCachePolicy) MessageCachePolicy { + for _, p := range policies { + policy = policy.Or(p) + } + return policy +} + +// MessageCachePolicyAll is a shorthand for MessageCachePolicy.And(MessageCachePolicy).And(MessageCachePolicy) etc. +func MessageCachePolicyAll(policy MessageCachePolicy, policies ...MessageCachePolicy) MessageCachePolicy { + for _, p := range policies { + policy = policy.And(p) + } + return policy +} diff --git a/api/options.go b/api/options.go index 75b3b660..90da003c 100644 --- a/api/options.go +++ b/api/options.go @@ -1,11 +1,16 @@ package api +import "github.com/DisgoOrg/log" + // Options is the configuration used when creating the client type Options struct { + Logger log.Logger Intents Intents RestTimeout int EnableWebhookInteractions bool ListenPort int ListenURL string PublicKey string + LargeThreshold int + RawGatewayEventsEnabled bool } diff --git a/api/permissions.go b/api/permissions.go index 0dc051d4..48b64be6 100644 --- a/api/permissions.go +++ b/api/permissions.go @@ -5,6 +5,23 @@ import ( "strconv" ) +// PermissionOverwriteType is the type of a PermissionOverwrite +type PermissionOverwriteType int + +// Constants for PermissionOverwriteType +const ( + PermissionOverwriteTypeRole PermissionOverwriteType = iota + PermissionOverwriteTypeMember +) + +// PermissionOverwrite is used to determine who can perform particular actions in a GuildChannel +type PermissionOverwrite struct { + ID Snowflake `json:"id"` + Type PermissionOverwriteType `json:"type"` + Allow Permissions `json:"allow"` + Deny Permissions `json:"deny"` +} + // Permissions extends the Bit structure, and is used within roles and channels type Permissions int64 @@ -20,7 +37,7 @@ func (p Permissions) MarshalJSON() ([]byte, error) { return jsonValue, nil } -// UnmarshalJSON unmarshals permissions into a int64 +// UnmarshalJSON unmarshals permissions into an int64 func (p *Permissions) UnmarshalJSON(b []byte) error { var strPermissions string err := json.Unmarshal(b, &strPermissions) @@ -116,6 +133,7 @@ const ( PermissionManageRoles PermissionManageWebhooks PermissionManageEmojis + PermissionUseSlashCommands ) // Constants for the different bit offsets of general permissions diff --git a/api/restclient.go b/api/restclient.go index 13acef68..4eb40110 100644 --- a/api/restclient.go +++ b/api/restclient.go @@ -24,6 +24,11 @@ type RestClient interface { Request(route endpoints.CompiledAPIRoute, rqBody interface{}, rsBody interface{}) error SendMessage(channelID Snowflake, message MessageCreate) (*Message, error) + EditMessage(channelID Snowflake, messageID Snowflake, message MessageUpdate) (*Message, error) + DeleteMessage(channelID Snowflake, messageID Snowflake) error + BulkDeleteMessages(channelID Snowflake, messageIDs ...Snowflake) error + CrosspostMessage(channelID Snowflake, messageID Snowflake) (*Message, error) + OpenDMChannel(userID Snowflake) (*DMChannel, error) UpdateSelfNick(guildID Snowflake, nick *string) (*string, error) @@ -48,27 +53,32 @@ type RestClient interface { RemoveOwnReaction(channelID Snowflake, messageID Snowflake, emoji string) error RemoveUserReaction(channelID Snowflake, messageID Snowflake, emoji string, userID Snowflake) error - GetGlobalCommands(applicationID Snowflake) ([]*SlashCommand, error) - CreateGlobalCommand(applicationID Snowflake, command SlashCommand) (*SlashCommand, error) - SetGlobalCommands(applicationID Snowflake, commands ...SlashCommand) ([]*SlashCommand, error) - GetGlobalCommand(applicationID Snowflake, commandID Snowflake) (*SlashCommand, error) - EditGlobalCommand(applicationID Snowflake, commandID Snowflake, command SlashCommand) (*SlashCommand, error) + GetGlobalCommands(applicationID Snowflake) ([]*Command, error) + GetGlobalCommand(applicationID Snowflake, commandID Snowflake) (*Command, error) + CreateGlobalCommand(applicationID Snowflake, command Command) (*Command, error) + SetGlobalCommands(applicationID Snowflake, commands ...Command) ([]*Command, error) + EditGlobalCommand(applicationID Snowflake, commandID Snowflake, command UpdateCommand) (*Command, error) DeleteGlobalCommand(applicationID Snowflake, commandID Snowflake) error - GetGuildCommands(applicationID Snowflake, guildID Snowflake) ([]*SlashCommand, error) - CreateGuildGuildCommand(applicationID Snowflake, guildID Snowflake, command SlashCommand) (*SlashCommand, error) - SetGuildCommands(applicationID Snowflake, guildID Snowflake, commands ...SlashCommand) ([]*SlashCommand, error) - GetGuildCommand(applicationID Snowflake, guildID Snowflake, commandID Snowflake) (*SlashCommand, error) - EditGuildCommand(applicationID Snowflake, guildID Snowflake, commandID Snowflake, command SlashCommand) (*SlashCommand, error) + GetGuildCommands(applicationID Snowflake, guildID Snowflake) ([]*Command, error) + GetGuildCommand(applicationID Snowflake, guildID Snowflake, commandID Snowflake) (*Command, error) + CreateGuildCommand(applicationID Snowflake, guildID Snowflake, command Command) (*Command, error) + SetGuildCommands(applicationID Snowflake, guildID Snowflake, commands ...Command) ([]*Command, error) + EditGuildCommand(applicationID Snowflake, guildID Snowflake, commandID Snowflake, command UpdateCommand) (*Command, error) DeleteGuildCommand(applicationID Snowflake, guildID Snowflake, commandID Snowflake) error - SendInteractionResponse(interactionID Snowflake, interactionToken string, interactionResponse InteractionResponse) error - EditInteractionResponse(applicationID Snowflake, interactionToken string, interactionResponse InteractionResponse) (*Message, error) - DeleteInteractionResponse(applicationID Snowflake, interactionToken string) error + GetGuildCommandsPermissions(applicationID Snowflake, guildID Snowflake) ([]*GuildCommandPermissions, error) + GetGuildCommandPermissions(applicationID Snowflake, guildID Snowflake, commandID Snowflake) (*GuildCommandPermissions, error) + SetGuildCommandsPermissions(applicationID Snowflake, guildID Snowflake, commandPermissions ...SetGuildCommandPermissions) ([]*GuildCommandPermissions, error) + SetGuildCommandPermissions(applicationID Snowflake, guildID Snowflake, commandID Snowflake, commandPermissions SetGuildCommandPermissions) (*GuildCommandPermissions, error) + + SendInteractionResponse(interactionID Snowflake, interactionToken endpoints.Token, interactionResponse InteractionResponse) error + EditInteractionResponse(applicationID Snowflake, interactionToken endpoints.Token, followupMessage FollowupMessage) (*Message, error) + DeleteInteractionResponse(applicationID Snowflake, interactionToken endpoints.Token) error - SendFollowupMessage(applicationID Snowflake, interactionToken string, followupMessage FollowupMessage) (*Message, error) - EditFollowupMessage(applicationID Snowflake, interactionToken string, messageID Snowflake, followupMessage FollowupMessage) (*Message, error) - DeleteFollowupMessage(applicationID Snowflake, interactionToken string, followupMessageID Snowflake) error + SendFollowupMessage(applicationID Snowflake, interactionToken endpoints.Token, followupMessage FollowupMessage) (*Message, error) + EditFollowupMessage(applicationID Snowflake, interactionToken endpoints.Token, messageID Snowflake, followupMessage FollowupMessage) (*Message, error) + DeleteFollowupMessage(applicationID Snowflake, interactionToken endpoints.Token, followupMessageID Snowflake) error } // ErrorResponse contains custom errors from discord diff --git a/api/slash_command.go b/api/slash_command.go deleted file mode 100644 index 7d556ef1..00000000 --- a/api/slash_command.go +++ /dev/null @@ -1,41 +0,0 @@ -package api - -// SlashCommand is the base "command" model that belongs to an application. -type SlashCommand struct { - ID Snowflake `json:"id,omitempty"` - ApplicationID Snowflake `json:"application_id,omitempty"` - Name string `json:"name"` - Description string `json:"description"` - Options []*CommandOption `json:"options,omitempty"` -} - -// SlashCommandOptionType specifies the type of the arguments used in SlashCommand.Options -type SlashCommandOptionType int - -// Constants for each slash command option type -const ( - OptionTypeSubCommand SlashCommandOptionType = iota + 1 - OptionTypeSubCommandGroup - OptionTypeString - OptionTypeInteger - OptionTypeBoolean - OptionTypeUser - OptionTypeChannel - OptionTypeRole -) - -// CommandOption are the arguments used in SlashCommand.Options -type CommandOption struct { - Type SlashCommandOptionType `json:"type"` - Name string `json:"name"` - Description string `json:"description"` - Required bool `json:"required,omitempty"` - Choices []OptionChoice `json:"choices,omitempty"` - Options []CommandOption `json:"options,omitempty"` -} - -// OptionChoice contains the data for a user using your command -type OptionChoice struct { - Name string `json:"name"` - Value interface{} `json:"value"` -} diff --git a/api/voice_dispatch_interceptor.go b/api/voice_dispatch_interceptor.go new file mode 100644 index 00000000..eae50a6d --- /dev/null +++ b/api/voice_dispatch_interceptor.go @@ -0,0 +1,37 @@ +package api + +// VoiceServerUpdateEvent sent when a guilds voice server is updated +type VoiceServerUpdateEvent struct { + VoiceServerUpdate + Disgo Disgo +} + +// Guild returns the Guild for this VoiceServerUpdate from the Cache +func (u *VoiceServerUpdateEvent) Guild() *Guild { + return u.Disgo.Cache().Guild(u.GuildID) +} + +// VoiceStateUpdateEvent sent when someone joins/leaves/moves voice channels +type VoiceStateUpdateEvent struct { + VoiceState + Member *Member `json:"member"` +} + +// Guild returns the Guild for this VoiceStateUpdate from the Cache +func (u *VoiceStateUpdateEvent) Guild() *Guild { + return u.Disgo.Cache().Guild(u.GuildID) +} + +// VoiceChannel returns the VoiceChannel for this VoiceStateUpdate from the Cache +func (u *VoiceStateUpdateEvent) VoiceChannel() *VoiceChannel { + if u.ChannelID == nil { + return nil + } + return u.Disgo.Cache().VoiceChannel(*u.ChannelID) +} + +// VoiceDispatchInterceptor lets you listen to VoiceServerUpdate & VoiceStateUpdate +type VoiceDispatchInterceptor interface { + OnVoiceServerUpdate(voiceServerUpdateEvent *VoiceServerUpdateEvent) + OnVoiceStateUpdate(voiceStateUpdateEvent *VoiceStateUpdateEvent) +} diff --git a/api/voice_state.go b/api/voice_state.go index 8fb900a5..8b71099d 100644 --- a/api/voice_state.go +++ b/api/voice_state.go @@ -1,17 +1,57 @@ package api -// A VoiceState from Discord +// VoiceServerUpdate from Discord +type VoiceServerUpdate struct { + Token string `json:"token"` + GuildID Snowflake `json:"guild_id"` + Endpoint *string `json:"endpoint"` +} + +// VoiceState from Discord type VoiceState struct { - GuildID *Snowflake `json:"guild_id,omitempty"` - ChannelID *Snowflake `json:"channel_id"` - UserID Snowflake `json:"user_id"` - Member *Member `json:"member,omitempty"` - SessionID string `json:"session_id"` - Deaf bool `json:"deaf"` - Mute bool `json:"mute"` - SelfDeaf bool `json:"self_deaf"` - SelfMute bool `json:"self_mute"` - SelfStream *bool `json:"self_stream,omitempty"` - SelfVideo bool `json:"self_video"` - Suppress bool `json:"suppress"` + Disgo Disgo + GuildID Snowflake `json:"guild_id"` + ChannelID *Snowflake `json:"channel_id"` + UserID Snowflake `json:"user_id"` + SessionID string `json:"session_id"` + GuildDeafened bool `json:"deaf"` + GuildMuted bool `json:"mute"` + SelfDeafened bool `json:"self_deaf"` + SelfMuted bool `json:"self_mute"` + Stream bool `json:"self_stream"` + Video bool `json:"self_video"` + Suppressed bool `json:"suppress"` +} + +// Muted returns if the Member is muted +func (s VoiceState) Muted() bool { + return s.GuildMuted || s.SelfMuted +} + +// Deafened returns if the Member is deafened +func (s VoiceState) Deafened() bool { + return s.GuildDeafened || s.SelfDeafened +} + +// Member returns the Member of this VoiceState from the Cache +func (s VoiceState) Member() *Member { + return s.Disgo.Cache().Member(s.GuildID, s.UserID) +} + +// User returns the User of this VoiceState from the Cache +func (s VoiceState) User() *User { + return s.Disgo.Cache().User(s.UserID) +} + +// Guild returns the Guild of this VoiceState from the Cache +func (s VoiceState) Guild() *Guild { + return s.Disgo.Cache().Guild(s.GuildID) +} + +// VoiceChannel returns the VoiceChannel of this VoiceState from the Cache +func (s VoiceState) VoiceChannel() *VoiceChannel { + if s.ChannelID == nil { + return nil + } + return s.Disgo.Cache().VoiceChannel(*s.ChannelID) } diff --git a/api/webhook_server.go b/api/webhook_server.go index 6852f5f6..40312720 100644 --- a/api/webhook_server.go +++ b/api/webhook_server.go @@ -17,14 +17,14 @@ type WebhookServer interface { PublicKey() ed25519.PublicKey ListenURL() string Router() *mux.Router - Start() error + Start() Close() } // Verify implements the verification side of the discord interactions api signing algorithm, as documented here: // https://discord.com/developers/docs/interactions/slash-commands#security-and-authorization // Credit: https://github.com/bsdlp/discord-interactions-go/blob/main/interactions/verify.go -func Verify(r *http.Request, key ed25519.PublicKey) bool { +func Verify(disgo Disgo, r *http.Request, key ed25519.PublicKey) bool { var msg bytes.Buffer signature := r.Header.Get("X-Signature-Ed25519") @@ -48,7 +48,12 @@ func Verify(r *http.Request, key ed25519.PublicKey) bool { msg.WriteString(timestamp) - defer r.Body.Close() + defer func() { + err = r.Body.Close() + if err != nil { + disgo.Logger().Errorf("error while closing request body: %s", err) + } + }() var body bytes.Buffer defer func() { diff --git a/disgo.go b/disgo.go index 1e61da43..8f708591 100644 --- a/disgo.go +++ b/disgo.go @@ -2,15 +2,16 @@ package disgo import ( "github.com/DisgoOrg/disgo/api" + "github.com/DisgoOrg/disgo/api/endpoints" "github.com/DisgoOrg/disgo/internal" ) // New Initialises a new Disgo client -func New(token string, options api.Options) (api.Disgo, error) { +func New(token endpoints.Token, options api.Options) (api.Disgo, error) { return internal.New(token, options) } // NewBuilder creates an api.DisgoBuilder for the client -func NewBuilder(token string) api.DisgoBuilder { +func NewBuilder(token endpoints.Token) api.DisgoBuilder { return internal.NewBuilder(token) } diff --git a/go.mod b/go.mod index 72783e3e..460f2c3c 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module github.com/DisgoOrg/disgo go 1.16 require ( + github.com/DisgoOrg/log v1.0.3 github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.4.2 - github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.0 ) diff --git a/go.sum b/go.sum index 5af0c914..7fd0a7b8 100644 --- a/go.sum +++ b/go.sum @@ -1,20 +1,16 @@ +github.com/DisgoOrg/log v1.0.3 h1:IjmZQQu/kuBIui22EdXmxzQGYwcPCJEkXa0Fe6W9fJk= +github.com/DisgoOrg/log v1.0.3/go.mod h1:KFGKhBQr37d6rxZ7p2bmc8BEmDH8DZbtgdlJDSCsE7I= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= diff --git a/internal/audio_controller_impl.go b/internal/audio_controller_impl.go new file mode 100644 index 00000000..6b201ff5 --- /dev/null +++ b/internal/audio_controller_impl.go @@ -0,0 +1,56 @@ +package internal + +import ( + "github.com/DisgoOrg/disgo/api" + "github.com/gorilla/websocket" +) + +func newAudioControllerImpl(disgo api.Disgo) api.AudioController { + return &AudioControllerImpl{disgo: disgo} +} + +// AudioControllerImpl lets you Connect / Disconnect from a api.VoiceChannel +type AudioControllerImpl struct { + disgo api.Disgo +} + +// Disgo returns the api.Disgo instance +func (c *AudioControllerImpl) Disgo() api.Disgo { + return c.disgo +} + +// Connect sends a api.GatewayCommand to connect to a api.VoiceChannel +func (c *AudioControllerImpl) Connect(guildID api.Snowflake, channelID api.Snowflake) error { + conn, err := c.getConn() + if err != nil { + return err + } + return conn.WriteJSON(api.NewGatewayCommand(api.OpVoiceStateUpdate, api.UpdateVoiceStateCommand{ + GuildID: guildID, + ChannelID: &channelID, + })) +} + +// Disconnect sends a api.GatewayCommand to disconnect from a api.VoiceChannel +func (c *AudioControllerImpl) Disconnect(guildID api.Snowflake) error { + conn, err := c.getConn() + if err != nil { + return err + } + return conn.WriteJSON(api.NewGatewayCommand(api.OpVoiceStateUpdate, api.UpdateVoiceStateCommand{ + GuildID: guildID, + ChannelID: nil, + })) +} + +func (c *AudioControllerImpl) getConn() (*websocket.Conn, error) { + gateway := c.Disgo().Gateway() + if gateway == nil { + return nil, api.ErrNoGateway + } + conn := gateway.Conn() + if conn == nil { + return nil, api.ErrNoGatewayConn + } + return conn, nil +} diff --git a/internal/cache.go b/internal/cache_impl.go similarity index 63% rename from internal/cache.go rename to internal/cache_impl.go index e2fd4206..3c16a37d 100644 --- a/internal/cache.go +++ b/internal/cache_impl.go @@ -5,59 +5,78 @@ import ( "strings" "time" - log "github.com/sirupsen/logrus" - "github.com/DisgoOrg/disgo/api" ) -func newCacheImpl(memberCachePolicy api.MemberCachePolicy) api.Cache { +func newCacheImpl(disgo api.Disgo, memberCachePolicy api.MemberCachePolicy, messageCachePolicy api.MessageCachePolicy, cacheFlags api.CacheFlags) api.Cache { cache := &CacheImpl{ - memberCachePolicy: memberCachePolicy, - users: map[api.Snowflake]*api.User{}, - guilds: map[api.Snowflake]*api.Guild{}, - members: map[api.Snowflake]map[api.Snowflake]*api.Member{}, - roles: map[api.Snowflake]map[api.Snowflake]*api.Role{}, - dmChannels: map[api.Snowflake]*api.DMChannel{}, - categories: map[api.Snowflake]map[api.Snowflake]*api.Category{}, - textChannels: map[api.Snowflake]map[api.Snowflake]*api.TextChannel{}, - voiceChannels: map[api.Snowflake]map[api.Snowflake]*api.VoiceChannel{}, - storeChannels: map[api.Snowflake]map[api.Snowflake]*api.StoreChannel{}, - } - go cache.cleanup(10 * time.Second) + disgo: disgo, + quit: make(chan interface{}), + memberCachePolicy: memberCachePolicy, + messageCachePolicy: messageCachePolicy, + cacheFlags: cacheFlags, + globalCommands: map[api.Snowflake]*api.Command{}, + guildCommands: map[api.Snowflake]map[api.Snowflake]*api.Command{}, + users: map[api.Snowflake]*api.User{}, + guilds: map[api.Snowflake]*api.Guild{}, + members: map[api.Snowflake]map[api.Snowflake]*api.Member{}, + voiceStates: map[api.Snowflake]map[api.Snowflake]*api.VoiceState{}, + roles: map[api.Snowflake]map[api.Snowflake]*api.Role{}, + dmChannels: map[api.Snowflake]*api.DMChannel{}, + categories: map[api.Snowflake]map[api.Snowflake]*api.Category{}, + textChannels: map[api.Snowflake]map[api.Snowflake]*api.TextChannel{}, + voiceChannels: map[api.Snowflake]map[api.Snowflake]*api.VoiceChannel{}, + storeChannels: map[api.Snowflake]map[api.Snowflake]*api.StoreChannel{}, + emotes: map[api.Snowflake]map[api.Snowflake]*api.Emote{}, + } + go cache.startCleanup(10 * time.Second) return cache } -// CacheImpl is used for Disgo's Cache +// CacheImpl is used for api.Disgo's api.Cache type CacheImpl struct { - quit chan bool - memberCachePolicy api.MemberCachePolicy - users map[api.Snowflake]*api.User - guilds map[api.Snowflake]*api.Guild - members map[api.Snowflake]map[api.Snowflake]*api.Member - roles map[api.Snowflake]map[api.Snowflake]*api.Role - dmChannels map[api.Snowflake]*api.DMChannel - categories map[api.Snowflake]map[api.Snowflake]*api.Category - textChannels map[api.Snowflake]map[api.Snowflake]*api.TextChannel - voiceChannels map[api.Snowflake]map[api.Snowflake]*api.VoiceChannel - storeChannels map[api.Snowflake]map[api.Snowflake]*api.StoreChannel + disgo api.Disgo + quit chan interface{} + memberCachePolicy api.MemberCachePolicy + messageCachePolicy api.MessageCachePolicy + cacheFlags api.CacheFlags + globalCommands map[api.Snowflake]*api.Command + guildCommands map[api.Snowflake]map[api.Snowflake]*api.Command + users map[api.Snowflake]*api.User + guilds map[api.Snowflake]*api.Guild + messages map[api.Snowflake]map[api.Snowflake]*api.Message + members map[api.Snowflake]map[api.Snowflake]*api.Member + voiceStates map[api.Snowflake]map[api.Snowflake]*api.VoiceState + roles map[api.Snowflake]map[api.Snowflake]*api.Role + dmChannels map[api.Snowflake]*api.DMChannel + categories map[api.Snowflake]map[api.Snowflake]*api.Category + textChannels map[api.Snowflake]map[api.Snowflake]*api.TextChannel + voiceChannels map[api.Snowflake]map[api.Snowflake]*api.VoiceChannel + storeChannels map[api.Snowflake]map[api.Snowflake]*api.StoreChannel + emotes map[api.Snowflake]map[api.Snowflake]*api.Emote +} + +// Disgo returns the current api.Disgo instance +func (c *CacheImpl) Disgo() api.Disgo { + return c.disgo } // Close cleans up the cache and it's internal tasks func (c *CacheImpl) Close() { - log.Info("closing cache goroutines...") - close(c.quit) - log.Info("closed cache goroutines") + c.Disgo().Logger().Info("closing cache goroutines...") + c.quit <- true + c.Disgo().Logger().Info("closed cache goroutines") } -func (c CacheImpl) cleanup(cleanupInterval time.Duration) { +func (c CacheImpl) startCleanup(cleanupInterval time.Duration) { defer func() { if r := recover(); r != nil { - log.Errorf("recovered cache cleanup goroutine error: %s", r) + c.Disgo().Logger().Panicf("recovered cache cleanup goroutine error: %s", r) debug.PrintStack() - c.cleanup(cleanupInterval) + c.startCleanup(cleanupInterval) return } - log.Info("shut down cache cleanup goroutine") + c.Disgo().Logger().Info("shut down cache cleanup goroutine") }() ticker := time.NewTicker(cleanupInterval) @@ -74,7 +93,88 @@ func (c CacheImpl) cleanup(cleanupInterval time.Duration) { // DoCleanup removes items from the cache that no longer meet their policy func (c *CacheImpl) DoCleanup() { - // TODO cleanup cache + for channelID, channelMessages := range c.messages { + for _, message := range channelMessages { + if !c.messageCachePolicy(message) { + c.UncacheMessage(channelID, message.ID) + } + } + } +} + +// CacheFlags returns the current active api.CacheFlags +func (c CacheImpl) CacheFlags() api.CacheFlags { + return c.cacheFlags +} + +// Command returns an api.Command from cache +func (c *CacheImpl) Command(commandID api.Snowflake) *api.Command { + if command, ok := c.globalCommands[commandID]; ok { + return command + } + for _, guildCommands := range c.guildCommands { + if command, ok := guildCommands[commandID]; ok { + return command + } + } + return nil +} + +// GuildCommandCache returns the cache of commands in a Guild +func (c *CacheImpl) GuildCommandCache(guildID api.Snowflake) map[api.Snowflake]*api.Command { + return c.guildCommands[guildID] +} + +// AllGuildCommandCache returns the cache of all Guild Command(s) +func (c *CacheImpl) AllGuildCommandCache() map[api.Snowflake]map[api.Snowflake]*api.Command { + return c.guildCommands +} + +// GlobalCommandCache returns the cache of global Command(s) +func (c *CacheImpl) GlobalCommandCache() map[api.Snowflake]*api.Command { + return c.globalCommands +} + +// CacheGlobalCommand adds a global command to the cache +func (c *CacheImpl) CacheGlobalCommand(command *api.Command) *api.Command { + if !c.CacheFlags().Has(api.CacheFlagCommands) { + return command + } + if _, ok := c.globalCommands[command.ID]; ok { + *c.globalCommands[command.ID] = *command + return c.globalCommands[command.ID] + } + c.globalCommands[command.ID] = command + return command +} + +// CacheGuildCommand adds a Guild Command to the cache +func (c *CacheImpl) CacheGuildCommand(command *api.Command) *api.Command { + if !c.CacheFlags().Has(api.CacheFlagCommands) { + return command + } + if guildCommands, ok := c.guildCommands[command.ID]; ok { + if _, ok = guildCommands[command.ID]; ok { + *guildCommands[command.ID] = *command + return guildCommands[command.ID] + } + guildCommands[command.ID] = command + } + return command +} + +// UncacheCommand removes a global Command from the cache +func (c *CacheImpl) UncacheCommand(commandID api.Snowflake) { + if _, ok := c.globalCommands[commandID]; ok { + delete(c.globalCommands, commandID) + return + } + for _, guildCommands := range c.guildCommands { + if _, ok := guildCommands[commandID]; ok { + delete(c.guildCommands, commandID) + return + } + } } // User allows you to get a user from the cache by ID @@ -123,12 +223,14 @@ func (c *CacheImpl) UserCache() map[api.Snowflake]*api.User { } // CacheUser adds a user to the cache -func (c *CacheImpl) CacheUser(user *api.User) { - if _, ok := c.guilds[user.ID]; ok { - // update old user - return +func (c *CacheImpl) CacheUser(user *api.User) *api.User { + // TODO: only cache user if we have a mutal guild & always cache self user + if globalUser, ok := c.users[user.ID]; ok { + *globalUser = *user + return globalUser } c.users[user.ID] = user + return user } // UncacheUser removes a user from the cache @@ -193,26 +295,30 @@ func (c *CacheImpl) GuildCache() map[api.Snowflake]*api.Guild { } // CacheGuild adds a guild to the cache -func (c *CacheImpl) CacheGuild(guild *api.Guild) { +func (c *CacheImpl) CacheGuild(guild *api.Guild) *api.Guild { if _, ok := c.guilds[guild.ID]; ok { - // update old guild_events + // update old guild *c.guilds[guild.ID] = *guild - return + return c.guilds[guild.ID] } - // guild_events was not yet cached so cache it directly + // guild was not yet cached so cache it directly c.guilds[guild.ID] = guild + c.guildCommands[guild.ID] = map[api.Snowflake]*api.Command{} c.members[guild.ID] = map[api.Snowflake]*api.Member{} c.roles[guild.ID] = map[api.Snowflake]*api.Role{} c.categories[guild.ID] = map[api.Snowflake]*api.Category{} c.textChannels[guild.ID] = map[api.Snowflake]*api.TextChannel{} c.voiceChannels[guild.ID] = map[api.Snowflake]*api.VoiceChannel{} c.storeChannels[guild.ID] = map[api.Snowflake]*api.StoreChannel{} + return guild } //UncacheGuild removes a guild and all of it's children from the cache func (c *CacheImpl) UncacheGuild(guildID api.Snowflake) { delete(c.guilds, guildID) + delete(c.guildCommands, guildID) delete(c.members, guildID) + delete(c.voiceStates, guildID) delete(c.roles, guildID) delete(c.categories, guildID) delete(c.textChannels, guildID) @@ -241,6 +347,66 @@ func (c *CacheImpl) FindGuilds(check func(g *api.Guild) bool) []*api.Guild { return guilds } +// Message returns a message from cache +func (c *CacheImpl) Message(channelID api.Snowflake, messageID api.Snowflake) *api.Message { + if channelMessages, ok := c.messages[channelID]; ok { + return channelMessages[messageID] + } + return nil +} + +// Messages returns the messages of a channel from cache +func (c *CacheImpl) Messages(channelID api.Snowflake) []*api.Message { + if channelMessages, ok := c.messages[channelID]; ok { + messages := make([]*api.Message, len(channelMessages)) + i := 0 + for _, message := range channelMessages { + messages[i] = message + i++ + } + return messages + } + return nil +} + +// MessageCache returns the entire cache of Message(s) for a Channel +func (c *CacheImpl) MessageCache(channelID api.Snowflake) map[api.Snowflake]*api.Message { + return c.messages[channelID] +} + +// AllMessageCache returns the entire cache of messages +func (c *CacheImpl) AllMessageCache() map[api.Snowflake]map[api.Snowflake]*api.Message { + return c.messages +} + +//CacheMessage adds a message to the cache +func (c *CacheImpl) CacheMessage(message *api.Message) *api.Message { + // only cache message if we want to + if !c.messageCachePolicy(message) { + return message + } + if channelMessages, ok := c.messages[message.ChannelID]; ok { + if channelMessage, ok := channelMessages[message.ID]; ok { + *channelMessage = *message + return channelMessage + } + channelMessages[message.ID] = message + } + return message +} + +// UncacheMessage removes a Message from the cache if the cache policy allows it +func (c *CacheImpl) UncacheMessage(channelID api.Snowflake, messageID api.Snowflake) { + if channelMessages, ok := c.messages[channelID]; ok { + if message, ok := channelMessages[messageID]; ok { + // check if we really want to uncache that message according to our policy + if !c.messageCachePolicy(message) { + delete(channelMessages, messageID) + } + } + } +} + // Member returns a member from cache by guild ID and user ID func (c *CacheImpl) Member(guildID api.Snowflake, userID api.Snowflake) *api.Member { if guildMembers, ok := c.members[guildID]; ok { @@ -314,19 +480,33 @@ func (c *CacheImpl) AllMemberCache() map[api.Snowflake]map[api.Snowflake]*api.Me } // CacheMember adds a member to the cache -func (c *CacheImpl) CacheMember(member *api.Member) { +func (c *CacheImpl) CacheMember(member *api.Member) *api.Member { + // only cache member if we want to & always cache self member! + if member.User.ID != member.Disgo.ApplicationID() && !c.memberCachePolicy(member) { + return member + } if guildMembers, ok := c.members[member.GuildID]; ok { - if _, ok := guildMembers[member.User.ID]; ok { - // update old guild_events - return + if guildMember, ok := guildMembers[member.User.ID]; ok { + *guildMember = *member + return guildMember } guildMembers[member.User.ID] = member } + return member } // UncacheMember removes a guild member from the cache func (c *CacheImpl) UncacheMember(guildID api.Snowflake, userID api.Snowflake) { - delete(c.members[guildID], userID) + // TODO: add UncacheUser call! + if guildMembers, ok := c.members[guildID]; ok { + if member, ok := guildMembers[userID]; ok { + // check if we really want to uncache that member according to our policy + if !c.memberCachePolicy(member) { + delete(guildMembers, userID) + } + } + } + } // FindMember allows you to find a member in a guild by custom method @@ -350,10 +530,60 @@ func (c *CacheImpl) FindMembers(guildID api.Snowflake, check func(u *api.Member) return members } +// VoiceState returns a Member's api.VoiceState for a api.Guild +func (c *CacheImpl) VoiceState(guildID api.Snowflake, userID api.Snowflake) *api.VoiceState { + if voiceStates, ok := c.voiceStates[guildID]; ok { + return voiceStates[userID] + } + return nil +} + +// VoiceStates returns the member cache of a guild by snowflake +func (c *CacheImpl) VoiceStates(guildID api.Snowflake) []*api.VoiceState { + if guildVoiceStates, ok := c.voiceStates[guildID]; ok { + voiceStates := make([]*api.VoiceState, len(guildVoiceStates)) + i := 0 + for _, voiceState := range guildVoiceStates { + voiceStates[i] = voiceState + i++ + } + return voiceStates + } + return nil +} + +// VoiceStateCache returns the api.VoiceState api.Cache of a api.Guild as a map +func (c *CacheImpl) VoiceStateCache(guildID api.Snowflake) map[api.Snowflake]*api.VoiceState { + return c.voiceStates[guildID] +} + +// CacheVoiceState adds a api.VoiceState from the api.Cache +func (c *CacheImpl) CacheVoiceState(voiceState *api.VoiceState) *api.VoiceState { + // only cache voice states for ourself or member is cached & cache flag activated + if voiceState.UserID != c.disgo.ApplicationID() && (!c.cacheFlags.Has(api.CacheFlagVoiceState) || c.Member(voiceState.GuildID, voiceState.UserID) == nil) { + return voiceState + } + if guildVoiceStates, ok := c.voiceStates[voiceState.GuildID]; ok { + if guildVoiceState, ok := guildVoiceStates[voiceState.UserID]; ok { + *guildVoiceState = *voiceState + return guildVoiceState + } + guildVoiceStates[voiceState.UserID] = voiceState + } + return voiceState +} + +// UncacheVoiceState removes a api.VoiceState from the api.Cache +func (c *CacheImpl) UncacheVoiceState(guildID api.Snowflake, userID api.Snowflake) { + delete(c.voiceStates[guildID], userID) +} + // Role returns a role from cache by guild ID and role ID -func (c *CacheImpl) Role(guildID api.Snowflake, roleID api.Snowflake) *api.Role { - if guildRoles, ok := c.roles[guildID]; ok { - return guildRoles[roleID] +func (c *CacheImpl) Role(roleID api.Snowflake) *api.Role { + for _, guildRoles := range c.roles { + if role, ok := guildRoles[roleID]; ok { + return role + } } return nil } @@ -411,14 +641,18 @@ func (c *CacheImpl) AllRoleCache() map[api.Snowflake]map[api.Snowflake]*api.Role } // CacheRole adds a role to the cache -func (c *CacheImpl) CacheRole(role *api.Role) { +func (c *CacheImpl) CacheRole(role *api.Role) *api.Role { + if !c.cacheFlags.Has(api.CacheFlagRoles) { + return role + } if guildRoles, ok := c.roles[role.GuildID]; ok { - if _, ok := guildRoles[role.ID]; ok { - // update old role - return + if guildRole, ok := guildRoles[role.ID]; ok { + *guildRole = *role + return guildRole } guildRoles[role.ID] = role } + return role } // UncacheRole removes a role from cache @@ -528,17 +762,26 @@ func (c *CacheImpl) DMChannelCache() map[api.Snowflake]*api.DMChannel { } // CacheDMChannel adds a DM channel to the cache -func (c *CacheImpl) CacheDMChannel(dmChannel *api.DMChannel) { +func (c *CacheImpl) CacheDMChannel(dmChannel *api.DMChannel) *api.DMChannel { + if !c.cacheFlags.Has(api.CacheFlagDMChannels) { + return dmChannel + } if oldChannel, ok := c.dmChannels[dmChannel.ID]; ok { *oldChannel = *dmChannel - return + return oldChannel } c.dmChannels[dmChannel.ID] = dmChannel + return dmChannel } // UncacheDMChannel removes a DM channel from cache -func (c *CacheImpl) UncacheDMChannel(channelID api.Snowflake) { - delete(c.dmChannels, channelID) +func (c *CacheImpl) UncacheDMChannel(dmChannelID api.Snowflake) { + // TODO: check this + // should be okay to just uncache all messages if the channel gets uncached as that should mean it got deleted + if _, ok := c.messages[dmChannelID]; ok { + delete(c.messages, dmChannelID) + } + delete(c.dmChannels, dmChannelID) } // FindDMChannel finds a DM channel in cache with a custom method @@ -625,18 +868,27 @@ func (c *CacheImpl) AllTextChannelCache() map[api.Snowflake]map[api.Snowflake]*a } // CacheTextChannel adds a channel to the cache -func (c *CacheImpl) CacheTextChannel(textChannel *api.TextChannel) { - if guildTextChannels, ok := c.textChannels[textChannel.GuildID]; ok { +func (c *CacheImpl) CacheTextChannel(textChannel *api.TextChannel) *api.TextChannel { + if !c.cacheFlags.Has(api.CacheFlagTextChannels) { + return textChannel + } + if guildTextChannels, ok := c.textChannels[*textChannel.GuildChannel.GuildID]; ok { if guildTextChannel, ok := guildTextChannels[textChannel.MessageChannel.ID]; ok { *guildTextChannel = *textChannel - return + return guildTextChannel } guildTextChannels[textChannel.MessageChannel.ID] = textChannel } + return textChannel } // UncacheTextChannel removes a text channel from the cache func (c *CacheImpl) UncacheTextChannel(guildID api.Snowflake, textChannelID api.Snowflake) { + // TODO: check this + // should be okay to just uncache all messages if the channel gets uncached as that should mean it got deleted + if _, ok := c.messages[textChannelID]; ok { + delete(c.messages, textChannelID) + } delete(c.textChannels[guildID], textChannelID) } @@ -724,14 +976,18 @@ func (c *CacheImpl) AllStoreChannelCache() map[api.Snowflake]map[api.Snowflake]* } // CacheStoreChannel adds a store channel to the cache -func (c *CacheImpl) CacheStoreChannel(storeChannel *api.StoreChannel) { - if guildStoreChannels, ok := c.storeChannels[storeChannel.GuildID]; ok { +func (c *CacheImpl) CacheStoreChannel(storeChannel *api.StoreChannel) *api.StoreChannel { + if !c.cacheFlags.Has(api.CacheFlagStoreChannels) { + return storeChannel + } + if guildStoreChannels, ok := c.storeChannels[*storeChannel.GuildID]; ok { if guildStoreChannel, ok := guildStoreChannels[storeChannel.ID]; ok { *guildStoreChannel = *storeChannel - return + return guildStoreChannel } guildStoreChannels[storeChannel.ID] = storeChannel } + return storeChannel } // UncacheStoreChannel removes a store channel from cache @@ -823,14 +1079,18 @@ func (c *CacheImpl) AllVoiceChannelCache() map[api.Snowflake]map[api.Snowflake]* } // CacheVoiceChannel adds a voice channel to cache -func (c *CacheImpl) CacheVoiceChannel(voiceChannel *api.VoiceChannel) { - if guildVoiceChannels, ok := c.voiceChannels[voiceChannel.GuildID]; ok { +func (c *CacheImpl) CacheVoiceChannel(voiceChannel *api.VoiceChannel) *api.VoiceChannel { + if !c.cacheFlags.Has(api.CacheFlagVoiceChannels) { + return voiceChannel + } + if guildVoiceChannels, ok := c.voiceChannels[*voiceChannel.GuildID]; ok { if guildVoiceChannel, ok := guildVoiceChannels[voiceChannel.ID]; ok { *guildVoiceChannel = *voiceChannel - return + return guildVoiceChannel } guildVoiceChannels[voiceChannel.ID] = voiceChannel } + return voiceChannel } // UncacheVoiceChannel removes a voice channel from cache @@ -861,8 +1121,8 @@ func (c *CacheImpl) FindVoiceChannels(guildID api.Snowflake, check func(u *api.V // Category returns a category from cache by ID func (c *CacheImpl) Category(categoryID api.Snowflake) *api.Category { - for _, guild := range c.categories { - if channel, ok := guild[categoryID]; ok { + for _, guildCategories := range c.categories { + if channel, ok := guildCategories[categoryID]; ok { return channel } } @@ -921,23 +1181,27 @@ func (c *CacheImpl) AllCategoryCache() map[api.Snowflake]map[api.Snowflake]*api. return c.categories } -//CacheCategory adds a category to the cache -func (c *CacheImpl) CacheCategory(category *api.Category) { - if guildCategories, ok := c.categories[category.GuildID]; ok { +// CacheCategory adds a category to the cache +func (c *CacheImpl) CacheCategory(category *api.Category) *api.Category { + if !c.cacheFlags.Has(api.CacheFlagCategories) { + return category + } + if guildCategories, ok := c.categories[*category.GuildID]; ok { if guildCategory, ok := guildCategories[category.ID]; ok { *guildCategory = *category - return + return guildCategory } guildCategories[category.ID] = category } + return category } -//UncacheCategory removes a category from cache +// UncacheCategory removes a category from cache func (c *CacheImpl) UncacheCategory(guildID api.Snowflake, categoryID api.Snowflake) { delete(c.categories[guildID], categoryID) } -//FindCategory finds a category in a guild by custom method +// FindCategory finds a category in a guild by custom method func (c *CacheImpl) FindCategory(guildID api.Snowflake, check func(u *api.Category) bool) *api.Category { for _, category := range c.CategoryCache(guildID) { if check(category) { @@ -947,7 +1211,7 @@ func (c *CacheImpl) FindCategory(guildID api.Snowflake, check func(u *api.Catego return nil } -//FindCategories finds categories in a guild by custom method +// FindCategories finds categories in a guild by custom method func (c *CacheImpl) FindCategories(guildID api.Snowflake, check func(u *api.Category) bool) []*api.Category { categories := make([]*api.Category, 1) for _, category := range c.CategoryCache(guildID) { @@ -957,3 +1221,74 @@ func (c *CacheImpl) FindCategories(guildID api.Snowflake, check func(u *api.Cate } return categories } + +// Emote returns a specific emote from the cache +func (c *CacheImpl) Emote(emoteID api.Snowflake) *api.Emote { + for _, guildEmotes := range c.emotes { + if emote, ok := guildEmotes[emoteID]; ok { + return emote + } + } + return nil +} + +// EmotesByName returns all emotes for a guild by name +func (c *CacheImpl) EmotesByName(guildID api.Snowflake, name string, ignoreCase bool) []*api.Emote { + if guildEmotes, ok := c.emotes[guildID]; ok { + if ignoreCase { + name = strings.ToLower(name) + } + emotes := make([]*api.Emote, 1) + for _, emote := range guildEmotes { + if ignoreCase && strings.ToLower(emote.Name) == name || !ignoreCase && emote.Name == name { + emotes = append(emotes, emote) + } + } + return emotes + } + return nil +} + +// Emotes returns all cached emotes for a guild +func (c *CacheImpl) Emotes(guildID api.Snowflake) []*api.Emote { + if guildEmotes, ok := c.emotes[guildID]; ok { + emotes := make([]*api.Emote, len(guildEmotes)) + i := 0 + for _, emote := range guildEmotes { + emotes[i] = emote + i++ + } + return emotes + } + return nil +} + +// EmoteCache returns the emote cache for a specific guild +func (c *CacheImpl) EmoteCache(guildID api.Snowflake) map[api.Snowflake]*api.Emote { + return c.emotes[guildID] +} + +// AllEmoteCache returns the full emote cache +func (c *CacheImpl) AllEmoteCache() map[api.Snowflake]map[api.Snowflake]*api.Emote { + return c.emotes +} + +// CacheEmote adds an Emote to the api.Cache if emoji caches are used +func (c *CacheImpl) CacheEmote(emote *api.Emote) *api.Emote { + if !c.cacheFlags.Has(api.CacheFlagEmotes) { + return emote + } + if guildEmotes, ok := c.emotes[emote.GuildID]; ok { + if guildEmote, ok := guildEmotes[emote.ID]; ok { + *guildEmote = *emote + return guildEmote + } + guildEmotes[emote.ID] = emote + } + return emote +} + +// UncacheEmote removes an Emote from api.Cache +func (c *CacheImpl) UncacheEmote(guildID api.Snowflake, emoteID api.Snowflake) { + delete(c.emotes[guildID], emoteID) +} diff --git a/internal/disgo.go b/internal/disgo.go deleted file mode 100644 index 480a6bfd..00000000 --- a/internal/disgo.go +++ /dev/null @@ -1,163 +0,0 @@ -package internal - -import ( - "time" - - log "github.com/sirupsen/logrus" - - "github.com/DisgoOrg/disgo/api" -) - -// New creates a new api.Disgo instance -func New(token string, options api.Options) (api.Disgo, error) { - disgo := &DisgoImpl{ - token: token, - intents: options.Intents, - } - - id, err := IDFromToken(token) - if err != nil { - log.Errorf("error while getting application id from token: %s", err) - return nil, err - } - - disgo.applicationID = *id - - disgo.restClient = newRestClientImpl(disgo, token) - - disgo.eventManager = newEventManagerImpl(disgo, make([]api.EventListener, 0)) - - if options.EnableWebhookInteractions { - disgo.webhookServer = newWebhookServerImpl(disgo, options.ListenURL, options.ListenPort, options.PublicKey) - } - - disgo.gateway = newGatewayImpl(disgo) - - return disgo, nil -} - -// DisgoImpl is the main discord client -type DisgoImpl struct { - token string - gateway api.Gateway - restClient api.RestClient - intents api.Intents - selfUser *api.User - eventManager api.EventManager - webhookServer api.WebhookServer - cache api.Cache - applicationID api.Snowflake -} - -// Connect opens the gateway connection to discord -func (d *DisgoImpl) Connect() error { - err := d.Gateway().Open() - if err != nil { - log.Errorf("Unable to connect to gateway. error: %s", err) - return err - } - return nil -} - -// Start starts the interaction webhook server -func (d *DisgoImpl) Start() error { - err := d.WebhookServer().Start() - if err != nil { - log.Errorf("Unable to connect to gateway. error: %s", err) - return err - } - return nil -} - -// Close will cleanup all disgo internals and close the discord connection safely -func (d *DisgoImpl) Close() { - d.RestClient().Close() - d.Gateway().Close() -} - -// Token returns the token of the client -func (d *DisgoImpl) Token() string { - return d.token -} - -// Gateway returns the websocket information -func (d *DisgoImpl) Gateway() api.Gateway { - return d.gateway -} - -// RestClient returns the HTTP client used by disgo -func (d *DisgoImpl) RestClient() api.RestClient { - return d.restClient -} - -// EventManager returns the api.EventManager -func (d *DisgoImpl) EventManager() api.EventManager { - return d.eventManager -} - -// WebhookServer returns the api.EventManager -func (d *DisgoImpl) WebhookServer() api.WebhookServer { - return d.webhookServer -} - -// Cache returns the entity api.Cache used by disgo -func (d *DisgoImpl) Cache() api.Cache { - return d.cache -} - -// Intents returns the Intents originally specified when creating the client -func (d *DisgoImpl) Intents() api.Intents { - // clones the intents so they can't be modified - c := d.intents - return c -} - -// ApplicationID returns the current application id -func (d *DisgoImpl) ApplicationID() api.Snowflake { - return d.applicationID -} - -// SelfUser returns a user object for the client, if available -func (d *DisgoImpl) SelfUser() *api.User { - return d.selfUser -} - -// SetSelfUser sets the self user -func (d *DisgoImpl) SetSelfUser(user *api.User) { - d.selfUser = user -} - -// HeartbeatLatency returns the heartbeat latency -func (d *DisgoImpl) HeartbeatLatency() time.Duration { - return d.Gateway().Latency() -} - -// GetCommand fetches a specific guild command -func (d DisgoImpl) GetCommand(commandID api.Snowflake) (*api.SlashCommand, error) { - return d.RestClient().GetGlobalCommand(d.ApplicationID(), commandID) -} - -// GetCommands fetches all guild commands -func (d DisgoImpl) GetCommands() ([]*api.SlashCommand, error) { - return d.RestClient().GetGlobalCommands(d.ApplicationID()) -} - -// CreateCommand creates a new command for this guild -func (d DisgoImpl) CreateCommand(command api.SlashCommand) (*api.SlashCommand, error) { - return d.RestClient().CreateGlobalCommand(d.ApplicationID(), command) -} - -// EditCommand edits a specific guild command -func (d DisgoImpl) EditCommand(commandID api.Snowflake, command api.SlashCommand) (*api.SlashCommand, error) { - return d.RestClient().EditGlobalCommand(d.ApplicationID(), commandID, command) -} - -// DeleteCommand creates a new command for this guild -func (d DisgoImpl) DeleteCommand(command api.SlashCommand) (*api.SlashCommand, error) { - return d.RestClient().CreateGlobalCommand(d.ApplicationID(), command) -} - -// SetCommands overrides all commands for this guild -func (d DisgoImpl) SetCommands(commands ...api.SlashCommand) ([]*api.SlashCommand, error) { - return d.RestClient().SetGlobalCommands(d.ApplicationID(), commands...) -} diff --git a/internal/disgo_builder.go b/internal/disgo_builder.go deleted file mode 100644 index c567a7b9..00000000 --- a/internal/disgo_builder.go +++ /dev/null @@ -1,155 +0,0 @@ -package internal - -import ( - "errors" - - log "github.com/sirupsen/logrus" - - "github.com/DisgoOrg/disgo/api" -) - -// NewBuilder returns a new api.DisgoBuilder instance -func NewBuilder(token string) api.DisgoBuilder { - return DisgoBuilderImpl{ - logLevel: log.InfoLevel, - token: &token, - } -} - -// DisgoBuilderImpl implementation of the api.DisgoBuilder interface -type DisgoBuilderImpl struct { - logLevel log.Level - token *string - gateway api.Gateway - restClient api.RestClient - cache api.Cache - memberCachePolicy api.MemberCachePolicy - intents api.Intents - eventManager api.EventManager - webhookServer api.WebhookServer - listenURL *string - listenPort *int - publicKey *string - eventListeners []api.EventListener -} - -// SetLogLevel sets logrus.Level of logrus -func (b DisgoBuilderImpl) SetLogLevel(logLevel log.Level) api.DisgoBuilder { - b.logLevel = logLevel - return b -} - -// SetToken sets the token to connect to discord -func (b DisgoBuilderImpl) SetToken(token string) api.DisgoBuilder { - b.token = &token - return b -} - -// SetIntents sets the api.Intents to connect to discord -func (b DisgoBuilderImpl) SetIntents(intents api.Intents) api.DisgoBuilder { - b.intents = intents - return b -} - -// SetEventManager lets you inject your own api.EventManager -func (b DisgoBuilderImpl) SetEventManager(eventManager api.EventManager) api.DisgoBuilder { - b.eventManager = eventManager - return b -} - -// AddEventListeners lets you add an api.EventListener to your api.EventManager -func (b DisgoBuilderImpl) AddEventListeners(eventListeners ...api.EventListener) api.DisgoBuilder { - for _, eventListener := range eventListeners { - b.eventListeners = append(b.eventListeners, eventListener) - } - return b -} - -// SetWebhookServer lets you inject your own api.EventManager -func (b DisgoBuilderImpl) SetWebhookServer(webhookServer api.WebhookServer) api.DisgoBuilder { - b.webhookServer = webhookServer - return b -} - -// SetWebhookServerProperties sets the default api.WebhookServer properties -func (b DisgoBuilderImpl) SetWebhookServerProperties(listenURL string, listenPort int, publicKey string) api.DisgoBuilder { - b.listenURL = &listenURL - b.listenPort = &listenPort - b.publicKey = &publicKey - return b -} - -// SetRestClient lets you inject your own api.RestClient -func (b DisgoBuilderImpl) SetRestClient(restClient api.RestClient) api.DisgoBuilder { - b.restClient = restClient - return b -} - -// SetCache lets you inject your own api.Cache -func (b DisgoBuilderImpl) SetCache(cache api.Cache) api.DisgoBuilder { - b.cache = cache - return b -} - -// SetMemberCachePolicy lets oyu set your own api.MemberCachePolicy -func (b DisgoBuilderImpl) SetMemberCachePolicy(memberCachePolicy api.MemberCachePolicy) api.DisgoBuilder { - b.memberCachePolicy = memberCachePolicy - return b -} - -// SetGateway lets you inject your own api.Gateway -func (b DisgoBuilderImpl) SetGateway(gateway api.Gateway) api.DisgoBuilder { - b.gateway = gateway - return b -} - -// Build builds your api.Disgo instance -func (b DisgoBuilderImpl) Build() (api.Disgo, error) { - log.SetLevel(b.logLevel) - - disgo := &DisgoImpl{} - if b.token == nil { - return nil, errors.New("please specify the token") - } - disgo.token = *b.token - - id, err := IDFromToken(disgo.token) - if err != nil { - log.Errorf("error while getting application id from token: %s", err) - return nil, err - } - - disgo.applicationID = *id - - if b.gateway == nil { - b.gateway = newGatewayImpl(disgo) - } - disgo.gateway = b.gateway - - if b.restClient == nil { - b.restClient = newRestClientImpl(disgo, *b.token) - } - disgo.restClient = b.restClient - - disgo.intents = b.intents - - if b.eventManager == nil { - b.eventManager = newEventManagerImpl(disgo, b.eventListeners) - } - disgo.eventManager = b.eventManager - - if b.webhookServer == nil && b.listenURL != nil && b.listenPort != nil && b.publicKey != nil { - b.webhookServer = newWebhookServerImpl(disgo, *b.listenURL, *b.listenPort, *b.publicKey) - } - disgo.webhookServer = b.webhookServer - - if b.cache == nil { - if b.memberCachePolicy == nil { - b.memberCachePolicy = api.MemberCachePolicyDefault - } - b.cache = newCacheImpl(b.memberCachePolicy) - } - disgo.cache = b.cache - - return disgo, nil -} diff --git a/internal/disgo_builder_impl.go b/internal/disgo_builder_impl.go new file mode 100644 index 00000000..31e9003d --- /dev/null +++ b/internal/disgo_builder_impl.go @@ -0,0 +1,228 @@ +package internal + +import ( + "errors" + + "github.com/DisgoOrg/log" + + "github.com/DisgoOrg/disgo/api" + "github.com/DisgoOrg/disgo/api/endpoints" +) + +// NewBuilder returns a new api.DisgoBuilder instance +func NewBuilder(token endpoints.Token) api.DisgoBuilder { + return &DisgoBuilderImpl{ + BotToken: token, + cacheFlags: api.CacheFlagsDefault, + } +} + +// DisgoBuilderImpl implementation of the api.DisgoBuilder interface +type DisgoBuilderImpl struct { + logger log.Logger + // make this public so it does not print in fmt.Sprint("%+v, DisgoBuilderImpl{}) + BotToken endpoints.Token + gateway api.Gateway + restClient api.RestClient + audioController api.AudioController + cache api.Cache + memberCachePolicy api.MemberCachePolicy + messageCachePolicy api.MessageCachePolicy + cacheFlags api.CacheFlags + intents api.Intents + rawGatewayEventsEnabled bool + entityBuilder api.EntityBuilder + eventManager api.EventManager + voiceDispatchInterceptor api.VoiceDispatchInterceptor + webhookServer api.WebhookServer + listenURL *string + listenPort *int + publicKey *string + eventListeners []api.EventListener +} + +// SetLogger sets logger implementation disgo should use as an example logrus +func (b *DisgoBuilderImpl) SetLogger(logger log.Logger) api.DisgoBuilder { + b.logger = logger + return b +} + +// SetToken sets the BotToken to connect to discord +func (b *DisgoBuilderImpl) SetToken(token endpoints.Token) api.DisgoBuilder { + b.BotToken = token + return b +} + +// SetIntents sets the api.Intents to connect to discord +func (b *DisgoBuilderImpl) SetIntents(intents api.Intents) api.DisgoBuilder { + b.intents = intents + return b +} + +// SetRawGatewayEventsEnabled enables/disables the events.RawGatewayEvent +func (b *DisgoBuilderImpl) SetRawGatewayEventsEnabled(enabled bool) api.DisgoBuilder { + b.rawGatewayEventsEnabled = enabled + return b +} + +// SetEntityBuilder lets you inject your own api.EntityBuilder +func (b *DisgoBuilderImpl) SetEntityBuilder(entityBuilder api.EntityBuilder) api.DisgoBuilder { + b.entityBuilder = entityBuilder + return b +} + +// SetEventManager lets you inject your own api.EventManager +func (b *DisgoBuilderImpl) SetEventManager(eventManager api.EventManager) api.DisgoBuilder { + b.eventManager = eventManager + return b +} + +// AddEventListeners lets you add an api.EventListener to your api.EventManager +func (b *DisgoBuilderImpl) AddEventListeners(eventListeners ...api.EventListener) api.DisgoBuilder { + for _, eventListener := range eventListeners { + b.eventListeners = append(b.eventListeners, eventListener) + } + return b +} + +// SetVoiceDispatchInterceptor sets the api.VoiceDispatchInterceptor +func (b *DisgoBuilderImpl) SetVoiceDispatchInterceptor(voiceDispatchInterceptor api.VoiceDispatchInterceptor) api.DisgoBuilder { + b.voiceDispatchInterceptor = voiceDispatchInterceptor + return b +} + +// SetWebhookServer lets you inject your own api.EventManager +func (b *DisgoBuilderImpl) SetWebhookServer(webhookServer api.WebhookServer) api.DisgoBuilder { + b.webhookServer = webhookServer + return b +} + +// SetWebhookServerProperties sets the default api.WebhookServer properties +func (b *DisgoBuilderImpl) SetWebhookServerProperties(listenURL string, listenPort int, publicKey string) api.DisgoBuilder { + b.listenURL = &listenURL + b.listenPort = &listenPort + b.publicKey = &publicKey + return b +} + +// SetRestClient lets you inject your own api.RestClient +func (b *DisgoBuilderImpl) SetRestClient(restClient api.RestClient) api.DisgoBuilder { + b.restClient = restClient + return b +} + +// SetAudioController lets you inject your own api.AudioController +func (b *DisgoBuilderImpl) SetAudioController(audioController api.AudioController) api.DisgoBuilder { + b.audioController = audioController + return b +} + +// SetCache lets you inject your own api.Cache +func (b *DisgoBuilderImpl) SetCache(cache api.Cache) api.DisgoBuilder { + b.cache = cache + return b +} + +// SetMemberCachePolicy lets you set your own api.MemberCachePolicy +func (b *DisgoBuilderImpl) SetMemberCachePolicy(memberCachePolicy api.MemberCachePolicy) api.DisgoBuilder { + b.memberCachePolicy = memberCachePolicy + return b +} + +// SetMessageCachePolicy lets you set your own api.MessageCachePolicy +func (b *DisgoBuilderImpl) SetMessageCachePolicy(messageCachePolicy api.MessageCachePolicy) api.DisgoBuilder { + b.messageCachePolicy = messageCachePolicy + return b +} + +// SetCacheFlags lets you set the api.CacheFlags +func (b *DisgoBuilderImpl) SetCacheFlags(cacheFlags api.CacheFlags) api.DisgoBuilder { + b.cacheFlags = cacheFlags + return b +} + +// EnableCacheFlags lets you enable certain api.CacheFlags +func (b *DisgoBuilderImpl) EnableCacheFlags(cacheFlags api.CacheFlags) api.DisgoBuilder { + b.cacheFlags.Add(cacheFlags) + return b +} + +// DisableCacheFlags lets you disable certain api.CacheFlags +func (b *DisgoBuilderImpl) DisableCacheFlags(cacheFlags api.CacheFlags) api.DisgoBuilder { + b.cacheFlags.Remove(cacheFlags) + return b +} + +// SetGateway lets you inject your own api.Gateway +func (b *DisgoBuilderImpl) SetGateway(gateway api.Gateway) api.DisgoBuilder { + b.gateway = gateway + return b +} + +// Build builds your api.Disgo instance +func (b *DisgoBuilderImpl) Build() (api.Disgo, error) { + + disgo := &DisgoImpl{ + logger: b.logger, + rawGatewayEventsEnabled: b.rawGatewayEventsEnabled, + } + if b.BotToken == "" { + return nil, errors.New("please specify the BotToken") + } + disgo.BotToken = b.BotToken + + id, err := IDFromToken(disgo.BotToken) + if err != nil { + disgo.Logger().Errorf("error while getting application id from BotToken: %s", err) + return nil, err + } + + disgo.selfUserID = *id + + if b.gateway == nil { + b.gateway = newGatewayImpl(disgo) + } + disgo.gateway = b.gateway + + if b.restClient == nil { + b.restClient = newRestClientImpl(disgo) + } + disgo.restClient = b.restClient + + if b.audioController == nil { + b.audioController = newAudioControllerImpl(disgo) + } + disgo.audioController = b.audioController + + disgo.intents = b.intents + + if b.entityBuilder == nil { + b.entityBuilder = newEntityBuilderImpl(disgo) + } + disgo.entityBuilder = b.entityBuilder + + if b.eventManager == nil { + b.eventManager = newEventManagerImpl(disgo, b.eventListeners) + } + disgo.eventManager = b.eventManager + + disgo.voiceDispatchInterceptor = b.voiceDispatchInterceptor + + if b.webhookServer == nil && b.listenURL != nil && b.listenPort != nil && b.publicKey != nil { + b.webhookServer = newWebhookServerImpl(disgo, *b.listenURL, *b.listenPort, *b.publicKey) + } + disgo.webhookServer = b.webhookServer + + if b.cache == nil { + if b.memberCachePolicy == nil { + b.memberCachePolicy = api.MemberCachePolicyDefault + } + if b.messageCachePolicy == nil { + b.messageCachePolicy = api.MessageCachePolicyDefault + } + b.cache = newCacheImpl(disgo, b.memberCachePolicy, b.messageCachePolicy, b.cacheFlags) + } + disgo.cache = b.cache + + return disgo, nil +} diff --git a/internal/disgo_impl.go b/internal/disgo_impl.go new file mode 100644 index 00000000..6b4dfc4f --- /dev/null +++ b/internal/disgo_impl.go @@ -0,0 +1,226 @@ +package internal + +import ( + "time" + + "github.com/DisgoOrg/log" + + "github.com/DisgoOrg/disgo/api" + "github.com/DisgoOrg/disgo/api/endpoints" +) + +// New creates a new api.Disgo instance +func New(token endpoints.Token, options api.Options) (api.Disgo, error) { + if options.LargeThreshold < 50 { + options.LargeThreshold = 50 + } else if options.LargeThreshold > 250 { + options.LargeThreshold = 250 + } + + disgo := &DisgoImpl{ + BotToken: token, + intents: options.Intents, + largeThreshold: options.LargeThreshold, + logger: options.Logger, + rawGatewayEventsEnabled: options.RawGatewayEventsEnabled, + } + + id, err := IDFromToken(token) + if err != nil { + disgo.Logger().Errorf("error while getting application id from BotToken: %s", err) + return nil, err + } + + disgo.selfUserID = *id + + disgo.restClient = newRestClientImpl(disgo) + + disgo.audioController = newAudioControllerImpl(disgo) + + disgo.entityBuilder = newEntityBuilderImpl(disgo) + + disgo.eventManager = newEventManagerImpl(disgo, []api.EventListener{}) + + if options.EnableWebhookInteractions { + disgo.webhookServer = newWebhookServerImpl(disgo, options.ListenURL, options.ListenPort, options.PublicKey) + } + + disgo.gateway = newGatewayImpl(disgo) + + return disgo, nil +} + +// DisgoImpl is the main discord client +type DisgoImpl struct { + // make this public so it does not print in fmt.Sprint("%+v, DisgoImpl{}) + BotToken endpoints.Token + logger log.Logger + gateway api.Gateway + restClient api.RestClient + intents api.Intents + rawGatewayEventsEnabled bool + entityBuilder api.EntityBuilder + eventManager api.EventManager + voiceDispatchInterceptor api.VoiceDispatchInterceptor + audioController api.AudioController + webhookServer api.WebhookServer + cache api.Cache + selfUserID api.Snowflake + largeThreshold int +} + +// Logger returns the logger instance disgo uses +func (d *DisgoImpl) Logger() log.Logger { + return d.logger +} + +// Connect opens the gateway connection to discord +func (d *DisgoImpl) Connect() error { + err := d.Gateway().Open() + if err != nil { + d.logger.Errorf("Unable to connect to gateway. error: %s", err) + return err + } + return nil +} + +// Start starts the interaction webhook server +func (d *DisgoImpl) Start() { + d.WebhookServer().Start() +} + +// Close will cleanup all disgo internals and close the discord connection safely +func (d *DisgoImpl) Close() { + if d.RestClient() != nil { + d.RestClient().Close() + } + if d.WebhookServer() != nil { + d.WebhookServer().Close() + } + if d.Gateway() != nil { + d.Gateway().Close() + } + if d.EventManager() != nil { + d.EventManager().Close() + } + if d.Cache() != nil { + d.Cache().Close() + } +} + +// Token returns the BotToken of the client +func (d *DisgoImpl) Token() endpoints.Token { + return d.BotToken +} + +// Gateway returns the websocket information +func (d *DisgoImpl) Gateway() api.Gateway { + return d.gateway +} + +// RestClient returns the HTTP client used by disgo +func (d *DisgoImpl) RestClient() api.RestClient { + return d.restClient +} + +// EntityBuilder returns the api.EntityBuilder +func (d *DisgoImpl) EntityBuilder() api.EntityBuilder { + return d.entityBuilder +} + +// EventManager returns the api.EventManager +func (d *DisgoImpl) EventManager() api.EventManager { + return d.eventManager +} + +// VoiceDispatchInterceptor returns the api.VoiceDispatchInterceptor +func (d *DisgoImpl) VoiceDispatchInterceptor() api.VoiceDispatchInterceptor { + return d.voiceDispatchInterceptor +} + +// SetVoiceDispatchInterceptor sets the api.VoiceDispatchInterceptor +func (d *DisgoImpl) SetVoiceDispatchInterceptor(voiceDispatchInterceptor api.VoiceDispatchInterceptor) { + d.voiceDispatchInterceptor = voiceDispatchInterceptor +} + +// AudioController returns the api.AudioController which can be used to connect/reconnect/disconnect to/fom a api.VoiceChannel +func (d *DisgoImpl) AudioController() api.AudioController { + return d.audioController +} + +// WebhookServer returns the api.EventManager +func (d *DisgoImpl) WebhookServer() api.WebhookServer { + return d.webhookServer +} + +// Cache returns the entity api.Cache used by disgo +func (d *DisgoImpl) Cache() api.Cache { + return d.cache +} + +// Intents returns the Intents originally specified when creating the client +func (d *DisgoImpl) Intents() api.Intents { + // clones the intents so they can't be modified + c := d.intents + return c +} + +// RawGatewayEventsEnabled returns if the events.RawGatewayEvent is enabled/disabled +func (d *DisgoImpl) RawGatewayEventsEnabled() bool { + return d.rawGatewayEventsEnabled +} + +// ApplicationID returns the current application id +func (d *DisgoImpl) ApplicationID() api.Snowflake { + return d.selfUserID +} + +// SelfUser returns a user object for the client, if available +func (d *DisgoImpl) SelfUser() *api.User { + return d.cache.User(d.selfUserID) +} + +// HeartbeatLatency returns the heartbeat latency +func (d *DisgoImpl) HeartbeatLatency() time.Duration { + return d.Gateway().Latency() +} + +// LargeThreshold returns the large threshold set +func (d *DisgoImpl) LargeThreshold() int { + return d.largeThreshold +} + +// HasGateway returns whether api.Disgo has an active api.Gateway connection or not +func (d DisgoImpl) HasGateway() bool { + return d.gateway != nil +} + +// GetCommand fetches a specific guild command +func (d DisgoImpl) GetCommand(commandID api.Snowflake) (*api.Command, error) { + return d.RestClient().GetGlobalCommand(d.ApplicationID(), commandID) +} + +// GetCommands fetches all guild commands +func (d DisgoImpl) GetCommands() ([]*api.Command, error) { + return d.RestClient().GetGlobalCommands(d.ApplicationID()) +} + +// CreateCommand creates a new command for this guild +func (d DisgoImpl) CreateCommand(command api.Command) (*api.Command, error) { + return d.RestClient().CreateGlobalCommand(d.ApplicationID(), command) +} + +// EditCommand edits a specific guild command +func (d DisgoImpl) EditCommand(commandID api.Snowflake, command api.UpdateCommand) (*api.Command, error) { + return d.RestClient().EditGlobalCommand(d.ApplicationID(), commandID, command) +} + +// DeleteCommand creates a new command for this guild +func (d DisgoImpl) DeleteCommand(command api.Command) (*api.Command, error) { + return d.RestClient().CreateGlobalCommand(d.ApplicationID(), command) +} + +// SetCommands overrides all commands for this guild +func (d DisgoImpl) SetCommands(commands ...api.Command) ([]*api.Command, error) { + return d.RestClient().SetGlobalCommands(d.ApplicationID(), commands...) +} diff --git a/internal/entity_builder_impl.go b/internal/entity_builder_impl.go new file mode 100644 index 00000000..d3c4976c --- /dev/null +++ b/internal/entity_builder_impl.go @@ -0,0 +1,196 @@ +package internal + +import ( + "github.com/DisgoOrg/disgo/api" +) + +func newEntityBuilderImpl(disgo api.Disgo) api.EntityBuilder { + return &EntityBuilderImpl{disgo: disgo} +} + +// EntityBuilderImpl is used for creating structs used by Disgo +type EntityBuilderImpl struct { + disgo api.Disgo +} + +// Disgo returns the api.Disgo client +func (b EntityBuilderImpl) Disgo() api.Disgo { + return b.disgo +} + +// CreateGlobalCommand returns a new api.Command entity +func (b EntityBuilderImpl) CreateGlobalCommand(command *api.Command, updateCache api.CacheStrategy) *api.Command { + command.Disgo = b.Disgo() + if updateCache(b.Disgo()) { + return b.Disgo().Cache().CacheGlobalCommand(command) + } + return command +} + +// CreateUser returns a new api.User entity +func (b EntityBuilderImpl) CreateUser(user *api.User, updateCache api.CacheStrategy) *api.User { + user.Disgo = b.Disgo() + if updateCache(b.Disgo()) { + return b.Disgo().Cache().CacheUser(user) + } + return user +} + +// CreateMessage returns a new api.Message entity +func (b EntityBuilderImpl) CreateMessage(message *api.Message, updateCache api.CacheStrategy) *api.Message { + message.Disgo = b.Disgo() + if message.Member != nil { + message.Member = b.CreateMember(*message.GuildID, message.Member, updateCache) + } + if message.Author != nil { + message.Author = b.CreateUser(message.Author, updateCache) + } + // TODO: should we cache mentioned users, members, etc? + if updateCache(b.Disgo()) { + return b.Disgo().Cache().CacheMessage(message) + } + return message +} + +// CreateGuild returns a new api.Guild entity +func (b EntityBuilderImpl) CreateGuild(guild *api.Guild, updateCache api.CacheStrategy) *api.Guild { + guild.Disgo = b.Disgo() + if updateCache(b.Disgo()) { + return b.Disgo().Cache().CacheGuild(guild) + } + return guild +} + +// CreateMember returns a new api.Member entity +func (b EntityBuilderImpl) CreateMember(guildID api.Snowflake, member *api.Member, updateCache api.CacheStrategy) *api.Member { + member.Disgo = b.Disgo() + member.GuildID = guildID + member.User = b.CreateUser(member.User, updateCache) + if updateCache(b.Disgo()) { + return b.Disgo().Cache().CacheMember(member) + } + return member +} + +// CreateVoiceState returns a new api.VoiceState entity +func (b EntityBuilderImpl) CreateVoiceState(voiceState *api.VoiceState, updateCache api.CacheStrategy) *api.VoiceState { + voiceState.Disgo = b.Disgo() + if updateCache(b.Disgo()) { + return b.Disgo().Cache().CacheVoiceState(voiceState) + } + return voiceState +} + +// CreateGuildCommand returns a new api.Command entity +func (b EntityBuilderImpl) CreateGuildCommand(guildID api.Snowflake, command *api.Command, updateCache api.CacheStrategy) *api.Command { + command.Disgo = b.Disgo() + command.GuildID = &guildID + if updateCache(b.Disgo()) { + return b.Disgo().Cache().CacheGuildCommand(command) + } + return command +} + +// CreateGuildCommandPermissions returns a new api.GuildCommandPermissions entity +func (b EntityBuilderImpl) CreateGuildCommandPermissions(guildCommandPermissions *api.GuildCommandPermissions, updateCache api.CacheStrategy) *api.GuildCommandPermissions { + guildCommandPermissions.Disgo = b.Disgo() + if updateCache(b.Disgo()) && b.Disgo().Cache().CacheFlags().Has(api.CacheFlagCommandPermissions) { + if cmd := b.Disgo().Cache().Command(guildCommandPermissions.ID); cmd != nil { + cmd.GuildPermissions[guildCommandPermissions.GuildID] = guildCommandPermissions + } + } + return guildCommandPermissions +} + +// CreateRole returns a new api.Role entity +func (b EntityBuilderImpl) CreateRole(guildID api.Snowflake, role *api.Role, updateCache api.CacheStrategy) *api.Role { + role.Disgo = b.Disgo() + role.GuildID = guildID + if updateCache(b.Disgo()) { + return b.Disgo().Cache().CacheRole(role) + } + return role +} + +// CreateTextChannel returns a new api.TextChannel entity +func (b EntityBuilderImpl) CreateTextChannel(channel *api.Channel, updateCache api.CacheStrategy) *api.TextChannel { + channel.Disgo = b.Disgo() + textChannel := &api.TextChannel{ + MessageChannel: api.MessageChannel{ + Channel: *channel, + }, + GuildChannel: api.GuildChannel{ + Channel: *channel, + }, + } + if updateCache(b.Disgo()) { + return b.Disgo().Cache().CacheTextChannel(textChannel) + } + return textChannel +} + +// CreateVoiceChannel returns a new api.VoiceChannel entity +func (b EntityBuilderImpl) CreateVoiceChannel(channel *api.Channel, updateCache api.CacheStrategy) *api.VoiceChannel { + channel.Disgo = b.Disgo() + voiceChannel := &api.VoiceChannel{ + GuildChannel: api.GuildChannel{ + Channel: *channel, + }, + } + if updateCache(b.Disgo()) { + return b.Disgo().Cache().CacheVoiceChannel(voiceChannel) + } + return voiceChannel +} + +// CreateStoreChannel returns a new api.StoreChannel entity +func (b EntityBuilderImpl) CreateStoreChannel(channel *api.Channel, updateCache api.CacheStrategy) *api.StoreChannel { + channel.Disgo = b.Disgo() + storeChannel := &api.StoreChannel{ + GuildChannel: api.GuildChannel{ + Channel: *channel, + }, + } + if updateCache(b.Disgo()) { + return b.Disgo().Cache().CacheStoreChannel(storeChannel) + } + return storeChannel +} + +// CreateCategory returns a new api.Category entity +func (b EntityBuilderImpl) CreateCategory(channel *api.Channel, updateCache api.CacheStrategy) *api.Category { + channel.Disgo = b.Disgo() + category := &api.Category{ + GuildChannel: api.GuildChannel{ + Channel: *channel, + }, + } + if updateCache(b.Disgo()) { + return b.Disgo().Cache().CacheCategory(category) + } + return category +} + +// CreateDMChannel returns a new api.DMChannel entity +func (b EntityBuilderImpl) CreateDMChannel(channel *api.Channel, updateCache api.CacheStrategy) *api.DMChannel { + channel.Disgo = b.Disgo() + dmChannel := &api.DMChannel{ + MessageChannel: api.MessageChannel{ + Channel: *channel, + }, + } + if updateCache(b.Disgo()) { + return b.Disgo().Cache().CacheDMChannel(dmChannel) + } + return dmChannel +} + +// CreateEmote returns a new api.Emote entity +func (b EntityBuilderImpl) CreateEmote(guildID api.Snowflake, emote *api.Emote, updateCache api.CacheStrategy) *api.Emote { + emote.Disgo = b.Disgo() + emote.GuildID = guildID + if updateCache(b.Disgo()) { + return b.Disgo().Cache().CacheEmote(emote) + } + return emote +} diff --git a/internal/event_manager.go b/internal/event_manager_impl.go similarity index 51% rename from internal/event_manager.go rename to internal/event_manager_impl.go index 0e9bcd5f..a6b112bc 100644 --- a/internal/event_manager.go +++ b/internal/event_manager_impl.go @@ -4,8 +4,6 @@ import ( "encoding/json" "runtime/debug" - log "github.com/sirupsen/logrus" - "github.com/DisgoOrg/disgo/api" "github.com/DisgoOrg/disgo/internal/handlers" ) @@ -13,12 +11,12 @@ import ( func newEventManagerImpl(disgo api.Disgo, listeners []api.EventListener) api.EventManager { eventManager := &EventManagerImpl{ disgo: disgo, - channel: make(chan api.GenericEvent), + channel: make(chan api.Event), listeners: listeners, - handlers: map[string]api.EventHandler{}, + handlers: map[api.GatewayEventType]api.EventHandler{}, } for _, handler := range handlers.GetAllHandlers() { - eventManager.handlers[handler.Name()] = handler + eventManager.handlers[handler.Event()] = handler } go eventManager.ListenEvents() return eventManager @@ -28,52 +26,67 @@ func newEventManagerImpl(disgo api.Disgo, listeners []api.EventListener) api.Eve type EventManagerImpl struct { disgo api.Disgo listeners []api.EventListener - handlers map[string]api.EventHandler - channel chan api.GenericEvent + handlers map[api.GatewayEventType]api.EventHandler + channel chan api.Event +} + +// Disgo returns the api.Disgo instance used by the api.EventManager +func (e *EventManagerImpl) Disgo() api.Disgo { + return e.disgo +} + +// Close closes all goroutines created by the api.EventManager +func (e *EventManagerImpl) Close() { + e.Disgo().Logger().Info("closing eventManager goroutines...") + close(e.channel) } // Handle calls the correct api.EventHandler -func (e EventManagerImpl) Handle(name string, payload json.RawMessage, c chan interface{}) { +func (e *EventManagerImpl) Handle(name api.GatewayEventType, c chan interface{}, sequenceNumber int, payload json.RawMessage) { if handler, ok := e.handlers[name]; ok { eventPayload := handler.New() if err := json.Unmarshal(payload, &eventPayload); err != nil { - log.Errorf("error while unmarshaling event. error: %s", err) + e.disgo.Logger().Errorf("error while unmarshaling event. error: %s", err) } switch h := handler.(type) { case api.GatewayEventHandler: - h.Handle(e.disgo, e, eventPayload) + h.HandleGatewayEvent(e.disgo, e, sequenceNumber, eventPayload) case api.WebhookEventHandler: - h.Handle(e.disgo, e, c, eventPayload) + h.HandleWebhookEvent(e.disgo, e, c, eventPayload) } } } // Dispatch dispatches a new event to the client -func (e EventManagerImpl) Dispatch(event api.GenericEvent) { - e.channel <- event +func (e *EventManagerImpl) Dispatch(event api.Event) { + go func() { + e.channel <- event + }() } // AddEventListeners adds one or more api.EventListener(s) to the api.EventManager -func (e EventManagerImpl) AddEventListeners(listeners ...api.EventListener) { +func (e *EventManagerImpl) AddEventListeners(listeners ...api.EventListener) { for _, listener := range listeners { e.listeners = append(e.listeners, listener) } } // ListenEvents starts the event goroutine -func (e EventManagerImpl) ListenEvents() { +func (e *EventManagerImpl) ListenEvents() { defer func() { if r := recover(); r != nil { - log.Errorf("recovered event listen goroutine error: %s", r) + e.Disgo().Logger().Panicf("recovered event listen goroutine error: %s", r) debug.PrintStack() e.ListenEvents() return } - log.Infof("closing event channel...") - close(e.channel) + e.Disgo().Logger().Infof("closed event goroutine") }() for { - event := <-e.channel + event, ok := <-e.channel + if !ok { + return + } for _, listener := range e.listeners { listener.OnEvent(event) } diff --git a/internal/gateway.go b/internal/gateway.go deleted file mode 100644 index 4b202655..00000000 --- a/internal/gateway.go +++ /dev/null @@ -1,283 +0,0 @@ -package internal - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io" - "runtime/debug" - "time" - - "github.com/gorilla/websocket" - log "github.com/sirupsen/logrus" - - "github.com/DisgoOrg/disgo/api" - "github.com/DisgoOrg/disgo/api/endpoints" -) - -func newGatewayImpl(disgo api.Disgo) api.Gateway { - return &GatewayImpl{ - disgo: disgo, - } -} - -// GatewayImpl is what is used to connect to discord -type GatewayImpl struct { - disgo api.Disgo - conn *websocket.Conn - quit chan interface{} - connectionStatus api.ConnectionStatus - heartbeatInterval time.Duration - lastHeartbeatSent time.Time - lastHeartbeatReceived time.Time - sessionID string - lastSequenceReceived *int - url *string -} - -// Disgo returns the gateway's disgo client -func (g *GatewayImpl) Disgo() api.Disgo { - return g.disgo -} - -// Open initializes the client and connection to discord -func (g *GatewayImpl) Open() error { - g.connectionStatus = api.Connecting - log.Info("starting ws...") - - if g.url == nil { - log.Debug("gateway url is nil, fetching...") - gatewayRs := api.GatewayRs{} - if err := g.Disgo().RestClient().Request(endpoints.GetGateway.Compile(), nil, &gatewayRs); err != nil { - return err - } - g.url = &gatewayRs.URL - } - - gatewayURL := *g.url + "?v=" + endpoints.APIVersion + "&encoding=json" - wsConn, _, err := websocket.DefaultDialer.Dial(gatewayURL, nil) - if err != nil { - log.Errorf("error connecting to gateway. url: %s, error: %s", gatewayURL, err.Error()) - return err - } - wsConn.SetCloseHandler(func(code int, error string) error { - log.Errorf("connection to websocket closed with code: %d, error: %s", code, error) - return nil - }) - - g.conn = wsConn - g.connectionStatus = api.WaitingForHello - - mt, data, err := g.conn.ReadMessage() - if err != nil { - return err - } - event, err := parseGatewayEvent(mt, data) - if err != nil { - return err - } - if event.Op != api.OpHello { - return fmt.Errorf("expected op: hello type: 10, received: %d", mt) - } - - g.lastHeartbeatReceived = time.Now().UTC() - - var eventData api.HelloEvent - if err = json.Unmarshal(event.D, &eventData); err != nil { - return err - } - - g.connectionStatus = api.Identifying - g.heartbeatInterval = eventData.HeartbeatInterval * time.Millisecond - - if err = wsConn.WriteJSON(api.IdentifyCommand{ - GatewayCommand: api.GatewayCommand{ - Op: api.OpIdentify, - }, - D: api.IdentifyCommandData{ - Token: g.Disgo().Token(), - Properties: api.IdentifyCommandDataProperties{ - OS: api.GetOS(), - Browser: "disgo", - Device: "disgo", - }, - Compress: false, - LargeThreshold: 50, - Intents: g.Disgo().Intents(), - }, - }); err != nil { - return err - } - - g.connectionStatus = api.WaitingForReady - g.quit = make(chan interface{}) - - go g.heartbeat() - go g.listen() - - return nil -} - -// Status returns the gateway connection status -func (g *GatewayImpl) Status() api.ConnectionStatus { - return g.connectionStatus -} - -func (g *GatewayImpl) heartbeat() { - defer func() { - if r := recover(); r != nil { - log.Errorf("recovered heartbeat goroutine error: %s", r) - debug.PrintStack() - g.heartbeat() - return - } - log.Info("shut down heartbeat goroutine") - }() - - ticker := time.NewTicker(g.heartbeatInterval) - for { - select { - case <-ticker.C: - g.sendHeartbeat() - case <-g.quit: - ticker.Stop() - return - } - } -} - -// Close cleans up the gateway internals -func (g *GatewayImpl) Close() { - if g.quit == nil { - return - } - log.Info("closing gateway goroutines...") - close(g.quit) - log.Info("closed gateway goroutines") -} - -// Latency returns the api.Gateway latency -func (g *GatewayImpl) Latency() time.Duration { - return g.lastHeartbeatSent.Sub(g.lastHeartbeatReceived) -} - -func (g *GatewayImpl) sendHeartbeat() { - log.Debug("sending heartbeat...") - - err := g.conn.WriteJSON(api.HeartbeatCommand{ - GatewayCommand: api.GatewayCommand{ - Op: api.OpHeartbeat, - }, - D: g.lastSequenceReceived, - }) - if err != nil { - log.Errorf("failed to send heartbeat with error: %s", err) - _ = g.conn.Close() - // Todo: reconnect - } - g.lastHeartbeatSent = time.Now().UTC() -} - -func (g *GatewayImpl) listen() { - defer func() { - if r := recover(); r != nil { - log.Errorf("recovered listen goroutine error: %s", r) - debug.PrintStack() - g.listen() - return - } - log.Info("shut down listen goroutine") - }() - for { - select { - case <-g.quit: - _ = g.conn.Close() - return - default: - mt, data, err := g.conn.ReadMessage() - if err != nil { - log.Errorf("error while reading from ws. error: %s", err) - } - - event, err := parseGatewayEvent(mt, data) - if err != nil { - log.Errorf("error while unpacking gateway event. error: %s", err) - } - - switch op := event.Op; op { - - case api.OpDispatch: - log.Debugf("received: OpDispatch") - if event.S != nil { - g.lastSequenceReceived = event.S - } - - log.Debugf("received: %s", *event.T) - - if event.T != nil && *event.T == api.ReadyGatewayEvent { - var readyEvent api.ReadyEventData - if err := parseEventToStruct(event, &readyEvent); err != nil { - log.Errorf("Error parsing ready event: %s", err) - continue - } - g.sessionID = readyEvent.SessionID - - log.Info("ready event received") - } - - if event.T == nil { - log.Errorf("received event without T. playload: %s", string(data)) - continue - } - d := g.Disgo() - e := d.EventManager() - e.Handle(*event.T, event.D, nil) - - case api.OpHeartbeat: - log.Debugf("received: OpHeartbeat") - g.sendHeartbeat() - - case api.OpReconnect: - log.Debugf("received: OpReconnect") - - case api.OpInvalidSession: - log.Debugf("received: OpInvalidSession") - - case api.OpHeartbeatACK: - log.Debugf("received: OpHeartbeatACK") - g.lastHeartbeatReceived = time.Now().UTC() - } - - } - - } -} - -func parseEventToStruct(event *api.RawGatewayCommand, v interface{}) error { - if err := json.Unmarshal(event.D, v); err != nil { - log.Errorf("error while unmarshaling event. error: %s", err) - return err - } - return nil -} - -func parseGatewayEvent(mt int, data []byte) (*api.RawGatewayCommand, error) { - - var reader io.Reader = bytes.NewBuffer(data) - - if mt == websocket.BinaryMessage { - return nil, errors.New("we don't handle compressed yet") - } - if mt != websocket.TextMessage { - return nil, fmt.Errorf("recieved unexpected message_events type: %d", mt) - } - var event api.RawGatewayCommand - - decoder := json.NewDecoder(reader) - if err := decoder.Decode(&event); err != nil { - log.Errorf("error decoding websocket message_events, %s", err) - return nil, err - } - return &event, nil -} diff --git a/internal/gateway_impl.go b/internal/gateway_impl.go new file mode 100644 index 00000000..f0856e80 --- /dev/null +++ b/internal/gateway_impl.go @@ -0,0 +1,397 @@ +package internal + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "runtime/debug" + "time" + + "github.com/DisgoOrg/disgo/api/events" + "github.com/gorilla/websocket" + + "github.com/DisgoOrg/disgo/api" + "github.com/DisgoOrg/disgo/api/endpoints" +) + +func newGatewayImpl(disgo api.Disgo) api.Gateway { + return &GatewayImpl{ + disgo: disgo, + status: api.Unconnected, + } +} + +// GatewayImpl is what is used to connect to discord +type GatewayImpl struct { + disgo api.Disgo + conn *websocket.Conn + quit chan interface{} + status api.GatewayStatus + heartbeatInterval time.Duration + lastHeartbeatSent time.Time + lastHeartbeatReceived time.Time + sessionID *string + lastSequenceReceived *int + url *string +} + +// Disgo returns the gateway's disgo client +func (g *GatewayImpl) Disgo() api.Disgo { + return g.disgo +} + +func (g *GatewayImpl) reconnect(delay time.Duration) { + go func() { + time.Sleep(delay) + + if g.Status() == api.Connecting || g.Status() == api.Reconnecting { + g.Disgo().Logger().Error("tried to reconnect gateway while connecting/reconnecting") + return + } + g.Disgo().Logger().Info("reconnecting gateway...") + if err := g.Open(); err != nil { + g.Disgo().Logger().Errorf("failed to reconnect gateway: %s", err) + g.status = api.Disconnected + g.reconnect(delay * 2) + } + }() +} + +// Open initializes the client and connection to discord +func (g *GatewayImpl) Open() error { + if g.lastSequenceReceived == nil || g.sessionID == nil { + g.status = api.Connecting + } else { + g.status = api.Reconnecting + } + + g.Disgo().Logger().Info("starting ws...") + + if g.url == nil { + g.Disgo().Logger().Debug("gateway url empty, fetching...") + gatewayRs := api.GatewayRs{} + compiledRoute, err := endpoints.GetGateway.Compile() + if err != nil { + return err + } + if err = g.Disgo().RestClient().Request(*compiledRoute, nil, &gatewayRs); err != nil { + return err + } + g.url = &gatewayRs.URL + } + + gatewayURL := *g.url + "?v=" + endpoints.APIVersion + "&encoding=json" + wsConn, rs, err := websocket.DefaultDialer.Dial(gatewayURL, nil) + if err != nil { + g.Close() + var body string + if rs != nil && rs.Body != nil { + rawBody, err := ioutil.ReadAll(rs.Body) + if err != nil { + g.Disgo().Logger().Errorf("error while reading response body: %s", err) + g.url = nil + return err + } + body = string(rawBody) + } else { + body = "null" + } + + g.Disgo().Logger().Errorf("error connecting to gateway. url: %s, error: %s, body: %s", gatewayURL, err.Error(), body) + return err + } + wsConn.SetCloseHandler(func(code int, error string) error { + g.Disgo().Logger().Infof("connection to websocket closed with code: %d, error: %s", code, error) + return nil + }) + + g.conn = wsConn + g.status = api.WaitingForHello + + mt, data, err := g.conn.ReadMessage() + if err != nil { + return err + } + event, err := g.parseGatewayEvent(mt, data) + if err != nil { + return err + } + if event.Op != api.OpHello { + return fmt.Errorf("expected op: hello type: 10, received: %d", mt) + } + + g.lastHeartbeatReceived = time.Now().UTC() + + var eventData api.HelloGatewayEventData + if err = json.Unmarshal(event.D, &eventData); err != nil { + return err + } + + g.heartbeatInterval = eventData.HeartbeatInterval * time.Millisecond + + if g.lastSequenceReceived == nil || g.sessionID == nil { + g.status = api.Identifying + g.Disgo().Logger().Infof("sending Identifying command...") + if err = wsConn.WriteJSON( + api.NewGatewayCommand(api.OpIdentify, api.IdentifyCommand{ + Token: g.Disgo().Token(), + Properties: api.IdentifyCommandDataProperties{ + OS: api.GetOS(), + Browser: "disgo", + Device: "disgo", + }, + Compress: false, + LargeThreshold: g.Disgo().LargeThreshold(), + Intents: g.Disgo().Intents(), + }), + ); err != nil { + return err + } + g.status = api.WaitingForReady + } else { + g.status = api.Resuming + cmd := api.NewGatewayCommand(api.OpResume, api.ResumeCommand{ + Token: g.Disgo().Token(), + SessionID: *g.sessionID, + Seq: *g.lastSequenceReceived, + }) + g.Disgo().Logger().Infof("sending Resuming command...") + + if err = wsConn.WriteJSON(cmd); err != nil { + return err + } + } + + g.quit = make(chan interface{}) + + go g.heartbeat() + go g.listen() + + return nil +} + +// Status returns the gateway connection status +func (g *GatewayImpl) Status() api.GatewayStatus { + return g.status +} + +// Latency returns the api.Gateway latency +func (g *GatewayImpl) Latency() time.Duration { + return g.lastHeartbeatReceived.Sub(g.lastHeartbeatSent) +} + +// Close cleans up the gateway internals +func (g *GatewayImpl) Close() { + g.closeWithCode(websocket.CloseNormalClosure) +} + +func (g *GatewayImpl) closeWithCode(code int) { + if g.quit != nil { + g.Disgo().Logger().Info("closing gateway goroutines...") + close(g.quit) + g.quit = nil + g.Disgo().Logger().Info("closed gateway goroutines") + } + if g.conn != nil { + err := g.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(code, "")) + if err != nil { + g.Disgo().Logger().Errorf("error writing close code: %s", err) + } + + // TODO: Wait for Discord to actually close the connection. + time.Sleep(1 * time.Second) + + err = g.conn.Close() + if err != nil { + g.Disgo().Logger().Errorf("error closing conn: %s", err) + } + g.conn = nil + } +} + +// Conn returns the underlying websocket.Conn of this api.Gateway +func (g *GatewayImpl) Conn() *websocket.Conn { + return g.conn +} + +func (g *GatewayImpl) heartbeat() { + defer func() { + if r := recover(); r != nil { + g.Disgo().Logger().Panicf("recovered heartbeat goroutine error: %s", r) + debug.PrintStack() + g.heartbeat() + return + } + g.Disgo().Logger().Info("shut down heartbeat goroutine") + }() + + ticker := time.NewTicker(g.heartbeatInterval) + for { + select { + case <-ticker.C: + g.sendHeartbeat() + case <-g.quit: + ticker.Stop() + return + } + } +} + +func (g *GatewayImpl) sendHeartbeat() { + g.Disgo().Logger().Debug("sending heartbeat...") + + heartbeatEvent := events.HeartbeatEvent{ + GenericEvent: events.NewEvent(g.Disgo(), 0), + OldPing: g.Latency(), + } + + if err := g.conn.WriteJSON(api.NewGatewayCommand(api.OpHeartbeat, g.lastSequenceReceived)); err != nil { + g.Disgo().Logger().Errorf("failed to send heartbeat with error: %s", err) + g.closeWithCode(websocket.CloseServiceRestart) + g.reconnect(1 * time.Second) + } + g.lastHeartbeatSent = time.Now().UTC() + + heartbeatEvent.NewPing = g.Latency() + g.Disgo().EventManager().Dispatch(heartbeatEvent) +} + +func (g *GatewayImpl) listen() { + defer func() { + if r := recover(); r != nil { + g.Disgo().Logger().Panicf("recovered listen goroutine error: %s", r) + debug.PrintStack() + g.listen() + return + } + g.Disgo().Logger().Info("shut down listen goroutine") + }() + for { + select { + case <-g.quit: + g.Disgo().Logger().Infof("existed listen routine") + return + default: + if g.conn == nil { + return + } + mt, data, err := g.conn.ReadMessage() + if err != nil { + g.Disgo().Logger().Errorf("error while reading from ws. error: %s", err) + g.closeWithCode(websocket.CloseServiceRestart) + g.reconnect(1 * time.Second) + return + } + + event, err := g.parseGatewayEvent(mt, data) + if err != nil { + g.Disgo().Logger().Errorf("error while unpacking gateway event. error: %s", err) + } + + switch op := event.Op; op { + + case api.OpDispatch: + g.Disgo().Logger().Debugf("received: OpDispatch") + if event.S != nil { + g.lastSequenceReceived = event.S + } + if event.T == nil { + g.Disgo().Logger().Errorf("received event without T. playload: %s", string(data)) + continue + } + + g.Disgo().Logger().Debugf("received: %s", *event.T) + + if *event.T == api.GatewayEventReady { + var readyEvent api.ReadyGatewayEvent + if err := g.parseEventToStruct(event, &readyEvent); err != nil { + g.Disgo().Logger().Errorf("Error parsing ready event: %s", err) + continue + } + g.sessionID = &readyEvent.SessionID + + g.Disgo().Logger().Info("ready event received") + } + + if g.Disgo().RawGatewayEventsEnabled() { + var payload map[string]interface{} + if err = g.parseEventToStruct(event, &payload); err != nil { + g.Disgo().Logger().Errorf("Error parsing raw gateway event: %s", err) + } + g.Disgo().EventManager().Dispatch(events.RawGatewayEvent{ + GenericEvent: events.NewEvent(g.Disgo(), *event.S), + Type: *event.T, + RawPayload: event.D, + Payload: payload, + }) + } + + d := g.Disgo() + e := d.EventManager() + e.Handle(*event.T, nil, *event.S, event.D) + + case api.OpHeartbeat: + g.Disgo().Logger().Debugf("received: OpHeartbeat") + g.sendHeartbeat() + + case api.OpReconnect: + g.Disgo().Logger().Debugf("received: OpReconnect") + g.closeWithCode(websocket.CloseServiceRestart) + g.reconnect(1 * time.Second) + + case api.OpInvalidSession: + var resumable bool + if err = g.parseEventToStruct(event, &resumable); err != nil { + g.Disgo().Logger().Errorf("Error parsing invalid session data: %s", err) + } + g.Disgo().Logger().Debugf("received: OpInvalidSession, resumable: %b", resumable) + if resumable { + g.closeWithCode(websocket.CloseServiceRestart) + } else { + g.Close() + // clear reconnect info + g.sessionID = nil + g.lastSequenceReceived = nil + } + g.reconnect(5 * time.Second) + + case api.OpHeartbeatACK: + g.Disgo().Logger().Debugf("received: OpHeartbeatACK") + g.lastHeartbeatReceived = time.Now().UTC() + } + + } + + } +} + +func (g *GatewayImpl) parseEventToStruct(event *api.RawGatewayEvent, v interface{}) error { + if err := json.Unmarshal(event.D, v); err != nil { + g.Disgo().Logger().Errorf("error while unmarshaling event. error: %s", err) + return err + } + return nil +} + +func (g *GatewayImpl) parseGatewayEvent(mt int, data []byte) (*api.RawGatewayEvent, error) { + + var reader io.Reader = bytes.NewBuffer(data) + + if mt == websocket.BinaryMessage { + return nil, errors.New("we don't handle compressed yet") + } + if mt != websocket.TextMessage { + return nil, fmt.Errorf("recieved unexpected message_events type: %d", mt) + } + var event api.RawGatewayEvent + + decoder := json.NewDecoder(reader) + if err := decoder.Decode(&event); err != nil { + g.Disgo().Logger().Errorf("error decoding websocket message_events, %s", err) + return nil, err + } + return &event, nil +} diff --git a/internal/handlers/all_handlers.go b/internal/handlers/all_handlers.go index e6a71d6d..e3f12a41 100644 --- a/internal/handlers/all_handlers.go +++ b/internal/handlers/all_handlers.go @@ -7,23 +7,34 @@ import ( // GetAllHandlers returns all api.GatewayEventHandler(s) func GetAllHandlers() []api.EventHandler { return []api.EventHandler{ - ReadyHandler{}, + ApplicationCommandCreateHandler{}, + ApplicationCommandDeleteHandler{}, + ApplicationCommandUpdateHandler{}, + + ChannelCreateHandler{}, + ChannelDeleteHandler{}, + ChannelUpdateHandler{}, GuildCreateHandler{}, - GuildUpdateHandler{}, GuildDeleteHandler{}, + GuildUpdateHandler{}, GuildMemberAddHandler{}, - GuildMemberUpdateHandler{}, GuildMemberRemoveHandler{}, + GuildMemberUpdateHandler{}, GuildRoleCreateHandler{}, - GuildRoleUpdateHandler{}, GuildRoleDeleteHandler{}, - - MessageCreateHandler{}, + GuildRoleUpdateHandler{}, InteractionCreateHandler{}, InteractionCreateWebhookHandler{}, + + MessageCreateHandler{}, + + ReadyHandler{}, + + VoiceServerUpdateHandler{}, + VoiceStateUpdateHandler{}, } } diff --git a/internal/handlers/application_command_create.go b/internal/handlers/application_command_create.go new file mode 100644 index 00000000..71d6de9e --- /dev/null +++ b/internal/handlers/application_command_create.go @@ -0,0 +1,51 @@ +package handlers + +import ( + "github.com/DisgoOrg/disgo/api" + "github.com/DisgoOrg/disgo/api/events" +) + +// ApplicationCommandCreateHandler handles api.ApplicationCommandCreateEvent +type ApplicationCommandCreateHandler struct{} + +// Event returns the raw gateway event Event +func (h ApplicationCommandCreateHandler) Event() api.GatewayEventType { + return api.GatewayEventApplicationCommandCreate +} + +// New constructs a new payload receiver for the raw gateway event +func (h ApplicationCommandCreateHandler) New() interface{} { + return &api.Command{} +} + +// HandleGatewayEvent handles the specific raw gateway event +func (h ApplicationCommandCreateHandler) HandleGatewayEvent(disgo api.Disgo, eventManager api.EventManager, sequenceNumber int, i interface{}) { + command, ok := i.(*api.Command) + if !ok { + return + } + + // only cache our own commands + cacheStrategy := api.CacheStrategyNo + if command.ApplicationID == disgo.ApplicationID() { + cacheStrategy = api.CacheStrategyYes + } + + if command.FromGuild() { + command = disgo.EntityBuilder().CreateGuildCommand(*command.GuildID, command, cacheStrategy) + } else { + command = disgo.EntityBuilder().CreateGlobalCommand(command, cacheStrategy) + } + + genericApplicationCommandEvent := events.GenericApplicationCommandEvent{ + GenericEvent: events.NewEvent(disgo, sequenceNumber), + CommandID: command.ID, + Command: command, + GuildID: command.GuildID, + } + eventManager.Dispatch(genericApplicationCommandEvent) + + eventManager.Dispatch(events.ApplicationCommandCreateEvent{ + GenericApplicationCommandEvent: genericApplicationCommandEvent, + }) +} diff --git a/internal/handlers/application_command_delete.go b/internal/handlers/application_command_delete.go new file mode 100644 index 00000000..d4353758 --- /dev/null +++ b/internal/handlers/application_command_delete.go @@ -0,0 +1,50 @@ +package handlers + +import ( + "github.com/DisgoOrg/disgo/api" + "github.com/DisgoOrg/disgo/api/events" +) + +// ApplicationCommandDeleteHandler handles api.ApplicationCommandCreateEvent +type ApplicationCommandDeleteHandler struct{} + +// Event returns the raw gateway event Event +func (h ApplicationCommandDeleteHandler) Event() api.GatewayEventType { + return api.GatewayEventApplicationCommandDelete +} + +// New constructs a new payload receiver for the raw gateway event +func (h ApplicationCommandDeleteHandler) New() interface{} { + return &api.Command{} +} + +// HandleGatewayEvent handles the specific raw gateway event +func (h ApplicationCommandDeleteHandler) HandleGatewayEvent(disgo api.Disgo, eventManager api.EventManager, sequenceNumber int, i interface{}) { + command, ok := i.(*api.Command) + if !ok { + return + } + + // we only cache our own commands + if command.ApplicationID == disgo.ApplicationID() { + disgo.Cache().UncacheCommand(command.ID) + } + + if command.FromGuild() { + command = disgo.EntityBuilder().CreateGuildCommand(*command.GuildID, command, api.CacheStrategyNo) + } else { + command = disgo.EntityBuilder().CreateGlobalCommand(command, api.CacheStrategyNo) + } + + genericApplicationCommandEvent := events.GenericApplicationCommandEvent{ + GenericEvent: events.NewEvent(disgo, sequenceNumber), + CommandID: command.ID, + Command: command, + GuildID: command.GuildID, + } + eventManager.Dispatch(genericApplicationCommandEvent) + + eventManager.Dispatch(events.ApplicationCommandDeleteEvent{ + GenericApplicationCommandEvent: genericApplicationCommandEvent, + }) +} diff --git a/internal/handlers/application_command_update.go b/internal/handlers/application_command_update.go new file mode 100644 index 00000000..341d7edc --- /dev/null +++ b/internal/handlers/application_command_update.go @@ -0,0 +1,55 @@ +package handlers + +import ( + "github.com/DisgoOrg/disgo/api" + "github.com/DisgoOrg/disgo/api/events" +) + +// ApplicationCommandUpdateHandler handles api.ApplicationCommandCreateEvent +type ApplicationCommandUpdateHandler struct{} + +// Event returns the raw gateway event Event +func (h ApplicationCommandUpdateHandler) Event() api.GatewayEventType { + return api.GatewayEventApplicationCommandUpdate +} + +// New constructs a new payload receiver for the raw gateway event +func (h ApplicationCommandUpdateHandler) New() interface{} { + return &api.Command{} +} + +// HandleGatewayEvent handles the specific raw gateway event +func (h ApplicationCommandUpdateHandler) HandleGatewayEvent(disgo api.Disgo, eventManager api.EventManager, sequenceNumber int, i interface{}) { + command, ok := i.(*api.Command) + if !ok { + return + } + + var oldCommand *api.Command + if command.ApplicationID == disgo.ApplicationID() { + oldCommand = disgo.Cache().Command(command.ID) + if oldCommand != nil { + oldCommand = &*oldCommand + } + } + + if command.FromGuild() { + command = disgo.EntityBuilder().CreateGuildCommand(*command.GuildID, command, api.CacheStrategyYes) + } else { + command = disgo.EntityBuilder().CreateGlobalCommand(command, api.CacheStrategyYes) + } + + genericApplicationCommandEvent := events.GenericApplicationCommandEvent{ + GenericEvent: events.NewEvent(disgo, sequenceNumber), + CommandID: command.ID, + Command: command, + GuildID: command.GuildID, + } + eventManager.Dispatch(genericApplicationCommandEvent) + + eventManager.Dispatch(events.ApplicationCommandUpdateEvent{ + GenericApplicationCommandEvent: genericApplicationCommandEvent, + // always nil for not our own commands + OldCommand: oldCommand, + }) +} diff --git a/internal/handlers/channel_create_handler.go b/internal/handlers/channel_create_handler.go new file mode 100644 index 00000000..23ef93e1 --- /dev/null +++ b/internal/handlers/channel_create_handler.go @@ -0,0 +1,107 @@ +package handlers + +import ( + "github.com/DisgoOrg/disgo/api" + "github.com/DisgoOrg/disgo/api/events" +) + +// ChannelCreateHandler handles api.GatewayEventChannelCreate +type ChannelCreateHandler struct{} + +// Event returns the raw gateway event Event +func (h ChannelCreateHandler) Event() api.GatewayEventType { + return api.GatewayEventChannelCreate +} + +// New constructs a new payload receiver for the raw gateway event +func (h ChannelCreateHandler) New() interface{} { + return &api.Channel{} +} + +// HandleGatewayEvent handles the specific raw gateway event +func (h ChannelCreateHandler) HandleGatewayEvent(disgo api.Disgo, eventManager api.EventManager, sequenceNumber int, i interface{}) { + channel, ok := i.(*api.Channel) + if !ok { + return + } + + genericChannelEvent := events.GenericChannelEvent{ + GenericEvent: events.NewEvent(disgo, sequenceNumber), + ChannelID: channel.ID, + } + eventManager.Dispatch(genericChannelEvent) + + switch channel.Type { + case api.ChannelTypeDM: + dmChannel := disgo.EntityBuilder().CreateDMChannel(channel, api.CacheStrategyYes) + + genericDMChannelEvent := events.GenericDMChannelEvent{ + GenericChannelEvent: genericChannelEvent, + DMChannel: dmChannel, + } + eventManager.Dispatch(genericDMChannelEvent) + + eventManager.Dispatch(events.DMChannelCreateEvent{ + GenericDMChannelEvent: genericDMChannelEvent, + }) + + case api.ChannelTypeGroupDM: + disgo.Logger().Warnf("ChannelTypeGroupDM received what the hell discord") + + case api.ChannelTypeText, api.ChannelTypeNews: + textChannel := disgo.EntityBuilder().CreateTextChannel(channel, api.CacheStrategyYes) + + genericTextChannelEvent := events.GenericTextChannelEvent{ + GenericChannelEvent: genericChannelEvent, + TextChannel: textChannel, + } + eventManager.Dispatch(genericTextChannelEvent) + + eventManager.Dispatch(events.TextChannelCreateEvent{ + GenericTextChannelEvent: genericTextChannelEvent, + }) + + case api.ChannelTypeStore: + storeChannel := disgo.EntityBuilder().CreateStoreChannel(channel, api.CacheStrategyYes) + + genericStoreChannelEvent := events.GenericStoreChannelEvent{ + GenericChannelEvent: genericChannelEvent, + StoreChannel: storeChannel, + } + eventManager.Dispatch(genericStoreChannelEvent) + + eventManager.Dispatch(events.StoreChannelCreateEvent{ + GenericStoreChannelEvent: genericStoreChannelEvent, + }) + + case api.ChannelTypeCategory: + category := disgo.EntityBuilder().CreateCategory(channel, api.CacheStrategyYes) + + genericCategoryEvent := events.GenericCategoryEvent{ + GenericChannelEvent: genericChannelEvent, + Category: category, + } + eventManager.Dispatch(genericCategoryEvent) + + eventManager.Dispatch(events.CategoryCreateEvent{ + GenericCategoryEvent: genericCategoryEvent, + }) + + case api.ChannelTypeVoice: + voiceChannel := disgo.EntityBuilder().CreateVoiceChannel(channel, api.CacheStrategyYes) + + genericVoiceChannelEvent := events.GenericVoiceChannelEvent{ + GenericChannelEvent: genericChannelEvent, + VoiceChannel: voiceChannel, + } + eventManager.Dispatch(genericVoiceChannelEvent) + + eventManager.Dispatch(events.VoiceChannelCreateEvent{ + GenericVoiceChannelEvent: genericVoiceChannelEvent, + }) + + default: + disgo.Logger().Warnf("unknown channel type received: %d", channel.Type) + } + +} diff --git a/internal/handlers/channel_delete_handler.go b/internal/handlers/channel_delete_handler.go new file mode 100644 index 00000000..5a06361d --- /dev/null +++ b/internal/handlers/channel_delete_handler.go @@ -0,0 +1,106 @@ +package handlers + +import ( + "github.com/DisgoOrg/disgo/api" + "github.com/DisgoOrg/disgo/api/events" +) + +// ChannelDeleteHandler handles api.GatewayEventChannelDelete +type ChannelDeleteHandler struct{} + +// Event returns the raw gateway event Event +func (h ChannelDeleteHandler) Event() api.GatewayEventType { + return api.GatewayEventChannelDelete +} + +// New constructs a new payload receiver for the raw gateway event +func (h ChannelDeleteHandler) New() interface{} { + return &api.Channel{} +} + +// HandleGatewayEvent handles the specific raw gateway event +func (h ChannelDeleteHandler) HandleGatewayEvent(disgo api.Disgo, eventManager api.EventManager, sequenceNumber int, i interface{}) { + channel, ok := i.(*api.Channel) + if !ok { + return + } + + genericChannelEvent := events.GenericChannelEvent{ + GenericEvent: events.NewEvent(disgo, sequenceNumber), + ChannelID: channel.ID, + } + eventManager.Dispatch(genericChannelEvent) + + switch channel.Type { + case api.ChannelTypeDM: + disgo.Cache().UncacheDMChannel(channel.ID) + + genericDMChannelEvent := events.GenericDMChannelEvent{ + GenericChannelEvent: genericChannelEvent, + DMChannel: disgo.EntityBuilder().CreateDMChannel(channel, api.CacheStrategyNo), + } + eventManager.Dispatch(genericDMChannelEvent) + + eventManager.Dispatch(events.DMChannelCreateEvent{ + GenericDMChannelEvent: genericDMChannelEvent, + }) + + case api.ChannelTypeGroupDM: + disgo.Logger().Warnf("ChannelTypeGroupDM received what the hell discord") + + case api.ChannelTypeText, api.ChannelTypeNews: + disgo.Cache().UncacheTextChannel(*channel.GuildID, channel.ID) + + genericTextChannelEvent := events.GenericTextChannelEvent{ + GenericChannelEvent: genericChannelEvent, + TextChannel: disgo.EntityBuilder().CreateTextChannel(channel, api.CacheStrategyNo), + } + eventManager.Dispatch(genericTextChannelEvent) + + eventManager.Dispatch(events.TextChannelCreateEvent{ + GenericTextChannelEvent: genericTextChannelEvent, + }) + + case api.ChannelTypeStore: + disgo.Cache().UncacheStoreChannel(*channel.GuildID, channel.ID) + + genericStoreChannelEvent := events.GenericStoreChannelEvent{ + GenericChannelEvent: genericChannelEvent, + StoreChannel: disgo.EntityBuilder().CreateStoreChannel(channel, api.CacheStrategyNo), + } + eventManager.Dispatch(genericStoreChannelEvent) + + eventManager.Dispatch(events.StoreChannelCreateEvent{ + GenericStoreChannelEvent: genericStoreChannelEvent, + }) + + case api.ChannelTypeCategory: + disgo.Cache().UncacheCategory(*channel.GuildID, channel.ID) + + genericCategoryEvent := events.GenericCategoryEvent{ + GenericChannelEvent: genericChannelEvent, + Category: disgo.EntityBuilder().CreateCategory(channel, api.CacheStrategyNo), + } + eventManager.Dispatch(genericCategoryEvent) + + eventManager.Dispatch(events.CategoryCreateEvent{ + GenericCategoryEvent: genericCategoryEvent, + }) + + case api.ChannelTypeVoice: + disgo.Cache().UncacheVoiceChannel(*channel.GuildID, channel.ID) + + genericVoiceChannelEvent := events.GenericVoiceChannelEvent{ + GenericChannelEvent: genericChannelEvent, + VoiceChannel: disgo.EntityBuilder().CreateVoiceChannel(channel, api.CacheStrategyNo), + } + eventManager.Dispatch(genericVoiceChannelEvent) + + eventManager.Dispatch(events.VoiceChannelCreateEvent{ + GenericVoiceChannelEvent: genericVoiceChannelEvent, + }) + + default: + disgo.Logger().Warnf("unknown channel type received: %d", channel.Type) + } +} diff --git a/internal/handlers/channel_pins_update_handler.go b/internal/handlers/channel_pins_update_handler.go new file mode 100644 index 00000000..5ac8282f --- /dev/null +++ b/internal/handlers/channel_pins_update_handler.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/handlers/channel_update_handler.go b/internal/handlers/channel_update_handler.go new file mode 100644 index 00000000..2320b53c --- /dev/null +++ b/internal/handlers/channel_update_handler.go @@ -0,0 +1,126 @@ +package handlers + +import ( + "github.com/DisgoOrg/disgo/api" + "github.com/DisgoOrg/disgo/api/events" +) + +// ChannelUpdateHandler handles api.GatewayEventChannelUpdate +type ChannelUpdateHandler struct{} + +// Event returns the raw gateway event Event +func (h ChannelUpdateHandler) Event() api.GatewayEventType { + return api.GatewayEventChannelUpdate +} + +// New constructs a new payload receiver for the raw gateway event +func (h ChannelUpdateHandler) New() interface{} { + return &api.Channel{} +} + +// HandleGatewayEvent handles the specific raw gateway event +func (h ChannelUpdateHandler) HandleGatewayEvent(disgo api.Disgo, eventManager api.EventManager, sequenceNumber int, i interface{}) { + channel, ok := i.(*api.Channel) + if !ok { + return + } + + genericChannelEvent := events.GenericChannelEvent{ + GenericEvent: events.NewEvent(disgo, sequenceNumber), + ChannelID: channel.ID, + } + eventManager.Dispatch(genericChannelEvent) + + switch channel.Type { + case api.ChannelTypeDM: + oldDMChannel := disgo.Cache().DMChannel(channel.ID) + if oldDMChannel != nil { + oldDMChannel = &*oldDMChannel + } + + genericDMChannelEvent := events.GenericDMChannelEvent{ + GenericChannelEvent: genericChannelEvent, + DMChannel: disgo.EntityBuilder().CreateDMChannel(channel, api.CacheStrategyYes), + } + eventManager.Dispatch(genericDMChannelEvent) + + eventManager.Dispatch(events.DMChannelUpdateEvent{ + GenericDMChannelEvent: genericDMChannelEvent, + OldDMChannel: oldDMChannel, + }) + + case api.ChannelTypeGroupDM: + disgo.Logger().Warnf("ChannelTypeGroupDM received what the hell discord") + + case api.ChannelTypeText, api.ChannelTypeNews: + oldTextChannel := disgo.Cache().TextChannel(channel.ID) + if oldTextChannel != nil { + oldTextChannel = &*oldTextChannel + } + + genericTextChannelEvent := events.GenericTextChannelEvent{ + GenericChannelEvent: genericChannelEvent, + TextChannel: disgo.EntityBuilder().CreateTextChannel(channel, api.CacheStrategyYes), + } + eventManager.Dispatch(genericTextChannelEvent) + + eventManager.Dispatch(events.TextChannelUpdateEvent{ + GenericTextChannelEvent: genericTextChannelEvent, + OldTextChannel: oldTextChannel, + }) + + case api.ChannelTypeStore: + oldStoreChannel := disgo.Cache().StoreChannel(channel.ID) + if oldStoreChannel != nil { + oldStoreChannel = &*oldStoreChannel + } + + genericStoreChannelEvent := events.GenericStoreChannelEvent{ + GenericChannelEvent: genericChannelEvent, + StoreChannel: disgo.EntityBuilder().CreateStoreChannel(channel, api.CacheStrategyYes), + } + eventManager.Dispatch(genericStoreChannelEvent) + + eventManager.Dispatch(events.StoreChannelUpdateEvent{ + GenericStoreChannelEvent: genericStoreChannelEvent, + OldStoreChannel: oldStoreChannel, + }) + + case api.ChannelTypeCategory: + oldCategory := disgo.Cache().Category(channel.ID) + if oldCategory != nil { + oldCategory = &*oldCategory + } + + genericCategoryEvent := events.GenericCategoryEvent{ + GenericChannelEvent: genericChannelEvent, + Category: disgo.EntityBuilder().CreateCategory(channel, api.CacheStrategyYes), + } + eventManager.Dispatch(genericCategoryEvent) + + eventManager.Dispatch(events.CategoryUpdateEvent{ + GenericCategoryEvent: genericCategoryEvent, + OldCategory: oldCategory, + }) + + case api.ChannelTypeVoice: + oldVoiceChannel := disgo.Cache().VoiceChannel(channel.ID) + if oldVoiceChannel != nil { + oldVoiceChannel = &*oldVoiceChannel + } + + genericVoiceChannelEvent := events.GenericVoiceChannelEvent{ + GenericChannelEvent: genericChannelEvent, + VoiceChannel: disgo.EntityBuilder().CreateVoiceChannel(channel, api.CacheStrategyYes), + } + eventManager.Dispatch(genericVoiceChannelEvent) + + eventManager.Dispatch(events.VoiceChannelUpdateEvent{ + GenericVoiceChannelEvent: genericVoiceChannelEvent, + OldVoiceChannel: oldVoiceChannel, + }) + + default: + disgo.Logger().Warnf("unknown channel type received: %d", channel.Type) + } +} diff --git a/internal/handlers/guild_ban_add_handler.go b/internal/handlers/guild_ban_add_handler.go new file mode 100644 index 00000000..5ac8282f --- /dev/null +++ b/internal/handlers/guild_ban_add_handler.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/handlers/guild_ban_remove_handler.go b/internal/handlers/guild_ban_remove_handler.go new file mode 100644 index 00000000..5ac8282f --- /dev/null +++ b/internal/handlers/guild_ban_remove_handler.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/handlers/guild_create_handler.go b/internal/handlers/guild_create_handler.go index defa9fdc..b0405f3e 100644 --- a/internal/handlers/guild_create_handler.go +++ b/internal/handlers/guild_create_handler.go @@ -8,85 +8,81 @@ import ( // GuildCreateHandler handles api.GuildCreateGatewayEvent type GuildCreateHandler struct{} -// Name returns the raw gateway event name -func (h GuildCreateHandler) Name() string { - return api.GuildCreateGatewayEvent +// Event returns the raw gateway event Event +func (h GuildCreateHandler) Event() api.GatewayEventType { + return api.GatewayEventGuildCreate } // New constructs a new payload receiver for the raw gateway event func (h GuildCreateHandler) New() interface{} { - return &api.Guild{} + return &api.FullGuild{} } -// Handle handles the specific raw gateway event -func (h GuildCreateHandler) Handle(disgo api.Disgo, eventManager api.EventManager, i interface{}) { - guild, ok := i.(*api.Guild) +// HandleGatewayEvent handles the specific raw gateway event +func (h GuildCreateHandler) HandleGatewayEvent(disgo api.Disgo, eventManager api.EventManager, sequenceNumber int, i interface{}) { + fullGuild, ok := i.(*api.FullGuild) if !ok { return } - guild.Disgo = disgo - oldGuild := disgo.Cache().Guild(guild.ID) - var wasUnavailable bool - if oldGuild == nil { - wasUnavailable = true - } else { + + oldGuild := disgo.Cache().Guild(fullGuild.ID) + wasUnavailable := true + if oldGuild != nil { + oldGuild = &*oldGuild wasUnavailable = oldGuild.Unavailable } + guild := disgo.EntityBuilder().CreateGuild(fullGuild.Guild, api.CacheStrategyYes) - disgo.Cache().CacheGuild(guild) - for i := range guild.Channels { - channel := guild.Channels[i] - channel.Disgo = disgo - channel.GuildID = guild.ID + for _, channel := range fullGuild.Channels { + channel.GuildID = &guild.ID switch channel.Type { case api.ChannelTypeText, api.ChannelTypeNews: - disgo.Cache().CacheTextChannel(&api.TextChannel{ - GuildChannel: *channel, - MessageChannel: api.MessageChannel{ - Channel: channel.Channel, - }, - }) + disgo.EntityBuilder().CreateTextChannel(channel, api.CacheStrategyYes) case api.ChannelTypeVoice: - disgo.Cache().CacheVoiceChannel(&api.VoiceChannel{ - GuildChannel: *channel, - }) + disgo.EntityBuilder().CreateVoiceChannel(channel, api.CacheStrategyYes) case api.ChannelTypeCategory: - disgo.Cache().CacheCategory(&api.Category{ - GuildChannel: *channel, - }) + disgo.EntityBuilder().CreateCategory(channel, api.CacheStrategyYes) case api.ChannelTypeStore: - disgo.Cache().CacheStoreChannel(&api.StoreChannel{ - GuildChannel: *channel, - }) + disgo.EntityBuilder().CreateStoreChannel(channel, api.CacheStrategyYes) } } - for i := range guild.Roles { - role := guild.Roles[i] - role.Disgo = disgo - role.GuildID = guild.ID - disgo.Cache().CacheRole(role) + for _, role := range fullGuild.Roles { + disgo.EntityBuilder().CreateRole(guild.ID, role, api.CacheStrategyYes) } - genericGuildEvent := events.GenericGuildEvent{ - Event: api.Event{ - Disgo: disgo, - }, - GuildID: guild.ID, + for _, member := range fullGuild.Members { + disgo.EntityBuilder().CreateMember(guild.ID, member, api.CacheStrategyYes) + } + + for _, voiceState := range fullGuild.VoiceStates { + disgo.EntityBuilder().CreateVoiceState(voiceState, api.CacheStrategyYes) } + for _, emote := range fullGuild.Emotes { + disgo.EntityBuilder().CreateEmote(guild.ID, emote, api.CacheStrategyYes) + } + + // TODO: presence + /*for i := range fullGuild.Presences { + presence := fullGuild.Presences[i] + presence.Disgo = disgo + disgo.Cache().CachePresence(presence) + }*/ + + genericGuildEvent := events.GenericGuildEvent{ + GenericEvent: events.NewEvent(disgo, sequenceNumber), + Guild: guild, + } eventManager.Dispatch(genericGuildEvent) if wasUnavailable { eventManager.Dispatch(events.GuildAvailableEvent{ GenericGuildEvent: genericGuildEvent, - Guild: guild, }) } else { - // guild join eventManager.Dispatch(events.GuildJoinEvent{ GenericGuildEvent: genericGuildEvent, - Guild: guild, }) } } diff --git a/internal/handlers/guild_delete_handler.go b/internal/handlers/guild_delete_handler.go index ecd97c90..1c4add1e 100644 --- a/internal/handlers/guild_delete_handler.go +++ b/internal/handlers/guild_delete_handler.go @@ -8,9 +8,9 @@ import ( // GuildDeleteHandler handles api.GuildDeleteGatewayEvent type GuildDeleteHandler struct{} -// Name returns the raw gateway event name -func (h GuildDeleteHandler) Name() string { - return api.GuildDeleteGatewayEvent +// Event returns the raw gateway event Event +func (h GuildDeleteHandler) Event() api.GatewayEventType { + return api.GatewayEventGuildDelete } // New constructs a new payload receiver for the raw gateway event @@ -18,39 +18,33 @@ func (h GuildDeleteHandler) New() interface{} { return &api.Guild{} } -// Handle handles the specific raw gateway event -func (h GuildDeleteHandler) Handle(disgo api.Disgo, eventManager api.EventManager, i interface{}) { +// HandleGatewayEvent handles the specific raw gateway event +func (h GuildDeleteHandler) HandleGatewayEvent(disgo api.Disgo, eventManager api.EventManager, sequenceNumber int, i interface{}) { guild, ok := i.(*api.Guild) if !ok { return } + guild = disgo.EntityBuilder().CreateGuild(guild, api.CacheStrategyNo) + + genericGuildEvent := events.GenericGuildEvent{ + GenericEvent: events.NewEvent(disgo, sequenceNumber), + Guild: guild, + } + eventManager.Dispatch(genericGuildEvent) + if guild.Unavailable { + // set guild to unavail for now disgo.Cache().Guild(guild.ID).Unavailable = true + eventManager.Dispatch(events.GuildUnavailableEvent{ - GenericGuildEvent: events.GenericGuildEvent{ - Event: api.Event{ - Disgo: disgo, - }, - GuildID: guild.ID, - }, + GenericGuildEvent: genericGuildEvent, }) } else { - cachedGuild := disgo.Cache().Guild(guild.ID) disgo.Cache().UncacheGuild(guild.ID) - genericGuildEvent := events.GenericGuildEvent{ - Event: api.Event{ - Disgo: disgo, - }, - GuildID: guild.ID, - } - - eventManager.Dispatch(genericGuildEvent) - eventManager.Dispatch(events.GuildLeaveEvent{ GenericGuildEvent: genericGuildEvent, - Guild: cachedGuild, }) } } diff --git a/internal/handlers/guild_emojis_update_handler.go b/internal/handlers/guild_emojis_update_handler.go new file mode 100644 index 00000000..5ac8282f --- /dev/null +++ b/internal/handlers/guild_emojis_update_handler.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/handlers/guild_integrations_update.go b/internal/handlers/guild_integrations_update.go new file mode 100644 index 00000000..5ac8282f --- /dev/null +++ b/internal/handlers/guild_integrations_update.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/handlers/guild_member_add_handler.go b/internal/handlers/guild_member_add_handler.go index b587e5a4..fc10eace 100644 --- a/internal/handlers/guild_member_add_handler.go +++ b/internal/handlers/guild_member_add_handler.go @@ -8,9 +8,9 @@ import ( // GuildMemberAddHandler handles api.GuildMemberAddGatewayEvent type GuildMemberAddHandler struct{} -// Name returns the raw gateway event name -func (h GuildMemberAddHandler) Name() string { - return api.GuildMemberAddGatewayEvent +// Event returns the raw gateway event Event +func (h GuildMemberAddHandler) Event() api.GatewayEventType { + return api.GatewayEventGuildMemberAdd } // New constructs a new payload receiver for the raw gateway event @@ -18,31 +18,33 @@ func (h GuildMemberAddHandler) New() interface{} { return &api.Member{} } -// Handle handles the specific raw gateway event -func (h GuildMemberAddHandler) Handle(disgo api.Disgo, eventManager api.EventManager, i interface{}) { +// HandleGatewayEvent handles the specific raw gateway event +func (h GuildMemberAddHandler) HandleGatewayEvent(disgo api.Disgo, eventManager api.EventManager, sequenceNumber int, i interface{}) { member, ok := i.(*api.Member) if !ok { return } - disgo.Cache().CacheMember(member) + guild := disgo.Cache().Guild(member.GuildID) + if guild == nil { + // todo: replay event later. maybe guild is not cached yet but in a few seconds + return + } + member = disgo.EntityBuilder().CreateMember(member.GuildID, member, api.CacheStrategyYes) genericGuildEvent := events.GenericGuildEvent{ - Event: api.Event{ - Disgo: disgo, - }, - GuildID: member.GuildID, + GenericEvent: events.NewEvent(disgo, sequenceNumber), + Guild: guild, } eventManager.Dispatch(genericGuildEvent) genericGuildMemberEvent := events.GenericGuildMemberEvent{ GenericGuildEvent: genericGuildEvent, - UserID: member.User.ID, + Member: member, } eventManager.Dispatch(genericGuildMemberEvent) eventManager.Dispatch(events.GuildMemberJoinEvent{ GenericGuildMemberEvent: genericGuildMemberEvent, - Member: member, }) } diff --git a/internal/handlers/guild_member_chunk.go b/internal/handlers/guild_member_chunk.go new file mode 100644 index 00000000..5ac8282f --- /dev/null +++ b/internal/handlers/guild_member_chunk.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/handlers/guild_member_remove_handler.go b/internal/handlers/guild_member_remove_handler.go index dd0566ea..6918e9e5 100644 --- a/internal/handlers/guild_member_remove_handler.go +++ b/internal/handlers/guild_member_remove_handler.go @@ -7,15 +7,15 @@ import ( type guildMemberRemoveData struct { GuildID api.Snowflake `json:"guild_id"` - User api.User `json:"user"` + User *api.User `json:"user"` } // GuildMemberRemoveHandler handles api.GuildMemberRemoveGatewayEvent type GuildMemberRemoveHandler struct{} -// Name returns the raw gateway event name -func (h GuildMemberRemoveHandler) Name() string { - return api.GuildMemberRemoveGatewayEvent +// Event returns the raw gateway event Event +func (h GuildMemberRemoveHandler) Event() api.GatewayEventType { + return api.GatewayEventGuildMemberRemove } // New constructs a new payload receiver for the raw gateway event @@ -23,32 +23,36 @@ func (h GuildMemberRemoveHandler) New() interface{} { return &guildMemberRemoveData{} } -// Handle handles the specific raw gateway event -func (h GuildMemberRemoveHandler) Handle(disgo api.Disgo, eventManager api.EventManager, i interface{}) { - member, ok := i.(*guildMemberRemoveData) +// HandleGatewayEvent handles the specific raw gateway event +func (h GuildMemberRemoveHandler) HandleGatewayEvent(disgo api.Disgo, eventManager api.EventManager, sequenceNumber int, i interface{}) { + memberData, ok := i.(*guildMemberRemoveData) if !ok { return } - oldMember := disgo.Cache().Member(member.GuildID, member.User.ID) - disgo.Cache().UncacheMember(member.GuildID, member.User.ID) + guild := disgo.Cache().Guild(memberData.GuildID) + if guild == nil { + // todo: replay event later. maybe guild is not cached yet but in a few seconds + return + } + memberData.User = disgo.EntityBuilder().CreateUser(memberData.User, api.CacheStrategyYes) + + member := disgo.Cache().Member(memberData.GuildID, memberData.User.ID) + disgo.Cache().UncacheMember(memberData.GuildID, memberData.User.ID) genericGuildEvent := events.GenericGuildEvent{ - Event: api.Event{ - Disgo: disgo, - }, - GuildID: member.GuildID, + GenericEvent: events.NewEvent(disgo, sequenceNumber), + Guild: guild, } eventManager.Dispatch(genericGuildEvent) genericGuildMemberEvent := events.GenericGuildMemberEvent{ GenericGuildEvent: genericGuildEvent, - UserID: member.User.ID, + Member: member, } eventManager.Dispatch(genericGuildMemberEvent) eventManager.Dispatch(events.GuildMemberLeaveEvent{ GenericGuildMemberEvent: genericGuildMemberEvent, - Member: oldMember, }) } diff --git a/internal/handlers/guild_member_update_handler.go b/internal/handlers/guild_member_update_handler.go index b5e076b0..7fc6a6f0 100644 --- a/internal/handlers/guild_member_update_handler.go +++ b/internal/handlers/guild_member_update_handler.go @@ -8,9 +8,9 @@ import ( // GuildMemberUpdateHandler handles api.GuildMemberUpdateGatewayEvent type GuildMemberUpdateHandler struct{} -// Name returns the raw gateway event name -func (h GuildMemberUpdateHandler) Name() string { - return api.GuildMemberUpdateGatewayEvent +// Event returns the raw gateway event Event +func (h GuildMemberUpdateHandler) Event() api.GatewayEventType { + return api.GatewayEventGuildMemberUpdate } // New constructs a new payload receiver for the raw gateway event @@ -18,33 +18,39 @@ func (h GuildMemberUpdateHandler) New() interface{} { return &api.Member{} } -// Handle handles the specific raw gateway event -func (h GuildMemberUpdateHandler) Handle(disgo api.Disgo, eventManager api.EventManager, i interface{}) { +// HandleGatewayEvent handles the specific raw gateway event +func (h GuildMemberUpdateHandler) HandleGatewayEvent(disgo api.Disgo, eventManager api.EventManager, sequenceNumber int, i interface{}) { member, ok := i.(*api.Member) if !ok { return } + guild := disgo.Cache().Guild(member.GuildID) + if guild == nil { + // todo: replay event later. maybe guild is not cached yet but in a few seconds + return + } + oldMember := disgo.Cache().Member(member.GuildID, member.User.ID) - disgo.Cache().CacheMember(member) + if oldMember != nil { + oldMember = &*oldMember + } + member = disgo.EntityBuilder().CreateMember(member.GuildID, member, api.CacheStrategyYes) genericGuildEvent := events.GenericGuildEvent{ - Event: api.Event{ - Disgo: disgo, - }, - GuildID: member.GuildID, + GenericEvent: events.NewEvent(disgo, sequenceNumber), + Guild: guild, } eventManager.Dispatch(genericGuildEvent) genericGuildMemberEvent := events.GenericGuildMemberEvent{ GenericGuildEvent: genericGuildEvent, - UserID: member.User.ID, + Member: member, } eventManager.Dispatch(genericGuildMemberEvent) eventManager.Dispatch(events.GuildMemberUpdateEvent{ GenericGuildMemberEvent: genericGuildMemberEvent, - NewMember: member, OldMember: oldMember, }) } diff --git a/internal/handlers/guild_role_create_handler.go b/internal/handlers/guild_role_create_handler.go index a62699ac..bfa10b7a 100644 --- a/internal/handlers/guild_role_create_handler.go +++ b/internal/handlers/guild_role_create_handler.go @@ -13,9 +13,9 @@ type roleCreateData struct { // GuildRoleCreateHandler handles api.GuildRoleCreateGatewayEvent type GuildRoleCreateHandler struct{} -// Name returns the raw gateway event name -func (h GuildRoleCreateHandler) Name() string { - return api.GuildRoleCreateGatewayEvent +// Event returns the raw gateway event Event +func (h GuildRoleCreateHandler) Event() api.GatewayEventType { + return api.GatewayEventGuildRoleCreate } // New constructs a new payload receiver for the raw gateway event @@ -23,32 +23,35 @@ func (h GuildRoleCreateHandler) New() interface{} { return &roleCreateData{} } -// Handle handles the specific raw gateway event -func (h GuildRoleCreateHandler) Handle(disgo api.Disgo, eventManager api.EventManager, i interface{}) { +// HandleGatewayEvent handles the specific raw gateway event +func (h GuildRoleCreateHandler) HandleGatewayEvent(disgo api.Disgo, eventManager api.EventManager, sequenceNumber int, i interface{}) { roleCreateData, ok := i.(*roleCreateData) if !ok { return } - roleCreateData.Role.Disgo = disgo - roleCreateData.Role.GuildID = roleCreateData.GuildID - disgo.Cache().CacheRole(roleCreateData.Role) + + guild := disgo.Cache().Guild(roleCreateData.GuildID) + if guild == nil { + // todo: replay event later. maybe guild is not cached yet but in a few seconds + return + } + + role := disgo.EntityBuilder().CreateRole(roleCreateData.GuildID, roleCreateData.Role, api.CacheStrategyYes) genericGuildEvent := events.GenericGuildEvent{ - Event: api.Event{ - Disgo: disgo, - }, - GuildID: roleCreateData.GuildID, + GenericEvent: events.NewEvent(disgo, sequenceNumber), + Guild: guild, } eventManager.Dispatch(genericGuildEvent) - genericRoleEvent := events.GenericGuildRoleEvent{ + genericRoleEvent := events.GenericRoleEvent{ GenericGuildEvent: genericGuildEvent, - Role: roleCreateData.Role, - RoleID: roleCreateData.Role.ID, + RoleID: role.ID, + Role: role, } eventManager.Dispatch(genericRoleEvent) - eventManager.Dispatch(events.GuildRoleCreateEvent{ + eventManager.Dispatch(events.RoleCreateEvent{ GenericGuildEvent: genericGuildEvent, }) } diff --git a/internal/handlers/guild_role_delete_handler.go b/internal/handlers/guild_role_delete_handler.go index 20d0ac55..b835541d 100644 --- a/internal/handlers/guild_role_delete_handler.go +++ b/internal/handlers/guild_role_delete_handler.go @@ -13,9 +13,9 @@ type roleDeleteData struct { // GuildRoleDeleteHandler handles api.GuildRoleDeleteGatewayEvent type GuildRoleDeleteHandler struct{} -// Name returns the raw gateway event name -func (h GuildRoleDeleteHandler) Name() string { - return api.GuildRoleDeleteGatewayEvent +// Event returns the raw gateway event Event +func (h GuildRoleDeleteHandler) Event() api.GatewayEventType { + return api.GatewayEventGuildRoleDelete } // New constructs a new payload receiver for the raw gateway event @@ -23,32 +23,38 @@ func (h GuildRoleDeleteHandler) New() interface{} { return &roleCreateData{} } -// Handle handles the specific raw gateway event -func (h GuildRoleDeleteHandler) Handle(disgo api.Disgo, eventManager api.EventManager, i interface{}) { +// HandleGatewayEvent handles the specific raw gateway event +func (h GuildRoleDeleteHandler) HandleGatewayEvent(disgo api.Disgo, eventManager api.EventManager, sequenceNumber int, i interface{}) { roleDeleteData, ok := i.(*roleDeleteData) if !ok { return } - role := *disgo.Cache().Role(roleDeleteData.GuildID, roleDeleteData.RoleID) - disgo.Cache().UncacheRole(roleDeleteData.GuildID, roleDeleteData.RoleID) + guild := disgo.Cache().Guild(roleDeleteData.GuildID) + if guild == nil { + // todo: replay event later. maybe guild is not cached yet but in a few seconds + return + } + + role := disgo.Cache().Role(roleDeleteData.RoleID) + if role != nil { + disgo.Cache().UncacheRole(roleDeleteData.GuildID, roleDeleteData.RoleID) + } genericGuildEvent := events.GenericGuildEvent{ - Event: api.Event{ - Disgo: disgo, - }, - GuildID: roleDeleteData.GuildID, + GenericEvent: events.NewEvent(disgo, sequenceNumber), + Guild: guild, } eventManager.Dispatch(genericGuildEvent) - genericRoleEvent := events.GenericGuildRoleEvent{ + genericRoleEvent := events.GenericRoleEvent{ GenericGuildEvent: genericGuildEvent, - Role: &role, RoleID: roleDeleteData.RoleID, + Role: role, } eventManager.Dispatch(genericRoleEvent) - eventManager.Dispatch(events.GuildRoleDeleteEvent{ + eventManager.Dispatch(events.RoleDeleteEvent{ GenericGuildEvent: genericGuildEvent, }) } diff --git a/internal/handlers/guild_role_update_handler.go b/internal/handlers/guild_role_update_handler.go index de9cb6df..2182612e 100644 --- a/internal/handlers/guild_role_update_handler.go +++ b/internal/handlers/guild_role_update_handler.go @@ -13,9 +13,9 @@ type roleUpdateData struct { // GuildRoleUpdateHandler handles api.GuildRoleUpdateGatewayEvent type GuildRoleUpdateHandler struct{} -// Name returns the raw gateway event name -func (h GuildRoleUpdateHandler) Name() string { - return api.GuildRoleUpdateGatewayEvent +// Event returns the raw gateway event Event +func (h GuildRoleUpdateHandler) Event() api.GatewayEventType { + return api.GatewayEventGuildRoleUpdate } // New constructs a new payload receiver for the raw gateway event @@ -23,35 +23,40 @@ func (h GuildRoleUpdateHandler) New() interface{} { return &roleUpdateData{} } -// Handle handles the specific raw gateway event -func (h GuildRoleUpdateHandler) Handle(disgo api.Disgo, eventManager api.EventManager, i interface{}) { +// HandleGatewayEvent handles the specific raw gateway event +func (h GuildRoleUpdateHandler) HandleGatewayEvent(disgo api.Disgo, eventManager api.EventManager, sequenceNumber int, i interface{}) { roleUpdateData, ok := i.(*roleUpdateData) if !ok { return } - roleUpdateData.Role.Disgo = disgo - roleUpdateData.Role.GuildID = roleUpdateData.GuildID - oldRole := *disgo.Cache().Role(roleUpdateData.GuildID, roleUpdateData.Role.ID) - disgo.Cache().CacheRole(roleUpdateData.Role) + guild := disgo.Cache().Guild(roleUpdateData.GuildID) + if guild == nil { + // todo: replay event later. maybe guild is not cached yet but in a few seconds + return + } + + oldRole := disgo.Cache().Role(roleUpdateData.Role.ID) + if oldRole != nil { + oldRole = &*oldRole + } + role := disgo.EntityBuilder().CreateRole(roleUpdateData.GuildID, roleUpdateData.Role, api.CacheStrategyYes) genericGuildEvent := events.GenericGuildEvent{ - Event: api.Event{ - Disgo: disgo, - }, - GuildID: roleUpdateData.GuildID, + GenericEvent: events.NewEvent(disgo, sequenceNumber), + Guild: guild, } eventManager.Dispatch(genericGuildEvent) - genericRoleEvent := events.GenericGuildRoleEvent{ + genericRoleEvent := events.GenericRoleEvent{ GenericGuildEvent: genericGuildEvent, - Role: roleUpdateData.Role, RoleID: roleUpdateData.Role.ID, + Role: role, } eventManager.Dispatch(genericRoleEvent) - eventManager.Dispatch(events.GuildRoleUpdateEvent{ + eventManager.Dispatch(events.RoleUpdateEvent{ GenericGuildEvent: genericGuildEvent, - OldRole: &oldRole, + OldRole: oldRole, }) } diff --git a/internal/handlers/guild_update_handler.go b/internal/handlers/guild_update_handler.go index 90def1e5..0179d101 100644 --- a/internal/handlers/guild_update_handler.go +++ b/internal/handlers/guild_update_handler.go @@ -8,9 +8,9 @@ import ( // GuildUpdateHandler handles api.GuildUpdateGatewayEvent type GuildUpdateHandler struct{} -// Name returns the raw gateway event name -func (h GuildUpdateHandler) Name() string { - return api.GuildUpdateGatewayEvent +// Event returns the raw gateway event Event +func (h GuildUpdateHandler) Event() api.GatewayEventType { + return api.GatewayEventGuildUpdate } // New constructs a new payload receiver for the raw gateway event @@ -18,28 +18,28 @@ func (h GuildUpdateHandler) New() interface{} { return &api.Guild{} } -// Handle handles the specific raw gateway event -func (h GuildUpdateHandler) Handle(disgo api.Disgo, eventManager api.EventManager, i interface{}) { +// HandleGatewayEvent handles the specific raw gateway event +func (h GuildUpdateHandler) HandleGatewayEvent(disgo api.Disgo, eventManager api.EventManager, sequenceNumber int, i interface{}) { guild, ok := i.(*api.Guild) if !ok { return } - oldGuild := *disgo.Cache().Guild(guild.ID) - disgo.Cache().CacheGuild(guild) + oldGuild := disgo.Cache().Guild(guild.ID) + if oldGuild != nil { + oldGuild = &*oldGuild + } + guild = disgo.EntityBuilder().CreateGuild(guild, api.CacheStrategyYes) genericGuildEvent := events.GenericGuildEvent{ - Event: api.Event{ - Disgo: disgo, - }, - GuildID: guild.ID, + GenericEvent: events.NewEvent(disgo, sequenceNumber), + Guild: guild, } - eventManager.Dispatch(genericGuildEvent) + eventManager.Dispatch(events.GuildUpdateEvent{ GenericGuildEvent: genericGuildEvent, - Guild: guild, - OldGuild: &oldGuild, + OldGuild: oldGuild, }) } diff --git a/internal/handlers/interaction_create_handler.go b/internal/handlers/interaction_create_handler.go index 82072329..861fa375 100644 --- a/internal/handlers/interaction_create_handler.go +++ b/internal/handlers/interaction_create_handler.go @@ -8,9 +8,9 @@ import ( // InteractionCreateHandler handles api.InteractionCreateGatewayEvent type InteractionCreateHandler struct{} -// Name returns the raw gateway event name -func (h InteractionCreateHandler) Name() string { - return api.InteractionCreateGatewayEvent +// Event returns the raw gateway event Event +func (h InteractionCreateHandler) Event() api.GatewayEventType { + return api.GatewayEventInteractionCreate } // New constructs a new payload receiver for the raw gateway event @@ -18,20 +18,25 @@ func (h InteractionCreateHandler) New() interface{} { return &api.Interaction{} } -// Handle handles the specific raw gateway event -func (h InteractionCreateHandler) Handle(disgo api.Disgo, eventManager api.EventManager, i interface{}) { +// HandleGatewayEvent handles the specific raw gateway event +func (h InteractionCreateHandler) HandleGatewayEvent(disgo api.Disgo, eventManager api.EventManager, sequenceNumber int, i interface{}) { interaction, ok := i.(*api.Interaction) if !ok { return } - handleInteractions(disgo, eventManager, nil, interaction) + handleInteraction(disgo, eventManager, sequenceNumber, interaction, nil) } -func handleInteractions(disgo api.Disgo, eventManager api.EventManager, c chan interface{}, interaction *api.Interaction) { +func handleInteraction(disgo api.Disgo, eventManager api.EventManager, sequenceNumber int, interaction *api.Interaction, c chan interface{}) { if interaction.Member != nil { + interaction.Member.Disgo = disgo + if interaction.Member.User != nil { + interaction.Member.User.Disgo = disgo + } disgo.Cache().CacheMember(interaction.Member) } if interaction.User != nil { + interaction.User.Disgo = disgo disgo.Cache().CacheUser(interaction.User) } @@ -39,55 +44,57 @@ func handleInteractions(disgo api.Disgo, eventManager api.EventManager, c chan i resolved := interaction.Data.Resolved if resolved.Users != nil { for _, user := range resolved.Users { + user.Disgo = disgo disgo.Cache().CacheUser(user) } } if resolved.Members != nil { for id, member := range resolved.Members { member.User = resolved.Users[id] + member.Disgo = disgo disgo.Cache().CacheMember(member) } } if resolved.Roles != nil { for _, role := range resolved.Roles { + role.Disgo = disgo disgo.Cache().CacheRole(role) } } // TODO how do we cache partial channels? /*if resolved.Channels != nil { - for _, user := range resolved.Users { - disgo.Cache().CacheChannel(user) + for _, channel := range resolved.Channels { + channel.Disgo = disgo + disgo.Cache().CacheChannel(channel) } }*/ } genericInteractionEvent := events.GenericInteractionEvent{ - Event: api.Event{ - Disgo: disgo, - }, - Interaction: *interaction, + GenericEvent: events.NewEvent(disgo, sequenceNumber), + Interaction: interaction, } eventManager.Dispatch(genericInteractionEvent) if interaction.Data != nil { options := interaction.Data.Options var subCommandName *string - var subCommandGroup *string + var subCommandGroupName *string if len(options) == 1 { option := interaction.Data.Options[0] - if option.Type == api.OptionTypeSubCommandGroup { - subCommandGroup = &option.Name + if option.Type == api.CommandOptionTypeSubCommandGroup { + subCommandGroupName = &option.Name options = option.Options option = option.Options[0] } - if option.Type == api.OptionTypeSubCommand { + if option.Type == api.CommandOptionTypeSubCommand { subCommandName = &option.Name options = option.Options } } - var newOptions []*events.Option + var newOptions []*api.Option for _, optionData := range options { - newOptions = append(newOptions, &events.Option{ + newOptions = append(newOptions, &api.Option{ Resolved: interaction.Data.Resolved, Name: optionData.Name, Type: optionData.Type, @@ -100,9 +107,9 @@ func handleInteractions(disgo api.Disgo, eventManager api.EventManager, c chan i FromWebhook: c != nil, GenericInteractionEvent: genericInteractionEvent, CommandID: interaction.Data.ID, - Name: interaction.Data.Name, + CommandName: interaction.Data.Name, SubCommandName: subCommandName, - SubCommandGroup: subCommandGroup, + SubCommandGroupName: subCommandGroupName, Options: newOptions, Replied: false, }) diff --git a/internal/handlers/interaction_create_webhook_handler.go b/internal/handlers/interaction_create_webhook_handler.go index 35252071..0cf2282a 100644 --- a/internal/handlers/interaction_create_webhook_handler.go +++ b/internal/handlers/interaction_create_webhook_handler.go @@ -7,9 +7,9 @@ import ( // InteractionCreateWebhookHandler handles api.InteractionCreateWebhookEvent type InteractionCreateWebhookHandler struct{} -// Name returns the raw gateway event name -func (h InteractionCreateWebhookHandler) Name() string { - return api.InteractionCreateWebhookEvent +// Event returns the raw gateway event Event +func (h InteractionCreateWebhookHandler) Event() api.GatewayEventType { + return api.WebhookEventInteractionCreate } // New constructs a new payload receiver for the raw gateway event @@ -17,8 +17,8 @@ func (h InteractionCreateWebhookHandler) New() interface{} { return &api.Interaction{} } -// Handle handles the specific raw gateway event -func (h InteractionCreateWebhookHandler) Handle(disgo api.Disgo, eventManager api.EventManager, c chan interface{}, i interface{}) { +// HandleWebhookEvent handles the specific raw gateway event +func (h InteractionCreateWebhookHandler) HandleWebhookEvent(disgo api.Disgo, eventManager api.EventManager, c chan interface{}, i interface{}) { interaction, ok := i.(*api.Interaction) if !ok { return @@ -30,5 +30,5 @@ func (h InteractionCreateWebhookHandler) Handle(disgo api.Disgo, eventManager ap } return } - handleInteractions(disgo, eventManager, c, interaction) + handleInteraction(disgo, eventManager, -1, interaction, c) } diff --git a/internal/handlers/invite_create_handler.go b/internal/handlers/invite_create_handler.go new file mode 100644 index 00000000..5ac8282f --- /dev/null +++ b/internal/handlers/invite_create_handler.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/handlers/invite_delete_handler.go b/internal/handlers/invite_delete_handler.go new file mode 100644 index 00000000..5ac8282f --- /dev/null +++ b/internal/handlers/invite_delete_handler.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/handlers/message_create_handler.go b/internal/handlers/message_create_handler.go index b6d32590..80dba3ad 100644 --- a/internal/handlers/message_create_handler.go +++ b/internal/handlers/message_create_handler.go @@ -8,9 +8,9 @@ import ( // MessageCreateHandler handles api.MessageCreateGatewayEvent type MessageCreateHandler struct{} -// Name returns the raw gateway event name -func (h MessageCreateHandler) Name() string { - return api.MessageCreateGatewayEvent +// Event returns the raw gateway event Event +func (h MessageCreateHandler) Event() api.GatewayEventType { + return api.GatewayEventMessageCreate } // New constructs a new payload receiver for the raw gateway event @@ -18,47 +18,46 @@ func (h MessageCreateHandler) New() interface{} { return &api.Message{} } -// Handle handles the specific raw gateway event -func (h MessageCreateHandler) Handle(disgo api.Disgo, eventManager api.EventManager, i interface{}) { +// HandleGatewayEvent handles the specific raw gateway event +func (h MessageCreateHandler) HandleGatewayEvent(disgo api.Disgo, eventManager api.EventManager, sequenceNumber int, i interface{}) { message, ok := i.(*api.Message) if !ok { return } + message.Disgo = disgo + message.Author.Disgo = disgo + genericMessageEvent := events.GenericMessageEvent{ - Event: api.Event{ - Disgo: disgo, - }, - MessageChannelID: message.ChannelID, - MessageID: message.ID, + GenericEvent: events.NewEvent(disgo, sequenceNumber), + MessageID: message.ID, + Message: message, + ChannelID: message.ChannelID, } eventManager.Dispatch(genericMessageEvent) - genericGuildEvent := events.GenericGuildEvent{ - Event: api.Event{ - Disgo: disgo, - }, - GuildID: *message.GuildID, - } - eventManager.Dispatch(genericGuildEvent) - - eventManager.Dispatch(events.MessageReceivedEvent{ + eventManager.Dispatch(events.MessageCreateEvent{ GenericMessageEvent: genericMessageEvent, - Message: *message, }) if message.GuildID == nil { - // dm channel + genericDMMessageEvent := events.GenericDMMessageEvent{ + GenericMessageEvent: genericMessageEvent, + } + eventManager.Dispatch(genericDMMessageEvent) + + eventManager.Dispatch(events.DMMessageCreateEvent{ + GenericDMMessageEvent: genericDMMessageEvent, + }) } else { - // text channel - message.Disgo = disgo - message.Author.Disgo = disgo - eventManager.Dispatch(events.GuildMessageReceivedEvent{ - Message: *message, - GenericGuildMessageEvent: events.GenericGuildMessageEvent{ - GenericGuildEvent: genericGuildEvent, - GenericMessageEvent: genericMessageEvent, - }, + genericGuildMessageEvent := events.GenericGuildMessageEvent{ + GenericMessageEvent: genericMessageEvent, + GuildID: *message.GuildID, + } + eventManager.Dispatch(genericGuildMessageEvent) + + eventManager.Dispatch(events.GuildMessageCreateEvent{ + GenericGuildMessageEvent: genericGuildMessageEvent, }) } diff --git a/internal/handlers/message_delete_bulk_handler.go b/internal/handlers/message_delete_bulk_handler.go new file mode 100644 index 00000000..5ac8282f --- /dev/null +++ b/internal/handlers/message_delete_bulk_handler.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/handlers/message_delete_handler.go b/internal/handlers/message_delete_handler.go new file mode 100644 index 00000000..5ac8282f --- /dev/null +++ b/internal/handlers/message_delete_handler.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/handlers/message_reaction_add_handler.go b/internal/handlers/message_reaction_add_handler.go new file mode 100644 index 00000000..5ac8282f --- /dev/null +++ b/internal/handlers/message_reaction_add_handler.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/handlers/message_reaction_remove_all_handler.go b/internal/handlers/message_reaction_remove_all_handler.go new file mode 100644 index 00000000..5ac8282f --- /dev/null +++ b/internal/handlers/message_reaction_remove_all_handler.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/handlers/message_reaction_remove_emoji_handler.go b/internal/handlers/message_reaction_remove_emoji_handler.go new file mode 100644 index 00000000..5ac8282f --- /dev/null +++ b/internal/handlers/message_reaction_remove_emoji_handler.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/handlers/message_reaction_remove_handler.go b/internal/handlers/message_reaction_remove_handler.go new file mode 100644 index 00000000..5ac8282f --- /dev/null +++ b/internal/handlers/message_reaction_remove_handler.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/handlers/message_update_handler.go b/internal/handlers/message_update_handler.go new file mode 100644 index 00000000..5ac8282f --- /dev/null +++ b/internal/handlers/message_update_handler.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/handlers/presence_update_handler.go b/internal/handlers/presence_update_handler.go new file mode 100644 index 00000000..5ac8282f --- /dev/null +++ b/internal/handlers/presence_update_handler.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/handlers/ready_handler.go b/internal/handlers/ready_handler.go index 58b3e94d..e2c95db3 100644 --- a/internal/handlers/ready_handler.go +++ b/internal/handlers/ready_handler.go @@ -4,35 +4,28 @@ import ( "github.com/DisgoOrg/disgo/api" ) -type readyEventData struct { - Version int `json:"v"` - SelfUser api.User `json:"user"` - PrivateChannels []*api.DMChannel `json:"private_channels"` - Guilds []*api.Guild `json:"guilds"` - SessionID string `json:"session_id"` - Shard *[2]int `json:"shard,omitempty"` -} - // ReadyHandler handles api.ReadyGatewayEvent type ReadyHandler struct{} -// Name returns the raw gateway event name -func (h ReadyHandler) Name() string { - return api.ReadyGatewayEvent +// Event returns the raw gateway event Event +func (h ReadyHandler) Event() api.GatewayEventType { + return api.GatewayEventReady } // New constructs a new payload receiver for the raw gateway event func (h ReadyHandler) New() interface{} { - return &readyEventData{} + return &api.ReadyGatewayEvent{} } -// Handle handles the specific raw gateway event -func (h ReadyHandler) Handle(disgo api.Disgo, eventManager api.EventManager, i interface{}) { - readyEvent, ok := i.(*readyEventData) +// HandleGatewayEvent handles the specific raw gateway event +func (h ReadyHandler) HandleGatewayEvent(disgo api.Disgo, eventManager api.EventManager, sequenceNumber int, i interface{}) { + readyEvent, ok := i.(*api.ReadyGatewayEvent) if !ok { return } - disgo.SetSelfUser(&readyEvent.SelfUser) + + disgo.Cache().CacheUser(&readyEvent.SelfUser) + for i := range readyEvent.Guilds { disgo.Cache().CacheGuild(readyEvent.Guilds[i]) } diff --git a/internal/handlers/typing_start_handler.go b/internal/handlers/typing_start_handler.go new file mode 100644 index 00000000..5ac8282f --- /dev/null +++ b/internal/handlers/typing_start_handler.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/handlers/user_update_handler.go b/internal/handlers/user_update_handler.go new file mode 100644 index 00000000..5ac8282f --- /dev/null +++ b/internal/handlers/user_update_handler.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/handlers/voice_server_update_handler.go b/internal/handlers/voice_server_update_handler.go new file mode 100644 index 00000000..33ee9fb1 --- /dev/null +++ b/internal/handlers/voice_server_update_handler.go @@ -0,0 +1,31 @@ +package handlers + +import "github.com/DisgoOrg/disgo/api" + +// VoiceServerUpdateHandler handles api.GatewayEventVoiceServerUpdate +type VoiceServerUpdateHandler struct{} + +// Event returns the raw gateway event Event +func (h VoiceServerUpdateHandler) Event() api.GatewayEventType { + return api.GatewayEventVoiceServerUpdate +} + +// New constructs a new payload receiver for the raw gateway event +func (h VoiceServerUpdateHandler) New() interface{} { + return &api.VoiceServerUpdate{} +} + +// HandleGatewayEvent handles the specific raw gateway event +func (h VoiceServerUpdateHandler) HandleGatewayEvent(disgo api.Disgo, eventManager api.EventManager, sequenceNumber int, i interface{}) { + voiceServerUpdate, ok := i.(*api.VoiceServerUpdate) + if !ok { + return + } + + if interceptor := disgo.VoiceDispatchInterceptor(); interceptor != nil { + interceptor.OnVoiceServerUpdate(&api.VoiceServerUpdateEvent{ + VoiceServerUpdate: *voiceServerUpdate, + Disgo: disgo, + }) + } +} diff --git a/internal/handlers/voice_state_update_handler.go b/internal/handlers/voice_state_update_handler.go new file mode 100644 index 00000000..124149b9 --- /dev/null +++ b/internal/handlers/voice_state_update_handler.go @@ -0,0 +1,43 @@ +package handlers + +import ( + "github.com/DisgoOrg/disgo/api" +) + +// VoiceStateUpdateHandler handles api.VoiceStateUpdateGatewayEvent +type VoiceStateUpdateHandler struct{} + +// Event returns the raw gateway event Event +func (h VoiceStateUpdateHandler) Event() api.GatewayEventType { + return api.GatewayEventVoiceStateUpdate +} + +// New constructs a new payload receiver for the raw gateway event +func (h VoiceStateUpdateHandler) New() interface{} { + return &api.VoiceStateUpdateEvent{} +} + +// HandleGatewayEvent handles the specific raw gateway event +func (h VoiceStateUpdateHandler) HandleGatewayEvent(disgo api.Disgo, eventManager api.EventManager, sequenceNumber int, i interface{}) { + voiceStateUpdate, ok := i.(*api.VoiceStateUpdateEvent) + if !ok { + return + } + + voiceStateUpdate.Disgo = disgo + + //guild := disgo.Cache().Guild(voiceStateUpdate.GuildID) + + //oldMember := disgo.Cache().Member(voiceStateUpdate.GuildID, voiceStateUpdate.UserID) + //newMember := voiceStateUpdate.Member + + // TODO update voice state cache + // TODO fire several events + + if disgo.ApplicationID() == voiceStateUpdate.UserID { + if interceptor := disgo.VoiceDispatchInterceptor(); interceptor != nil { + interceptor.OnVoiceStateUpdate(voiceStateUpdate) + } + } + +} diff --git a/internal/handlers/webhooks_update_handler.go b/internal/handlers/webhooks_update_handler.go new file mode 100644 index 00000000..5ac8282f --- /dev/null +++ b/internal/handlers/webhooks_update_handler.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/restclient.go b/internal/restclient.go deleted file mode 100644 index d6e763d6..00000000 --- a/internal/restclient.go +++ /dev/null @@ -1,439 +0,0 @@ -package internal - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net/http" - "strings" - - log "github.com/sirupsen/logrus" - - "github.com/DisgoOrg/disgo/api" - "github.com/DisgoOrg/disgo/api/endpoints" -) - -func newRestClientImpl(disgo api.Disgo, token string) api.RestClient { - return &RestClientImpl{ - disgo: disgo, - Client: &http.Client{}, - token: token, - } -} - -// RestClientImpl is the rest client implementation used for HTTP requests to discord -type RestClientImpl struct { - disgo api.Disgo - Client *http.Client - token string -} - -// Disgo returns the api.Disgo instance -func (r RestClientImpl) Disgo() api.Disgo { - return r.disgo -} - -// Close cleans up the http managers connections -func (r RestClientImpl) Close() { - r.Client.CloseIdleConnections() -} - -// UserAgent returns the user agent for this api.RestClient -func (r RestClientImpl) UserAgent() string { - return "DiscordBot (https://github.com/disgoorg/disgo, 0.0.1)" -} - -// Request makes a new rest request to discords api with the specific endpoints.APIRoute -func (r RestClientImpl) Request(route endpoints.CompiledAPIRoute, rqBody interface{}, rsBody interface{}) error { - var reader io.Reader - var rqJSON []byte - if rqBody != nil { - rqJSON, err := json.Marshal(rqBody) - if err != nil { - return err - } - reader = bytes.NewBuffer(rqJSON) - } else { - reader = nil - } - - rq, err := http.NewRequest(route.Method().String(), route.Route(), reader) - if err != nil { - return err - } - - rq.Header.Set("User-Agent", r.UserAgent()) - rq.Header.Set("Authorization", "Bot "+r.token) - rq.Header.Set("Content-Type", "application/json") - - rs, err := r.Client.Do(rq) - if err != nil { - return err - } - - defer func() { - err := rs.Body.Close() - if err != nil { - log.Error("error closing response body", err.Error()) - } - }() - - rawRsBody, err := ioutil.ReadAll(rs.Body) - if err != nil { - log.Errorf("error reading from response body: %s", err) - return err - } - - log.Debugf("code: %d, response: %s", rs.StatusCode, string(rawRsBody)) - - switch rs.StatusCode { - case http.StatusOK, http.StatusCreated, http.StatusNoContent: - if rsBody != nil { - if err = json.Unmarshal(rawRsBody, rsBody); err != nil { - log.Errorf("error unmarshalling response. error: %s", err) - return err - } - } - return nil - - case http.StatusTooManyRequests: - limit := rs.Header.Get("X-RateLimit-Limit") - remaining := rs.Header.Get("X-RateLimit-Limit") - reset := rs.Header.Get("X-RateLimit-Limit") - bucket := rs.Header.Get("X-RateLimit-Limit") - log.Errorf("too many requests. limit: %s, remaining: %s, reset: %s,bucket: %s", limit, remaining, reset, bucket) - return api.ErrRatelimited - - case http.StatusBadGateway: - log.Error(api.ErrBadGateway) - return api.ErrBadGateway - - case http.StatusBadRequest: - log.Errorf("bad request request: \"%s\", response: \"%s\"", string(rqJSON), string(rawRsBody)) - return api.ErrBadRequest - - case http.StatusUnauthorized: - log.Error(api.ErrUnauthorized) - return api.ErrUnauthorized - - default: - var errorRs api.ErrorResponse - if err = json.Unmarshal(rawRsBody, &errorRs); err != nil { - log.Errorf("error unmarshalling error response. code: %d, error: %s", rs.StatusCode, err) - return err - } - return fmt.Errorf("request to %s failed. statuscode: %d, errorcode: %d, message_events: %s", rq.URL, rs.StatusCode, errorRs.Code, errorRs.Message) - } -} - -// SendMessage lets you send a message_events to a channel -func (r RestClientImpl) SendMessage(channelID api.Snowflake, message api.MessageCreate) (rMessage *api.Message, err error) { - err = r.Request(endpoints.CreateMessage.Compile(channelID), message, &rMessage) - if rMessage != nil { - //r.Disgo().Cache().CacheMessage(rMessage) - } - return -} - -// OpenDMChannel opens a new dm channel a user -func (r RestClientImpl) OpenDMChannel(userID api.Snowflake) (channel *api.DMChannel, err error) { - body := struct { - RecipientID api.Snowflake `json:"recipient_id"` - }{ - RecipientID: userID, - } - err = r.Request(endpoints.CreateDMChannel.Compile(), body, &channel) - if channel != nil { - channel.Disgo = r.Disgo() - r.Disgo().Cache().CacheDMChannel(channel) - } - return -} - -// UpdateSelfNick updates the bots nickname in a guild -func (r RestClientImpl) UpdateSelfNick(guildID api.Snowflake, nick *string) (newNick *string, err error) { - var updateNick *api.UpdateSelfNick - err = r.Request(endpoints.UpdateSelfNick.Compile(guildID), api.UpdateSelfNick{Nick: nick}, &updateNick) - if updateNick != nil { - r.Disgo().Cache().Member(guildID, r.Disgo().ApplicationID()).Nick = updateNick.Nick - newNick = updateNick.Nick - } - return -} - -// GetUser fetches the specific user -func (r RestClientImpl) GetUser(userID api.Snowflake) (user *api.User, err error) { - err = r.Request(endpoints.GetUser.Compile(userID), nil, &user) - if user != nil { - r.Disgo().Cache().CacheUser(user) - } - return -} - -// GetMember fetches the specific member -func (r RestClientImpl) GetMember(guildID api.Snowflake, userID api.Snowflake) (member *api.Member, err error) { - err = r.Request(endpoints.GetMember.Compile(guildID, userID), nil, &member) - if member != nil { - r.Disgo().Cache().CacheMember(member) - } - return -} - -// GetMembers fetches all members for a guild -func (r RestClientImpl) GetMembers(guildID api.Snowflake) (members []*api.Member, err error) { - err = r.Request(endpoints.GetMembers.Compile(guildID), nil, &members) - if members != nil { - for _, member := range members { - r.Disgo().Cache().CacheMember(member) - } - } - return -} - -// AddMember adds a member to the guild with the oauth2 access token. requires api.PermissionCreateInstantInvite -func (r RestClientImpl) AddMember(guildID api.Snowflake, userID api.Snowflake, addGuildMemberData api.AddGuildMemberData) (member *api.Member, err error) { - err = r.Request(endpoints.AddMember.Compile(guildID, userID), addGuildMemberData, &member) - if member != nil { - r.Disgo().Cache().CacheMember(member) - } - return -} - -// KickMember kicks a member from the guild. requires api.PermissionKickMembers -func (r RestClientImpl) KickMember(guildID api.Snowflake, userID api.Snowflake, reason *string) (err error) { - var route endpoints.CompiledAPIRoute - if reason == nil { - route = endpoints.RemoveMember.Compile(guildID, userID) - } else { - route = endpoints.RemoveMemberReason.Compile(guildID, userID, *reason) - } - - err = r.Request(route, nil, nil) - if err == nil { - r.Disgo().Cache().UncacheMember(guildID, userID) - } - return -} - -// UpdateMember updates a member -func (r RestClientImpl) UpdateMember(guildID api.Snowflake, userID api.Snowflake, updateGuildMemberData api.UpdateGuildMemberData) (member *api.Member, err error) { - err = r.Request(endpoints.UpdateMember.Compile(guildID, userID), updateGuildMemberData, &member) - if member != nil { - r.Disgo().Cache().CacheMember(member) - } - return -} - -// MoveMember moves/kicks the member to/from a voice channel -func (r RestClientImpl) MoveMember(guildID api.Snowflake, userID api.Snowflake, channelID *api.Snowflake) (member *api.Member, err error) { - err = r.Request(endpoints.UpdateMember.Compile(guildID, userID), api.MoveGuildMemberData{ChannelID: channelID}, &member) - if member != nil { - r.Disgo().Cache().CacheMember(member) - } - return -} - -// AddMemberRole adds a role to a member -func (r RestClientImpl) AddMemberRole(guildID api.Snowflake, userID api.Snowflake, roleID api.Snowflake) (err error) { - err = r.Request(endpoints.AddMemberRole.Compile(guildID, userID, roleID), nil, nil) - if err == nil { - member := r.Disgo().Cache().Member(guildID, userID) - if member != nil { - member.Roles = append(member.Roles, roleID) - } - } - return -} - -// RemoveMemberRole removes a role from a member -func (r RestClientImpl) RemoveMemberRole(guildID api.Snowflake, userID api.Snowflake, roleID api.Snowflake) (err error) { - err = r.Request(endpoints.RemoveMemberRole.Compile(guildID, userID, roleID), nil, nil) - if err == nil { - member := r.Disgo().Cache().Member(guildID, userID) - if member != nil { - for i, id := range member.Roles { - if id == roleID { - member.Roles = append(member.Roles[:i], member.Roles[i+1:]...) - break - } - } - } - } - return -} - -// GetRoles fetches all roles from a guild -func (r RestClientImpl) GetRoles(guildID api.Snowflake) (roles []*api.Role, err error) { - err = r.Request(endpoints.GetRoles.Compile(guildID), nil, &roles) - if roles != nil { - for _, role := range roles { - role.Disgo = r.disgo - role.GuildID = guildID - r.disgo.Cache().CacheRole(role) - } - } - return -} - -// CreateRole creates a new role for a guild. Requires api.PermissionManageRoles -func (r RestClientImpl) CreateRole(guildID api.Snowflake, role api.UpdateRole) (newRole *api.Role, err error) { - err = r.Request(endpoints.CreateRole.Compile(guildID), role, &newRole) - if newRole != nil { - newRole.Disgo = r.disgo - newRole.GuildID = guildID - r.disgo.Cache().CacheRole(newRole) - } - return -} - -// UpdateRole updates a role from a guild. Requires api.PermissionManageRoles -func (r RestClientImpl) UpdateRole(guildID api.Snowflake, roleID api.Snowflake, role api.UpdateRole) (newRole *api.Role, err error) { - err = r.Request(endpoints.UpdateRole.Compile(guildID, roleID), role, &newRole) - if newRole != nil { - newRole.Disgo = r.disgo - newRole.GuildID = guildID - r.disgo.Cache().CacheRole(newRole) - } - return -} - -// UpdateRolePositions updates the position of a role from a guild. Requires api.PermissionManageRoles -func (r RestClientImpl) UpdateRolePositions(guildID api.Snowflake, roleUpdates ...api.UpdateRolePosition) (roles []*api.Role, err error) { - err = r.Request(endpoints.GetRoles.Compile(guildID), roleUpdates, &roles) - if roles != nil { - for _, role := range roles { - role.Disgo = r.disgo - role.GuildID = guildID - r.disgo.Cache().CacheRole(role) - } - } - return -} - -// DeleteRole deletes a role from a guild. Requires api.PermissionManageRoles -func (r RestClientImpl) DeleteRole(guildID api.Snowflake, roleID api.Snowflake) (err error) { - err = r.Request(endpoints.UpdateRole.Compile(guildID, roleID), nil, nil) - if err == nil { - r.disgo.Cache().UncacheRole(guildID, roleID) - } - return -} - -// AddReaction lets you add a reaction to a message_events -func (r RestClientImpl) AddReaction(channelID api.Snowflake, messageID api.Snowflake, emoji string) error { - return r.Request(endpoints.AddReaction.Compile(channelID, messageID, normalizeEmoji(emoji)), nil, nil) -} - -// RemoveOwnReaction lets you remove your own reaction from a message_events -func (r RestClientImpl) RemoveOwnReaction(channelID api.Snowflake, messageID api.Snowflake, emoji string) error { - return r.Request(endpoints.RemoveOwnReaction.Compile(channelID, messageID, normalizeEmoji(emoji)), nil, nil) -} - -// RemoveUserReaction lets you remove a specific reaction from a user from a message_events -func (r RestClientImpl) RemoveUserReaction(channelID api.Snowflake, messageID api.Snowflake, emoji string, userID api.Snowflake) error { - return r.Request(endpoints.RemoveUserReaction.Compile(channelID, messageID, normalizeEmoji(emoji), userID), nil, nil) -} - -// GetGlobalCommands gets you all global commands -func (r RestClientImpl) GetGlobalCommands(applicationID api.Snowflake) (commands []*api.SlashCommand, err error) { - return commands, r.Request(endpoints.GetGlobalCommands.Compile(applicationID), nil, &commands) -} - -// CreateGlobalCommand lets you create a new global command -func (r RestClientImpl) CreateGlobalCommand(applicationID api.Snowflake, command api.SlashCommand) (rCommand *api.SlashCommand, err error) { - return rCommand, r.Request(endpoints.CreateGlobalCommand.Compile(applicationID), command, &rCommand) -} - -// SetGlobalCommands lets you override all global commands -func (r RestClientImpl) SetGlobalCommands(applicationID api.Snowflake, commands ...api.SlashCommand) (rCommands []*api.SlashCommand, err error) { - if len(commands) > 100 { - err = api.ErrTooMuchApplicationCommands - return - } - return rCommands, r.Request(endpoints.SetGlobalCommands.Compile(applicationID), commands, &rCommands) -} - -// GetGlobalCommand gets you a specific global global command -func (r RestClientImpl) GetGlobalCommand(applicationID api.Snowflake, commandID api.Snowflake) (rCommand *api.SlashCommand, err error) { - return rCommand, r.Request(endpoints.GetGlobalCommand.Compile(applicationID, commandID), nil, &rCommand) -} - -// EditGlobalCommand lets you edit a specific global command -func (r RestClientImpl) EditGlobalCommand(applicationID api.Snowflake, commandID api.Snowflake, command api.SlashCommand) (rCommand *api.SlashCommand, err error) { - return rCommand, r.Request(endpoints.EditGlobalCommand.Compile(applicationID, commandID), command, &rCommand) -} - -// DeleteGlobalCommand lets you delete a specific global command -func (r RestClientImpl) DeleteGlobalCommand(applicationID api.Snowflake, commandID api.Snowflake) error { - return r.Request(endpoints.DeleteGlobalCommand.Compile(applicationID, commandID), nil, nil) -} - -// GetGuildCommands gets you all guild_events commands -func (r RestClientImpl) GetGuildCommands(applicationID api.Snowflake, guildID api.Snowflake) (commands []*api.SlashCommand, err error) { - return commands, r.Request(endpoints.GetGuildCommands.Compile(applicationID, guildID), nil, &commands) -} - -// CreateGuildGuildCommand lets you create a new guild_events command -func (r RestClientImpl) CreateGuildGuildCommand(applicationID api.Snowflake, guildID api.Snowflake, command api.SlashCommand) (rCommand *api.SlashCommand, err error) { - return rCommand, r.Request(endpoints.CreateGuildCommand.Compile(applicationID, guildID), command, &rCommand) -} - -// SetGuildCommands lets you override all guild_events commands -func (r RestClientImpl) SetGuildCommands(applicationID api.Snowflake, guildID api.Snowflake, commands ...api.SlashCommand) (rCommands []*api.SlashCommand, err error) { - if len(commands) > 100 { - err = api.ErrTooMuchApplicationCommands - return - } - return rCommands, r.Request(endpoints.SetGuildCommands.Compile(applicationID, guildID), commands, &rCommands) -} - -// GetGuildCommand gets you a specific guild_events command -func (r RestClientImpl) GetGuildCommand(applicationID api.Snowflake, guildID api.Snowflake, commandID api.Snowflake) (rCommand *api.SlashCommand, err error) { - return rCommand, r.Request(endpoints.GetGuildCommand.Compile(applicationID, guildID, commandID), nil, &rCommand) -} - -// EditGuildCommand lets you edit a specific guild_events command -func (r RestClientImpl) EditGuildCommand(applicationID api.Snowflake, guildID api.Snowflake, commandID api.Snowflake, command api.SlashCommand) (rCommand *api.SlashCommand, err error) { - return rCommand, r.Request(endpoints.EditGuildCommand.Compile(applicationID, guildID, commandID), command, &rCommand) -} - -// DeleteGuildCommand lets you delete a specific guild_events command -func (r RestClientImpl) DeleteGuildCommand(applicationID api.Snowflake, guildID api.Snowflake, commandID api.Snowflake) error { - return r.Request(endpoints.DeleteGuildCommand.Compile(applicationID, guildID, commandID), nil, nil) -} - -// SendInteractionResponse used to send the initial response on an interaction -func (r RestClientImpl) SendInteractionResponse(interactionID api.Snowflake, interactionToken string, interactionResponse api.InteractionResponse) error { - return r.Request(endpoints.CreateInteractionResponse.Compile(interactionID, interactionToken), interactionResponse, nil) -} - -// EditInteractionResponse used to edit the initial response on an interaction -func (r RestClientImpl) EditInteractionResponse(applicationID api.Snowflake, interactionToken string, interactionResponse api.InteractionResponse) (message *api.Message, err error) { - return message, r.Request(endpoints.EditInteractionResponse.Compile(applicationID, interactionToken), interactionResponse, &message) -} - -// DeleteInteractionResponse used to delete the initial response on an interaction -func (r RestClientImpl) DeleteInteractionResponse(applicationID api.Snowflake, interactionToken string) error { - return r.Request(endpoints.DeleteInteractionResponse.Compile(applicationID, interactionToken), nil, nil) -} - -// SendFollowupMessage used to send a followup message_events to an interaction -func (r RestClientImpl) SendFollowupMessage(applicationID api.Snowflake, interactionToken string, followupMessage api.FollowupMessage) (message *api.Message, err error) { - return message, r.Request(endpoints.CreateInteractionResponse.Compile(applicationID, interactionToken), followupMessage, &message) -} - -// EditFollowupMessage used to send the initial response on an interaction -func (r RestClientImpl) EditFollowupMessage(applicationID api.Snowflake, interactionToken string, messageID api.Snowflake, followupMessage api.FollowupMessage) (message *api.Message, err error) { - return message, r.Request(endpoints.CreateInteractionResponse.Compile(applicationID, interactionToken, messageID), followupMessage, &message) -} - -// DeleteFollowupMessage used to send a followup message_events to an interaction -func (r RestClientImpl) DeleteFollowupMessage(applicationID api.Snowflake, interactionToken string, messageID api.Snowflake) error { - return r.Request(endpoints.CreateInteractionResponse.Compile(applicationID, interactionToken, messageID), nil, nil) -} - -func normalizeEmoji(emoji string) string { - return strings.Replace(emoji, "#", "%23", -1) -} diff --git a/internal/restclient_impl.go b/internal/restclient_impl.go new file mode 100644 index 00000000..b56f66fb --- /dev/null +++ b/internal/restclient_impl.go @@ -0,0 +1,753 @@ +package internal + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "strings" + + "github.com/DisgoOrg/disgo/api/events" + + "github.com/DisgoOrg/disgo/api" + "github.com/DisgoOrg/disgo/api/endpoints" +) + +func newRestClientImpl(disgo api.Disgo) api.RestClient { + return &RestClientImpl{ + disgo: disgo, + Client: &http.Client{}, + } +} + +// RestClientImpl is the rest client implementation used for HTTP requests to discord +type RestClientImpl struct { + disgo api.Disgo + Client *http.Client + token string +} + +// Disgo returns the api.Disgo instance +func (r RestClientImpl) Disgo() api.Disgo { + return r.disgo +} + +// Close cleans up the http managers connections +func (r RestClientImpl) Close() { + r.Client.CloseIdleConnections() +} + +// UserAgent returns the user agent for this api.RestClient +func (r RestClientImpl) UserAgent() string { + return "DiscordBot (https://github.com/disgoorg/disgo, 0.0.1)" +} + +// Request makes a new rest request to discords api with the specific endpoints.APIRoute +func (r RestClientImpl) Request(route endpoints.CompiledAPIRoute, rqBody interface{}, rsBody interface{}) error { + var reader io.Reader + var rqJSON []byte + if rqBody != nil { + rqJSON, err := json.Marshal(rqBody) + if err != nil { + return err + } + r.Disgo().Logger().Debugf("request json: \"%s\"", string(rqJSON)) + reader = bytes.NewBuffer(rqJSON) + } else { + reader = nil + } + + rq, err := http.NewRequest(route.Method().String(), route.Route(), reader) + if err != nil { + return err + } + + rq.Header.Set("User-Agent", r.UserAgent()) + rq.Header.Set("Authorization", string("Bot "+r.disgo.Token())) + rq.Header.Set("Content-Type", "application/json") + + rs, err := r.Client.Do(rq) + if err != nil { + return err + } + + defer func() { + err = rs.Body.Close() + if err != nil { + r.Disgo().Logger().Error("error closing response body", err.Error()) + } + }() + + rawRsBody, err := ioutil.ReadAll(rs.Body) + if err != nil { + r.Disgo().Logger().Errorf("error reading from response body: %s", err) + return err + } + + r.Disgo().Logger().Debugf("code: %d, response: %s", rs.StatusCode, string(rawRsBody)) + + r.Disgo().EventManager().Dispatch(events.HTTPRequestEvent{ + GenericEvent: events.NewEvent(r.Disgo(), 0), + Request: rq, + Response: rs, + }) + + switch rs.StatusCode { + case http.StatusOK, http.StatusCreated, http.StatusNoContent: + if rsBody != nil { + if err = json.Unmarshal(rawRsBody, rsBody); err != nil { + r.Disgo().Logger().Errorf("error unmarshalling response. error: %s", err) + return err + } + } + return nil + + case http.StatusTooManyRequests: + limit := rs.Header.Get("X-RateLimit-Limit") + remaining := rs.Header.Get("X-RateLimit-Limit") + reset := rs.Header.Get("X-RateLimit-Limit") + bucket := rs.Header.Get("X-RateLimit-Limit") + r.Disgo().Logger().Errorf("too many requests. limit: %s, remaining: %s, reset: %s,bucket: %s", limit, remaining, reset, bucket) + return api.ErrRatelimited + + case http.StatusBadGateway: + r.Disgo().Logger().Error(api.ErrBadGateway) + return api.ErrBadGateway + + case http.StatusBadRequest: + r.Disgo().Logger().Errorf("bad request request: \"%s\", response: \"%s\"", string(rqJSON), string(rawRsBody)) + return api.ErrBadRequest + + case http.StatusUnauthorized: + r.Disgo().Logger().Error(api.ErrUnauthorized) + return api.ErrUnauthorized + + default: + var errorRs api.ErrorResponse + if err = json.Unmarshal(rawRsBody, &errorRs); err != nil { + r.Disgo().Logger().Errorf("error unmarshalling error response. code: %d, error: %s", rs.StatusCode, err) + return err + } + return fmt.Errorf("request to %s failed. statuscode: %d, errorcode: %d, message_events: %s", rq.URL, rs.StatusCode, errorRs.Code, errorRs.Message) + } +} + +// SendMessage lets you send a api.Message to a api.MessageChannel +func (r RestClientImpl) SendMessage(channelID api.Snowflake, message api.MessageCreate) (msg *api.Message, err error) { + compiledRoute, err := endpoints.CreateMessage.Compile(channelID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, message, &msg) + if err == nil { + msg = r.Disgo().EntityBuilder().CreateMessage(msg, api.CacheStrategyNoWs) + } + return +} + +// EditMessage lets you edit a api.Message +func (r RestClientImpl) EditMessage(channelID api.Snowflake, messageID api.Snowflake, message api.MessageUpdate) (msg *api.Message, err error) { + compiledRoute, err := endpoints.UpdateMessage.Compile(channelID, messageID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, message, &msg) + if err == nil { + msg = r.Disgo().EntityBuilder().CreateMessage(msg, api.CacheStrategyNoWs) + } + return +} + +// DeleteMessage lets you delete a api.Message +func (r RestClientImpl) DeleteMessage(channelID api.Snowflake, messageID api.Snowflake) (err error) { + compiledRoute, err := endpoints.DeleteMessage.Compile(channelID, messageID) + if err != nil { + return err + } + err = r.Request(*compiledRoute, nil, nil) + if err == nil && api.CacheStrategyNoWs(r.Disgo()) { + r.Disgo().Cache().UncacheMessage(channelID, messageID) + } + return +} + +// BulkDeleteMessages lets you bulk delete api.Message(s) +func (r RestClientImpl) BulkDeleteMessages(channelID api.Snowflake, messageIDs ...api.Snowflake) (err error) { + compiledRoute, err := endpoints.BulkDeleteMessage.Compile(channelID) + if err != nil { + return err + } + err = r.Request(*compiledRoute, api.MessageBulkDelete{Messages: messageIDs}, nil) + if err == nil && api.CacheStrategyNoWs(r.Disgo()) { + // TODO: check here if no err means all messages deleted + for _, messageID := range messageIDs { + r.Disgo().Cache().UncacheMessage(channelID, messageID) + } + } + return +} + +// CrosspostMessage lets you crosspost a api.Message in a channel with type api.ChannelTypeNews +func (r RestClientImpl) CrosspostMessage(channelID api.Snowflake, messageID api.Snowflake) (msg *api.Message, err error) { + compiledRoute, err := endpoints.CrosspostMessage.Compile(channelID, messageID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, nil, &msg) + if err == nil { + msg = r.Disgo().EntityBuilder().CreateMessage(msg, api.CacheStrategyNoWs) + } + return +} + +// OpenDMChannel opens a new dm channel a user +func (r RestClientImpl) OpenDMChannel(userID api.Snowflake) (channel *api.DMChannel, err error) { + compiledRoute, err := endpoints.CreateDMChannel.Compile() + if err != nil { + return nil, err + } + body := struct { + RecipientID api.Snowflake `json:"recipient_id"` + }{ + RecipientID: userID, + } + err = r.Request(*compiledRoute, body, &channel) + if err == nil { + channel = r.Disgo().EntityBuilder().CreateDMChannel(&channel.MessageChannel.Channel, api.CacheStrategyNoWs) + } + return +} + +// UpdateSelfNick updates the bots nickname in a guild +func (r RestClientImpl) UpdateSelfNick(guildID api.Snowflake, nick *string) (newNick *string, err error) { + compiledRoute, err := endpoints.UpdateSelfNick.Compile(guildID) + if err != nil { + return nil, err + } + var updateNick *api.UpdateSelfNick + err = r.Request(*compiledRoute, api.UpdateSelfNick{Nick: nick}, &updateNick) + if err == nil && api.CacheStrategyNoWs(r.Disgo()) { + r.Disgo().Cache().Member(guildID, r.Disgo().ApplicationID()).Nick = updateNick.Nick + newNick = updateNick.Nick + } + return +} + +// GetUser fetches the specific user +func (r RestClientImpl) GetUser(userID api.Snowflake) (user *api.User, err error) { + compiledRoute, err := endpoints.GetUser.Compile(userID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, nil, &user) + if err == nil { + user = r.Disgo().EntityBuilder().CreateUser(user, api.CacheStrategyNoWs) + } + return +} + +// GetMember fetches the specific member +func (r RestClientImpl) GetMember(guildID api.Snowflake, userID api.Snowflake) (member *api.Member, err error) { + compiledRoute, err := endpoints.GetMember.Compile(guildID, userID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, nil, &member) + if err == nil { + member = r.Disgo().EntityBuilder().CreateMember(guildID, member, api.CacheStrategyNoWs) + } + return +} + +// GetMembers fetches all members for a guild +func (r RestClientImpl) GetMembers(guildID api.Snowflake) (members []*api.Member, err error) { + compiledRoute, err := endpoints.GetMembers.Compile(guildID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, nil, &members) + if err == nil { + for _, member := range members { + member = r.Disgo().EntityBuilder().CreateMember(guildID, member, api.CacheStrategyNoWs) + } + } + return +} + +// AddMember adds a member to the guild with the oauth2 access BotToken. requires api.PermissionCreateInstantInvite +func (r RestClientImpl) AddMember(guildID api.Snowflake, userID api.Snowflake, addGuildMemberData api.AddGuildMemberData) (member *api.Member, err error) { + compiledRoute, err := endpoints.AddMember.Compile(guildID, userID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, addGuildMemberData, &member) + if err == nil { + member = r.Disgo().EntityBuilder().CreateMember(guildID, member, api.CacheStrategyNoWs) + } + return +} + +// KickMember kicks a member from the guild. requires api.PermissionKickMembers +func (r RestClientImpl) KickMember(guildID api.Snowflake, userID api.Snowflake, reason *string) (err error) { + var compiledRoute *endpoints.CompiledAPIRoute + if reason == nil { + compiledRoute, err = endpoints.RemoveMember.Compile(guildID, userID) + } else { + compiledRoute, err = endpoints.RemoveMemberReason.Compile(guildID, userID, *reason) + } + if err != nil { + return + } + err = r.Request(*compiledRoute, nil, nil) + if err == nil && api.CacheStrategyNoWs(r.Disgo()) { + r.Disgo().Cache().UncacheMember(guildID, userID) + } + return +} + +// UpdateMember updates a member +func (r RestClientImpl) UpdateMember(guildID api.Snowflake, userID api.Snowflake, updateGuildMemberData api.UpdateGuildMemberData) (member *api.Member, err error) { + compiledRoute, err := endpoints.UpdateMember.Compile(guildID, userID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, updateGuildMemberData, &member) + if err == nil { + member = r.Disgo().EntityBuilder().CreateMember(guildID, member, api.CacheStrategyNoWs) + } + return +} + +// MoveMember moves/kicks the member to/from a voice channel +func (r RestClientImpl) MoveMember(guildID api.Snowflake, userID api.Snowflake, channelID *api.Snowflake) (member *api.Member, err error) { + compiledRoute, err := endpoints.UpdateMember.Compile(guildID, userID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, api.MoveGuildMemberData{ChannelID: channelID}, &member) + if err == nil { + member = r.Disgo().EntityBuilder().CreateMember(guildID, member, api.CacheStrategyNoWs) + } + return +} + +// AddMemberRole adds a role to a member +func (r RestClientImpl) AddMemberRole(guildID api.Snowflake, userID api.Snowflake, roleID api.Snowflake) (err error) { + compiledRoute, err := endpoints.AddMemberRole.Compile(guildID, userID, roleID) + if err != nil { + return err + } + err = r.Request(*compiledRoute, nil, nil) + if err == nil && api.CacheStrategyNoWs(r.Disgo()) { + member := r.Disgo().Cache().Member(guildID, userID) + if member != nil { + member.Roles = append(member.Roles, roleID) + } + } + return +} + +// RemoveMemberRole removes a role from a member +func (r RestClientImpl) RemoveMemberRole(guildID api.Snowflake, userID api.Snowflake, roleID api.Snowflake) (err error) { + compiledRoute, err := endpoints.RemoveMemberRole.Compile(guildID, userID, roleID) + if err != nil { + return err + } + err = r.Request(*compiledRoute, nil, nil) + if err == nil && api.CacheStrategyNoWs(r.Disgo()) { + member := r.Disgo().Cache().Member(guildID, userID) + if member != nil { + for i, id := range member.Roles { + if id == roleID { + member.Roles = append(member.Roles[:i], member.Roles[i+1:]...) + break + } + } + } + } + return +} + +// GetRoles fetches all roles from a guild +func (r RestClientImpl) GetRoles(guildID api.Snowflake) (roles []*api.Role, err error) { + compiledRoute, err := endpoints.GetRoles.Compile(guildID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, nil, &roles) + if err == nil { + for _, role := range roles { + role = r.Disgo().EntityBuilder().CreateRole(guildID, role, api.CacheStrategyNoWs) + } + } + return +} + +// CreateRole creates a new role for a guild. Requires api.PermissionManageRoles +func (r RestClientImpl) CreateRole(guildID api.Snowflake, role api.UpdateRole) (newRole *api.Role, err error) { + compiledRoute, err := endpoints.CreateRole.Compile(guildID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, role, &newRole) + if err == nil { + newRole = r.Disgo().EntityBuilder().CreateRole(guildID, newRole, api.CacheStrategyNoWs) + } + return +} + +// UpdateRole updates a role from a guild. Requires api.PermissionManageRoles +func (r RestClientImpl) UpdateRole(guildID api.Snowflake, roleID api.Snowflake, role api.UpdateRole) (newRole *api.Role, err error) { + compiledRoute, err := endpoints.UpdateRole.Compile(guildID, roleID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, role, &newRole) + if err == nil { + newRole = r.Disgo().EntityBuilder().CreateRole(guildID, newRole, api.CacheStrategyNoWs) + } + return +} + +// UpdateRolePositions updates the position of a role from a guild. Requires api.PermissionManageRoles +func (r RestClientImpl) UpdateRolePositions(guildID api.Snowflake, roleUpdates ...api.UpdateRolePosition) (roles []*api.Role, err error) { + compiledRoute, err := endpoints.GetRoles.Compile(guildID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, roleUpdates, &roles) + if err == nil { + for _, role := range roles { + role = r.Disgo().EntityBuilder().CreateRole(guildID, role, api.CacheStrategyNoWs) + } + } + return +} + +// DeleteRole deletes a role from a guild. Requires api.PermissionManageRoles +func (r RestClientImpl) DeleteRole(guildID api.Snowflake, roleID api.Snowflake) (err error) { + compiledRoute, err := endpoints.UpdateRole.Compile(guildID, roleID) + if err != nil { + return err + } + err = r.Request(*compiledRoute, nil, nil) + if err == nil && api.CacheStrategyNoWs(r.Disgo()) { + r.disgo.Cache().UncacheRole(guildID, roleID) + } + return +} + +// AddReaction lets you add a reaction to a message_events +func (r RestClientImpl) AddReaction(channelID api.Snowflake, messageID api.Snowflake, emoji string) error { + compiledRoute, err := endpoints.AddReaction.Compile(channelID, messageID, normalizeEmoji(emoji)) + if err != nil { + return err + } + return r.Request(*compiledRoute, nil, nil) +} + +// RemoveOwnReaction lets you remove your own reaction from a message_events +func (r RestClientImpl) RemoveOwnReaction(channelID api.Snowflake, messageID api.Snowflake, emoji string) error { + compiledRoute, err := endpoints.RemoveOwnReaction.Compile(channelID, messageID, normalizeEmoji(emoji)) + if err != nil { + return err + } + return r.Request(*compiledRoute, nil, nil) +} + +// RemoveUserReaction lets you remove a specific reaction from a user from a message_events +func (r RestClientImpl) RemoveUserReaction(channelID api.Snowflake, messageID api.Snowflake, emoji string, userID api.Snowflake) error { + compiledRoute, err := endpoints.RemoveUserReaction.Compile(channelID, messageID, normalizeEmoji(emoji), userID) + if err != nil { + return err + } + return r.Request(*compiledRoute, nil, nil) +} + +// GetGlobalCommands gets you all global commands +func (r RestClientImpl) GetGlobalCommands(applicationID api.Snowflake) (commands []*api.Command, err error) { + compiledRoute, err := endpoints.GetGlobalCommands.Compile(applicationID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, nil, &commands) + if err == nil { + for _, cmd := range commands { + cmd = r.Disgo().EntityBuilder().CreateGlobalCommand(cmd, api.CacheStrategyNoWs) + } + } + return +} + +// GetGlobalCommand gets you a specific global global command +func (r RestClientImpl) GetGlobalCommand(applicationID api.Snowflake, commandID api.Snowflake) (cmd *api.Command, err error) { + compiledRoute, err := endpoints.GetGlobalCommand.Compile(applicationID, commandID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, nil, &cmd) + if err == nil { + cmd = r.Disgo().EntityBuilder().CreateGlobalCommand(cmd, api.CacheStrategyNoWs) + } + return +} + +// CreateGlobalCommand lets you create a new global command +func (r RestClientImpl) CreateGlobalCommand(applicationID api.Snowflake, command api.Command) (cmd *api.Command, err error) { + compiledRoute, err := endpoints.CreateGlobalCommand.Compile(applicationID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, command, &cmd) + if err == nil { + cmd = r.Disgo().EntityBuilder().CreateGlobalCommand(cmd, api.CacheStrategyNoWs) + } + return +} + +// SetGlobalCommands lets you override all global commands +func (r RestClientImpl) SetGlobalCommands(applicationID api.Snowflake, commands ...api.Command) (cmds []*api.Command, err error) { + compiledRoute, err := endpoints.SetGlobalCommands.Compile(applicationID) + if err != nil { + return nil, err + } + if len(commands) > 100 { + err = api.ErrTooMuchApplicationCommands + return + } + err = r.Request(*compiledRoute, commands, &cmds) + if err == nil { + for _, cmd := range cmds { + cmd = r.Disgo().EntityBuilder().CreateGlobalCommand(cmd, api.CacheStrategyNoWs) + } + } + return +} + +// EditGlobalCommand lets you edit a specific global command +func (r RestClientImpl) EditGlobalCommand(applicationID api.Snowflake, commandID api.Snowflake, command api.UpdateCommand) (cmd *api.Command, err error) { + compiledRoute, err := endpoints.EditGlobalCommand.Compile(applicationID, commandID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, command, &cmd) + if err == nil { + cmd = r.Disgo().EntityBuilder().CreateGlobalCommand(cmd, api.CacheStrategyNoWs) + } + return +} + +// DeleteGlobalCommand lets you delete a specific global command +func (r RestClientImpl) DeleteGlobalCommand(applicationID api.Snowflake, commandID api.Snowflake) (err error) { + compiledRoute, err := endpoints.DeleteGlobalCommand.Compile(applicationID, commandID) + if err != nil { + return err + } + err = r.Request(*compiledRoute, nil, nil) + if err == nil && api.CacheStrategyNoWs(r.Disgo()) { + r.Disgo().Cache().UncacheCommand(commandID) + } + return +} + +// GetGuildCommands gets you all guild_events commands +func (r RestClientImpl) GetGuildCommands(applicationID api.Snowflake, guildID api.Snowflake) (commands []*api.Command, err error) { + compiledRoute, err := endpoints.GetGuildCommands.Compile(applicationID, guildID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, nil, &commands) + if err == nil { + for _, cmd := range commands { + cmd = r.Disgo().EntityBuilder().CreateGuildCommand(guildID, cmd, api.CacheStrategyNoWs) + } + } + return +} + +// CreateGuildCommand lets you create a new guild_events command +func (r RestClientImpl) CreateGuildCommand(applicationID api.Snowflake, guildID api.Snowflake, command api.Command) (cmd *api.Command, err error) { + compiledRoute, err := endpoints.CreateGuildCommand.Compile(applicationID, guildID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, command, &cmd) + if err == nil { + cmd = r.Disgo().EntityBuilder().CreateGuildCommand(guildID, cmd, api.CacheStrategyNoWs) + } + return +} + +// SetGuildCommands lets you override all guild_events commands +func (r RestClientImpl) SetGuildCommands(applicationID api.Snowflake, guildID api.Snowflake, commands ...api.Command) (cmds []*api.Command, err error) { + compiledRoute, err := endpoints.SetGuildCommands.Compile(applicationID, guildID) + if err != nil { + return nil, err + } + if len(commands) > 100 { + err = api.ErrTooMuchApplicationCommands + return + } + err = r.Request(*compiledRoute, commands, &cmds) + if err == nil { + for _, cmd := range cmds { + cmd = r.Disgo().EntityBuilder().CreateGuildCommand(guildID, cmd, api.CacheStrategyNoWs) + } + } + return +} + +// GetGuildCommand gets you a specific guild_events command +func (r RestClientImpl) GetGuildCommand(applicationID api.Snowflake, guildID api.Snowflake, commandID api.Snowflake) (cmd *api.Command, err error) { + compiledRoute, err := endpoints.GetGuildCommand.Compile(applicationID, guildID, commandID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, nil, &cmd) + if err == nil { + cmd = r.Disgo().EntityBuilder().CreateGuildCommand(guildID, cmd, api.CacheStrategyNoWs) + } + return +} + +// EditGuildCommand lets you edit a specific guild_events command +func (r RestClientImpl) EditGuildCommand(applicationID api.Snowflake, guildID api.Snowflake, commandID api.Snowflake, command api.UpdateCommand) (cmd *api.Command, err error) { + compiledRoute, err := endpoints.EditGuildCommand.Compile(applicationID, guildID, commandID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, command, &cmd) + if err == nil { + cmd = r.Disgo().EntityBuilder().CreateGuildCommand(guildID, cmd, api.CacheStrategyNoWs) + } + return +} + +// DeleteGuildCommand lets you delete a specific guild_events command +func (r RestClientImpl) DeleteGuildCommand(applicationID api.Snowflake, guildID api.Snowflake, commandID api.Snowflake) (err error) { + compiledRoute, err := endpoints.DeleteGuildCommand.Compile(applicationID, guildID, commandID) + if err != nil { + return err + } + err = r.Request(*compiledRoute, nil, nil) + if err == nil && api.CacheStrategyNoWs(r.Disgo()) { + r.Disgo().Cache().UncacheCommand(commandID) + } + return +} + +// GetGuildCommandsPermissions returns the api.CommandPermission for a all api.Command(s) in a guild +func (r RestClientImpl) GetGuildCommandsPermissions(applicationID api.Snowflake, guildID api.Snowflake) (cmdsPerms []*api.GuildCommandPermissions, err error) { + compiledRoute, err := endpoints.GetGuildCommandPermissions.Compile(applicationID, guildID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, nil, &cmdsPerms) + if err == nil { + for _, cmdPerms := range cmdsPerms { + cmdPerms = r.Disgo().EntityBuilder().CreateGuildCommandPermissions(cmdPerms, api.CacheStrategyNoWs) + } + } + return +} + +// GetGuildCommandPermissions returns the api.CommandPermission for a specific api.Command in a guild +func (r RestClientImpl) GetGuildCommandPermissions(applicationID api.Snowflake, guildID api.Snowflake, commandID api.Snowflake) (cmdPerms *api.GuildCommandPermissions, err error) { + compiledRoute, err := endpoints.GetGuildCommandPermission.Compile(applicationID, guildID, commandID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, nil, &cmdPerms) + if err == nil { + cmdPerms = r.Disgo().EntityBuilder().CreateGuildCommandPermissions(cmdPerms, api.CacheStrategyNoWs) + } + return +} + +// SetGuildCommandsPermissions sets the api.GuildCommandPermissions for a all api.Command(s) +func (r RestClientImpl) SetGuildCommandsPermissions(applicationID api.Snowflake, guildID api.Snowflake, commandsPermissions ...api.SetGuildCommandPermissions) (cmdsPerms []*api.GuildCommandPermissions, err error) { + compiledRoute, err := endpoints.SetGuildCommandsPermissions.Compile(applicationID, guildID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, api.SetGuildCommandsPermissions(commandsPermissions), &cmdsPerms) + if err == nil { + for _, cmdPerms := range cmdsPerms { + cmdPerms = r.Disgo().EntityBuilder().CreateGuildCommandPermissions(cmdPerms, api.CacheStrategyNoWs) + } + } + return +} + +// SetGuildCommandPermissions sets the api.GuildCommandPermissions for a specific api.Command +func (r RestClientImpl) SetGuildCommandPermissions(applicationID api.Snowflake, guildID api.Snowflake, commandID api.Snowflake, commandPermissions api.SetGuildCommandPermissions) (cmdPerms *api.GuildCommandPermissions, err error) { + compiledRoute, err := endpoints.SetGuildCommandPermissions.Compile(applicationID, guildID, commandID) + if err != nil { + return nil, err + } + err = r.Request(*compiledRoute, commandPermissions, &cmdPerms) + if err == nil { + cmdPerms = r.Disgo().EntityBuilder().CreateGuildCommandPermissions(cmdPerms, api.CacheStrategyNoWs) + } + return +} + +// SendInteractionResponse used to send the initial response on an interaction +func (r RestClientImpl) SendInteractionResponse(interactionID api.Snowflake, interactionToken endpoints.Token, interactionResponse api.InteractionResponse) error { + compiledRoute, err := endpoints.CreateInteractionResponse.Compile(interactionID, interactionToken) + if err != nil { + return err + } + return r.Request(*compiledRoute, interactionResponse, nil) +} + +// EditInteractionResponse used to edit the initial response on an interaction +func (r RestClientImpl) EditInteractionResponse(applicationID api.Snowflake, interactionToken endpoints.Token, followupMessage api.FollowupMessage) (message *api.Message, err error) { + compiledRoute, err := endpoints.EditInteractionResponse.Compile(applicationID, interactionToken) + if err != nil { + return nil, err + } + return message, r.Request(*compiledRoute, followupMessage, &message) +} + +// DeleteInteractionResponse used to delete the initial response on an interaction +func (r RestClientImpl) DeleteInteractionResponse(applicationID api.Snowflake, interactionToken endpoints.Token) error { + compiledRoute, err := endpoints.DeleteInteractionResponse.Compile(applicationID, interactionToken) + if err != nil { + return err + } + return r.Request(*compiledRoute, nil, nil) +} + +// SendFollowupMessage used to send a followup message_events to an interaction +func (r RestClientImpl) SendFollowupMessage(applicationID api.Snowflake, interactionToken endpoints.Token, followupMessage api.FollowupMessage) (message *api.Message, err error) { + compiledRoute, err := endpoints.CreateFollowupMessage.Compile(applicationID, interactionToken) + if err != nil { + return nil, err + } + return message, r.Request(*compiledRoute, followupMessage, &message) +} + +// EditFollowupMessage used to edit a api.FollowupMessage from an api.Interaction +func (r RestClientImpl) EditFollowupMessage(applicationID api.Snowflake, interactionToken endpoints.Token, messageID api.Snowflake, followupMessage api.FollowupMessage) (message *api.Message, err error) { + compiledRoute, err := endpoints.EditFollowupMessage.Compile(applicationID, interactionToken, messageID) + if err != nil { + return nil, err + } + return message, r.Request(*compiledRoute, followupMessage, &message) +} + +// DeleteFollowupMessage used to delete a api.FollowupMessage from an api.Interaction +func (r RestClientImpl) DeleteFollowupMessage(applicationID api.Snowflake, interactionToken endpoints.Token, messageID api.Snowflake) error { + compiledRoute, err := endpoints.DeleteFollowupMessage.Compile(applicationID, interactionToken, messageID) + if err != nil { + return err + } + return r.Request(*compiledRoute, nil, nil) +} + +func normalizeEmoji(emoji string) string { + return strings.Replace(emoji, "#", "%23", -1) +} diff --git a/internal/util.go b/internal/util.go index e460137b..15a5f35c 100644 --- a/internal/util.go +++ b/internal/util.go @@ -6,13 +6,14 @@ import ( "strings" "github.com/DisgoOrg/disgo/api" + "github.com/DisgoOrg/disgo/api/endpoints" ) -// IDFromToken returns the applicationID from the token -func IDFromToken(token string) (*api.Snowflake, error) { - strs := strings.Split(token, ".") +// IDFromToken returns the applicationID from the BotToken +func IDFromToken(token endpoints.Token) (*api.Snowflake, error) { + strs := strings.Split(string(token), ".") if len(strs) == 0 { - return nil, errors.New("token is not in a valid format") + return nil, errors.New("BotToken is not in a valid format") } byteID, err := base64.StdEncoding.DecodeString(strs[0]) if err != nil { diff --git a/internal/webhook_server.go b/internal/webhook_server_impl.go similarity index 73% rename from internal/webhook_server.go rename to internal/webhook_server_impl.go index 637b0ad3..5a17e66c 100644 --- a/internal/webhook_server.go +++ b/internal/webhook_server_impl.go @@ -8,16 +8,14 @@ import ( "net/http" "strconv" - "github.com/gorilla/mux" - log "github.com/sirupsen/logrus" - "github.com/DisgoOrg/disgo/api" + "github.com/gorilla/mux" ) func newWebhookServerImpl(disgo api.Disgo, listenURL string, listenPort int, publicKey string) api.WebhookServer { hexDecodedKey, err := hex.DecodeString(publicKey) if err != nil { - log.Errorf("error while decoding hex string: %s", err) + disgo.Logger().Errorf("error while decoding hex string: %s", err) } w := &WebhookServerImpl{ disgo: disgo, @@ -26,7 +24,7 @@ func newWebhookServerImpl(disgo api.Disgo, listenURL string, listenPort int, pub listenPort: listenPort, } - w.interactionHandler = &webhookInteractionHandler{w} + w.interactionHandler = &webhookInteractionHandler{disgo: disgo, webhookServer: w} return w } @@ -62,12 +60,15 @@ func (w *WebhookServerImpl) Router() *mux.Router { } // Start makes the WebhookServerImpl listen on the specified port and handle requests -func (w *WebhookServerImpl) Start() error { +func (w *WebhookServerImpl) Start() { w.router = mux.NewRouter() w.router.Handle(w.ListenURL(), w.interactionHandler).Methods("POST") - go http.ListenAndServe(":"+strconv.Itoa(w.listenPort), w.router) - return nil + go func() { + if err := http.ListenAndServe(":"+strconv.Itoa(w.listenPort), w.router); err != nil { + w.Disgo().Logger().Errorf("error starting webhook server: %s", err) + } + }() } // Close shuts down the WebhookServerImpl @@ -76,27 +77,32 @@ func (w *WebhookServerImpl) Close() { } type webhookInteractionHandler struct { + disgo api.Disgo webhookServer api.WebhookServer } func (h *webhookInteractionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if ok := api.Verify(r, h.webhookServer.PublicKey()); !ok { + if ok := api.Verify(h.disgo, r, h.webhookServer.PublicKey()); !ok { w.WriteHeader(http.StatusUnauthorized) return } - defer r.Body.Close() + defer func() { + if err := r.Body.Close(); err != nil { + h.disgo.Logger().Errorf("error closing request body in WebhookServer: %s", err) + } + }() rawBody, err := ioutil.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) } c := make(chan interface{}) - go h.webhookServer.Disgo().EventManager().Handle(api.InteractionCreateWebhookEvent, rawBody, c) + go h.webhookServer.Disgo().EventManager().Handle(api.WebhookEventInteractionCreate, c, -1, rawBody) w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json") err = json.NewEncoder(w).Encode(<-c) if err != nil { - log.Errorf("error writing body: %s", err) + h.disgo.Logger().Errorf("error writing body: %s", err) } } diff --git a/testbot/go.mod b/testbot/go.mod new file mode 100644 index 00000000..911abcf9 --- /dev/null +++ b/testbot/go.mod @@ -0,0 +1,11 @@ +module github.com/DisgoOrg/disgo/testbot + +go 1.16 + +replace github.com/DisgoOrg/disgo => ../ + +require ( + github.com/DisgoOrg/disgo v0.1.6 + github.com/PaesslerAG/gval v1.1.0 + github.com/sirupsen/logrus v1.8.1 +) diff --git a/testbot/go.sum b/testbot/go.sum new file mode 100644 index 00000000..094591a0 --- /dev/null +++ b/testbot/go.sum @@ -0,0 +1,31 @@ +github.com/DisgoOrg/log v1.0.0 h1:X8WSKatQUhZ/D8nYerJz75X3yD4/0vQgmAVbQ7XSVCI= +github.com/DisgoOrg/log v1.0.0/go.mod h1:E60iniXaAsm9Iuxlz53nQDTYzJTSf1Kn/oH9KQhaK6w= +github.com/DisgoOrg/log v1.0.2 h1:qDOmBkB2xnN0sG74dTNKs/qdWekVAJAyVPDASofIWOc= +github.com/DisgoOrg/log v1.0.2/go.mod h1:KFGKhBQr37d6rxZ7p2bmc8BEmDH8DZbtgdlJDSCsE7I= +github.com/DisgoOrg/log v1.0.3 h1:IjmZQQu/kuBIui22EdXmxzQGYwcPCJEkXa0Fe6W9fJk= +github.com/DisgoOrg/log v1.0.3/go.mod h1:KFGKhBQr37d6rxZ7p2bmc8BEmDH8DZbtgdlJDSCsE7I= +github.com/PaesslerAG/gval v1.1.0 h1:k3RuxeZDO3eejD4cMPSt+74tUSvTnbGvLx0df4mdwFc= +github.com/PaesslerAG/gval v1.1.0/go.mod h1:y/nm5yEyTeX6av0OfKJNp9rBNj2XrGhAf5+v24IBN1I= +github.com/PaesslerAG/jsonpath v0.1.0 h1:gADYeifvlqK3R3i2cR5B4DGgxLXIPb3TRTH1mGi0jPI= +github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/testbot/testbot.go b/testbot/testbot.go index 5a6724ea..77a0ebd0 100644 --- a/testbot/testbot.go +++ b/testbot/testbot.go @@ -1,153 +1,272 @@ package main import ( + "fmt" "os" "os/signal" + "strconv" "syscall" + "time" - log "github.com/sirupsen/logrus" + "github.com/PaesslerAG/gval" + "github.com/sirupsen/logrus" "github.com/DisgoOrg/disgo" "github.com/DisgoOrg/disgo/api" + "github.com/DisgoOrg/disgo/api/endpoints" "github.com/DisgoOrg/disgo/api/events" ) +const red = 16711680 +const orange = 16562691 +const green = 65280 + +const guildID = "817327181659111454" +const adminRoleID = "817327279583264788" +const testRoleID = "825156597935243304" + +var logger = logrus.New() + func main() { - log.Infof("starting testbot...") - token := os.Getenv("token") - publicKey := os.Getenv("public-key") + logger.SetLevel(logrus.DebugLevel) + logger.Info("starting testbot...") - dgo, err := disgo.NewBuilder(token). - SetLogLevel(log.InfoLevel). - SetIntents(api.IntentsGuilds|api.IntentsGuildMessages|api.IntentsGuildMembers). + dgo, err := disgo.NewBuilder(endpoints.Token(os.Getenv("token"))). + SetLogger(logger). + SetIntents(api.IntentsGuilds | api.IntentsGuildMessages | api.IntentsGuildMembers). SetMemberCachePolicy(api.MemberCachePolicyAll). - SetWebhookServerProperties("/webhooks/interactions/callback", 80, publicKey). AddEventListeners(&events.ListenerAdapter{ OnGuildAvailable: guildAvailListener, - OnGuildMessageReceived: messageListener, + OnGuildMessageCreate: messageListener, OnSlashCommand: slashCommandListener, }). Build() if err != nil { - log.Fatalf("error while building disgo instance: %s", err) + logger.Fatalf("error while building disgo instance: %s", err) return } - _, err = dgo.RestClient().SetGuildCommands(dgo.ApplicationID(), "817327181659111454", - api.SlashCommand{ - Name: "test", - Description: "test test test test test test", + rawCmds := []api.Command{ + { + Name: "eval", + Description: "runs some go code", + DefaultPermission: false, + Options: []*api.CommandOption{ + { + Type: api.CommandOptionTypeString, + Name: "code", + Description: "the code to eval", + Required: true, + }, + }, }, - api.SlashCommand{ - Name: "say", - Description: "says what you say", + { + Name: "test", + Description: "test test test test test test", + DefaultPermission: false, + }, + { + Name: "say", + Description: "says what you say", + DefaultPermission: false, Options: []*api.CommandOption{ { - Type: api.OptionTypeString, + Type: api.CommandOptionTypeString, Name: "message", Description: "What to say", Required: true, }, }, }, - api.SlashCommand{ - Name: "addrole", - Description: "This command adds a role to a member", + { + Name: "addrole", + Description: "This command adds a role to a member", + DefaultPermission: false, Options: []*api.CommandOption{ { - Type: api.OptionTypeUser, + Type: api.CommandOptionTypeUser, Name: "member", Description: "The member to add a role to", Required: true, }, { - Type: api.OptionTypeRole, + Type: api.CommandOptionTypeRole, Name: "role", Description: "The role to add to a member", Required: true, }, }, }, - api.SlashCommand{ - Name: "removerole", - Description: "This command removes a role from a member", + { + Name: "removerole", + Description: "This command removes a role from a member", + DefaultPermission: false, Options: []*api.CommandOption{ { - Type: api.OptionTypeUser, + Type: api.CommandOptionTypeUser, Name: "member", Description: "The member to removes a role from", Required: true, }, { - Type: api.OptionTypeRole, + Type: api.CommandOptionTypeRole, Name: "role", Description: "The role to removes from a member", Required: true, }, }, }, - ) - if err != nil { - log.Errorf("error while registering guild commands: %s", err) } - err = dgo.Start() + // using the api.RestClient directly to avoid the guild needing to be cached + cmds, err := dgo.RestClient().SetGuildCommands(dgo.ApplicationID(), guildID, rawCmds...) if err != nil { - log.Fatalf("error while starting webhookserver: %s", err) + logger.Errorf("error while registering guild commands: %s", err) + } + + var cmdsPermissions []api.SetGuildCommandPermissions + for _, cmd := range cmds { + var perms api.CommandPermission + if cmd.Name == "eval" { + perms = api.CommandPermission{ + ID: adminRoleID, + Type: api.CommandPermissionTypeRole, + Permission: true, + } + } else { + perms = api.CommandPermission{ + ID: testRoleID, + Type: api.CommandPermissionTypeRole, + Permission: true, + } + } + cmdsPermissions = append(cmdsPermissions, api.SetGuildCommandPermissions{ + ID: cmd.ID, + Permissions: []api.CommandPermission{perms}, + }) + } + if _, err = dgo.RestClient().SetGuildCommandsPermissions(dgo.ApplicationID(), guildID, cmdsPermissions...); err != nil { + logger.Errorf("error while setting command permissions: %s", err) } err = dgo.Connect() if err != nil { - log.Fatalf("error while connecting to discord: %s", err) + logger.Fatalf("error while connecting to discord: %s", err) } defer dgo.Close() - log.Infof("Bot is now running. Press CTRL-C to exit.") + logger.Infof("Bot is now running. Press CTRL-C to exit.") s := make(chan os.Signal, 1) signal.Notify(s, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) <-s } func guildAvailListener(event *events.GuildAvailableEvent) { - log.Printf("guild loaded: %s", event.GuildID) + logger.Printf("guild loaded: %s", event.Guild.ID) } func slashCommandListener(event *events.SlashCommandEvent) { - switch event.Interaction.Data.Name { + switch event.CommandName { + case "eval": + go func() { + start := time.Now() + code := event.Option("code").String() + embed := api.NewEmbedBuilder(). + SetColor(orange). + AddField("Status", "...", true). + AddField("Time", "...", true). + AddField("Code", "```go\n"+code+"\n```", false). + AddField("Output", "```\n...\n```", false) + + _ = event.Reply(api.NewInteractionResponseBuilder().SetEmbeds(embed.Build()).Build()) + + vars := map[string]interface{}{ + "disgo": event.Disgo(), + "dgo": event.Disgo(), + "event": event, + } + output, err := gval.Evaluate(code, vars) + + elapsed := time.Since(start) + embed.SetField(1, "Time", strconv.Itoa(int(elapsed.Milliseconds()))+"ms", true) + + if err != nil { + _, _ = event.EditOriginal(api.NewFollowupMessageBuilder(). + SetEmbeds(embed. + SetColor(red). + SetField(0, "Status", "failed", true). + SetField(3, "Output", "```"+err.Error()+"```", false). + Build(), + ). + Build(), + ) + return + } + _, _ = event.EditOriginal(api.NewFollowupMessageBuilder(). + SetEmbeds(embed. + SetColor(green). + SetField(0, "Status", "success", true). + SetField(3, "Output", "```"+fmt.Sprintf("%+v", output)+"```", false). + Build(), + ). + Build(), + ) + }() + case "say": _ = event.Reply(api.NewInteractionResponseBuilder(). - SetContent(event.OptionByName("message").String()). + SetContent(event.Option("message").String()). SetAllowedMentionsEmpty(). Build(), ) + case "test": - _ = event.Reply(api.NewInteractionResponseBuilder(). - SetContent("test"). - SetEphemeral(true). - AddEmbeds( - api.NewEmbedBuilder().SetDescription("test1").Build(), - api.NewEmbedBuilder().SetDescription("test2").Build(), - ). - Build(), - ) + go func() { + _ = event.Acknowledge() + + time.Sleep(2 * time.Second) + _, _ = event.EditOriginal(api.NewFollowupMessageBuilder(). + SetEmbeds(api.NewEmbedBuilder(). + SetDescription("finished with thinking"). + Build(), + ).Build(), + ) + + time.Sleep(1 * time.Second) + _, _ = event.SendFollowup(api.NewFollowupMessageBuilder(). + SetEmbeds(api.NewEmbedBuilder(). + SetDescription("followup 1"). + Build(), + ).Build(), + ) + + time.Sleep(1 * time.Second) + _, _ = event.SendFollowup(api.NewFollowupMessageBuilder(). + SetEphemeral(true). + SetContent("followup 2 only you can see"). + Build(), + ) + }() + case "addrole": - user := event.OptionByName("member").User() - role := event.OptionByName("role").Role() - err := event.Disgo.RestClient().AddMemberRole(*event.Interaction.GuildID, user.ID, role.ID) + user := event.Option("member").User() + role := event.Option("role").Role() + err := event.Disgo().RestClient().AddMemberRole(*event.Interaction.GuildID, user.ID, role.ID) if err == nil { _ = event.Reply(api.NewInteractionResponseBuilder().AddEmbeds( - api.NewEmbedBuilder().SetColor(65280).SetDescriptionf("Added %s to %s", role, user).Build(), + api.NewEmbedBuilder().SetColor(green).SetDescriptionf("Added %s to %s", role, user).Build(), ).Build()) } else { _ = event.Reply(api.NewInteractionResponseBuilder().AddEmbeds( - api.NewEmbedBuilder().SetColor(16711680).SetDescriptionf("Failed to add %s to %s", role, user).Build(), + api.NewEmbedBuilder().SetColor(red).SetDescriptionf("Failed to add %s to %s", role, user).Build(), ).Build()) } + case "removerole": - user := event.OptionByName("member").User() - role := event.OptionByName("role").Role() - err := event.Disgo.RestClient().RemoveMemberRole(*event.Interaction.GuildID, user.ID, role.ID) + user := event.Option("member").User() + role := event.Option("role").Role() + err := event.Disgo().RestClient().RemoveMemberRole(*event.Interaction.GuildID, user.ID, role.ID) if err == nil { _ = event.Reply(api.NewInteractionResponseBuilder().AddEmbeds( api.NewEmbedBuilder().SetColor(65280).SetDescriptionf("Removed %s from %s", role, user).Build(), @@ -160,7 +279,7 @@ func slashCommandListener(event *events.SlashCommandEvent) { } } -func messageListener(event *events.GuildMessageReceivedEvent) { +func messageListener(event *events.GuildMessageCreateEvent) { if event.Message.Author.IsBot { return }