From 9cbba0bb75451832e1f67a418a51204e3158d9ef Mon Sep 17 00:00:00 2001 From: caneleex Date: Wed, 8 Feb 2023 22:24:11 +0100 Subject: [PATCH 001/119] add MessageFlagSuppressNotifications and MessageFlagFailedToMentionSomeRolesInThread --- discord/message.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/discord/message.go b/discord/message.go index 81466c94d..c0e3716e3 100644 --- a/discord/message.go +++ b/discord/message.go @@ -406,8 +406,13 @@ const ( MessageFlagUrgent MessageFlagHasThread MessageFlagEphemeral - MessageFlagLoading // Message is an interaction of type 5, awaiting further response - MessageFlagsNone MessageFlags = 0 + MessageFlagLoading // Message is an interaction of type 5, awaiting further response + MessageFlagFailedToMentionSomeRolesInThread + _ + _ + _ + MessageFlagSuppressNotifications + MessageFlagsNone MessageFlags = 0 ) // Add allows you to add multiple bits together, producing a new bit From 742d87e78a40d9f764fc0128f229b617202602a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80Senpai?= Date: Thu, 9 Feb 2023 20:29:48 +0000 Subject: [PATCH 002/119] fix some minor broke/wrong stuff (#237) * fix marshalling of interactions * fix missing type in application command interaction data --- ...application_command_autocomplete_option.go | 8 +- discord/interaction.go | 29 ++++--- discord/interaction_application_command.go | 73 +++++++++++++----- discord/interaction_autocomplete.go | 59 ++++++++++---- discord/interaction_base.go | 77 ++++--------------- discord/interaction_component.go | 48 +++++++++--- discord/interaction_modal_submit.go | 44 +++++++++-- discord/slash_command_option.go | 7 +- gateway/gateway_events.go | 12 ++- handlers/interaction_create_handler.go | 3 +- httpserver/server.go | 10 ++- 11 files changed, 232 insertions(+), 138 deletions(-) diff --git a/discord/application_command_autocomplete_option.go b/discord/application_command_autocomplete_option.go index e31b5d244..20dbc135f 100644 --- a/discord/application_command_autocomplete_option.go +++ b/discord/application_command_autocomplete_option.go @@ -55,9 +55,10 @@ func (o *UnmarshalAutocompleteOption) UnmarshalJSON(data []byte) error { var _ internalAutocompleteOption = (*AutocompleteOptionSubCommand)(nil) type AutocompleteOptionSubCommand struct { - Name string `json:"name"` - Description string `json:"description"` - Options []AutocompleteOption `json:"options,omitempty"` + Name string `json:"name"` + Description string `json:"description"` + Type ApplicationCommandOptionType `json:"type"` + Options []AutocompleteOption `json:"options,omitempty"` } func (o AutocompleteOptionSubCommand) name() string { @@ -70,6 +71,7 @@ var _ internalAutocompleteOption = (*AutocompleteOptionSubCommandGroup)(nil) type AutocompleteOptionSubCommandGroup struct { Name string `json:"name"` Description string `json:"description"` + Type ApplicationCommandOptionType `json:"type"` Options []AutocompleteOptionSubCommand `json:"options,omitempty"` } diff --git a/discord/interaction.go b/discord/interaction.go index 2c76006b5..61d969a98 100644 --- a/discord/interaction.go +++ b/discord/interaction.go @@ -2,6 +2,7 @@ package discord import ( "fmt" + "time" "github.com/disgoorg/json" "github.com/disgoorg/snowflake/v2" @@ -37,22 +38,29 @@ type rawInteraction struct { // Interaction is used for easier unmarshalling of different Interaction(s) type Interaction interface { Type() InteractionType - BaseInteraction + ID() snowflake.ID + ApplicationID() snowflake.ID + Token() string + Version() int + GuildID() *snowflake.ID + ChannelID() snowflake.ID + Locale() Locale + GuildLocale() *Locale + Member() *ResolvedMember + User() User + AppPermissions() *Permissions + CreatedAt() time.Time interaction() } -type UnmarshalInteraction struct { - Interaction -} - -func (i *UnmarshalInteraction) UnmarshalJSON(data []byte) error { +func UnmarshalInteraction(data []byte) (Interaction, error) { var iType struct { Type InteractionType `json:"type"` } if err := json.Unmarshal(data, &iType); err != nil { - return err + return nil, err } var ( @@ -87,14 +95,13 @@ func (i *UnmarshalInteraction) UnmarshalJSON(data []byte) error { interaction = v default: - return fmt.Errorf("unknown rawInteraction with type %d received", iType.Type) + err = fmt.Errorf("unknown rawInteraction with type %d received", iType.Type) } if err != nil { - return err + return nil, err } - i.Interaction = interaction - return nil + return interaction, nil } type ( diff --git a/discord/interaction_application_command.go b/discord/interaction_application_command.go index e2ce57a7e..398312eba 100644 --- a/discord/interaction_application_command.go +++ b/discord/interaction_application_command.go @@ -12,22 +12,19 @@ var ( ) type ApplicationCommandInteraction struct { - BaseInteraction + baseInteraction Data ApplicationCommandInteractionData `json:"data"` } func (i *ApplicationCommandInteraction) UnmarshalJSON(data []byte) error { - var baseInteraction baseInteractionImpl - if err := json.Unmarshal(data, &baseInteraction); err != nil { - return err - } - var interaction struct { + rawInteraction Data json.RawMessage `json:"data"` } if err := json.Unmarshal(data, &interaction); err != nil { return err } + var cType struct { Type ApplicationCommandType `json:"type"` } @@ -39,7 +36,6 @@ func (i *ApplicationCommandInteraction) UnmarshalJSON(data []byte) error { interactionData ApplicationCommandInteractionData err error ) - switch cType.Type { case ApplicationCommandTypeSlash: v := SlashCommandInteractionData{} @@ -55,10 +51,10 @@ func (i *ApplicationCommandInteraction) UnmarshalJSON(data []byte) error { v := MessageCommandInteractionData{} err = json.Unmarshal(interaction.Data, &v) interactionData = v - if baseInteraction.GuildID() != nil { + if interaction.GuildID != nil { for id := range v.Resolved.Messages { msg := v.Resolved.Messages[id] - msg.GuildID = baseInteraction.guildID + msg.GuildID = interaction.GuildID v.Resolved.Messages[id] = msg } } @@ -70,12 +66,45 @@ func (i *ApplicationCommandInteraction) UnmarshalJSON(data []byte) error { return err } - i.BaseInteraction = baseInteraction + i.baseInteraction.id = interaction.ID + i.baseInteraction.applicationID = interaction.ApplicationID + i.baseInteraction.token = interaction.Token + i.baseInteraction.version = interaction.Version + i.baseInteraction.guildID = interaction.GuildID + i.baseInteraction.channelID = interaction.ChannelID + i.baseInteraction.locale = interaction.Locale + i.baseInteraction.guildLocale = interaction.GuildLocale + i.baseInteraction.member = interaction.Member + i.baseInteraction.user = interaction.User + i.baseInteraction.appPermissions = interaction.AppPermissions i.Data = interactionData return nil } +func (i ApplicationCommandInteraction) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + rawInteraction + Data ApplicationCommandInteractionData `json:"data"` + }{ + rawInteraction: rawInteraction{ + ID: i.id, + Type: i.Type(), + ApplicationID: i.applicationID, + Token: i.token, + Version: i.version, + GuildID: i.guildID, + ChannelID: i.channelID, + Locale: i.locale, + GuildLocale: i.guildLocale, + Member: i.member, + User: i.user, + AppPermissions: i.appPermissions, + }, + Data: i.Data, + }) +} + func (ApplicationCommandInteraction) Type() InteractionType { return InteractionTypeApplicationCommand } @@ -106,6 +135,7 @@ type ApplicationCommandInteractionData interface { type rawSlashCommandInteractionData struct { ID snowflake.ID `json:"id"` Name string `json:"name"` + Type ApplicationCommandType `json:"type"` GuildID *snowflake.ID `json:"guild_id,omitempty"` Resolved SlashCommandResolved `json:"resolved"` Options []internalSlashCommandOption `json:"options"` @@ -183,7 +213,7 @@ func (d *SlashCommandInteractionData) UnmarshalJSON(data []byte) error { } func (d SlashCommandInteractionData) MarshalJSON() ([]byte, error) { - options := make([]internalSlashCommandOption, len(d.Options)) + options := make([]internalSlashCommandOption, 0, len(d.Options)) for _, option := range d.Options { options = append(options, option) } @@ -191,7 +221,8 @@ func (d SlashCommandInteractionData) MarshalJSON() ([]byte, error) { if d.SubCommandName != nil { subCmd := SlashCommandOptionSubCommand{ Name: *d.SubCommandName, - Options: make([]SlashCommandOption, len(options)), + Options: make([]SlashCommandOption, 0, len(options)), + Type: ApplicationCommandOptionTypeSubCommand, } for _, option := range options { subCmd.Options = append(subCmd.Options, option.(SlashCommandOption)) @@ -202,7 +233,8 @@ func (d SlashCommandInteractionData) MarshalJSON() ([]byte, error) { if d.SubCommandGroupName != nil { groupCmd := SlashCommandOptionSubCommandGroup{ Name: *d.SubCommandGroupName, - Options: make([]SlashCommandOptionSubCommand, len(options)), + Options: make([]SlashCommandOptionSubCommand, 0, len(options)), + Type: ApplicationCommandOptionTypeSubCommandGroup, } for _, option := range options { groupCmd.Options = append(groupCmd.Options, option.(SlashCommandOptionSubCommand)) @@ -213,6 +245,7 @@ func (d SlashCommandInteractionData) MarshalJSON() ([]byte, error) { return json.Marshal(rawSlashCommandInteractionData{ ID: d.id, Name: d.name, + Type: d.Type(), GuildID: d.guildID, Resolved: d.Resolved, Options: options, @@ -484,11 +517,12 @@ var ( ) type rawUserCommandInteractionData struct { - ID snowflake.ID `json:"id"` - Name string `json:"name"` - GuildID *snowflake.ID `json:"guild_id,omitempty"` - Resolved UserCommandResolved `json:"resolved"` - TargetID snowflake.ID `json:"target_id"` + ID snowflake.ID `json:"id"` + Name string `json:"name"` + Type ApplicationCommandType `json:"type"` + GuildID *snowflake.ID `json:"guild_id,omitempty"` + Resolved UserCommandResolved `json:"resolved"` + TargetID snowflake.ID `json:"target_id"` } type UserCommandInteractionData struct { @@ -516,6 +550,7 @@ func (d *UserCommandInteractionData) MarshalJSON() ([]byte, error) { return json.Marshal(rawUserCommandInteractionData{ ID: d.id, Name: d.name, + Type: d.Type(), GuildID: d.guildID, Resolved: d.Resolved, TargetID: d.targetID, @@ -566,6 +601,7 @@ var ( type rawMessageCommandInteractionData struct { ID snowflake.ID `json:"id"` Name string `json:"name"` + Type ApplicationCommandType `json:"type"` GuildID *snowflake.ID `json:"guild_id,omitempty"` Resolved MessageCommandResolved `json:"resolved"` TargetID snowflake.ID `json:"target_id"` @@ -596,6 +632,7 @@ func (d *MessageCommandInteractionData) MarshalJSON() ([]byte, error) { return json.Marshal(rawMessageCommandInteractionData{ ID: d.id, Name: d.name, + Type: d.Type(), GuildID: d.guildID, Resolved: d.Resolved, TargetID: d.targetID, diff --git a/discord/interaction_autocomplete.go b/discord/interaction_autocomplete.go index ff12a088f..64e1624d8 100644 --- a/discord/interaction_autocomplete.go +++ b/discord/interaction_autocomplete.go @@ -10,29 +10,58 @@ var ( ) type AutocompleteInteraction struct { - BaseInteraction + baseInteraction Data AutocompleteInteractionData `json:"data"` } func (i *AutocompleteInteraction) UnmarshalJSON(data []byte) error { - var baseInteraction baseInteractionImpl - if err := json.Unmarshal(data, &baseInteraction); err != nil { - return err - } - - var v struct { + var interaction struct { + rawInteraction Data AutocompleteInteractionData `json:"data"` } - if err := json.Unmarshal(data, &v); err != nil { + if err := json.Unmarshal(data, &interaction); err != nil { return err } - i.BaseInteraction = baseInteraction - - i.Data = v.Data + i.baseInteraction.id = interaction.ID + i.baseInteraction.applicationID = interaction.ApplicationID + i.baseInteraction.token = interaction.Token + i.baseInteraction.version = interaction.Version + i.baseInteraction.guildID = interaction.GuildID + i.baseInteraction.channelID = interaction.ChannelID + i.baseInteraction.locale = interaction.Locale + i.baseInteraction.guildLocale = interaction.GuildLocale + i.baseInteraction.member = interaction.Member + i.baseInteraction.user = interaction.User + i.baseInteraction.appPermissions = interaction.AppPermissions + + i.Data = interaction.Data return nil } +func (i AutocompleteInteraction) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + rawInteraction + Data AutocompleteInteractionData `json:"data"` + }{ + rawInteraction: rawInteraction{ + ID: i.id, + Type: i.Type(), + ApplicationID: i.applicationID, + Token: i.token, + Version: i.version, + GuildID: i.guildID, + ChannelID: i.channelID, + Locale: i.locale, + GuildLocale: i.guildLocale, + Member: i.member, + User: i.user, + AppPermissions: i.appPermissions, + }, + Data: i.Data, + }) +} + func (AutocompleteInteraction) Type() InteractionType { return InteractionTypeAutocomplete } @@ -116,7 +145,7 @@ func (d *AutocompleteInteractionData) UnmarshalJSON(data []byte) error { } func (d AutocompleteInteractionData) MarshalJSON() ([]byte, error) { - options := make([]internalAutocompleteOption, len(d.Options)) + options := make([]internalAutocompleteOption, 0, len(d.Options)) for _, option := range d.Options { options = append(options, option) } @@ -124,7 +153,8 @@ func (d AutocompleteInteractionData) MarshalJSON() ([]byte, error) { if d.SubCommandName != nil { subCmd := AutocompleteOptionSubCommand{ Name: *d.SubCommandName, - Options: make([]AutocompleteOption, len(options)), + Options: make([]AutocompleteOption, 0, len(options)), + Type: ApplicationCommandOptionTypeSubCommand, } for _, option := range options { subCmd.Options = append(subCmd.Options, option.(AutocompleteOption)) @@ -135,7 +165,8 @@ func (d AutocompleteInteractionData) MarshalJSON() ([]byte, error) { if d.SubCommandGroupName != nil { groupCmd := AutocompleteOptionSubCommandGroup{ Name: *d.SubCommandGroupName, - Options: make([]AutocompleteOptionSubCommand, len(options)), + Options: make([]AutocompleteOptionSubCommand, 0, len(options)), + Type: ApplicationCommandOptionTypeSubCommandGroup, } for _, option := range options { groupCmd.Options = append(groupCmd.Options, option.(AutocompleteOptionSubCommand)) diff --git a/discord/interaction_base.go b/discord/interaction_base.go index ab56a7b59..3576803ae 100644 --- a/discord/interaction_base.go +++ b/discord/interaction_base.go @@ -3,26 +3,10 @@ package discord import ( "time" - "github.com/disgoorg/json" "github.com/disgoorg/snowflake/v2" ) -type BaseInteraction interface { - ID() snowflake.ID - ApplicationID() snowflake.ID - Token() string - Version() int - GuildID() *snowflake.ID - ChannelID() snowflake.ID - Locale() Locale - GuildLocale() *Locale - Member() *ResolvedMember - User() User - AppPermissions() *Permissions - CreatedAt() time.Time -} - -type baseInteractionImpl struct { +type baseInteraction struct { id snowflake.ID applicationID snowflake.ID token string @@ -36,79 +20,44 @@ type baseInteractionImpl struct { appPermissions *Permissions } -func (i *baseInteractionImpl) UnmarshalJSON(data []byte) error { - var v rawInteraction - if err := json.Unmarshal(data, &v); err != nil { - return err - } - i.id = v.ID - i.applicationID = v.ApplicationID - i.token = v.Token - i.version = v.Version - i.guildID = v.GuildID - i.channelID = v.ChannelID - i.locale = v.Locale - i.guildLocale = v.GuildLocale - i.member = v.Member - i.user = v.User - i.appPermissions = v.AppPermissions - return nil -} - -func (i baseInteractionImpl) MarshalJSON() ([]byte, error) { - return json.Marshal(rawInteraction{ - ID: i.id, - ApplicationID: i.applicationID, - Token: i.token, - Version: i.version, - GuildID: i.guildID, - ChannelID: i.channelID, - Locale: i.locale, - GuildLocale: i.guildLocale, - Member: i.member, - User: i.user, - AppPermissions: i.appPermissions, - }) -} - -func (i baseInteractionImpl) ID() snowflake.ID { +func (i baseInteraction) ID() snowflake.ID { return i.id } -func (i baseInteractionImpl) ApplicationID() snowflake.ID { +func (i baseInteraction) ApplicationID() snowflake.ID { return i.applicationID } -func (i baseInteractionImpl) Token() string { +func (i baseInteraction) Token() string { return i.token } -func (i baseInteractionImpl) Version() int { +func (i baseInteraction) Version() int { return i.version } -func (i baseInteractionImpl) GuildID() *snowflake.ID { +func (i baseInteraction) GuildID() *snowflake.ID { return i.guildID } -func (i baseInteractionImpl) ChannelID() snowflake.ID { +func (i baseInteraction) ChannelID() snowflake.ID { return i.channelID } -func (i baseInteractionImpl) Locale() Locale { +func (i baseInteraction) Locale() Locale { return i.locale } -func (i baseInteractionImpl) GuildLocale() *Locale { +func (i baseInteraction) GuildLocale() *Locale { return i.guildLocale } -func (i baseInteractionImpl) Member() *ResolvedMember { +func (i baseInteraction) Member() *ResolvedMember { return i.member } -func (i baseInteractionImpl) User() User { +func (i baseInteraction) User() User { if i.user != nil { return *i.user } return i.member.User } -func (i baseInteractionImpl) AppPermissions() *Permissions { +func (i baseInteraction) AppPermissions() *Permissions { return i.appPermissions } -func (i baseInteractionImpl) CreatedAt() time.Time { +func (i baseInteraction) CreatedAt() time.Time { return i.id.Time() } diff --git a/discord/interaction_component.go b/discord/interaction_component.go index ea6a1ca05..c2ed2396c 100644 --- a/discord/interaction_component.go +++ b/discord/interaction_component.go @@ -12,18 +12,14 @@ var ( ) type ComponentInteraction struct { - BaseInteraction + baseInteraction Data ComponentInteractionData `json:"data"` Message Message `json:"message"` } func (i *ComponentInteraction) UnmarshalJSON(data []byte) error { - var baseInteraction baseInteractionImpl - if err := json.Unmarshal(data, &baseInteraction); err != nil { - return err - } - var interaction struct { + rawInteraction Data json.RawMessage `json:"data"` Message Message `json:"message"` } @@ -34,7 +30,6 @@ func (i *ComponentInteraction) UnmarshalJSON(data []byte) error { var cType struct { Type ComponentType `json:"component_type"` } - if err := json.Unmarshal(interaction.Data, &cType); err != nil { return err } @@ -81,14 +76,49 @@ func (i *ComponentInteraction) UnmarshalJSON(data []byte) error { return err } - i.BaseInteraction = baseInteraction + i.baseInteraction.id = interaction.ID + i.baseInteraction.applicationID = interaction.ApplicationID + i.baseInteraction.token = interaction.Token + i.baseInteraction.version = interaction.Version + i.baseInteraction.guildID = interaction.GuildID + i.baseInteraction.channelID = interaction.ChannelID + i.baseInteraction.locale = interaction.Locale + i.baseInteraction.guildLocale = interaction.GuildLocale + i.baseInteraction.member = interaction.Member + i.baseInteraction.user = interaction.User + i.baseInteraction.appPermissions = interaction.AppPermissions i.Data = interactionData i.Message = interaction.Message - i.Message.GuildID = baseInteraction.guildID + i.Message.GuildID = i.baseInteraction.guildID return nil } +func (i ComponentInteraction) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + rawInteraction + Data ComponentInteractionData `json:"data"` + Message Message `json:"message"` + }{ + rawInteraction: rawInteraction{ + ID: i.id, + Type: i.Type(), + ApplicationID: i.applicationID, + Token: i.token, + Version: i.version, + GuildID: i.guildID, + ChannelID: i.channelID, + Locale: i.locale, + GuildLocale: i.guildLocale, + Member: i.member, + User: i.user, + AppPermissions: i.appPermissions, + }, + Data: i.Data, + Message: i.Message, + }) +} + func (ComponentInteraction) Type() InteractionType { return InteractionTypeComponent } diff --git a/discord/interaction_modal_submit.go b/discord/interaction_modal_submit.go index d3301a1d1..dc9b477e8 100644 --- a/discord/interaction_modal_submit.go +++ b/discord/interaction_modal_submit.go @@ -7,28 +7,58 @@ var ( ) type ModalSubmitInteraction struct { - BaseInteraction + baseInteraction Data ModalSubmitInteractionData `json:"data"` } func (i *ModalSubmitInteraction) UnmarshalJSON(data []byte) error { - var baseInteraction baseInteractionImpl - if err := json.Unmarshal(data, &baseInteraction); err != nil { - return err - } - var interaction struct { + rawInteraction Data ModalSubmitInteractionData `json:"data"` } if err := json.Unmarshal(data, &interaction); err != nil { return err } - i.BaseInteraction = baseInteraction + i.baseInteraction.id = interaction.ID + i.baseInteraction.applicationID = interaction.ApplicationID + i.baseInteraction.token = interaction.Token + i.baseInteraction.version = interaction.Version + i.baseInteraction.guildID = interaction.GuildID + i.baseInteraction.channelID = interaction.ChannelID + i.baseInteraction.locale = interaction.Locale + i.baseInteraction.guildLocale = interaction.GuildLocale + i.baseInteraction.member = interaction.Member + i.baseInteraction.user = interaction.User + i.baseInteraction.appPermissions = interaction.AppPermissions + i.Data = interaction.Data return nil } +func (i ModalSubmitInteraction) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + rawInteraction + Data ModalSubmitInteractionData `json:"data"` + }{ + rawInteraction: rawInteraction{ + ID: i.id, + Type: i.Type(), + ApplicationID: i.applicationID, + Token: i.token, + Version: i.version, + GuildID: i.guildID, + ChannelID: i.channelID, + Locale: i.locale, + GuildLocale: i.guildLocale, + Member: i.member, + User: i.user, + AppPermissions: i.appPermissions, + }, + Data: i.Data, + }) +} + func (ModalSubmitInteraction) Type() InteractionType { return InteractionTypeModalSubmit } diff --git a/discord/slash_command_option.go b/discord/slash_command_option.go index 05f743445..513a549b1 100644 --- a/discord/slash_command_option.go +++ b/discord/slash_command_option.go @@ -17,7 +17,6 @@ func (o *UnmarshalSlashCommandOption) UnmarshalJSON(data []byte) error { var oType struct { Type ApplicationCommandOptionType `json:"type"` } - if err := json.Unmarshal(data, &oType); err != nil { return err } @@ -54,8 +53,9 @@ func (o *UnmarshalSlashCommandOption) UnmarshalJSON(data []byte) error { var _ internalSlashCommandOption = (*SlashCommandOptionSubCommand)(nil) type SlashCommandOptionSubCommand struct { - Name string `json:"name"` - Options []SlashCommandOption `json:"options,omitempty"` + Name string `json:"name"` + Type ApplicationCommandOptionType `json:"type"` + Options []SlashCommandOption `json:"options,omitempty"` } func (o SlashCommandOptionSubCommand) name() string { @@ -67,6 +67,7 @@ var _ internalSlashCommandOption = (*SlashCommandOptionSubCommandGroup)(nil) type SlashCommandOptionSubCommandGroup struct { Name string `json:"name"` + Type ApplicationCommandOptionType `json:"type"` Options []SlashCommandOptionSubCommand `json:"options,omitempty"` } diff --git a/gateway/gateway_events.go b/gateway/gateway_events.go index 3f08ba640..9f02fec52 100644 --- a/gateway/gateway_events.go +++ b/gateway/gateway_events.go @@ -36,7 +36,7 @@ type EventReady struct { Guilds []discord.UnavailableGuild `json:"guilds"` SessionID string `json:"session_id"` ResumeGatewayURL string `json:"resume_gateway_url"` - Shard []int `json:"shard,omitempty"` + Shard [2]int `json:"shard,omitempty"` Application discord.PartialApplication `json:"application"` } @@ -439,14 +439,18 @@ type EventInteractionCreate struct { } func (e *EventInteractionCreate) UnmarshalJSON(data []byte) error { - var interaction discord.UnmarshalInteraction - if err := json.Unmarshal(data, &interaction); err != nil { + interaction, err := discord.UnmarshalInteraction(data) + if err != nil { return err } - e.Interaction = interaction.Interaction + e.Interaction = interaction return nil } +func (e EventInteractionCreate) MarshalJSON() ([]byte, error) { + return json.Marshal(e.Interaction) +} + func (EventInteractionCreate) messageData() {} func (EventInteractionCreate) eventData() {} diff --git a/handlers/interaction_create_handler.go b/handlers/interaction_create_handler.go index 8768092b0..9c7988eb9 100644 --- a/handlers/interaction_create_handler.go +++ b/handlers/interaction_create_handler.go @@ -13,7 +13,7 @@ func gatewayHandlerInteractionCreate(client bot.Client, sequenceNumber int, shar handleInteraction(client, sequenceNumber, shardID, nil, event.Interaction) } -func respond(client bot.Client, respondFunc httpserver.RespondFunc, interaction discord.BaseInteraction) events.InteractionResponderFunc { +func respond(client bot.Client, respondFunc httpserver.RespondFunc, interaction discord.Interaction) events.InteractionResponderFunc { return func(responseType discord.InteractionResponseType, data discord.InteractionResponseData, opts ...rest.RequestOpt) error { response := discord.InteractionResponse{ Type: responseType, @@ -27,7 +27,6 @@ func respond(client bot.Client, respondFunc httpserver.RespondFunc, interaction } func handleInteraction(client bot.Client, sequenceNumber int, shardID int, respondFunc httpserver.RespondFunc, interaction discord.Interaction) { - genericEvent := events.NewGenericEvent(client, sequenceNumber, shardID) client.EventManager().DispatchEvent(&events.InteractionCreate{ diff --git a/httpserver/server.go b/httpserver/server.go index 1c35646ea..ab25f3b72 100644 --- a/httpserver/server.go +++ b/httpserver/server.go @@ -29,14 +29,18 @@ type EventInteractionCreate struct { } func (e *EventInteractionCreate) UnmarshalJSON(data []byte) error { - var interaction discord.UnmarshalInteraction - if err := json.Unmarshal(data, &interaction); err != nil { + interaction, err := discord.UnmarshalInteraction(data) + if err != nil { return err } - e.Interaction = interaction.Interaction + e.Interaction = interaction return nil } +func (e EventInteractionCreate) MarshalJSON() ([]byte, error) { + return json.Marshal(e.Interaction) +} + // Server is used for receiving Discord's interactions via Outgoing Webhooks type Server interface { // Start starts the Server From 4eaed8d86e578065b9e12c5e01a21f8b7d750a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80Senpai?= Date: Thu, 9 Feb 2023 20:33:04 +0000 Subject: [PATCH 003/119] allow easy replacing of rest url (#236) --- rest/rest_client.go | 7 +++++-- rest/rest_config.go | 10 ++++++++++ rest/rest_endpoints.go | 8 ++++---- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/rest/rest_client.go b/rest/rest_client.go index 28ba32193..8330178b9 100644 --- a/rest/rest_client.go +++ b/rest/rest_client.go @@ -21,7 +21,10 @@ func NewClient(botToken string, opts ...ConfigOpt) Client { config.RateLimiter.Reset() - return &clientImpl{botToken: botToken, config: *config} + return &clientImpl{ + botToken: botToken, + config: *config, + } } // Client allows doing requests to different endpoints @@ -83,7 +86,7 @@ func (c *clientImpl) retry(endpoint *CompiledEndpoint, rqBody any, rsBody any, t c.config.Logger.Tracef("request to %s, body: %s", endpoint.URL, string(rawRqBody)) } - rq, err := http.NewRequest(endpoint.Endpoint.Method, endpoint.URL, bytes.NewReader(rawRqBody)) + rq, err := http.NewRequest(endpoint.Endpoint.Method, c.config.URL+endpoint.URL, bytes.NewReader(rawRqBody)) if err != nil { return err } diff --git a/rest/rest_config.go b/rest/rest_config.go index f5011c08a..1a576f97f 100644 --- a/rest/rest_config.go +++ b/rest/rest_config.go @@ -1,6 +1,7 @@ package rest import ( + "fmt" "net/http" "time" @@ -12,6 +13,7 @@ func DefaultConfig() *Config { return &Config{ Logger: log.Default(), HTTPClient: &http.Client{Timeout: 20 * time.Second}, + URL: fmt.Sprintf("%s/v%d", API, Version), } } @@ -21,6 +23,7 @@ type Config struct { HTTPClient *http.Client RateLimiter RateLimiter RateRateLimiterConfigOpts []RateLimiterConfigOpt + URL string UserAgent string } @@ -65,6 +68,13 @@ func WithRateRateLimiterConfigOpts(opts ...RateLimiterConfigOpt) ConfigOpt { } } +// WithURL sets the api url for all requests +func WithURL(url string) ConfigOpt { + return func(config *Config) { + config.URL = url + } +} + // WithUserAgent sets the user agent for all requests func WithUserAgent(userAgent string) ConfigOpt { return func(config *Config) { diff --git a/rest/rest_endpoints.go b/rest/rest_endpoints.go index 0d646318e..1e9b5c3d9 100644 --- a/rest/rest_endpoints.go +++ b/rest/rest_endpoints.go @@ -9,11 +9,11 @@ import ( ) var ( - // APIVersion is the Discord API version DisGo should use - APIVersion = 10 + // Version is the Discord API version DisGo should use + Version = 10 // API is the base path of the Discord API - API = fmt.Sprintf("https://discord.com/api/v%d", APIVersion) + API = "https://discord.com/api/" ) // MajorParameters is a list of url parameters which decide in which bucket a route belongs (https://discord.com/developers/docs/topics/rate-limits#rate-limits) @@ -346,7 +346,7 @@ func (e *Endpoint) Compile(values discord.QueryValues, params ...any) *CompiledE return &CompiledEndpoint{ Endpoint: e, - URL: API + path + query, + URL: path + query, MajorParams: strings.Join(majorParams, ":"), } } From e6eacc06182d02867b1c343eaf95cccd1faa2c7f Mon Sep 17 00:00:00 2001 From: caneleex Date: Fri, 10 Feb 2023 17:49:55 +0100 Subject: [PATCH 004/119] use blank identifier instead of assignment --- discord/channel.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/discord/channel.go b/discord/channel.go index 7fb5744fb..fe8e54a9d 100644 --- a/discord/channel.go +++ b/discord/channel.go @@ -36,9 +36,11 @@ const ( type ChannelFlags int const ( - ChannelFlagPinned ChannelFlags = 1 << 1 - ChannelFlagRequireTag ChannelFlags = 1 << 4 - ChannelFlagsNone ChannelFlags = 0 + ChannelFlagPinned ChannelFlags = 1 << (iota + 1) + _ + _ + ChannelFlagRequireTag + ChannelFlagsNone ChannelFlags = 0 ) // Add allows you to add multiple bits together, producing a new bit From 4aca7c2ba9932fb346e55d4fa1682f0200404e73 Mon Sep 17 00:00:00 2001 From: caneleex Date: Fri, 10 Feb 2023 17:50:31 +0100 Subject: [PATCH 005/119] match flag const names --- discord/member.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/discord/member.go b/discord/member.go index 915d8275f..6e4d3c929 100644 --- a/discord/member.go +++ b/discord/member.go @@ -95,10 +95,10 @@ type CurrentMemberUpdate struct { type MemberFlags int const ( - MemberFlagsDidRejoin MemberFlags = 1 << iota - MemberFlagsCompletedOnboarding - MemberFlagsBypassesVerification - MemberFlagsStartedOnboarding + MemberFlagDidRejoin MemberFlags = 1 << iota + MemberFlagCompletedOnboarding + MemberFlagBypassesVerification + MemberFlagStartedOnboarding MemberFlagsNone MemberFlags = 0 ) From b780a98e18a9c90fae756feac4104c1c36d382f9 Mon Sep 17 00:00:00 2001 From: caneleex Date: Fri, 10 Feb 2023 17:51:43 +0100 Subject: [PATCH 006/119] fix field name capitalization --- cache/cache.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cache/cache.go b/cache/cache.go index 2accd7241..99dced2eb 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -36,10 +36,10 @@ var _ Cache[any] = (*DefaultCache[any])(nil) // NewCache returns a new DefaultCache implementation which filter the entities after the gives Flags and Policy. // This cache implementation is thread safe and can be used in multiple goroutines without any issues. // It also only hands out copies to the entities. Regardless these entities should be handles as immutable. -func NewCache[T any](flags Flags, neededFLags Flags, policy Policy[T]) Cache[T] { +func NewCache[T any](flags Flags, neededFlags Flags, policy Policy[T]) Cache[T] { return &DefaultCache[T]{ flags: flags, - neededFLags: neededFLags, + neededFlags: neededFlags, policy: policy, cache: make(map[snowflake.ID]T), } @@ -49,7 +49,7 @@ func NewCache[T any](flags Flags, neededFLags Flags, policy Policy[T]) Cache[T] type DefaultCache[T any] struct { mu sync.RWMutex flags Flags - neededFLags Flags + neededFlags Flags policy Policy[T] cache map[snowflake.ID]T } @@ -62,7 +62,7 @@ func (c *DefaultCache[T]) Get(id snowflake.ID) (T, bool) { } func (c *DefaultCache[T]) Put(id snowflake.ID, entity T) { - if c.flags.Missing(c.neededFLags) { + if c.flags.Missing(c.neededFlags) { return } if c.policy != nil && !c.policy(entity) { From 24260e4bca6f06c27e1c0d7dabee85a8f2717c52 Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Sun, 12 Feb 2023 23:55:47 +0100 Subject: [PATCH 007/119] add noop rate limiter and proxy example and always send shard info --- _examples/proxy/README.md | 17 +++++ _examples/proxy/example.go | 100 ++++++++++++++++++++++++++++ gateway/gateway_impl.go | 4 +- rest/rest_rate_limiter_noop.go | 23 +++++++ sharding/shard_rate_limiter_noop.go | 18 +++++ 5 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 _examples/proxy/README.md create mode 100644 _examples/proxy/example.go create mode 100644 rest/rest_rate_limiter_noop.go create mode 100644 sharding/shard_rate_limiter_noop.go diff --git a/_examples/proxy/README.md b/_examples/proxy/README.md new file mode 100644 index 000000000..44b157fda --- /dev/null +++ b/_examples/proxy/README.md @@ -0,0 +1,17 @@ +# Proxy + +This example shows how to use disgo with a gateway-proxy & rest-proxy such as https://github.com/Gelbpunkt/gateway-proxy and https://github.com/twilight-rs/http-proxy + +For configuring those proxies, please refer to their documentation. + +## Environment Variables + +```env +disgo_token=discord bot token + +disgo_guild_id=guild id to register commmands to + +disgo_gateway_url=url to your gateway proxy. example: ws://gateway-proxy:7878 + +disgo_rest_url=url to your rest proxy. example: http://rest-proxy:7979/api/v10 +``` \ No newline at end of file diff --git a/_examples/proxy/example.go b/_examples/proxy/example.go new file mode 100644 index 000000000..4dfd5810d --- /dev/null +++ b/_examples/proxy/example.go @@ -0,0 +1,100 @@ +package main + +import ( + "context" + "os" + "os/signal" + "syscall" + + "github.com/disgoorg/log" + "github.com/disgoorg/snowflake/v2" + + "github.com/disgoorg/disgo" + "github.com/disgoorg/disgo/bot" + "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/disgo/events" + "github.com/disgoorg/disgo/gateway" + "github.com/disgoorg/disgo/rest" + "github.com/disgoorg/disgo/sharding" +) + +var ( + token = os.Getenv("disgo_token") + guildID = snowflake.GetEnv("disgo_guild_id") + gatewayURL = os.Getenv("disgo_gateway_url") + restURL = os.Getenv("disgo_rest_url") + + commands = []discord.ApplicationCommandCreate{ + discord.SlashCommandCreate{ + Name: "say", + Description: "says what you say", + Options: []discord.ApplicationCommandOption{ + discord.ApplicationCommandOptionString{ + Name: "message", + Description: "What to say", + Required: true, + }, + discord.ApplicationCommandOptionBool{ + Name: "ephemeral", + Description: "If the response should only be visible to you", + Required: true, + }, + }, + }, + } +) + +func main() { + log.SetLevel(log.LevelInfo) + log.Info("starting example...") + log.Info("disgo version: ", disgo.Version) + + client, err := disgo.New(token, + bot.WithShardManagerConfigOpts( + sharding.WithGatewayConfigOpts( // gateway intents are set in the proxy not here + gateway.WithURL(gatewayURL), // set the custom gateway url + gateway.WithCompress(false), // we don't want compression as that would be additional overhead + ), + sharding.WithRateLimiter(sharding.NewNoopRateLimiter()), // disable sharding rate limiter as the proxy handles it + ), + bot.WithRestClientConfigOpts( + rest.WithURL(restURL), // set the custom rest url + rest.WithRateLimiter(rest.NewNoopRateLimiter()), // disable rest rate limiter as the proxy handles it + ), + bot.WithEventListenerFunc(commandListener), + ) + + if err != nil { + log.Fatal("error while building disgo instance: ", err) + return + } + + defer client.Close(context.TODO()) + + if _, err = client.Rest().SetGuildCommands(client.ApplicationID(), guildID, commands); err != nil { + log.Fatal("error while registering commands: ", err) + } + + if err = client.OpenGateway(context.TODO()); err != nil { + log.Fatal("error while connecting to gateway: ", err) + } + + log.Infof("example is now running. Press CTRL-C to exit.") + s := make(chan os.Signal, 1) + signal.Notify(s, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) + <-s +} + +func commandListener(event *events.ApplicationCommandInteractionCreate) { + data := event.SlashCommandInteractionData() + if data.CommandName() == "say" { + err := event.CreateMessage(discord.NewMessageCreateBuilder(). + SetContent(data.String("message")). + SetEphemeral(data.Bool("ephemeral")). + Build(), + ) + if err != nil { + event.Client().Logger().Error("error on sending response: ", err) + } + } +} diff --git a/gateway/gateway_impl.go b/gateway/gateway_impl.go index 3cbf885f4..a2ee68352 100644 --- a/gateway/gateway_impl.go +++ b/gateway/gateway_impl.go @@ -286,9 +286,7 @@ func (g *gatewayImpl) identify() { LargeThreshold: g.config.LargeThreshold, Intents: g.config.Intents, Presence: g.config.Presence, - } - if g.ShardCount() > 1 { - identify.Shard = &[2]int{g.ShardID(), g.ShardCount()} + Shard: &[2]int{g.ShardID(), g.ShardCount()}, } if err := g.Send(context.TODO(), OpcodeIdentify, identify); err != nil { diff --git a/rest/rest_rate_limiter_noop.go b/rest/rest_rate_limiter_noop.go new file mode 100644 index 000000000..11eb44159 --- /dev/null +++ b/rest/rest_rate_limiter_noop.go @@ -0,0 +1,23 @@ +package rest + +import ( + "context" + "net/http" +) + +// NewNoopRateLimiter return a new noop RateLimiter. +func NewNoopRateLimiter() RateLimiter { + return &noopRateLimiter{} +} + +type noopRateLimiter struct{} + +func (l *noopRateLimiter) MaxRetries() int { return 0 } + +func (l *noopRateLimiter) Close(_ context.Context) {} + +func (l *noopRateLimiter) Reset() {} + +func (l *noopRateLimiter) WaitBucket(_ context.Context, _ *CompiledEndpoint) error { return nil } + +func (l *noopRateLimiter) UnlockBucket(_ *CompiledEndpoint, _ *http.Response) error { return nil } diff --git a/sharding/shard_rate_limiter_noop.go b/sharding/shard_rate_limiter_noop.go new file mode 100644 index 000000000..9d15fa637 --- /dev/null +++ b/sharding/shard_rate_limiter_noop.go @@ -0,0 +1,18 @@ +package sharding + +import ( + "context" +) + +var _ RateLimiter = (*noopRateLimiter)(nil) + +// NewNoopRateLimiter creates a new noop RateLimiter. +func NewNoopRateLimiter() RateLimiter { + return &noopRateLimiter{} +} + +type noopRateLimiter struct{} + +func (r *noopRateLimiter) Close(_ context.Context) {} +func (r *noopRateLimiter) WaitBucket(_ context.Context, _ int) error { return nil } +func (r *noopRateLimiter) UnlockBucket(_ int) {} From 5c4a0c559b203bedc2fae8609ecca5b36d0ea4d2 Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Tue, 14 Feb 2023 02:15:51 +0100 Subject: [PATCH 008/119] fix double slash in endpoint --- rest/rest_config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest/rest_config.go b/rest/rest_config.go index 1a576f97f..799b49037 100644 --- a/rest/rest_config.go +++ b/rest/rest_config.go @@ -13,7 +13,7 @@ func DefaultConfig() *Config { return &Config{ Logger: log.Default(), HTTPClient: &http.Client{Timeout: 20 * time.Second}, - URL: fmt.Sprintf("%s/v%d", API, Version), + URL: fmt.Sprintf("%sv%d", API, Version), } } From c377083bc38500119d78c9bd95cfea61f1218040 Mon Sep 17 00:00:00 2001 From: cane Date: Wed, 15 Feb 2023 21:16:07 +0100 Subject: [PATCH 009/119] add ConnectionTypeInstagram (#234) --- discord/connection.go | 1 + 1 file changed, 1 insertion(+) diff --git a/discord/connection.go b/discord/connection.go index 2aa47b61a..771d52b7e 100644 --- a/discord/connection.go +++ b/discord/connection.go @@ -20,6 +20,7 @@ const ( ConnectionTypeEpicGames ConnectionType = "epicgames" ConnectionTypeFacebook ConnectionType = "facebook" ConnectionTypeGitHub ConnectionType = "github" + ConnectionTypeInstagram ConnectionType = "instagram" ConnectionTypeLeagueOfLegends ConnectionType = "leagueoflegends" ConnectionTypePayPal ConnectionType = "paypal" ConnectionTypePlayStationNetwork ConnectionType = "playstation" From 84cfc8d27835037d5526e3f0cff0905d653446b5 Mon Sep 17 00:00:00 2001 From: caneleex Date: Wed, 15 Feb 2023 21:19:43 +0100 Subject: [PATCH 010/119] add StorePageAsset cdn endpoint --- discord/cdn_endpoints.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/discord/cdn_endpoints.go b/discord/cdn_endpoints.go index d6c379096..8ba464bb8 100644 --- a/discord/cdn_endpoints.go +++ b/discord/cdn_endpoints.go @@ -30,6 +30,8 @@ var ( AchievementIcon = NewCDN("/app-assets/{application.id}/achievements/{achievement.id}/icons/{icon.hash}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP) + StorePageAsset = NewCDN("/app-assets/{application.id}/store/{asset.id}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP) + TeamIcon = NewCDN("/team-icons/{team.id}/{team.icon.hash}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP) StickerPackBanner = NewCDN("app-assets/710982414301790216/store/{banner.asset.id}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP) From 356ddcbf0f749c0711150ae82df25a934e2062dd Mon Sep 17 00:00:00 2001 From: caneleex Date: Wed, 15 Feb 2023 21:25:01 +0100 Subject: [PATCH 011/119] fix StickerPackBanner cdn endpoint path --- discord/cdn_endpoints.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/cdn_endpoints.go b/discord/cdn_endpoints.go index 8ba464bb8..654e17ecb 100644 --- a/discord/cdn_endpoints.go +++ b/discord/cdn_endpoints.go @@ -34,7 +34,7 @@ var ( TeamIcon = NewCDN("/team-icons/{team.id}/{team.icon.hash}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP) - StickerPackBanner = NewCDN("app-assets/710982414301790216/store/{banner.asset.id}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP) + StickerPackBanner = NewCDN("/app-assets/710982414301790216/store/{banner.asset.id}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP) CustomSticker = NewCDN("/stickers/{sticker.id}", ImageFormatPNG, ImageFormatLottie, ImageFormatGIF) AttachmentFile = NewCDN("/attachments/{channel.id}/{attachment.id}/{file.name}", ImageFormatNone) From 0f4d6ca243fe367bb535b54df07c4de15537add2 Mon Sep 17 00:00:00 2001 From: caneleex Date: Wed, 15 Feb 2023 22:24:59 +0100 Subject: [PATCH 012/119] add NSFW and VideoQualityMode to GuildStageVoiceChannelUpdate https://github.com/discord/discord-api-docs/pull/5928 --- discord/channel_update.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/discord/channel_update.go b/discord/channel_update.go index d49a0e5d4..82bdf470f 100644 --- a/discord/channel_update.go +++ b/discord/channel_update.go @@ -89,6 +89,8 @@ type GuildStageVoiceChannelUpdate struct { PermissionOverwrites *[]PermissionOverwrite `json:"permission_overwrites,omitempty"` ParentID *snowflake.ID `json:"parent_id,omitempty"` RTCRegion *string `json:"rtc_region,omitempty"` + NSFW *bool `json:"nsfw,omitempty"` + VideoQualityMode *VideoQualityMode `json:"video_quality_mode,omitempty"` } func (GuildStageVoiceChannelUpdate) channelUpdate() {} From 12bf0cce55c558d07d5ea003f6cc168a3e50cd0c Mon Sep 17 00:00:00 2001 From: caneleex Date: Wed, 15 Feb 2023 22:27:22 +0100 Subject: [PATCH 013/119] add stage message types (again) https://github.com/discord/discord-api-docs/pull/5927 --- discord/message.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/discord/message.go b/discord/message.go index c0e3716e3..76aeb4d09 100644 --- a/discord/message.go +++ b/discord/message.go @@ -42,11 +42,11 @@ const ( MessageTypeAutoModerationAction _ MessageTypeInteractionPremiumUpsell + MessageTypeStageStart + MessageTypeStageEnd + MessageTypeStageSpeaker _ - _ - _ - _ - _ + MessageTypeStageTopic MessageTypeGuildApplicationPremiumSubscription ) From 604780dafa084876c9ab2122af776c980dbe18bf Mon Sep 17 00:00:00 2001 From: Thunder33345 Date: Thu, 16 Feb 2023 22:50:43 +0800 Subject: [PATCH 014/119] handler improvements (#240) * added not found handler to mux * add HandleNotFound into interface * removed redundant handle prefixes in mux * fix panic * remove NotFound method from Router & export Mux type * add not found handler to handler example * add docs --------- Co-authored-by: TopiSenpai --- _examples/handler/example.go | 24 ++++++++----- handler/handler.go | 19 +++++++++- handler/mux.go | 68 ++++++++++++++++++++++++------------ handler/router.go | 29 +++++++++------ 4 files changed, 97 insertions(+), 43 deletions(-) diff --git a/_examples/handler/example.go b/_examples/handler/example.go index 33cfe256d..092211456 100644 --- a/_examples/handler/example.go +++ b/_examples/handler/example.go @@ -12,6 +12,7 @@ import ( "github.com/disgoorg/disgo" "github.com/disgoorg/disgo/bot" "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/disgo/events" "github.com/disgoorg/disgo/handler" "github.com/disgoorg/disgo/handler/middleware" ) @@ -72,18 +73,19 @@ func main() { r.Group(func(r handler.Router) { r.Use(middleware.Print("group1")) r.Route("/test", func(r handler.Router) { - r.HandleCommand("/sub2", handleContent("/test/sub2")) + r.Command("/sub2", handleContent("/test/sub2")) r.Route("/{group}", func(r handler.Router) { - r.HandleCommand("/sub", handleVariableContent) + r.Command("/sub", handleVariableContent) }) }) }) r.Group(func(r handler.Router) { r.Use(middleware.Print("group2")) - r.HandleCommand("/ping", handlePing) - r.HandleCommand("/ping2", handleContent("pong2")) - r.HandleComponent("button1/{data}", handleComponent) + r.Command("/ping", handlePing) + r.Command("/ping2", handleContent("pong2")) + r.Component("button1/{data}", handleComponent) }) + r.NotFound(handleNotFound) client, err := disgo.New(token, bot.WithDefaultGateway(), @@ -111,17 +113,17 @@ func main() { } func handleContent(content string) handler.CommandHandler { - return func(client bot.Client, event *handler.CommandEvent) error { + return func(event *handler.CommandEvent) error { return event.CreateMessage(discord.MessageCreate{Content: content}) } } -func handleVariableContent(client bot.Client, event *handler.CommandEvent) error { +func handleVariableContent(event *handler.CommandEvent) error { group := event.Variables["group"] return event.CreateMessage(discord.MessageCreate{Content: "group: " + group}) } -func handlePing(client bot.Client, event *handler.CommandEvent) error { +func handlePing(event *handler.CommandEvent) error { return event.CreateMessage(discord.MessageCreate{ Content: "pong", Components: []discord.ContainerComponent{ @@ -132,7 +134,11 @@ func handlePing(client bot.Client, event *handler.CommandEvent) error { }) } -func handleComponent(client bot.Client, event *handler.ComponentEvent) error { +func handleComponent(event *handler.ComponentEvent) error { data := event.Variables["data"] return event.CreateMessage(discord.MessageCreate{Content: "component: " + data}) } + +func handleNotFound(event *events.InteractionCreate) error { + return event.Respond(discord.InteractionResponseTypeCreateMessage, discord.MessageCreate{Content: "not found"}) +} diff --git a/handler/handler.go b/handler/handler.go index 32ba79e9a..0f9fe6e91 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -1,3 +1,20 @@ +// Package handler provides a way to handle interactions like application commands, autocomplete, buttons, select menus & modals with a simple interface. +// +// The handler package is inspired by the go-chi/chi http router. +// Each interaction has a path which is either the command name (starting with /) or the custom id. According to this path all interactions are routed to the correct handler. +// Slash Commands can have subcommands, which are nested paths. For example /test/subcommand1 or /test/subcommandgroup/subcommand. +// +// The handler also supports variables in its path which is especially useful for subcommands, components and modals. +// Variables are defined by curly braces like {variable} and can be accessed in the handler via the Variables map. +// +// You can also register middlewares, which are executed before the handler is called. Middlewares can be used to check permissions, validate input or do other things. +// Middlewares can also be attached to sub-routers, which is useful if you want to have a middleware for all subcommands of a command as an example. +// A middleware does not care which interaction type it is, it is just executed before the handler and has the following signature: +// type Middleware func(next func(e *events.InteractionCreate)) func(e *events.InteractionCreate) +// +// The handler iterates over all routes until it finds the fist matching route. If no route matches, the handler will call the NotFoundHandler. +// The NotFoundHandler can be set via the `NotFound` method on the *Mux. If no NotFoundHandler is set nothing will happen. + package handler import ( @@ -25,7 +42,7 @@ func (h *handlerHolder[T]) Match(path string, t discord.InteractionType) bool { if strings.HasPrefix(part, "{") && strings.HasSuffix(part, "}") { continue } - if part != parts[i] { + if len(parts) <= i || part != parts[i] { return false } } diff --git a/handler/mux.go b/handler/mux.go index 2e804f013..ebdeeb94e 100644 --- a/handler/mux.go +++ b/handler/mux.go @@ -8,25 +8,29 @@ import ( "github.com/disgoorg/disgo/events" ) -func New() Router { - return &mux{} +// New returns a new Router. +func New() *Mux { + return &Mux{} } -func newRouter(pattern string, middlewares []Middleware, routes []Route) *mux { - return &mux{ +func newRouter(pattern string, middlewares []Middleware, routes []Route) *Mux { + return &Mux{ pattern: pattern, middlewares: middlewares, routes: routes, } } -type mux struct { - pattern string - middlewares []Middleware - routes []Route +// Mux is a basic Router implementation. +type Mux struct { + pattern string + middlewares []Middleware + routes []Route + notFoundHandler NotFoundHandler } -func (r *mux) OnEvent(event bot.Event) { +// OnEvent is called when a new event is received. +func (r *Mux) OnEvent(event bot.Event) { e, ok := event.(*events.InteractionCreate) if !ok { return @@ -53,7 +57,8 @@ func (r *mux) OnEvent(event bot.Event) { } } -func (r *mux) Match(path string, t discord.InteractionType) bool { +// Match returns true if the given path matches the Route. +func (r *Mux) Match(path string, t discord.InteractionType) bool { if r.pattern != "" { parts := splitPath(path) patternParts := splitPath(r.pattern) @@ -63,7 +68,7 @@ func (r *mux) Match(path string, t discord.InteractionType) bool { if strings.HasPrefix(part, "{") && strings.HasSuffix(part, "}") { continue } - if part != parts[i] { + if len(parts) <= i || part != parts[i] { return false } } @@ -77,7 +82,8 @@ func (r *mux) Match(path string, t discord.InteractionType) bool { return false } -func (r *mux) Handle(path string, variables map[string]string, e *events.InteractionCreate) error { +// Handle handles the given interaction event. +func (r *Mux) Handle(path string, variables map[string]string, e *events.InteractionCreate) error { path = parseVariables(path, r.pattern, variables) middlewares := func(event *events.InteractionCreate) {} for i := len(r.middlewares) - 1; i >= 0; i-- { @@ -90,24 +96,31 @@ func (r *mux) Handle(path string, variables map[string]string, e *events.Interac return route.Handle(path, variables, e) } } + if r.notFoundHandler != nil { + return r.notFoundHandler(e) + } return nil } -func (r *mux) Use(middlewares ...Middleware) { +// Use adds the given middlewares to the current Router. +func (r *Mux) Use(middlewares ...Middleware) { r.middlewares = append(r.middlewares, middlewares...) } -func (r *mux) With(middlewares ...Middleware) Router { +// With returns a new Router with the given middlewares. +func (r *Mux) With(middlewares ...Middleware) Router { return newRouter("", middlewares, nil) } -func (r *mux) Group(fn func(router Router)) { +// Group creates a new Router and adds it to the current Router. +func (r *Mux) Group(fn func(router Router)) { router := New() fn(router) r.handle(router) } -func (r *mux) Route(pattern string, fn func(r Router)) Router { +// Route creates a new sub-router with the given pattern and adds it to the current Router. +func (r *Mux) Route(pattern string, fn func(r Router)) Router { checkPattern(pattern) router := newRouter(pattern, nil, nil) fn(router) @@ -115,7 +128,8 @@ func (r *mux) Route(pattern string, fn func(r Router)) Router { return router } -func (r *mux) Mount(pattern string, router Router) { +// Mount mounts the given router with the given pattern to the current Router. +func (r *Mux) Mount(pattern string, router Router) { if pattern == "" { r.handle(router) return @@ -123,11 +137,12 @@ func (r *mux) Mount(pattern string, router Router) { r.handle(newRouter(pattern, nil, []Route{router})) } -func (r *mux) handle(route Route) { +func (r *Mux) handle(route Route) { r.routes = append(r.routes, route) } -func (r *mux) HandleCommand(pattern string, h CommandHandler) { +// Command registers the given CommandHandler to the current Router. +func (r *Mux) Command(pattern string, h CommandHandler) { checkPattern(pattern) r.handle(&handlerHolder[CommandHandler]{ pattern: pattern, @@ -136,7 +151,8 @@ func (r *mux) HandleCommand(pattern string, h CommandHandler) { }) } -func (r *mux) HandleAutocomplete(pattern string, h AutocompleteHandler) { +// Autocomplete registers the given AutocompleteHandler to the current Router. +func (r *Mux) Autocomplete(pattern string, h AutocompleteHandler) { checkPattern(pattern) r.handle(&handlerHolder[AutocompleteHandler]{ pattern: pattern, @@ -145,7 +161,8 @@ func (r *mux) HandleAutocomplete(pattern string, h AutocompleteHandler) { }) } -func (r *mux) HandleComponent(pattern string, h ComponentHandler) { +// Component registers the given ComponentHandler to the current Router. +func (r *Mux) Component(pattern string, h ComponentHandler) { checkPatternEmpty(pattern) r.handle(&handlerHolder[ComponentHandler]{ pattern: pattern, @@ -154,7 +171,8 @@ func (r *mux) HandleComponent(pattern string, h ComponentHandler) { }) } -func (r *mux) HandleModal(pattern string, h ModalHandler) { +// Modal registers the given ModalHandler to the current Router. +func (r *Mux) Modal(pattern string, h ModalHandler) { checkPatternEmpty(pattern) r.handle(&handlerHolder[ModalHandler]{ pattern: pattern, @@ -163,6 +181,12 @@ func (r *mux) HandleModal(pattern string, h ModalHandler) { }) } +// NotFound sets the NotFoundHandler for this router. +// This handler only works for the root router and will be ignored for sub routers. +func (r *Mux) NotFound(h NotFoundHandler) { + r.notFoundHandler = h +} + func checkPatternEmpty(pattern string) { if pattern == "" { panic("pattern must not be empty") diff --git a/handler/router.go b/handler/router.go index 3fb32f7ed..04774a445 100644 --- a/handler/router.go +++ b/handler/router.go @@ -11,13 +11,18 @@ type ( AutocompleteHandler func(e *AutocompleteEvent) error ComponentHandler func(e *ComponentEvent) error ModalHandler func(e *ModalEvent) error + NotFoundHandler func(event *events.InteractionCreate) error ) var ( - _ Route = (*mux)(nil) + _ Route = (*Mux)(nil) _ Route = (*handlerHolder[CommandHandler])(nil) + _ Route = (*handlerHolder[AutocompleteHandler])(nil) + _ Route = (*handlerHolder[ComponentHandler])(nil) + _ Route = (*handlerHolder[ModalHandler])(nil) ) +// Route is a basic interface for a route in a Router. type Route interface { // Match returns true if the given path matches the Route. Match(path string, t discord.InteractionType) bool @@ -26,14 +31,16 @@ type Route interface { Handle(path string, variables map[string]string, e *events.InteractionCreate) error } +// Router provides with the core routing functionality. +// It is used to register handlers and middlewares and sub-routers. type Router interface { bot.EventListener Route - // Use adds the given middlewares to the current Router + // Use adds the given middlewares to the current Router. Use(middlewares ...Middleware) - // With returns a new Router with the given middlewares + // With returns a new Router with the given middlewares. With(middlewares ...Middleware) Router // Group creates a new Router and adds it to the current Router. @@ -45,15 +52,15 @@ type Router interface { // Mount mounts the given router with the given pattern to the current Router. Mount(pattern string, r Router) - // HandleCommand registers the given CommandHandler to the current Router. - HandleCommand(pattern string, h CommandHandler) + // Command registers the given CommandHandler to the current Router. + Command(pattern string, h CommandHandler) - // HandleAutocomplete registers the given AutocompleteHandler to the current Router. - HandleAutocomplete(pattern string, h AutocompleteHandler) + // Autocomplete registers the given AutocompleteHandler to the current Router. + Autocomplete(pattern string, h AutocompleteHandler) - // HandleComponent registers the given ComponentHandler to the current Router. - HandleComponent(pattern string, h ComponentHandler) + // Component registers the given ComponentHandler to the current Router. + Component(pattern string, h ComponentHandler) - // HandleModal registers the given ModalHandler to the current Router. - HandleModal(pattern string, h ModalHandler) + // Modal registers the given ModalHandler to the current Router. + Modal(pattern string, h ModalHandler) } From 76fb9f05bb7d7cfca68e7976e90f060960f8121e Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Sat, 18 Feb 2023 23:53:34 +0100 Subject: [PATCH 015/119] use http status 303 for redirect --- _examples/oauth2/example.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_examples/oauth2/example.go b/_examples/oauth2/example.go index 308cb8a6a..c9878bd8e 100644 --- a/_examples/oauth2/example.go +++ b/_examples/oauth2/example.go @@ -89,7 +89,7 @@ func handleRoot(w http.ResponseWriter, r *http.Request) { } func handleLogin(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, client.GenerateAuthorizationURL(baseURL+"/trylogin", discord.PermissionsNone, 0, false, discord.OAuth2ScopeIdentify, discord.OAuth2ScopeGuilds, discord.OAuth2ScopeEmail, discord.OAuth2ScopeConnections, discord.OAuth2ScopeWebhookIncoming), http.StatusMovedPermanently) + http.Redirect(w, r, client.GenerateAuthorizationURL(baseURL+"/trylogin", discord.PermissionsNone, 0, false, discord.OAuth2ScopeIdentify, discord.OAuth2ScopeGuilds, discord.OAuth2ScopeEmail, discord.OAuth2ScopeConnections, discord.OAuth2ScopeWebhookIncoming), http.StatusSeeOther) } func handleTryLogin(w http.ResponseWriter, r *http.Request) { From faa79470af9647b9eff1855d3b8e510d7fdf979a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80Senpai?= Date: Mon, 20 Feb 2023 04:06:56 +0000 Subject: [PATCH 016/119] simplify oauth2 by removing session controller & converting a session to a struct (#241) --- _examples/oauth2/example.go | 8 +++-- oauth2/client.go | 61 +++++++++++++++++++++++----------- oauth2/client_impl.go | 58 ++++++++++++++++++++------------- oauth2/config.go | 11 +------ oauth2/session.go | 63 ------------------------------------ oauth2/session_controller.go | 58 --------------------------------- 6 files changed, 84 insertions(+), 175 deletions(-) delete mode 100644 oauth2/session.go delete mode 100644 oauth2/session_controller.go diff --git a/_examples/oauth2/example.go b/_examples/oauth2/example.go index c9878bd8e..46111f755 100644 --- a/_examples/oauth2/example.go +++ b/_examples/oauth2/example.go @@ -25,6 +25,7 @@ var ( logger = log.Default() httpClient = http.DefaultClient client oauth2.Client + sessions map[string]oauth2.Session ) func init() { @@ -49,8 +50,8 @@ func handleRoot(w http.ResponseWriter, r *http.Request) { var body string cookie, err := r.Cookie("token") if err == nil { - session := client.SessionController().GetSession(cookie.Value) - if session != nil { + session, ok := sessions[cookie.Value] + if ok { var user *discord.OAuth2User user, err = client.GetUser(session) if err != nil { @@ -100,11 +101,12 @@ func handleTryLogin(w http.ResponseWriter, r *http.Request) { ) if code != "" && state != "" { identifier := randStr(32) - _, err := client.StartSession(code, state, identifier) + session, _, err := client.StartSession(code, state) if err != nil { writeError(w, "error while starting session", err) return } + sessions[identifier] = session http.SetCookie(w, &http.Cookie{Name: "token", Value: identifier}) } http.Redirect(w, r, "/", http.StatusTemporaryRedirect) diff --git a/oauth2/client.go b/oauth2/client.go index 83e0d6429..fefbda7d8 100644 --- a/oauth2/client.go +++ b/oauth2/client.go @@ -3,6 +3,7 @@ package oauth2 import ( "errors" "fmt" + "time" "github.com/disgoorg/snowflake/v2" @@ -14,8 +15,8 @@ var ( // ErrStateNotFound is returned when the state is not found in the SessionController. ErrStateNotFound = errors.New("state could not be found") - // ErrAccessTokenExpired is returned when the access token has expired. - ErrAccessTokenExpired = errors.New("access token expired. refresh the session") + // ErrSessionExpired is returned when the Session has expired. + ErrSessionExpired = errors.New("access token expired. refresh the session") // ErrMissingOAuth2Scope is returned when a specific OAuth2 scope is missing. ErrMissingOAuth2Scope = func(scope discord.OAuth2Scope) error { @@ -23,40 +24,62 @@ var ( } ) +// Session represents a discord access token response (https://discord.com/developers/docs/topics/oauth2#authorization-code-grant-access-token-response) +type Session struct { + // AccessToken allows requesting user information + AccessToken string `json:"access_token"` + + // RefreshToken allows refreshing the AccessToken + RefreshToken string `json:"refresh_token"` + + // Scopes returns the discord.OAuth2Scope(s) of the Session + Scopes []discord.OAuth2Scope `json:"scope"` + + // TokenType returns the discord.TokenType of the AccessToken + TokenType discord.TokenType `json:"token_type"` + + // Expiration returns the time.Time when the AccessToken expires and needs to be refreshed + Expiration time.Time `json:"expiration"` +} + +func (s Session) Expired() bool { + return s.Expiration.Before(time.Now()) +} + // Client is a high level wrapper around Discord's OAuth2 API. type Client interface { - // ID returns the configured client ID + // ID returns the configured client ID. ID() snowflake.ID - // Secret returns the configured client secret + // Secret returns the configured client secret. Secret() string - // Rest returns the underlying rest.OAuth2 + // Rest returns the underlying rest.OAuth2. Rest() rest.OAuth2 - // SessionController returns the configured SessionController - SessionController() SessionController - // StateController returns the configured StateController + // StateController returns the configured StateController. StateController() StateController - // GenerateAuthorizationURL generates an authorization URL with the given redirect URI, permissions, guildID, disableGuildSelect & scopes. State is automatically generated + // GenerateAuthorizationURL generates an authorization URL with the given redirect URI, permissions, guildID, disableGuildSelect & scopes. State is automatically generated. GenerateAuthorizationURL(redirectURI string, permissions discord.Permissions, guildID snowflake.ID, disableGuildSelect bool, scopes ...discord.OAuth2Scope) string - // GenerateAuthorizationURLState generates an authorization URL with the given redirect URI, permissions, guildID, disableGuildSelect & scopes. State is automatically generated & returned + // GenerateAuthorizationURLState generates an authorization URL with the given redirect URI, permissions, guildID, disableGuildSelect & scopes. State is automatically generated & returned. GenerateAuthorizationURLState(redirectURI string, permissions discord.Permissions, guildID snowflake.ID, disableGuildSelect bool, scopes ...discord.OAuth2Scope) (string, string) - // StartSession starts a new Session with the given authorization code & state - StartSession(code string, state string, identifier string, opts ...rest.RequestOpt) (Session, error) - // RefreshSession refreshes the given Session with the refresh token - RefreshSession(identifier string, session Session, opts ...rest.RequestOpt) (Session, error) + // StartSession starts a new Session with the given authorization code & state. + StartSession(code string, state string, opts ...rest.RequestOpt) (Session, *discord.IncomingWebhook, error) + // RefreshSession refreshes the given Session with the refresh token. + RefreshSession(session Session, opts ...rest.RequestOpt) (Session, error) + // VerifySession verifies the given Session & refreshes it if needed. + VerifySession(session Session, opts ...rest.RequestOpt) (Session, error) - // GetUser returns the discord.OAuth2User associated with the given Session. Fields filled in the struct depend on the Session.Scopes + // GetUser returns the discord.OAuth2User associated with the given Session. Fields filled in the struct depend on the Session.Scopes. GetUser(session Session, opts ...rest.RequestOpt) (*discord.OAuth2User, error) // GetMember returns the discord.Member associated with the given Session in a specific guild. GetMember(session Session, guildID snowflake.ID, opts ...rest.RequestOpt) (*discord.Member, error) - // GetGuilds returns the discord.OAuth2Guild(s) the user is a member of. This requires the discord.OAuth2ScopeGuilds scope in the Session + // GetGuilds returns the discord.OAuth2Guild(s) the user is a member of. This requires the discord.OAuth2ScopeGuilds scope in the Session. GetGuilds(session Session, opts ...rest.RequestOpt) ([]discord.OAuth2Guild, error) - // GetConnections returns the discord.Connection(s) the user has connected. This requires the discord.OAuth2ScopeConnections scope in the Session + // GetConnections returns the discord.Connection(s) the user has connected. This requires the discord.OAuth2ScopeConnections scope in the Session. GetConnections(session Session, opts ...rest.RequestOpt) ([]discord.Connection, error) - // GetApplicationRoleConnection returns the discord.ApplicationRoleConnection for the given application. This requires the discord.OAuth2ScopeRoleConnectionsWrite scope in the Session + // GetApplicationRoleConnection returns the discord.ApplicationRoleConnection for the given application. This requires the discord.OAuth2ScopeRoleConnectionsWrite scope in the Session. GetApplicationRoleConnection(session Session, applicationID snowflake.ID, opts ...rest.RequestOpt) (*discord.ApplicationRoleConnection, error) - // UpdateApplicationRoleConnection updates the discord.ApplicationRoleConnection for the given application. This requires the discord.OAuth2ScopeRoleConnectionsWrite scope in the Session + // UpdateApplicationRoleConnection updates the discord.ApplicationRoleConnection for the given application. This requires the discord.OAuth2ScopeRoleConnectionsWrite scope in the Session. UpdateApplicationRoleConnection(session Session, applicationID snowflake.ID, update discord.ApplicationRoleConnectionUpdate, opts ...rest.RequestOpt) (*discord.ApplicationRoleConnection, error) } diff --git a/oauth2/client_impl.go b/oauth2/client_impl.go index e5075af87..1220b9360 100644 --- a/oauth2/client_impl.go +++ b/oauth2/client_impl.go @@ -35,10 +35,6 @@ func (c *clientImpl) Rest() rest.OAuth2 { return c.config.OAuth2 } -func (c *clientImpl) SessionController() SessionController { - return c.config.SessionController -} - func (c *clientImpl) StateController() StateController { return c.config.StateController } @@ -70,74 +66,92 @@ func (c *clientImpl) GenerateAuthorizationURLState(redirectURI string, permissio return discord.AuthorizeURL(values), state } -func (c *clientImpl) StartSession(code string, state string, identifier string, opts ...rest.RequestOpt) (Session, error) { +func (c *clientImpl) StartSession(code string, state string, opts ...rest.RequestOpt) (Session, *discord.IncomingWebhook, error) { redirectURI := c.StateController().ConsumeState(state) if redirectURI == "" { - return nil, ErrStateNotFound + return Session{}, nil, ErrStateNotFound } - exchange, err := c.Rest().GetAccessToken(c.id, c.secret, code, redirectURI, opts...) + accessToken, err := c.Rest().GetAccessToken(c.id, c.secret, code, redirectURI, opts...) if err != nil { - return nil, err + return Session{}, nil, err } - return c.SessionController().CreateSessionFromResponse(identifier, *exchange), nil + + return newSession(*accessToken), accessToken.Webhook, nil } -func (c *clientImpl) RefreshSession(identifier string, session Session, opts ...rest.RequestOpt) (Session, error) { - exchange, err := c.Rest().RefreshAccessToken(c.id, c.secret, session.RefreshToken(), opts...) +func (c *clientImpl) RefreshSession(session Session, opts ...rest.RequestOpt) (Session, error) { + accessToken, err := c.Rest().RefreshAccessToken(c.id, c.secret, session.RefreshToken, opts...) if err != nil { - return nil, err + return Session{}, err } - return c.SessionController().CreateSessionFromResponse(identifier, *exchange), nil + return newSession(*accessToken), nil +} + +func (c *clientImpl) VerifySession(session Session, opts ...rest.RequestOpt) (Session, error) { + if session.Expired() { + return c.RefreshSession(session, opts...) + } + return session, nil } func (c *clientImpl) GetUser(session Session, opts ...rest.RequestOpt) (*discord.OAuth2User, error) { if err := checkSession(session, discord.OAuth2ScopeIdentify); err != nil { return nil, err } - return c.Rest().GetCurrentUser(session.AccessToken(), opts...) + return c.Rest().GetCurrentUser(session.AccessToken, opts...) } func (c *clientImpl) GetMember(session Session, guildID snowflake.ID, opts ...rest.RequestOpt) (*discord.Member, error) { if err := checkSession(session, discord.OAuth2ScopeGuildsMembersRead); err != nil { return nil, err } - return c.Rest().GetCurrentMember(session.AccessToken(), guildID, opts...) + return c.Rest().GetCurrentMember(session.AccessToken, guildID, opts...) } func (c *clientImpl) GetGuilds(session Session, opts ...rest.RequestOpt) ([]discord.OAuth2Guild, error) { if err := checkSession(session, discord.OAuth2ScopeGuilds); err != nil { return nil, err } - return c.Rest().GetCurrentUserGuilds(session.AccessToken(), 0, 0, 0, opts...) + return c.Rest().GetCurrentUserGuilds(session.AccessToken, 0, 0, 0, opts...) } func (c *clientImpl) GetConnections(session Session, opts ...rest.RequestOpt) ([]discord.Connection, error) { if err := checkSession(session, discord.OAuth2ScopeConnections); err != nil { return nil, err } - return c.Rest().GetCurrentUserConnections(session.AccessToken(), opts...) + return c.Rest().GetCurrentUserConnections(session.AccessToken, opts...) } func (c *clientImpl) GetApplicationRoleConnection(session Session, applicationID snowflake.ID, opts ...rest.RequestOpt) (*discord.ApplicationRoleConnection, error) { if err := checkSession(session, discord.OAuth2ScopeRoleConnectionsWrite); err != nil { return nil, err } - return c.Rest().GetCurrentUserApplicationRoleConnection(session.AccessToken(), applicationID, opts...) + return c.Rest().GetCurrentUserApplicationRoleConnection(session.AccessToken, applicationID, opts...) } func (c *clientImpl) UpdateApplicationRoleConnection(session Session, applicationID snowflake.ID, update discord.ApplicationRoleConnectionUpdate, opts ...rest.RequestOpt) (*discord.ApplicationRoleConnection, error) { if err := checkSession(session, discord.OAuth2ScopeRoleConnectionsWrite); err != nil { return nil, err } - return c.Rest().UpdateCurrentUserApplicationRoleConnection(session.AccessToken(), applicationID, update, opts...) + return c.Rest().UpdateCurrentUserApplicationRoleConnection(session.AccessToken, applicationID, update, opts...) } func checkSession(session Session, scope discord.OAuth2Scope) error { - if session.Expiration().Before(time.Now()) { - return ErrAccessTokenExpired + if session.Expired() { + return ErrSessionExpired } - if !discord.HasScope(scope, session.Scopes()...) { + if !discord.HasScope(scope, session.Scopes...) { return ErrMissingOAuth2Scope(scope) } return nil } + +func newSession(accessToken discord.AccessTokenResponse) Session { + return Session{ + AccessToken: accessToken.AccessToken, + RefreshToken: accessToken.RefreshToken, + Scopes: accessToken.Scope, + TokenType: accessToken.TokenType, + Expiration: time.Now().Add(accessToken.ExpiresIn * time.Second), + } +} diff --git a/oauth2/config.go b/oauth2/config.go index 92d9f31b3..4e9703ec0 100644 --- a/oauth2/config.go +++ b/oauth2/config.go @@ -9,8 +9,7 @@ import ( // DefaultConfig is the configuration which is used by default func DefaultConfig() *Config { return &Config{ - Logger: log.Default(), - SessionController: NewSessionController(), + Logger: log.Default(), } } @@ -20,7 +19,6 @@ type Config struct { RestClient rest.Client RestClientConfigOpts []rest.ConfigOpt OAuth2 rest.OAuth2 - SessionController SessionController StateController StateController StateControllerConfigOpts []StateControllerConfigOpt } @@ -72,13 +70,6 @@ func WithOAuth2(oauth2 rest.OAuth2) ConfigOpt { } } -// WithSessionController applies a custom SessionController to the OAuth2 client -func WithSessionController(sessionController SessionController) ConfigOpt { - return func(config *Config) { - config.SessionController = sessionController - } -} - // WithStateController applies a custom StateController to the OAuth2 client func WithStateController(stateController StateController) ConfigOpt { return func(config *Config) { diff --git a/oauth2/session.go b/oauth2/session.go deleted file mode 100644 index 90f663ca7..000000000 --- a/oauth2/session.go +++ /dev/null @@ -1,63 +0,0 @@ -package oauth2 - -import ( - "time" - - "github.com/disgoorg/disgo/discord" -) - -var _ Session = (*sessionImpl)(nil) - -// Session represents a discord access token response (https://discord.com/developers/docs/topics/oauth2#authorization-code-grant-access-token-response) -type Session interface { - // AccessToken allows requesting user information - AccessToken() string - - // RefreshToken allows refreshing the AccessToken - RefreshToken() string - - // Scopes returns the discord.OAuth2Scope(s) of the Session - Scopes() []discord.OAuth2Scope - - // TokenType returns the discord.TokenType of the AccessToken - TokenType() discord.TokenType - - // Expiration returns the time.Time when the AccessToken expires and needs to be refreshed - Expiration() time.Time - - // Webhook returns the discord.IncomingWebhook when the discord.OAuth2ScopeWebhookIncoming is set - Webhook() *discord.IncomingWebhook -} - -type sessionImpl struct { - accessToken string - refreshToken string - scopes []discord.OAuth2Scope - tokenType discord.TokenType - expiration time.Time - webhook *discord.IncomingWebhook -} - -func (s *sessionImpl) AccessToken() string { - return s.accessToken -} - -func (s *sessionImpl) RefreshToken() string { - return s.refreshToken -} - -func (s *sessionImpl) Scopes() []discord.OAuth2Scope { - return s.scopes -} - -func (s *sessionImpl) TokenType() discord.TokenType { - return s.tokenType -} - -func (s *sessionImpl) Expiration() time.Time { - return s.expiration -} - -func (s *sessionImpl) Webhook() *discord.IncomingWebhook { - return s.webhook -} diff --git a/oauth2/session_controller.go b/oauth2/session_controller.go deleted file mode 100644 index 6aa61fde3..000000000 --- a/oauth2/session_controller.go +++ /dev/null @@ -1,58 +0,0 @@ -package oauth2 - -import ( - "time" - - "github.com/disgoorg/disgo/discord" -) - -var _ SessionController = (*sessionControllerImpl)(nil) - -// SessionController lets you manage your Session(s) -type SessionController interface { - // GetSession returns the Session for the given identifier or nil if none was found - GetSession(identifier string) Session - - // CreateSession creates a new Session from the given identifier, access token, refresh token, scope, token type, expiration and webhook - CreateSession(identifier string, accessToken string, refreshToken string, scopes []discord.OAuth2Scope, tokenType discord.TokenType, expiration time.Time, webhook *discord.IncomingWebhook) Session - - // CreateSessionFromResponse creates a new Session from the given identifier and discord.AccessTokenResponse payload - CreateSessionFromResponse(identifier string, response discord.AccessTokenResponse) Session -} - -// NewSessionController returns a new empty SessionController -func NewSessionController() SessionController { - return NewSessionControllerWithSessions(map[string]Session{}) -} - -// NewSessionControllerWithSessions returns a new SessionController with the given Session(s) -func NewSessionControllerWithSessions(sessions map[string]Session) SessionController { - return &sessionControllerImpl{sessions: sessions} -} - -type sessionControllerImpl struct { - sessions map[string]Session -} - -func (c *sessionControllerImpl) GetSession(identifier string) Session { - return c.sessions[identifier] -} - -func (c *sessionControllerImpl) CreateSession(identifier string, accessToken string, refreshToken string, scopes []discord.OAuth2Scope, tokenType discord.TokenType, expiration time.Time, webhook *discord.IncomingWebhook) Session { - session := &sessionImpl{ - accessToken: accessToken, - refreshToken: refreshToken, - scopes: scopes, - tokenType: tokenType, - expiration: expiration, - webhook: webhook, - } - - c.sessions[identifier] = session - - return session -} - -func (c *sessionControllerImpl) CreateSessionFromResponse(identifier string, response discord.AccessTokenResponse) Session { - return c.CreateSession(identifier, response.AccessToken, response.RefreshToken, response.Scope, response.TokenType, time.Now().Add(response.ExpiresIn*time.Second), response.Webhook) -} From a32b7f1d3685053aae66ddfc805795913c675f4e Mon Sep 17 00:00:00 2001 From: ftqo Date: Thu, 23 Feb 2023 13:31:32 -0500 Subject: [PATCH 017/119] fix README example (#242) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b1f3c53c5..8e25802bc 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ func main() { panic(err) } // connect to the gateway - if err = client.Open(context.TODO()); err != nil { + if err = client.OpenGateway(context.TODO()); err != nil { panic(err) } From 8cb8e68a90887f236c185406e2ffd60687e35ed1 Mon Sep 17 00:00:00 2001 From: cane Date: Sat, 25 Feb 2023 02:48:24 +0100 Subject: [PATCH 018/119] add CustomMessage to AutoModerationActionMetadata (#243) --- discord/auto_moderation.go | 1 + 1 file changed, 1 insertion(+) diff --git a/discord/auto_moderation.go b/discord/auto_moderation.go index ce92fcb57..3f8a4e020 100644 --- a/discord/auto_moderation.go +++ b/discord/auto_moderation.go @@ -54,6 +54,7 @@ type AutoModerationAction struct { type AutoModerationActionMetadata struct { ChannelID snowflake.ID `json:"channel_id"` DurationSeconds int `json:"duration_seconds"` + CustomMessage *string `json:"custom_message"` } type AutoModerationRule struct { From 14761a378519e1797f3b91cbe721f94c5dca915f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 25 Feb 2023 11:27:33 +0100 Subject: [PATCH 019/119] Bump golang.org/x/sys from 0.0.0-20211019181941-9d821ace8654 to 0.1.0 (#245) Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.0.0-20211019181941-9d821ace8654 to 0.1.0. - [Release notes](https://github.com/golang/sys/releases) - [Commits](https://github.com/golang/sys/commits/v0.1.0) --- updated-dependencies: - dependency-name: golang.org/x/sys dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7705d1860..8cca4ea20 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,6 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect + golang.org/x/sys v0.1.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 4189aa2a5..b018ce554 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,8 @@ golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20220325121720-054d8573a5d8 h1:Xt4/LzbTwfocTk9ZLEu4onjeFucl88iW+v4j4PWbQuE= golang.org/x/exp v0.0.0-20220325121720-054d8573a5d8/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 9ab5fbde32406f9343077eaca175348938861046 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 25 Feb 2023 11:28:01 +0100 Subject: [PATCH 020/119] Bump golang.org/x/sys in /_examples/application_commands/http (#244) Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.0.0-20211019181941-9d821ace8654 to 0.1.0. - [Release notes](https://github.com/golang/sys/releases) - [Commits](https://github.com/golang/sys/commits/v0.1.0) --- updated-dependencies: - dependency-name: golang.org/x/sys dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- _examples/application_commands/http/go.mod | 4 ++-- _examples/application_commands/http/go.sum | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/_examples/application_commands/http/go.mod b/_examples/application_commands/http/go.mod index b8b351cc8..6067252ec 100644 --- a/_examples/application_commands/http/go.mod +++ b/_examples/application_commands/http/go.mod @@ -15,7 +15,7 @@ require ( github.com/disgoorg/json v1.0.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b // indirect - golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e // indirect + golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect golang.org/x/exp v0.0.0-20220325121720-054d8573a5d8 // indirect - golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect + golang.org/x/sys v0.1.0 // indirect ) diff --git a/_examples/application_commands/http/go.sum b/_examples/application_commands/http/go.sum index 6be6d7d82..93cb7acc4 100644 --- a/_examples/application_commands/http/go.sum +++ b/_examples/application_commands/http/go.sum @@ -13,15 +13,16 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b h1:qYTY2tN72LhgDj2rtWG+LI6TXFl2ygFQQ4YezfVaGQE= github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b/go.mod h1:/pA7k3zsXKdjjAiUhB5CjuKib9KJGCaLvZwtxGC8U0s= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e h1:VvfwVmMH40bpMeizC9/K7ipM5Qjucuu16RWfneFPyhQ= golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20220325121720-054d8573a5d8 h1:Xt4/LzbTwfocTk9ZLEu4onjeFucl88iW+v4j4PWbQuE= golang.org/x/exp v0.0.0-20220325121720-054d8573a5d8/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 0b775b65f2f64ef1f6c4a2d2a65806a04b21b36f Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Sun, 26 Feb 2023 15:49:30 +0100 Subject: [PATCH 021/119] fix json tag name --- oauth2/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauth2/client.go b/oauth2/client.go index fefbda7d8..91cbb27d7 100644 --- a/oauth2/client.go +++ b/oauth2/client.go @@ -33,7 +33,7 @@ type Session struct { RefreshToken string `json:"refresh_token"` // Scopes returns the discord.OAuth2Scope(s) of the Session - Scopes []discord.OAuth2Scope `json:"scope"` + Scopes []discord.OAuth2Scope `json:"scopes"` // TokenType returns the discord.TokenType of the AccessToken TokenType discord.TokenType `json:"token_type"` From 2fc89bc438ab907d22fb63f595ebc6ccf50bd02a Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Sun, 26 Feb 2023 16:01:00 +0100 Subject: [PATCH 022/119] minor refactor & cleanup of oauth2 suff --- oauth2/client_impl.go | 22 ++++++++++++++-------- oauth2/config.go | 4 ++-- oauth2/state_controller.go | 17 +++++++++++------ oauth2/state_controller_config.go | 11 +++++++++++ 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/oauth2/client_impl.go b/oauth2/client_impl.go index 1220b9360..c535b63b1 100644 --- a/oauth2/client_impl.go +++ b/oauth2/client_impl.go @@ -14,13 +14,19 @@ func New(id snowflake.ID, secret string, opts ...ConfigOpt) Client { config := DefaultConfig() config.Apply(opts) - return &clientImpl{id: id, secret: secret, config: *config} + return &clientImpl{ + id: id, + secret: secret, + oAuth2: config.OAuth2, + stateController: config.StateController, + } } type clientImpl struct { - id snowflake.ID - secret string - config Config + id snowflake.ID + secret string + oAuth2 rest.OAuth2 + stateController StateController } func (c *clientImpl) ID() snowflake.ID { @@ -32,11 +38,11 @@ func (c *clientImpl) Secret() string { } func (c *clientImpl) Rest() rest.OAuth2 { - return c.config.OAuth2 + return c.oAuth2 } func (c *clientImpl) StateController() StateController { - return c.config.StateController + return c.stateController } func (c *clientImpl) GenerateAuthorizationURL(redirectURI string, permissions discord.Permissions, guildID snowflake.ID, disableGuildSelect bool, scopes ...discord.OAuth2Scope) string { @@ -45,7 +51,7 @@ func (c *clientImpl) GenerateAuthorizationURL(redirectURI string, permissions di } func (c *clientImpl) GenerateAuthorizationURLState(redirectURI string, permissions discord.Permissions, guildID snowflake.ID, disableGuildSelect bool, scopes ...discord.OAuth2Scope) (string, string) { - state := c.StateController().GenerateNewState(redirectURI) + state := c.StateController().NewState(redirectURI) values := discord.QueryValues{ "client_id": c.id, "redirect_uri": redirectURI, @@ -67,7 +73,7 @@ func (c *clientImpl) GenerateAuthorizationURLState(redirectURI string, permissio } func (c *clientImpl) StartSession(code string, state string, opts ...rest.RequestOpt) (Session, *discord.IncomingWebhook, error) { - redirectURI := c.StateController().ConsumeState(state) + redirectURI := c.StateController().UseState(state) if redirectURI == "" { return Session{}, nil, ErrStateNotFound } diff --git a/oauth2/config.go b/oauth2/config.go index 4e9703ec0..64c5a4074 100644 --- a/oauth2/config.go +++ b/oauth2/config.go @@ -32,13 +32,13 @@ func (c *Config) Apply(opts []ConfigOpt) { opt(c) } if c.RestClient == nil { - c.RestClient = rest.NewClient("", c.RestClientConfigOpts...) + c.RestClient = rest.NewClient("", append([]rest.ConfigOpt{rest.WithLogger(c.Logger)}, c.RestClientConfigOpts...)...) } if c.OAuth2 == nil { c.OAuth2 = rest.NewOAuth2(c.RestClient) } if c.StateController == nil { - c.StateController = NewStateController(c.StateControllerConfigOpts...) + c.StateController = NewStateController(append([]StateControllerConfigOpt{WithStateControllerLogger(c.Logger)}, c.StateControllerConfigOpts...)...) } } diff --git a/oauth2/state_controller.go b/oauth2/state_controller.go index 9f4740656..643f22dd2 100644 --- a/oauth2/state_controller.go +++ b/oauth2/state_controller.go @@ -1,16 +1,18 @@ package oauth2 +import "github.com/disgoorg/log" + var ( _ StateController = (*stateControllerImpl)(nil) ) // StateController is responsible for generating, storing and validating states. type StateController interface { - // GenerateNewState generates a new random state to be used as a state. - GenerateNewState(redirectURI string) string + // NewState generates a new random state to be used as a state. + NewState(redirectURI string) string - // ConsumeState validates a state and returns the redirect url or nil if it is invalid. - ConsumeState(state string) string + // UseState validates a state and returns the redirect url or nil if it is invalid. + UseState(state string) string } // NewStateController returns a new empty StateController. @@ -30,21 +32,24 @@ func NewStateController(opts ...StateControllerConfigOpt) StateController { } type stateControllerImpl struct { + logger log.Logger states *ttlMap newStateFunc func() string } -func (c *stateControllerImpl) GenerateNewState(redirectURI string) string { +func (c *stateControllerImpl) NewState(redirectURI string) string { state := c.newStateFunc() + c.logger.Debugf("new state: %s for redirect uri: %s", state, redirectURI) c.states.put(state, redirectURI) return state } -func (c *stateControllerImpl) ConsumeState(state string) string { +func (c *stateControllerImpl) UseState(state string) string { uri := c.states.get(state) if uri == "" { return "" } + c.logger.Debugf("using state: %s for redirect uri: %s", state, uri) c.states.delete(state) return uri } diff --git a/oauth2/state_controller_config.go b/oauth2/state_controller_config.go index cde0eac1b..e9cd8067b 100644 --- a/oauth2/state_controller_config.go +++ b/oauth2/state_controller_config.go @@ -3,12 +3,15 @@ package oauth2 import ( "time" + "github.com/disgoorg/log" + "github.com/disgoorg/disgo/internal/insecurerandstr" ) // DefaultStateControllerConfig is the default configuration for the StateController func DefaultStateControllerConfig() *StateControllerConfig { return &StateControllerConfig{ + Logger: log.Default(), States: map[string]string{}, NewStateFunc: func() string { return insecurerandstr.RandStr(32) }, MaxTTL: time.Hour, @@ -17,6 +20,7 @@ func DefaultStateControllerConfig() *StateControllerConfig { // StateControllerConfig is the configuration for the StateController type StateControllerConfig struct { + Logger log.Logger States map[string]string NewStateFunc func() string MaxTTL time.Duration @@ -32,6 +36,13 @@ func (c *StateControllerConfig) Apply(opts []StateControllerConfigOpt) { } } +// WithStateControllerLogger sets the logger for the StateController +func WithStateControllerLogger(logger log.Logger) StateControllerConfigOpt { + return func(config *StateControllerConfig) { + config.Logger = logger + } +} + // WithStates loads states from an existing map func WithStates(states map[string]string) StateControllerConfigOpt { return func(config *StateControllerConfig) { From 3dbf6a6c066c4462a7ac3a67394c9e10c21fef2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80Senpai?= Date: Mon, 27 Feb 2023 11:46:28 +0100 Subject: [PATCH 023/119] handler add helper to sync commands for guilds or globally --- handler/handler.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/handler/handler.go b/handler/handler.go index 0f9fe6e91..63d60b805 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -21,10 +21,29 @@ import ( "errors" "strings" + "github.com/disgoorg/snowflake/v2" + + "github.com/disgoorg/disgo/bot" "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/events" + "github.com/disgoorg/disgo/rest" ) +// SyncCommands sets the given commands for the given guilds or globally if no guildIDs are empty. It will return on the first error for multiple guilds. +func SyncCommands(client bot.Client, commands []discord.ApplicationCommandCreate, guildIDs []snowflake.ID, opts ...rest.RequestOpt) error { + if len(guildIDs) == 0 { + _, err := client.Rest().SetGlobalCommands(client.ApplicationID(), commands, opts...) + return err + } + for _, guildID := range guildIDs { + _, err := client.Rest().SetGuildCommands(client.ApplicationID(), guildID, commands, opts...) + if err != nil { + return err + } + } + return nil +} + type handlerHolder[T any] struct { pattern string handler T From a3ddd4aacacc34f3645d3d78577d54c1d62d1172 Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Wed, 1 Mar 2023 22:16:35 +0100 Subject: [PATCH 024/119] fix voice ip discovery packet size --- voice/udp_conn.go | 48 +++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/voice/udp_conn.go b/voice/udp_conn.go index cf6e83f80..f8a23ca67 100644 --- a/voice/udp_conn.go +++ b/voice/udp_conn.go @@ -96,10 +96,6 @@ func NewUDPConn(opts ...UDPConnConfigOpt) UDPConn { type udpConnImpl struct { config UDPConnConfig - ip string - port int - ssrc uint32 - conn net.Conn connMu sync.Mutex @@ -149,13 +145,9 @@ func (u *udpConnImpl) SetWriteDeadline(t time.Time) error { } func (u *udpConnImpl) Open(ctx context.Context, ip string, port int, ssrc uint32) (string, int, error) { - u.ip = ip - u.port = port - u.ssrc = ssrc - u.connMu.Lock() defer u.connMu.Unlock() - host := net.JoinHostPort(u.ip, strconv.Itoa(u.port)) + host := net.JoinHostPort(ip, strconv.Itoa(port)) u.config.Logger.Debugf("Opening UDPConn connection to: %s\n", host) var err error u.conn, err = u.config.Dialer.DialContext(ctx, "udp", host) @@ -163,19 +155,43 @@ func (u *udpConnImpl) Open(ctx context.Context, ip string, port int, ssrc uint32 return "", 0, fmt.Errorf("failed to open UDPConn connection: %w", err) } - sb := make([]byte, 70) - binary.BigEndian.PutUint32(sb, u.ssrc) + // see payload here https://discord.com/developers/docs/topics/voice-connections#ip-discovery + sb := make([]byte, 74) + binary.BigEndian.PutUint16(sb[:2], 1) // 1 = send + binary.BigEndian.PutUint16(sb[2:4], 70) // 70 = length + binary.BigEndian.PutUint32(sb[4:74], ssrc) // ssrc + + if err = u.conn.SetWriteDeadline(time.Now().Add(5 * time.Second)); err != nil { + return "", 0, fmt.Errorf("failed to set write deadline on UDPConn connection: %w", err) + } if _, err = u.conn.Write(sb); err != nil { return "", 0, fmt.Errorf("failed to write ssrc to UDPConn connection: %w", err) } - rb := make([]byte, 70) + rb := make([]byte, 74) + if err = u.conn.SetReadDeadline(time.Now().Add(5 * time.Second)); err != nil { + return "", 0, fmt.Errorf("failed to set read deadline on UDPConn connection: %w", err) + } if _, err = u.conn.Read(rb); err != nil { return "", 0, fmt.Errorf("failed to read ip discovery from UDPConn connection: %w", err) } - ourAddress := rb[4:68] - ourPort := binary.BigEndian.Uint16(rb[68:70]) + if binary.BigEndian.Uint16(rb[0:2]) != 2 { + return "", 0, fmt.Errorf("invalid ip discovery response") + } + + size := binary.BigEndian.Uint16(rb[2:4]) + if size != 70 { + return "", 0, fmt.Errorf("invalid ip discovery response size") + } + + returnedSSRC := binary.BigEndian.Uint32(rb[4:8]) // ssrc + ourAddress := strings.TrimSpace(string(rb[8:72])) // our ip + ourPort := int(binary.BigEndian.Uint16(rb[72:74])) // our port + + if returnedSSRC != ssrc { + return "", 0, fmt.Errorf("invalid ssrc in ip discovery response") + } u.packet = [12]byte{ 0: 0x80, // Version + Flags @@ -184,9 +200,9 @@ func (u *udpConnImpl) Open(ctx context.Context, ip string, port int, ssrc uint32 // [4:8] // Timestamp } - binary.BigEndian.PutUint32(u.packet[8:12], u.ssrc) // SSRC + binary.BigEndian.PutUint32(u.packet[8:12], ssrc) // SSRC - return strings.Replace(string(ourAddress), "\x00", "", -1), int(ourPort), nil + return ourAddress, ourPort, nil } func (u *udpConnImpl) Write(p []byte) (int, error) { From 2ac4b5df9b10c8350dc67abeec8d6885de51308e Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Wed, 1 Mar 2023 22:21:46 +0100 Subject: [PATCH 025/119] use handler.SyncCommands in handler example --- _examples/handler/example.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/_examples/handler/example.go b/_examples/handler/example.go index 092211456..44c2a826c 100644 --- a/_examples/handler/example.go +++ b/_examples/handler/example.go @@ -95,9 +95,8 @@ func main() { log.Fatal("error while building bot: ", err) } - // register commands - if _, err = client.Rest().SetGuildCommands(client.ApplicationID(), guildID, commands); err != nil { - log.Fatal("error while setting global commands: ", err) + if err = handler.SyncCommands(client, commands, []snowflake.ID{guildID}); err != nil { + log.Fatal("error while syncing commands: ", err) } defer client.Close(context.TODO()) From 381f278234b1a77d8d13ab5b9b76c1a1ef972fe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80Senpai?= Date: Thu, 2 Mar 2023 09:59:36 +0100 Subject: [PATCH 026/119] maybe fix goroutine leaks from reconnects & resumes (#246) --- gateway/gateway_impl.go | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/gateway/gateway_impl.go b/gateway/gateway_impl.go index a2ee68352..d1e34ec31 100644 --- a/gateway/gateway_impl.go +++ b/gateway/gateway_impl.go @@ -40,10 +40,10 @@ type gatewayImpl struct { closeHandlerFunc CloseHandlerFunc token string - conn *websocket.Conn - connMu sync.Mutex - heartbeatTicker *time.Ticker - status Status + conn *websocket.Conn + connMu sync.Mutex + heartbeatChan chan struct{} + status Status heartbeatInterval time.Duration lastHeartbeatSent time.Time @@ -144,10 +144,9 @@ func (g *gatewayImpl) Close(ctx context.Context) { } func (g *gatewayImpl) CloseWithCode(ctx context.Context, code int, message string) { - if g.heartbeatTicker != nil { + if g.heartbeatChan != nil { g.config.Logger.Debug(g.formatLogs("closing heartbeat goroutines...")) - g.heartbeatTicker.Stop() - g.heartbeatTicker = nil + g.heartbeatChan <- struct{}{} } g.connMu.Lock() @@ -245,12 +244,21 @@ func (g *gatewayImpl) reconnect() { } func (g *gatewayImpl) heartbeat() { - g.heartbeatTicker = time.NewTicker(g.heartbeatInterval) - defer g.heartbeatTicker.Stop() + if g.heartbeatChan == nil { + g.heartbeatChan = make(chan struct{}) + } + heartbeatTicker := time.NewTicker(g.heartbeatInterval) + defer heartbeatTicker.Stop() defer g.config.Logger.Debug(g.formatLogs("exiting heartbeat goroutine...")) - for range g.heartbeatTicker.C { - g.sendHeartbeat() + for { + select { + case <-g.heartbeatChan: + return + + case <-heartbeatTicker.C: + g.sendHeartbeat() + } } } From 8369a3b9721d62ff9a24b93d799218b4b07f15f2 Mon Sep 17 00:00:00 2001 From: Leo <88405502+TisLeo@users.noreply.github.com> Date: Mon, 20 Mar 2023 20:34:44 +0000 Subject: [PATCH 027/119] Added Len func to each XCache interface (#248) --- cache/caches.go | 110 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/cache/caches.go b/cache/caches.go index e59638ebd..d31704afb 100644 --- a/cache/caches.go +++ b/cache/caches.go @@ -51,6 +51,7 @@ type GuildCache interface { Guild(guildID snowflake.ID) (discord.Guild, bool) GuildsForEach(fn func(guild discord.Guild)) + GuildsLen() int AddGuild(guild discord.Guild) RemoveGuild(guildID snowflake.ID) (discord.Guild, bool) } @@ -117,6 +118,10 @@ func (c *guildCacheImpl) GuildsForEach(fn func(guild discord.Guild)) { c.cache.ForEach(fn) } +func (c *guildCacheImpl) GuildsLen() int { + return c.cache.Len() +} + func (c *guildCacheImpl) AddGuild(guild discord.Guild) { c.cache.Put(guild.ID, guild) } @@ -128,6 +133,7 @@ func (c *guildCacheImpl) RemoveGuild(guildID snowflake.ID) (discord.Guild, bool) type ChannelCache interface { Channel(channelID snowflake.ID) (discord.GuildChannel, bool) ChannelsForEach(fn func(channel discord.GuildChannel)) + ChannelsLen() int AddChannel(channel discord.GuildChannel) RemoveChannel(channelID snowflake.ID) (discord.GuildChannel, bool) RemoveChannelsByGuildID(guildID snowflake.ID) @@ -151,6 +157,10 @@ func (c *channelCacheImpl) ChannelsForEach(fn func(channel discord.GuildChannel) c.cache.ForEach(fn) } +func (c *channelCacheImpl) ChannelsLen() int { + return c.cache.Len() +} + func (c *channelCacheImpl) AddChannel(channel discord.GuildChannel) { c.cache.Put(channel.ID(), channel) } @@ -168,6 +178,8 @@ func (c *channelCacheImpl) RemoveChannelsByGuildID(guildID snowflake.ID) { type StageInstanceCache interface { StageInstance(guildID snowflake.ID, stageInstanceID snowflake.ID) (discord.StageInstance, bool) StageInstanceForEach(guildID snowflake.ID, fn func(stageInstance discord.StageInstance)) + StageInstancesAllLen() int + StageInstancesLen(guildID snowflake.ID) int AddStageInstance(stageInstance discord.StageInstance) RemoveStageInstance(guildID snowflake.ID, stageInstanceID snowflake.ID) (discord.StageInstance, bool) RemoveStageInstancesByGuildID(guildID snowflake.ID) @@ -193,6 +205,14 @@ func (c *stageInstanceCacheImpl) StageInstanceForEach(guildID snowflake.ID, fn f }) } +func (c *stageInstanceCacheImpl) StageInstancesAllLen() int { + return c.cache.Len() +} + +func (c *stageInstanceCacheImpl) StageInstancesLen(guildID snowflake.ID) int { + return c.cache.GroupLen(guildID) +} + func (c *stageInstanceCacheImpl) AddStageInstance(stageInstance discord.StageInstance) { c.cache.Put(stageInstance.GuildID, stageInstance.ID, stageInstance) } @@ -208,6 +228,8 @@ func (c *stageInstanceCacheImpl) RemoveStageInstancesByGuildID(guildID snowflake type GuildScheduledEventCache interface { GuildScheduledEvent(guildID snowflake.ID, guildScheduledEventID snowflake.ID) (discord.GuildScheduledEvent, bool) GuildScheduledEventsForEach(guildID snowflake.ID, fn func(guildScheduledEvent discord.GuildScheduledEvent)) + GuildScheduledEventsAllLen() int + GuildScheduledEventsLen(guildID snowflake.ID) int AddGuildScheduledEvent(guildScheduledEvent discord.GuildScheduledEvent) RemoveGuildScheduledEvent(guildID snowflake.ID, guildScheduledEventID snowflake.ID) (discord.GuildScheduledEvent, bool) RemoveGuildScheduledEventsByGuildID(guildID snowflake.ID) @@ -231,6 +253,14 @@ func (c *guildScheduledEventCacheImpl) GuildScheduledEventsForEach(guildID snowf c.cache.GroupForEach(guildID, fn) } +func (c *guildScheduledEventCacheImpl) GuildScheduledEventsAllLen() int { + return c.cache.Len() +} + +func (c *guildScheduledEventCacheImpl) GuildScheduledEventsLen(guildID snowflake.ID) int { + return c.cache.GroupLen(guildID) +} + func (c *guildScheduledEventCacheImpl) AddGuildScheduledEvent(guildScheduledEvent discord.GuildScheduledEvent) { c.cache.Put(guildScheduledEvent.GuildID, guildScheduledEvent.ID, guildScheduledEvent) } @@ -246,6 +276,8 @@ func (c *guildScheduledEventCacheImpl) RemoveGuildScheduledEventsByGuildID(guild type RoleCache interface { Role(guildID snowflake.ID, roleID snowflake.ID) (discord.Role, bool) RolesForEach(guildID snowflake.ID, fn func(role discord.Role)) + RolesAllLen() int + RolesLen(guildID snowflake.ID) int AddRole(role discord.Role) RemoveRole(guildID snowflake.ID, roleID snowflake.ID) (discord.Role, bool) RemoveRolesByGuildID(guildID snowflake.ID) @@ -269,6 +301,14 @@ func (c *roleCacheImpl) RolesForEach(guildID snowflake.ID, fn func(role discord. c.cache.GroupForEach(guildID, fn) } +func (c *roleCacheImpl) RolesAllLen() int { + return c.cache.Len() +} + +func (c *roleCacheImpl) RolesLen(guildID snowflake.ID) int { + return c.cache.GroupLen(guildID) +} + func (c *roleCacheImpl) AddRole(role discord.Role) { c.cache.Put(role.GuildID, role.ID, role) } @@ -284,6 +324,8 @@ func (c *roleCacheImpl) RemoveRolesByGuildID(guildID snowflake.ID) { type MemberCache interface { Member(guildID snowflake.ID, userID snowflake.ID) (discord.Member, bool) MembersForEach(guildID snowflake.ID, fn func(member discord.Member)) + MembersAllLen() int + MembersLen(guildID snowflake.ID) int AddMember(member discord.Member) RemoveMember(guildID snowflake.ID, userID snowflake.ID) (discord.Member, bool) RemoveMembersByGuildID(guildID snowflake.ID) @@ -307,6 +349,14 @@ func (c *memberCacheImpl) MembersForEach(guildID snowflake.ID, fn func(member di c.cache.GroupForEach(guildID, fn) } +func (c *memberCacheImpl) MembersAllLen() int { + return c.cache.Len() +} + +func (c *memberCacheImpl) MembersLen(guildID snowflake.ID) int { + return c.cache.GroupLen(guildID) +} + func (c *memberCacheImpl) AddMember(member discord.Member) { c.cache.Put(member.GuildID, member.User.ID, member) } @@ -322,6 +372,8 @@ func (c *memberCacheImpl) RemoveMembersByGuildID(guildID snowflake.ID) { type ThreadMemberCache interface { ThreadMember(threadID snowflake.ID, userID snowflake.ID) (discord.ThreadMember, bool) ThreadMemberForEach(threadID snowflake.ID, fn func(threadMember discord.ThreadMember)) + ThreadMembersAllLen() int + ThreadMembersLen(guildID snowflake.ID) int AddThreadMember(threadMember discord.ThreadMember) RemoveThreadMember(threadID snowflake.ID, userID snowflake.ID) (discord.ThreadMember, bool) RemoveThreadMembersByThreadID(threadID snowflake.ID) @@ -347,6 +399,14 @@ func (c *threadMemberCacheImpl) ThreadMemberForEach(threadID snowflake.ID, fn fu }) } +func (c *threadMemberCacheImpl) ThreadMembersAllLen() int { + return c.cache.Len() +} + +func (c *threadMemberCacheImpl) ThreadMembersLen(guildID snowflake.ID) int { + return c.cache.GroupLen(guildID) +} + func (c *threadMemberCacheImpl) AddThreadMember(threadMember discord.ThreadMember) { c.cache.Put(threadMember.ThreadID, threadMember.UserID, threadMember) } @@ -362,6 +422,8 @@ func (c *threadMemberCacheImpl) RemoveThreadMembersByThreadID(threadID snowflake type PresenceCache interface { Presence(guildID snowflake.ID, userID snowflake.ID) (discord.Presence, bool) PresenceForEach(guildID snowflake.ID, fn func(presence discord.Presence)) + PresencesAllLen() int + PresencesLen(guildID snowflake.ID) int AddPresence(presence discord.Presence) RemovePresence(guildID snowflake.ID, userID snowflake.ID) (discord.Presence, bool) RemovePresencesByGuildID(guildID snowflake.ID) @@ -387,6 +449,14 @@ func (c *presenceCacheImpl) PresenceForEach(guildID snowflake.ID, fn func(presen }) } +func (c *presenceCacheImpl) PresencesAllLen() int { + return c.cache.Len() +} + +func (c *presenceCacheImpl) PresencesLen(guildID snowflake.ID) int { + return c.cache.GroupLen(guildID) +} + func (c *presenceCacheImpl) AddPresence(presence discord.Presence) { c.cache.Put(presence.GuildID, presence.PresenceUser.ID, presence) } @@ -402,6 +472,8 @@ func (c *presenceCacheImpl) RemovePresencesByGuildID(guildID snowflake.ID) { type VoiceStateCache interface { VoiceState(guildID snowflake.ID, userID snowflake.ID) (discord.VoiceState, bool) VoiceStatesForEach(guildID snowflake.ID, fn func(discord.VoiceState)) + VoiceStatesAllLen() int + VoiceStatesLen(guildID snowflake.ID) int AddVoiceState(voiceState discord.VoiceState) RemoveVoiceState(guildID snowflake.ID, userID snowflake.ID) (discord.VoiceState, bool) RemoveVoiceStatesByGuildID(guildID snowflake.ID) @@ -425,6 +497,14 @@ func (c *voiceStateCacheImpl) VoiceStatesForEach(guildID snowflake.ID, fn func(d c.cache.GroupForEach(guildID, fn) } +func (c *voiceStateCacheImpl) VoiceStatesAllLen() int { + return c.cache.Len() +} + +func (c *voiceStateCacheImpl) VoiceStatesLen(guildID snowflake.ID) int { + return c.cache.GroupLen(guildID) +} + func (c *voiceStateCacheImpl) AddVoiceState(voiceState discord.VoiceState) { c.cache.Put(voiceState.GuildID, voiceState.UserID, voiceState) } @@ -440,6 +520,8 @@ func (c *voiceStateCacheImpl) RemoveVoiceStatesByGuildID(guildID snowflake.ID) { type MessageCache interface { Message(channelID snowflake.ID, messageID snowflake.ID) (discord.Message, bool) MessagesForEach(channelID snowflake.ID, fn func(message discord.Message)) + MessagesAllLen() int + MessagesLen(guildID snowflake.ID) int AddMessage(message discord.Message) RemoveMessage(channelID snowflake.ID, messageID snowflake.ID) (discord.Message, bool) RemoveMessagesByChannelID(channelID snowflake.ID) @@ -464,6 +546,14 @@ func (c *messageCacheImpl) MessagesForEach(channelID snowflake.ID, fn func(messa c.cache.GroupForEach(channelID, fn) } +func (c *messageCacheImpl) MessagesAllLen() int { + return c.cache.Len() +} + +func (c *messageCacheImpl) MessagesLen(guildID snowflake.ID) int { + return c.cache.GroupLen(guildID) +} + func (c *messageCacheImpl) AddMessage(message discord.Message) { c.cache.Put(message.ChannelID, message.ID, message) } @@ -485,6 +575,8 @@ func (c *messageCacheImpl) RemoveMessagesByGuildID(guildID snowflake.ID) { type EmojiCache interface { Emoji(guildID snowflake.ID, emojiID snowflake.ID) (discord.Emoji, bool) EmojisForEach(guildID snowflake.ID, fn func(emoji discord.Emoji)) + EmojisAllLen() int + EmojisLen(guildID snowflake.ID) int AddEmoji(emoji discord.Emoji) RemoveEmoji(guildID snowflake.ID, emojiID snowflake.ID) (discord.Emoji, bool) RemoveEmojisByGuildID(guildID snowflake.ID) @@ -508,6 +600,14 @@ func (c *emojiCacheImpl) EmojisForEach(guildID snowflake.ID, fn func(emoji disco c.cache.GroupForEach(guildID, fn) } +func (c *emojiCacheImpl) EmojisAllLen() int { + return c.cache.Len() +} + +func (c *emojiCacheImpl) EmojisLen(guildID snowflake.ID) int { + return c.cache.GroupLen(guildID) +} + func (c *emojiCacheImpl) AddEmoji(emoji discord.Emoji) { c.cache.Put(emoji.GuildID, emoji.ID, emoji) } @@ -523,6 +623,8 @@ func (c *emojiCacheImpl) RemoveEmojisByGuildID(guildID snowflake.ID) { type StickerCache interface { Sticker(guildID snowflake.ID, stickerID snowflake.ID) (discord.Sticker, bool) StickersForEach(guildID snowflake.ID, fn func(sticker discord.Sticker)) + StickersAllLen() int + StickersLen(guildID snowflake.ID) int AddSticker(sticker discord.Sticker) RemoveSticker(guildID snowflake.ID, stickerID snowflake.ID) (discord.Sticker, bool) RemoveStickersByGuildID(guildID snowflake.ID) @@ -546,6 +648,14 @@ func (c *stickerCacheImpl) StickersForEach(guildID snowflake.ID, fn func(sticker c.cache.GroupForEach(guildID, fn) } +func (c *stickerCacheImpl) StickersAllLen() int { + return c.cache.Len() +} + +func (c *stickerCacheImpl) StickersLen(guildID snowflake.ID) int { + return c.cache.GroupLen(guildID) +} + func (c *stickerCacheImpl) AddSticker(sticker discord.Sticker) { if sticker.GuildID == nil { return From 99774451641746a6d4cb6ac7f871c324d8cc1182 Mon Sep 17 00:00:00 2001 From: jkdlgy <65353031+jkdlgy@users.noreply.github.com> Date: Tue, 21 Mar 2023 23:19:53 +0300 Subject: [PATCH 028/119] fix: event dispatcher goroutine captures only the last loop index when asynchronously handling events with e.config.AsyncEventsEnabled == true (#250) Co-authored-by: jkdlgy <65353031+jkdlgy@users.noreply.github.com > --- bot/event_manager.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/event_manager.go b/bot/event_manager.go index d13d6727b..84565fed3 100644 --- a/bot/event_manager.go +++ b/bot/event_manager.go @@ -146,7 +146,7 @@ func (e *eventManagerImpl) DispatchEvent(event Event) { defer e.eventListenerMu.Unlock() for i := range e.config.EventListeners { if e.config.AsyncEventsEnabled { - go func() { + go func(i int) { defer func() { if r := recover(); r != nil { e.config.Logger.Errorf("recovered from panic in event listener: %+v\nstack: %s", r, string(debug.Stack())) @@ -154,7 +154,7 @@ func (e *eventManagerImpl) DispatchEvent(event Event) { } }() e.config.EventListeners[i].OnEvent(event) - }() + }(i) continue } e.config.EventListeners[i].OnEvent(event) From 196d118d6d5c48b339279d27666dfe5540906955 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80Senpai?= Date: Mon, 27 Mar 2023 15:24:10 +0200 Subject: [PATCH 029/119] add jetbrains support notice --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 8e25802bc..3636c3284 100644 --- a/README.md +++ b/README.md @@ -178,3 +178,7 @@ Contributions are welcomed but for bigger changes we recommend first reaching ou ## License Distributed under the [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/disgoorg/disgo/blob/master/LICENSE). See LICENSE for more information. + +## Supported by Jetbrains + +[![Jetbrain Open Source Community Support](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png)](https://www.jetbrains.com/community/opensource) From 39b633417b876e0e7a702b1fb82b8c623634b0e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80Senpai?= Date: Mon, 27 Mar 2023 15:26:05 +0200 Subject: [PATCH 030/119] limit svg size --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3636c3284..bcaf10a7b 100644 --- a/README.md +++ b/README.md @@ -181,4 +181,4 @@ Distributed under the [![License](https://img.shields.io/badge/License-Apache%20 ## Supported by Jetbrains -[![Jetbrain Open Source Community Support](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png)](https://www.jetbrains.com/community/opensource) +[Jetbrain Open Source Community Support)](https://www.jetbrains.com/community/opensource) From f3d92fa706bdc74358dd114a31d5bc2f9dc938d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80Senpai?= Date: Mon, 27 Mar 2023 15:26:41 +0200 Subject: [PATCH 031/119] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bcaf10a7b..19b4fe3d3 100644 --- a/README.md +++ b/README.md @@ -181,4 +181,4 @@ Distributed under the [![License](https://img.shields.io/badge/License-Apache%20 ## Supported by Jetbrains -[Jetbrain Open Source Community Support)](https://www.jetbrains.com/community/opensource) +[Jetbrain Open Source Community Support](https://www.jetbrains.com/community/opensource) From 90f185c69b800e34447fe8592ab0df85ce93823d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80Senpai?= Date: Mon, 27 Mar 2023 15:28:24 +0200 Subject: [PATCH 032/119] use html syntax for markdown link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 19b4fe3d3..571240c10 100644 --- a/README.md +++ b/README.md @@ -181,4 +181,4 @@ Distributed under the [![License](https://img.shields.io/badge/License-Apache%20 ## Supported by Jetbrains -[Jetbrain Open Source Community Support](https://www.jetbrains.com/community/opensource) +Jetbrain Open Source Community Support From 1037f73a930350b6a9f5d38f5d96fa0637df4df1 Mon Sep 17 00:00:00 2001 From: Zen <45705890+ZenShibata@users.noreply.github.com> Date: Wed, 29 Mar 2023 18:41:52 +0700 Subject: [PATCH 033/119] feat(gateway): add HeartbeatAck event (#253) * feat(gateway): add HeartbeatAck event * feat: HEARTBEAT_ACK -> __HEARTBEAT_ACK__ * feat(events): handle heartbeat ack events * Added event payload: LastHeartbeat & NewHeartbeat --- events/heartbeat_ack.go | 8 ++++++++ gateway/gateway_event_type.go | 1 + gateway/gateway_events.go | 8 ++++++++ gateway/gateway_impl.go | 7 ++++++- handlers/all_handlers.go | 1 + handlers/status_handler.go | 7 +++++++ 6 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 events/heartbeat_ack.go diff --git a/events/heartbeat_ack.go b/events/heartbeat_ack.go new file mode 100644 index 000000000..f54aa32df --- /dev/null +++ b/events/heartbeat_ack.go @@ -0,0 +1,8 @@ +package events + +import "github.com/disgoorg/disgo/gateway" + +type HeartbeatAck struct { + *GenericEvent + gateway.EventHeartbeatAck +} diff --git a/gateway/gateway_event_type.go b/gateway/gateway_event_type.go index 74a4d7d92..88ebd49c4 100644 --- a/gateway/gateway_event_type.go +++ b/gateway/gateway_event_type.go @@ -7,6 +7,7 @@ type EventType string const ( // EventTypeRaw is not a real event type, but is used to pass raw payloads to the bot.EventManager EventTypeRaw EventType = "__RAW__" + EventTypeHeartbeatAck EventType = "__HEARTBEAT_ACK__" EventTypeReady EventType = "READY" EventTypeResumed EventType = "RESUMED" EventTypeApplicationCommandPermissionsUpdate EventType = "APPLICATION_COMMAND_PERMISSIONS_UPDATE" diff --git a/gateway/gateway_events.go b/gateway/gateway_events.go index 9f02fec52..c2905de32 100644 --- a/gateway/gateway_events.go +++ b/gateway/gateway_events.go @@ -692,3 +692,11 @@ type EventRaw struct { func (EventRaw) messageData() {} func (EventRaw) eventData() {} + +type EventHeartbeatAck struct { + LastHeartbeat time.Time + NewHeartbeat time.Time +} + +func (EventHeartbeatAck) messageData() {} +func (EventHeartbeatAck) eventData() {} diff --git a/gateway/gateway_impl.go b/gateway/gateway_impl.go index d1e34ec31..d73e93d89 100644 --- a/gateway/gateway_impl.go +++ b/gateway/gateway_impl.go @@ -448,7 +448,12 @@ loop: break loop case OpcodeHeartbeatACK: - g.lastHeartbeatReceived = time.Now().UTC() + newHeartbeat := time.Now().UTC() + g.eventHandlerFunc(EventTypeHeartbeatAck, message.S, g.config.ShardID, EventHeartbeatAck{ + LastHeartbeat: g.lastHeartbeatReceived, + NewHeartbeat: newHeartbeat, + }) + g.lastHeartbeatReceived = newHeartbeat default: g.config.Logger.Debug(g.formatLogsf("unknown opcode received: %d, data: %s", message.Op, message.D)) diff --git a/handlers/all_handlers.go b/handlers/all_handlers.go index 4f220fbcc..5e1f2b5b0 100644 --- a/handlers/all_handlers.go +++ b/handlers/all_handlers.go @@ -32,6 +32,7 @@ func GetGatewayHandlers() map[gateway.EventType]bot.GatewayEventHandler { var allEventHandlers = []bot.GatewayEventHandler{ bot.NewGatewayEventHandler(gateway.EventTypeRaw, gatewayHandlerRaw), + bot.NewGatewayEventHandler(gateway.EventTypeHeartbeatAck, gatewayHandlerHeartbeatAck), bot.NewGatewayEventHandler(gateway.EventTypeReady, gatewayHandlerReady), bot.NewGatewayEventHandler(gateway.EventTypeResumed, gatewayHandlerResumed), diff --git a/handlers/status_handler.go b/handlers/status_handler.go index 003a8dc0f..e77b2befa 100644 --- a/handlers/status_handler.go +++ b/handlers/status_handler.go @@ -13,6 +13,13 @@ func gatewayHandlerRaw(client bot.Client, sequenceNumber int, shardID int, event }) } +func gatewayHandlerHeartbeatAck(client bot.Client, sequenceNumber int, shardID int, event gateway.EventHeartbeatAck) { + client.EventManager().DispatchEvent(&events.HeartbeatAck{ + GenericEvent: events.NewGenericEvent(client, sequenceNumber, shardID), + EventHeartbeatAck: event, + }) +} + func gatewayHandlerReady(client bot.Client, sequenceNumber int, shardID int, event gateway.EventReady) { client.Caches().SetSelfUser(event.User) From 5eb387174c46412a94fe230b396de42c118414f1 Mon Sep 17 00:00:00 2001 From: cane Date: Fri, 31 Mar 2023 21:35:57 +0200 Subject: [PATCH 034/119] Add ApplicationFlagApplicationAutoModerationRuleCreateBadge (#252) --- discord/application.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/discord/application.go b/discord/application.go index 5675240d4..c91266aad 100644 --- a/discord/application.go +++ b/discord/application.go @@ -163,7 +163,14 @@ func (t TokenType) Apply(token string) string { type ApplicationFlags int const ( - ApplicationFlagGatewayPresence = 1 << (iota + 12) + ApplicationFlagApplicationAutoModerationRuleCreateBadge = 1 << (iota + 6) + _ + _ + _ + _ + _ + _ + ApplicationFlagGatewayPresence ApplicationFlagGatewayPresenceLimited ApplicationFlagGatewayGuildMembers ApplicationFlagGatewayGuildMemberLimited From 37ae304ec1a7acb758d74769a429b174dd0e5e84 Mon Sep 17 00:00:00 2001 From: caneleex Date: Fri, 31 Mar 2023 21:46:55 +0200 Subject: [PATCH 035/119] fix incorrect iota --- discord/application.go | 1 - 1 file changed, 1 deletion(-) diff --git a/discord/application.go b/discord/application.go index c91266aad..0c37bd210 100644 --- a/discord/application.go +++ b/discord/application.go @@ -169,7 +169,6 @@ const ( _ _ _ - _ ApplicationFlagGatewayPresence ApplicationFlagGatewayPresenceLimited ApplicationFlagGatewayGuildMembers From 7b58ad9cd80b3b2c7174baaa08ebdd288acb883e Mon Sep 17 00:00:00 2001 From: cane Date: Sat, 1 Apr 2023 00:37:44 +0200 Subject: [PATCH 036/119] add MaxStageVideoChannelUsers to Guild (#191) --- discord/guild.go | 1 + 1 file changed, 1 insertion(+) diff --git a/discord/guild.go b/discord/guild.go index 044006621..ce58ec000 100644 --- a/discord/guild.go +++ b/discord/guild.go @@ -152,6 +152,7 @@ type Guild struct { PreferredLocale string `json:"preferred_locale"` PublicUpdatesChannelID *snowflake.ID `json:"public_updates_channel_id"` MaxVideoChannelUsers int `json:"max_video_channel_users"` + MaxStageVideoChannelUsers int `json:"max_stage_video_channel_users"` WelcomeScreen GuildWelcomeScreen `json:"welcome_screen"` NSFWLevel NSFWLevel `json:"nsfw_level"` BoostProgressBarEnabled bool `json:"premium_progress_bar_enabled"` From 785b21e491f2de653fc7bc0dfcbde28919a0475b Mon Sep 17 00:00:00 2001 From: cane Date: Sat, 1 Apr 2023 00:40:34 +0200 Subject: [PATCH 037/119] Update permissions (#251) --- discord/permissions.go | 100 ++++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 47 deletions(-) diff --git a/discord/permissions.go b/discord/permissions.go index be9b5e906..99bcb85ce 100644 --- a/discord/permissions.go +++ b/discord/permissions.go @@ -44,17 +44,19 @@ const ( PermissionManageNicknames PermissionManageRoles PermissionManageWebhooks - PermissionManageEmojisAndStickers + PermissionManageGuildExpressions PermissionUseApplicationCommands PermissionRequestToSpeak PermissionManageEvents PermissionManageThreads - PermissionCreatePublicThread - PermissionCreatePrivateThread + PermissionCreatePublicThreads + PermissionCreatePrivateThreads PermissionUseExternalStickers PermissionSendMessagesInThreads - PermissionStartEmbeddedActivities + PermissionUseEmbeddedActivities PermissionModerateMembers + PermissionViewCreatorMonetizationAnalytics + PermissionUseSoundboard ) // Constants for the different bit offsets of general permissions @@ -79,8 +81,8 @@ const ( PermissionMentionEveryone PermissionsAllThread = PermissionManageThreads | - PermissionCreatePublicThread | - PermissionCreatePrivateThread | + PermissionCreatePublicThreads | + PermissionCreatePrivateThreads | PermissionSendMessagesInThreads PermissionsAllVoice = PermissionViewChannel | @@ -90,7 +92,8 @@ const ( PermissionVoiceDeafenMembers | PermissionVoiceMoveMembers | PermissionVoiceUseVAD | - PermissionVoicePrioritySpeaker + PermissionVoicePrioritySpeaker | + PermissionUseSoundboard PermissionsAllChannel = PermissionsAllText | PermissionsAllThread | @@ -107,7 +110,8 @@ const ( PermissionManageServer | PermissionAdministrator | PermissionManageWebhooks | - PermissionManageEmojisAndStickers + PermissionManageGuildExpressions | + PermissionViewCreatorMonetizationAnalytics PermissionsStageModerator = PermissionManageChannels | PermissionVoiceMuteMembers | @@ -117,45 +121,47 @@ const ( ) var permissions = map[Permissions]string{ - PermissionCreateInstantInvite: "Create Instant Invite", - PermissionKickMembers: "Kick Members", - PermissionBanMembers: "Ban Members", - PermissionAdministrator: "Administrator", - PermissionManageChannels: "Manage Channels", - PermissionManageServer: "Manage Server", - PermissionAddReactions: "Add Reactions", - PermissionViewAuditLogs: "View Audit Logs", - PermissionViewChannel: "View Channel", - PermissionSendMessages: "Send Messages", - PermissionSendTTSMessages: "Send TTS Messages", - PermissionManageMessages: "Manage Messages", - PermissionEmbedLinks: "Embed Links", - PermissionAttachFiles: "Attach Files", - PermissionReadMessageHistory: "Read Message History", - PermissionMentionEveryone: "Mention Everyone", - PermissionUseExternalEmojis: "Use External Emojis", - PermissionVoiceConnect: "Connect", - PermissionVoiceSpeak: "Speak", - PermissionVoiceMuteMembers: "Mute Members", - PermissionVoiceDeafenMembers: "Deafen Members", - PermissionVoiceMoveMembers: "Move Members", - PermissionVoiceUseVAD: "Use Voice Activity", - PermissionVoicePrioritySpeaker: "Priority Speaker", - PermissionChangeNickname: "Change Nickname", - PermissionManageNicknames: "Manage Nicknames", - PermissionManageRoles: "Manage Roles", - PermissionManageWebhooks: "Manage Webhooks", - PermissionManageEmojisAndStickers: "Manage Emojis and Stickers", - PermissionUseApplicationCommands: "Use Application Commands", - PermissionRequestToSpeak: "Request to Speak", - PermissionManageEvents: "Manage Events", - PermissionManageThreads: "Manage Threads", - PermissionCreatePublicThread: "Create Public Threads", - PermissionCreatePrivateThread: "Create Private Threads", - PermissionUseExternalStickers: "Use External Stickers", - PermissionSendMessagesInThreads: "Send Messages in Threads", - PermissionStartEmbeddedActivities: "Start Embedded Activities", - PermissionModerateMembers: "Moderate Members", + PermissionCreateInstantInvite: "Create Instant Invite", + PermissionKickMembers: "Kick Members", + PermissionBanMembers: "Ban Members", + PermissionAdministrator: "Administrator", + PermissionManageChannels: "Manage Channels", + PermissionManageServer: "Manage Server", + PermissionAddReactions: "Add Reactions", + PermissionViewAuditLogs: "View Audit Logs", + PermissionViewChannel: "View Channel", + PermissionSendMessages: "Send Messages", + PermissionSendTTSMessages: "Send TTS Messages", + PermissionManageMessages: "Manage Messages", + PermissionEmbedLinks: "Embed Links", + PermissionAttachFiles: "Attach Files", + PermissionReadMessageHistory: "Read Message History", + PermissionMentionEveryone: "Mention Everyone", + PermissionUseExternalEmojis: "Use External Emojis", + PermissionVoiceConnect: "Connect", + PermissionVoiceSpeak: "Speak", + PermissionVoiceMuteMembers: "Mute Members", + PermissionVoiceDeafenMembers: "Deafen Members", + PermissionVoiceMoveMembers: "Move Members", + PermissionVoiceUseVAD: "Use Voice Activity", + PermissionVoicePrioritySpeaker: "Priority Speaker", + PermissionChangeNickname: "Change Nickname", + PermissionManageNicknames: "Manage Nicknames", + PermissionManageRoles: "Manage Roles", + PermissionManageWebhooks: "Manage Webhooks", + PermissionManageGuildExpressions: "Manage Expressions", + PermissionUseApplicationCommands: "Use Application Commands", + PermissionRequestToSpeak: "Request to Speak", + PermissionManageEvents: "Manage Events", + PermissionManageThreads: "Manage Threads", + PermissionCreatePublicThreads: "Create Public Threads", + PermissionCreatePrivateThreads: "Create Private Threads", + PermissionUseExternalStickers: "Use External Stickers", + PermissionSendMessagesInThreads: "Send Messages in Threads", + PermissionUseEmbeddedActivities: "Use Activities", + PermissionModerateMembers: "Moderate Members", + PermissionViewCreatorMonetizationAnalytics: "View Creator Monetization Analytics", + PermissionUseSoundboard: "Use Soundboard", } func (p Permissions) String() string { From cb10239a227c475ed1a008ab7c0e9dfd0c47b5a7 Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Sat, 1 Apr 2023 02:03:23 +0200 Subject: [PATCH 038/119] allow middlewares to abort handler execution --- handler/middleware.go | 2 +- handler/middleware/logger.go | 4 ++-- handler/middleware/print.go | 4 ++-- handler/mux.go | 27 +++++++++++++++------------ 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/handler/middleware.go b/handler/middleware.go index 4eade2682..d38732b95 100644 --- a/handler/middleware.go +++ b/handler/middleware.go @@ -5,7 +5,7 @@ import ( ) type ( - Handler func(e *events.InteractionCreate) + Handler func(e *events.InteractionCreate) error Middleware func(next Handler) Handler diff --git a/handler/middleware/logger.go b/handler/middleware/logger.go index abcfeb1fb..2e9f1dea9 100644 --- a/handler/middleware/logger.go +++ b/handler/middleware/logger.go @@ -6,8 +6,8 @@ import ( ) var Logger handler.Middleware = func(next handler.Handler) handler.Handler { - return func(e *events.InteractionCreate) { + return func(e *events.InteractionCreate) error { e.Client().Logger().Infof("handling interaction: %s\n", e.Interaction.ID()) - next(e) + return next(e) } } diff --git a/handler/middleware/print.go b/handler/middleware/print.go index b50e42b34..f209bc83b 100644 --- a/handler/middleware/print.go +++ b/handler/middleware/print.go @@ -7,9 +7,9 @@ import ( func Print(content string) handler.Middleware { return func(next handler.Handler) handler.Handler { - return func(event *events.InteractionCreate) { + return func(event *events.InteractionCreate) error { println(content) - next(event) + return next(event) } } } diff --git a/handler/mux.go b/handler/mux.go index ebdeeb94e..f49160835 100644 --- a/handler/mux.go +++ b/handler/mux.go @@ -84,22 +84,25 @@ func (r *Mux) Match(path string, t discord.InteractionType) bool { // Handle handles the given interaction event. func (r *Mux) Handle(path string, variables map[string]string, e *events.InteractionCreate) error { - path = parseVariables(path, r.pattern, variables) - middlewares := func(event *events.InteractionCreate) {} - for i := len(r.middlewares) - 1; i >= 0; i-- { - middlewares = r.middlewares[i](middlewares) - } - middlewares(e) + handlerChain := func(event *events.InteractionCreate) error { + path = parseVariables(path, r.pattern, variables) - for _, route := range r.routes { - if route.Match(path, e.Type()) { - return route.Handle(path, variables, e) + for _, route := range r.routes { + if route.Match(path, e.Type()) { + return route.Handle(path, variables, e) + } + } + if r.notFoundHandler != nil { + return r.notFoundHandler(e) } + return nil } - if r.notFoundHandler != nil { - return r.notFoundHandler(e) + + for i := len(r.middlewares) - 1; i >= 0; i-- { + handlerChain = r.middlewares[i](handlerChain) } - return nil + + return handlerChain(e) } // Use adds the given middlewares to the current Router. From bbc3a1bb868032d3d9d7bd09e6e789faa4e0b3c4 Mon Sep 17 00:00:00 2001 From: cane Date: Sat, 1 Apr 2023 13:33:30 +0200 Subject: [PATCH 039/119] add guild onboarding (#238) --- discord/guild_onboarding.go | 37 +++++++++++++++++++++++++++++++++++++ rest/guilds.go | 7 +++++++ rest/rest_endpoints.go | 2 ++ 3 files changed, 46 insertions(+) create mode 100644 discord/guild_onboarding.go diff --git a/discord/guild_onboarding.go b/discord/guild_onboarding.go new file mode 100644 index 000000000..26e2b458c --- /dev/null +++ b/discord/guild_onboarding.go @@ -0,0 +1,37 @@ +package discord + +import "github.com/disgoorg/snowflake/v2" + +type GuildOnboarding struct { + GuildID snowflake.ID `json:"guild_id"` + Prompts []GuildOnboardingPrompt `json:"prompts"` + DefaultChannelIDs []snowflake.ID `json:"default_channel_ids"` + Enabled bool `json:"enabled"` +} + +type GuildOnboardingPrompt struct { + ID snowflake.ID `json:"id"` + Options []GuildOnboardingPromptOption `json:"options"` + Title string `json:"title"` + SingleSelect bool `json:"single_select"` + Required bool `json:"required"` + InOnboarding bool `json:"in_onboarding"` + Type GuildOnboardingPromptType `json:"type"` +} + +type GuildOnboardingPromptOption struct { + ID snowflake.ID `json:"id"` + ChannelIDs []snowflake.ID `json:"channel_ids"` + RoleIDs []snowflake.ID `json:"role_ids"` + EmojiID *snowflake.ID `json:"emoji_id"` + EmojiName *string `json:"emoji_name"` + Title string `json:"title"` + Description *string `json:"description"` +} + +type GuildOnboardingPromptType int + +const ( + GuildOnboardingPromptTypeMultipleChoice GuildOnboardingPromptType = iota + GuildOnboardingPromptTypeDropdown +) diff --git a/rest/guilds.go b/rest/guilds.go index 5ec314557..7a086e2de 100644 --- a/rest/guilds.go +++ b/rest/guilds.go @@ -55,6 +55,8 @@ type Guilds interface { GetGuildWelcomeScreen(guildID snowflake.ID, opts ...RequestOpt) (*discord.GuildWelcomeScreen, error) UpdateGuildWelcomeScreen(guildID snowflake.ID, screenUpdate discord.GuildWelcomeScreenUpdate, opts ...RequestOpt) (*discord.GuildWelcomeScreen, error) + + GetGuildOnboarding(guildID snowflake.ID, opts ...RequestOpt) (*discord.GuildOnboarding, error) } type guildImpl struct { @@ -290,3 +292,8 @@ func (s *guildImpl) UpdateGuildWelcomeScreen(guildID snowflake.ID, screenUpdate err = s.client.Do(UpdateGuildWelcomeScreen.Compile(nil, guildID), screenUpdate, &welcomeScreen, opts...) return } + +func (s *guildImpl) GetGuildOnboarding(guildID snowflake.ID, opts ...RequestOpt) (onboarding *discord.GuildOnboarding, err error) { + err = s.client.Do(GetGuildOnboarding.Compile(nil, guildID), nil, &onboarding, opts...) + return +} diff --git a/rest/rest_endpoints.go b/rest/rest_endpoints.go index 1e9b5c3d9..7160b62cc 100644 --- a/rest/rest_endpoints.go +++ b/rest/rest_endpoints.go @@ -89,6 +89,8 @@ var ( GetGuildWelcomeScreen = NewEndpoint(http.MethodGet, "/guilds/{guild.id}/welcome-screen") UpdateGuildWelcomeScreen = NewEndpoint(http.MethodPatch, "/guilds/{guild.id}/welcome-screen") + GetGuildOnboarding = NewEndpoint(http.MethodGet, "/guilds/{guild.id}/onboarding") + UpdateCurrentUserVoiceState = NewEndpoint(http.MethodPatch, "/guilds/{guild.id}/voice-states/@me") UpdateUserVoiceState = NewEndpoint(http.MethodPatch, "/guilds/{guild.id}/voice-states/{user.id}") ) From 6125af7621d7e451b6ed23b4b170ac3f480a6d50 Mon Sep 17 00:00:00 2001 From: cane Date: Sat, 1 Apr 2023 14:58:47 +0200 Subject: [PATCH 040/119] Refactor permissions (#255) --- discord/permissions.go | 115 +++++++++++++++++++++-------------------- 1 file changed, 59 insertions(+), 56 deletions(-) diff --git a/discord/permissions.go b/discord/permissions.go index 99bcb85ce..980872afc 100644 --- a/discord/permissions.go +++ b/discord/permissions.go @@ -15,9 +15,19 @@ var EmptyStringBytes = []byte(`""`) // Permissions extends the Bit structure, and is used within roles and channels (https://discord.com/developers/docs/topics/permissions#permissions) type Permissions int64 -// Constants for the different bit offsets of text channel permissions const ( - PermissionSendMessages Permissions = 1 << (iota + 11) + PermissionCreateInstantInvite Permissions = 1 << iota + PermissionKickMembers + PermissionBanMembers + PermissionAdministrator + PermissionManageChannels + PermissionManageGuild + PermissionAddReactions + PermissionViewAuditLog + PermissionPrioritySpeaker + PermissionStream + PermissionViewChannel + PermissionSendMessages PermissionSendTTSMessages PermissionManageMessages PermissionEmbedLinks @@ -25,22 +35,14 @@ const ( PermissionReadMessageHistory PermissionMentionEveryone PermissionUseExternalEmojis -) - -// Constants for the different bit offsets of voice permissions -const ( - PermissionVoiceConnect Permissions = 1 << (iota + 20) - PermissionVoiceSpeak - PermissionVoiceMuteMembers - PermissionVoiceDeafenMembers - PermissionVoiceMoveMembers - PermissionVoiceUseVAD - PermissionVoicePrioritySpeaker Permissions = 1 << (iota + 2) -) - -// Constants for general management. -const ( - PermissionChangeNickname Permissions = 1 << (iota + 26) + PermissionViewGuildInsights + PermissionConnect + PermissionSpeak + PermissionMuteMembers + PermissionDeafenMembers + PermissionMoveMembers + PermissionUseVAD + PermissionChangeNickname PermissionManageNicknames PermissionManageRoles PermissionManageWebhooks @@ -57,19 +59,9 @@ const ( PermissionModerateMembers PermissionViewCreatorMonetizationAnalytics PermissionUseSoundboard -) - -// Constants for the different bit offsets of general permissions -const ( - PermissionCreateInstantInvite Permissions = 1 << iota - PermissionKickMembers - PermissionBanMembers - PermissionAdministrator - PermissionManageChannels - PermissionManageServer - PermissionAddReactions - PermissionViewAuditLogs - PermissionViewChannel Permissions = 1 << (iota + 2) + _ + _ + PermissionUseExternalSounds PermissionsAllText = PermissionViewChannel | PermissionSendMessages | @@ -86,36 +78,44 @@ const ( PermissionSendMessagesInThreads PermissionsAllVoice = PermissionViewChannel | - PermissionVoiceConnect | - PermissionVoiceSpeak | - PermissionVoiceMuteMembers | - PermissionVoiceDeafenMembers | - PermissionVoiceMoveMembers | - PermissionVoiceUseVAD | - PermissionVoicePrioritySpeaker | - PermissionUseSoundboard + PermissionConnect | + PermissionSpeak | + PermissionStream | + PermissionMuteMembers | + PermissionDeafenMembers | + PermissionMoveMembers | + PermissionUseVAD | + PermissionPrioritySpeaker | + PermissionUseSoundboard | + PermissionUseExternalSounds | + PermissionRequestToSpeak | + PermissionUseEmbeddedActivities PermissionsAllChannel = PermissionsAllText | PermissionsAllThread | PermissionsAllVoice | PermissionCreateInstantInvite | - PermissionManageRoles | PermissionManageChannels | PermissionAddReactions | - PermissionViewAuditLogs + PermissionUseExternalEmojis | + PermissionUseApplicationCommands | + PermissionUseExternalStickers PermissionsAll = PermissionsAllChannel | PermissionKickMembers | PermissionBanMembers | - PermissionManageServer | + PermissionManageGuild | PermissionAdministrator | PermissionManageWebhooks | PermissionManageGuildExpressions | - PermissionViewCreatorMonetizationAnalytics - - PermissionsStageModerator = PermissionManageChannels | - PermissionVoiceMuteMembers | - PermissionVoiceMoveMembers + PermissionViewCreatorMonetizationAnalytics | + PermissionViewGuildInsights | + PermissionViewAuditLog | + PermissionManageRoles | + PermissionChangeNickname | + PermissionManageNicknames | + PermissionManageEvents | + PermissionModerateMembers PermissionsNone Permissions = 0 ) @@ -126,9 +126,9 @@ var permissions = map[Permissions]string{ PermissionBanMembers: "Ban Members", PermissionAdministrator: "Administrator", PermissionManageChannels: "Manage Channels", - PermissionManageServer: "Manage Server", + PermissionManageGuild: "Manage Server", PermissionAddReactions: "Add Reactions", - PermissionViewAuditLogs: "View Audit Logs", + PermissionViewAuditLog: "View Audit Logs", PermissionViewChannel: "View Channel", PermissionSendMessages: "Send Messages", PermissionSendTTSMessages: "Send TTS Messages", @@ -138,13 +138,13 @@ var permissions = map[Permissions]string{ PermissionReadMessageHistory: "Read Message History", PermissionMentionEveryone: "Mention Everyone", PermissionUseExternalEmojis: "Use External Emojis", - PermissionVoiceConnect: "Connect", - PermissionVoiceSpeak: "Speak", - PermissionVoiceMuteMembers: "Mute Members", - PermissionVoiceDeafenMembers: "Deafen Members", - PermissionVoiceMoveMembers: "Move Members", - PermissionVoiceUseVAD: "Use Voice Activity", - PermissionVoicePrioritySpeaker: "Priority Speaker", + PermissionConnect: "Connect", + PermissionSpeak: "Speak", + PermissionMuteMembers: "Mute Members", + PermissionDeafenMembers: "Deafen Members", + PermissionMoveMembers: "Move Members", + PermissionUseVAD: "Use Voice Activity", + PermissionPrioritySpeaker: "Priority Speaker", PermissionChangeNickname: "Change Nickname", PermissionManageNicknames: "Manage Nicknames", PermissionManageRoles: "Manage Roles", @@ -162,6 +162,9 @@ var permissions = map[Permissions]string{ PermissionModerateMembers: "Moderate Members", PermissionViewCreatorMonetizationAnalytics: "View Creator Monetization Analytics", PermissionUseSoundboard: "Use Soundboard", + PermissionUseExternalSounds: "Use External Sounds", + PermissionStream: "Video", + PermissionViewGuildInsights: "View Server Insights", } func (p Permissions) String() string { From e3e7a65426f678d8c23f882a2adea5d4b460bc32 Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Sat, 1 Apr 2023 15:21:23 +0200 Subject: [PATCH 041/119] document shard manager & gateway config fields --- gateway/gateway_config.go | 59 ++++++++++++++++++++++---------- sharding/shard_manager_config.go | 27 ++++++++++----- 2 files changed, 58 insertions(+), 28 deletions(-) diff --git a/gateway/gateway_config.go b/gateway/gateway_config.go index bc0b8ec84..bb02be4d1 100644 --- a/gateway/gateway_config.go +++ b/gateway/gateway_config.go @@ -23,26 +23,47 @@ func DefaultConfig() *Config { // Config lets you configure your Gateway instance. type Config struct { - Logger log.Logger - Dialer *websocket.Dialer - LargeThreshold int - Intents Intents - Compress bool - URL string - ShardID int - ShardCount int - SessionID *string - ResumeURL *string - LastSequenceReceived *int - AutoReconnect bool - EnableRawEvents bool - EnableResumeURL bool - RateLimiter RateLimiter + // Logger is the logger of the Gateway. Defaults to log.Default(). + Logger log.Logger + // Dialer is the websocket.Dialer of the Gateway. Defaults to websocket.DefaultDialer. + Dialer *websocket.Dialer + // LargeThreshold is the threshold for the Gateway. Defaults to 50 + // See here for more information: https://discord.com/developers/docs/topics/gateway-events#identify-identify-structure. + LargeThreshold int + // Intents is the Intents for the Gateway. Defaults to IntentsNone. + Intents Intents + // Compress is whether the Gateway should compress payloads. Defaults to true. + Compress bool + // URL is the URL of the Gateway. Defaults to fetch from Discord. + URL string + // ShardID is the shardID of the Gateway. Defaults to 0. + ShardID int + // ShardCount is the shardCount of the Gateway. Defaults to 1. + ShardCount int + // SessionID is the last sessionID of the Gateway. Defaults to nil (no resume). + SessionID *string + // ResumeURL is the last resumeURL of the Gateway. Defaults to nil (no resume). + ResumeURL *string + // LastSequenceReceived is the last sequence received by the Gateway. Defaults to nil (no resume). + LastSequenceReceived *int + // AutoReconnect is whether the Gateway should automatically reconnect or call the CloseHandlerFunc. Defaults to true. + AutoReconnect bool + // EnableRawEvents is whether the Gateway should emit EventRaw. Defaults to false. + EnableRawEvents bool + // EnableResumeURL is whether the Gateway should enable the resumeURL. Defaults to true. + EnableResumeURL bool + // RateLimiter is the RateLimiter of the Gateway. Defaults to NewRateLimiter(). + RateLimiter RateLimiter + // RateRateLimiterConfigOpts is the RateLimiterConfigOpts of the Gateway. Defaults to nil. RateRateLimiterConfigOpts []RateLimiterConfigOpt - Presence *MessageDataPresenceUpdate - OS string - Browser string - Device string + // Presence is the presence it should send on login. Defaults to nil. + Presence *MessageDataPresenceUpdate + // OS is the OS it should send on login. Defaults to runtime.GOOS. + OS string + // Browser is the Browser it should send on login. Defaults to "disgo". + Browser string + // Device is the Device it should send on login. Defaults to "disgo". + Device string } // ConfigOpt is a type alias for a function that takes a Config and is used to configure your Server. diff --git a/sharding/shard_manager_config.go b/sharding/shard_manager_config.go index 08ffe19fe..bb2ecb3d4 100644 --- a/sharding/shard_manager_config.go +++ b/sharding/shard_manager_config.go @@ -17,14 +17,23 @@ func DefaultConfig() *Config { // Config lets you configure your ShardManager instance. type Config struct { - Logger log.Logger - ShardIDs map[int]struct{} - ShardCount int - ShardSplitCount int - AutoScaling bool - GatewayCreateFunc gateway.CreateFunc - GatewayConfigOpts []gateway.ConfigOpt - RateLimiter RateLimiter + // Logger is the logger of the ShardManager. Defaults to log.Default() + Logger log.Logger + // ShardIDs is a map of shardIDs the ShardManager should manage. Leave this nil to manage all shards. + ShardIDs map[int]struct{} + // ShardCount is the total shard count of the ShardManager. Leave this at 0 to let Discord calculate the shard count for you. + ShardCount int + // ShardSplitCount is the count a shard should be split into if it is too large. This is only used if AutoScaling is enabled. + ShardSplitCount int + // AutoScaling will automatically re-shard shards if they are too large. This is disabled by default. + AutoScaling bool + // GatewayCreateFunc is the function which is used by the ShardManager to create a new gateway.Gateway. Defaults to gateway.New. + GatewayCreateFunc gateway.CreateFunc + // GatewayConfigOpts are the ConfigOpt(s) which are applied to the gateway.Gateway. + GatewayConfigOpts []gateway.ConfigOpt + // RateLimiter is the RateLimiter which is used by the ShardManager. Defaults to NewRateLimiter() + RateLimiter RateLimiter + // RateRateLimiterConfigOpts are the RateLimiterConfigOpt(s) which are applied to the RateLimiter. RateRateLimiterConfigOpts []RateLimiterConfigOpt } @@ -75,7 +84,7 @@ func WithShardSplitCount(shardSplitCount int) ConfigOpt { } } -// WithAutoScaling sets whether the ShardManager should automatically re-shard shards if they are too large. +// WithAutoScaling sets whether the ShardManager should automatically re-shard shards if they are too large. This is disabled by default. func WithAutoScaling(autoScaling bool) ConfigOpt { return func(config *Config) { config.AutoScaling = autoScaling From 46f5a9132f6adfb3b26d61ad108339af06cc0f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80Senpai?= Date: Sat, 1 Apr 2023 16:19:26 +0200 Subject: [PATCH 042/119] add json error code & message to rest.Error (#257) --- rest/rest_error.go | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/rest/rest_error.go b/rest/rest_error.go index 2bb36b90f..c01e08a79 100644 --- a/rest/rest_error.go +++ b/rest/rest_error.go @@ -3,43 +3,59 @@ package rest import ( "fmt" "net/http" + + "github.com/disgoorg/json" ) +// JSONErrorCode is the error code returned by the Discord API. +// See https://discord.com/developers/docs/topics/opcodes-and-status-codes#json-json-error-codes +type JSONErrorCode int + var _ error = (*Error)(nil) // Error holds the http.Response & an error related to a REST request type Error struct { - Request *http.Request - RqBody []byte - Response *http.Response - RsBody []byte + Request *http.Request `json:"-"` + RqBody []byte `json:"-"` + Response *http.Response `json:"-"` + RsBody []byte `json:"-"` + + Code JSONErrorCode `json:"code"` + Errors json.RawMessage `json:"errors"` + Message string `json:"message"` } // NewError returns a new Error with the given http.Request, http.Response func NewError(rq *http.Request, rqBody []byte, rs *http.Response, rsBody []byte) error { - return &Error{ - Request: rq, - RqBody: rqBody, - Response: rs, - RsBody: rsBody, - } + var err Error + _ = json.Unmarshal(rsBody, &err) + + err.Request = rq + err.RqBody = rqBody + err.Response = rs + err.RsBody = rsBody + + return err } -// Is returns true if the error is a discord.APIError 6 has the same StatusCode +// Is returns true if the error is a *Error with the same status code as the target error func (e Error) Is(target error) bool { err, ok := target.(*Error) if !ok { return false } + if e.Code != 0 && err.Code != 0 { + return e.Code == err.Code + } return err.Response != nil && e.Response != nil && err.Response.StatusCode == e.Response.StatusCode } // Error returns the error formatted as string func (e Error) Error() string { - if e.Response != nil { - return fmt.Sprintf("Status: %s, Body: %s", e.Response.Status, string(e.RsBody)) + if e.Code != 0 { + return fmt.Sprintf("%d: %s", e.Code, e.Message) } - return "unknown error" + return fmt.Sprintf("Status: %s, Body: %s", e.Response.Status, string(e.RsBody)) } // Error returns the error formatted as string From 94621e3ddb62bfd6d939166095ebd34e7ec0a7c3 Mon Sep 17 00:00:00 2001 From: cane Date: Sat, 1 Apr 2023 19:36:07 +0200 Subject: [PATCH 043/119] Introduce PartialEmoji (#259) --- discord/emoji.go | 8 +++---- discord/guild_onboarding.go | 3 +-- events/dm_message_reaction_events.go | 4 ++-- events/guild_message_reaction_events.go | 6 ++--- events/message_reaction_events.go | 4 ++-- gateway/gateway_events.go | 30 ++++++++++++------------- 6 files changed, 27 insertions(+), 28 deletions(-) diff --git a/discord/emoji.go b/discord/emoji.go index 6af9bf495..077a3d113 100644 --- a/discord/emoji.go +++ b/discord/emoji.go @@ -56,8 +56,8 @@ type EmojiUpdate struct { Roles *[]snowflake.ID `json:"roles,omitempty"` } -type ReactionEmoji struct { - ID snowflake.ID `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Animated bool `json:"animated"` +type PartialEmoji struct { + ID *snowflake.ID `json:"id"` + Name *string `json:"name"` + Animated bool `json:"animated"` } diff --git a/discord/guild_onboarding.go b/discord/guild_onboarding.go index 26e2b458c..6c06e2299 100644 --- a/discord/guild_onboarding.go +++ b/discord/guild_onboarding.go @@ -23,8 +23,7 @@ type GuildOnboardingPromptOption struct { ID snowflake.ID `json:"id"` ChannelIDs []snowflake.ID `json:"channel_ids"` RoleIDs []snowflake.ID `json:"role_ids"` - EmojiID *snowflake.ID `json:"emoji_id"` - EmojiName *string `json:"emoji_name"` + Emoji PartialEmoji `json:"emoji"` Title string `json:"title"` Description *string `json:"description"` } diff --git a/events/dm_message_reaction_events.go b/events/dm_message_reaction_events.go index 8bb2046b3..a7540fcfd 100644 --- a/events/dm_message_reaction_events.go +++ b/events/dm_message_reaction_events.go @@ -12,7 +12,7 @@ type GenericDMMessageReaction struct { UserID snowflake.ID ChannelID snowflake.ID MessageID snowflake.ID - Emoji discord.ReactionEmoji + Emoji discord.PartialEmoji } // DMMessageReactionAdd indicates that a discord.User added a discord.MessageReaction to a discord.Message in a Channel (requires the gateway.IntentDirectMessageReactions) @@ -30,7 +30,7 @@ type DMMessageReactionRemoveEmoji struct { *GenericEvent ChannelID snowflake.ID MessageID snowflake.ID - Emoji discord.ReactionEmoji + Emoji discord.PartialEmoji } // DMMessageReactionRemoveAll indicates someone removed all discord.MessageReaction(s) from a discord.Message in a Channel (requires the gateway.IntentDirectMessageReactions) diff --git a/events/guild_message_reaction_events.go b/events/guild_message_reaction_events.go index 0e3b31e3e..d3ed49d72 100644 --- a/events/guild_message_reaction_events.go +++ b/events/guild_message_reaction_events.go @@ -13,7 +13,7 @@ type GenericGuildMessageReaction struct { ChannelID snowflake.ID MessageID snowflake.ID GuildID snowflake.ID - Emoji discord.ReactionEmoji + Emoji discord.PartialEmoji } // Member returns the Member that reacted to the discord.Message from the cache. @@ -21,7 +21,7 @@ func (e *GenericGuildMessageReaction) Member() (discord.Member, bool) { return e.Client().Caches().Member(e.GuildID, e.UserID) } -// GuildMessageReactionAdd indicates that a discord.Member added a discord.ReactionEmoji to a discord.Message in a discord.GuildMessageChannel(requires the gateway.IntentGuildMessageReactions) +// GuildMessageReactionAdd indicates that a discord.Member added a discord.PartialEmoji to a discord.Message in a discord.GuildMessageChannel(requires the gateway.IntentGuildMessageReactions) type GuildMessageReactionAdd struct { *GenericGuildMessageReaction Member discord.Member @@ -38,7 +38,7 @@ type GuildMessageReactionRemoveEmoji struct { ChannelID snowflake.ID MessageID snowflake.ID GuildID snowflake.ID - Emoji discord.ReactionEmoji + Emoji discord.PartialEmoji } // GuildMessageReactionRemoveAll indicates someone removed all discord.MessageReaction(s) from a discord.Message in a Channel (requires the gateway.IntentGuildMessageReactions) diff --git a/events/message_reaction_events.go b/events/message_reaction_events.go index 28f231a55..583e7243d 100644 --- a/events/message_reaction_events.go +++ b/events/message_reaction_events.go @@ -13,7 +13,7 @@ type GenericReaction struct { ChannelID snowflake.ID MessageID snowflake.ID GuildID *snowflake.ID - Emoji discord.ReactionEmoji + Emoji discord.PartialEmoji } // MessageReactionAdd indicates that a discord.User added a discord.MessageReaction to a discord.Message in a discord.Channel(this+++ requires the gateway.IntentGuildMessageReactions and/or gateway.IntentDirectMessageReactions) @@ -33,7 +33,7 @@ type MessageReactionRemoveEmoji struct { ChannelID snowflake.ID MessageID snowflake.ID GuildID *snowflake.ID - Emoji discord.ReactionEmoji + Emoji discord.PartialEmoji } // MessageReactionRemoveAll indicates someone removed all discord.MessageReaction(s) from a discord.Message in a discord.Channel(requires the gateway.IntentGuildMessageReactions and/or gateway.IntentDirectMessageReactions) diff --git a/gateway/gateway_events.go b/gateway/gateway_events.go index c2905de32..3793f8050 100644 --- a/gateway/gateway_events.go +++ b/gateway/gateway_events.go @@ -187,12 +187,12 @@ func (EventGuildAuditLogEntryCreate) messageData() {} func (EventGuildAuditLogEntryCreate) eventData() {} type EventMessageReactionAdd struct { - UserID snowflake.ID `json:"user_id"` - ChannelID snowflake.ID `json:"channel_id"` - MessageID snowflake.ID `json:"message_id"` - GuildID *snowflake.ID `json:"guild_id"` - Member *discord.Member `json:"member"` - Emoji discord.ReactionEmoji `json:"emoji"` + UserID snowflake.ID `json:"user_id"` + ChannelID snowflake.ID `json:"channel_id"` + MessageID snowflake.ID `json:"message_id"` + GuildID *snowflake.ID `json:"guild_id"` + Member *discord.Member `json:"member"` + Emoji discord.PartialEmoji `json:"emoji"` } func (e *EventMessageReactionAdd) UnmarshalJSON(data []byte) error { @@ -212,21 +212,21 @@ func (EventMessageReactionAdd) messageData() {} func (EventMessageReactionAdd) eventData() {} type EventMessageReactionRemove struct { - UserID snowflake.ID `json:"user_id"` - ChannelID snowflake.ID `json:"channel_id"` - MessageID snowflake.ID `json:"message_id"` - GuildID *snowflake.ID `json:"guild_id"` - Emoji discord.ReactionEmoji `json:"emoji"` + UserID snowflake.ID `json:"user_id"` + ChannelID snowflake.ID `json:"channel_id"` + MessageID snowflake.ID `json:"message_id"` + GuildID *snowflake.ID `json:"guild_id"` + Emoji discord.PartialEmoji `json:"emoji"` } func (EventMessageReactionRemove) messageData() {} func (EventMessageReactionRemove) eventData() {} type EventMessageReactionRemoveEmoji struct { - ChannelID snowflake.ID `json:"channel_id"` - MessageID snowflake.ID `json:"message_id"` - GuildID *snowflake.ID `json:"guild_id"` - Emoji discord.ReactionEmoji `json:"emoji"` + ChannelID snowflake.ID `json:"channel_id"` + MessageID snowflake.ID `json:"message_id"` + GuildID *snowflake.ID `json:"guild_id"` + Emoji discord.PartialEmoji `json:"emoji"` } func (EventMessageReactionRemoveEmoji) messageData() {} From f689ccb52370dd4b52c85c27586e7152bde2f0a1 Mon Sep 17 00:00:00 2001 From: Ian Carroll Date: Tue, 4 Apr 2023 00:18:18 -0700 Subject: [PATCH 044/119] Pass through logger from config. (#260) --- oauth2/state_controller.go | 1 + 1 file changed, 1 insertion(+) diff --git a/oauth2/state_controller.go b/oauth2/state_controller.go index 643f22dd2..f239dca9f 100644 --- a/oauth2/state_controller.go +++ b/oauth2/state_controller.go @@ -28,6 +28,7 @@ func NewStateController(opts ...StateControllerConfigOpt) StateController { return &stateControllerImpl{ states: states, newStateFunc: config.NewStateFunc, + logger: config.Logger, } } From a3e65bd6955b281fbd28833edaf59db76d89110a Mon Sep 17 00:00:00 2001 From: cane Date: Thu, 6 Apr 2023 23:52:34 +0200 Subject: [PATCH 045/119] Add Channel to interactions (#261) --- discord/channel.go | 5 +++++ discord/interaction.go | 15 +++++++++------ discord/interaction_application_command.go | 2 ++ discord/interaction_autocomplete.go | 2 ++ discord/interaction_base.go | 6 ++++++ discord/interaction_component.go | 2 ++ discord/interaction_modal_submit.go | 2 ++ discord/interaction_ping.go | 4 ++++ events/interaction_events.go | 8 ++++---- 9 files changed, 36 insertions(+), 10 deletions(-) diff --git a/discord/channel.go b/discord/channel.go index fe8e54a9d..30f195f0b 100644 --- a/discord/channel.go +++ b/discord/channel.go @@ -1145,6 +1145,11 @@ type FollowChannel struct { ChannelID snowflake.ID `json:"webhook_channel_id"` } +type PartialChannel struct { + ID snowflake.ID `json:"id"` + Type ChannelType `json:"type"` +} + // VideoQualityMode https://com/developers/docs/resources/channel#channel-object-video-quality-modes type VideoQualityMode int diff --git a/discord/interaction.go b/discord/interaction.go index 61d969a98..e6835d6a0 100644 --- a/discord/interaction.go +++ b/discord/interaction.go @@ -21,13 +21,15 @@ const ( ) type rawInteraction struct { - ID snowflake.ID `json:"id"` - Type InteractionType `json:"type"` - ApplicationID snowflake.ID `json:"application_id"` - Token string `json:"token"` - Version int `json:"version"` - GuildID *snowflake.ID `json:"guild_id,omitempty"` + ID snowflake.ID `json:"id"` + Type InteractionType `json:"type"` + ApplicationID snowflake.ID `json:"application_id"` + Token string `json:"token"` + Version int `json:"version"` + GuildID *snowflake.ID `json:"guild_id,omitempty"` + // Deprecated: Use Channel instead ChannelID snowflake.ID `json:"channel_id,omitempty"` + Channel *PartialChannel `json:"channel,omitempty"` Locale Locale `json:"locale,omitempty"` GuildLocale *Locale `json:"guild_locale,omitempty"` Member *ResolvedMember `json:"member,omitempty"` @@ -44,6 +46,7 @@ type Interaction interface { Version() int GuildID() *snowflake.ID ChannelID() snowflake.ID + Channel() *PartialChannel Locale() Locale GuildLocale() *Locale Member() *ResolvedMember diff --git a/discord/interaction_application_command.go b/discord/interaction_application_command.go index 398312eba..3622d05b4 100644 --- a/discord/interaction_application_command.go +++ b/discord/interaction_application_command.go @@ -72,6 +72,7 @@ func (i *ApplicationCommandInteraction) UnmarshalJSON(data []byte) error { i.baseInteraction.version = interaction.Version i.baseInteraction.guildID = interaction.GuildID i.baseInteraction.channelID = interaction.ChannelID + i.baseInteraction.channel = interaction.Channel i.baseInteraction.locale = interaction.Locale i.baseInteraction.guildLocale = interaction.GuildLocale i.baseInteraction.member = interaction.Member @@ -95,6 +96,7 @@ func (i ApplicationCommandInteraction) MarshalJSON() ([]byte, error) { Version: i.version, GuildID: i.guildID, ChannelID: i.channelID, + Channel: i.channel, Locale: i.locale, GuildLocale: i.guildLocale, Member: i.member, diff --git a/discord/interaction_autocomplete.go b/discord/interaction_autocomplete.go index 64e1624d8..3d52f8729 100644 --- a/discord/interaction_autocomplete.go +++ b/discord/interaction_autocomplete.go @@ -29,6 +29,7 @@ func (i *AutocompleteInteraction) UnmarshalJSON(data []byte) error { i.baseInteraction.version = interaction.Version i.baseInteraction.guildID = interaction.GuildID i.baseInteraction.channelID = interaction.ChannelID + i.baseInteraction.channel = interaction.Channel i.baseInteraction.locale = interaction.Locale i.baseInteraction.guildLocale = interaction.GuildLocale i.baseInteraction.member = interaction.Member @@ -52,6 +53,7 @@ func (i AutocompleteInteraction) MarshalJSON() ([]byte, error) { Version: i.version, GuildID: i.guildID, ChannelID: i.channelID, + Channel: i.channel, Locale: i.locale, GuildLocale: i.guildLocale, Member: i.member, diff --git a/discord/interaction_base.go b/discord/interaction_base.go index 3576803ae..760674783 100644 --- a/discord/interaction_base.go +++ b/discord/interaction_base.go @@ -13,6 +13,7 @@ type baseInteraction struct { version int guildID *snowflake.ID channelID snowflake.ID + channel *PartialChannel locale Locale guildLocale *Locale member *ResolvedMember @@ -35,9 +36,14 @@ func (i baseInteraction) Version() int { func (i baseInteraction) GuildID() *snowflake.ID { return i.guildID } + +// Deprecated: Use Channel() instead func (i baseInteraction) ChannelID() snowflake.ID { return i.channelID } +func (i baseInteraction) Channel() *PartialChannel { + return i.channel +} func (i baseInteraction) Locale() Locale { return i.locale } diff --git a/discord/interaction_component.go b/discord/interaction_component.go index c2ed2396c..12164e83b 100644 --- a/discord/interaction_component.go +++ b/discord/interaction_component.go @@ -82,6 +82,7 @@ func (i *ComponentInteraction) UnmarshalJSON(data []byte) error { i.baseInteraction.version = interaction.Version i.baseInteraction.guildID = interaction.GuildID i.baseInteraction.channelID = interaction.ChannelID + i.baseInteraction.channel = interaction.Channel i.baseInteraction.locale = interaction.Locale i.baseInteraction.guildLocale = interaction.GuildLocale i.baseInteraction.member = interaction.Member @@ -108,6 +109,7 @@ func (i ComponentInteraction) MarshalJSON() ([]byte, error) { Version: i.version, GuildID: i.guildID, ChannelID: i.channelID, + Channel: i.channel, Locale: i.locale, GuildLocale: i.guildLocale, Member: i.member, diff --git a/discord/interaction_modal_submit.go b/discord/interaction_modal_submit.go index dc9b477e8..fe96e274a 100644 --- a/discord/interaction_modal_submit.go +++ b/discord/interaction_modal_submit.go @@ -26,6 +26,7 @@ func (i *ModalSubmitInteraction) UnmarshalJSON(data []byte) error { i.baseInteraction.version = interaction.Version i.baseInteraction.guildID = interaction.GuildID i.baseInteraction.channelID = interaction.ChannelID + i.baseInteraction.channel = interaction.Channel i.baseInteraction.locale = interaction.Locale i.baseInteraction.guildLocale = interaction.GuildLocale i.baseInteraction.member = interaction.Member @@ -49,6 +50,7 @@ func (i ModalSubmitInteraction) MarshalJSON() ([]byte, error) { Version: i.version, GuildID: i.guildID, ChannelID: i.channelID, + Channel: i.channel, Locale: i.locale, GuildLocale: i.guildLocale, Member: i.member, diff --git a/discord/interaction_ping.go b/discord/interaction_ping.go index 0bc76ad94..5e23d0343 100644 --- a/discord/interaction_ping.go +++ b/discord/interaction_ping.go @@ -72,6 +72,10 @@ func (PingInteraction) ChannelID() snowflake.ID { return 0 } +func (PingInteraction) Channel() *PartialChannel { + return nil +} + func (PingInteraction) Locale() Locale { return "" } diff --git a/events/interaction_events.go b/events/interaction_events.go index b873656d9..8dff24499 100644 --- a/events/interaction_events.go +++ b/events/interaction_events.go @@ -51,7 +51,7 @@ func (e *ApplicationCommandInteractionCreate) Guild() (discord.Guild, bool) { // Channel returns the discord.GuildMessageChannel that the interaction happened in. // This only returns cached channels. func (e *ApplicationCommandInteractionCreate) Channel() (discord.GuildMessageChannel, bool) { - return e.Client().Caches().GuildMessageChannel(e.ChannelID()) + return e.Client().Caches().GuildMessageChannel(e.ApplicationCommandInteraction.Channel().ID) } // CreateMessage responds to the interaction with a new message. @@ -93,7 +93,7 @@ func (e *ComponentInteractionCreate) Guild() (discord.Guild, bool) { // Channel returns the discord.GuildMessageChannel that the interaction happened in. // This only returns cached channels. func (e *ComponentInteractionCreate) Channel() (discord.GuildMessageChannel, bool) { - return e.Client().Caches().GuildMessageChannel(e.ChannelID()) + return e.Client().Caches().GuildMessageChannel(e.ComponentInteraction.Channel().ID) } // CreateMessage responds to the interaction with a new message. @@ -145,7 +145,7 @@ func (e *AutocompleteInteractionCreate) Guild() (discord.Guild, bool) { // Channel returns the discord.GuildMessageChannel that the interaction happened in. // This only returns cached channels. func (e *AutocompleteInteractionCreate) Channel() (discord.GuildMessageChannel, bool) { - return e.Client().Caches().GuildMessageChannel(e.ChannelID()) + return e.Client().Caches().GuildMessageChannel(e.AutocompleteInteraction.Channel().ID) } // Result responds to the interaction with a slice of choices. @@ -173,7 +173,7 @@ func (e *ModalSubmitInteractionCreate) Guild() (discord.Guild, bool) { // Channel returns the discord.GuildMessageChannel that the interaction happened in. // This only returns cached channels. func (e *ModalSubmitInteractionCreate) Channel() (discord.GuildMessageChannel, bool) { - return e.Client().Caches().GuildMessageChannel(e.ChannelID()) + return e.Client().Caches().GuildMessageChannel(e.ModalSubmitInteraction.Channel().ID) } // CreateMessage responds to the interaction with a new message. From 250d1c9a92d38e4235ba65873f4dbc278042a865 Mon Sep 17 00:00:00 2001 From: caneleex Date: Sat, 8 Apr 2023 17:49:41 +0200 Subject: [PATCH 046/119] Add OnHeartbeatAck to ListenerAdapter Fixes #263 --- events/listener_adapter.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/events/listener_adapter.go b/events/listener_adapter.go index bc9dd76f4..126d44938 100644 --- a/events/listener_adapter.go +++ b/events/listener_adapter.go @@ -11,6 +11,9 @@ type ListenerAdapter struct { // raw event OnRaw func(event *Raw) + // heartbeat ack event + OnHeartbeatAck func(event *HeartbeatAck) + // GuildApplicationCommandPermissionsUpdate OnGuildApplicationCommandPermissionsUpdate func(event *GuildApplicationCommandPermissionsUpdate) @@ -175,6 +178,11 @@ func (l *ListenerAdapter) OnEvent(event bot.Event) { listener(e) } + case *HeartbeatAck: + if listener := l.OnHeartbeatAck; listener != nil { + listener(e) + } + case *GuildApplicationCommandPermissionsUpdate: if listener := l.OnGuildApplicationCommandPermissionsUpdate; listener != nil { listener(e) From a50f1ef2d3d1d890874be72d634e3adb8c5b99b6 Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Mon, 10 Apr 2023 20:17:34 +0200 Subject: [PATCH 047/119] fix verified roles examples --- _examples/verified_roles/main.go | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/_examples/verified_roles/main.go b/_examples/verified_roles/main.go index 9230a1c15..cd5096b2c 100644 --- a/_examples/verified_roles/main.go +++ b/_examples/verified_roles/main.go @@ -63,8 +63,7 @@ func handleCallback(w http.ResponseWriter, r *http.Request) { state = query.Get("state") ) if code != "" && state != "" { - identifier := randStr(32) - session, err := oAuth2Client.StartSession(code, state, identifier) + session, _, err := oAuth2Client.StartSession(code, state) if err != nil { writeError(w, "error while starting session", err) return @@ -104,11 +103,3 @@ func writeError(w http.ResponseWriter, text string, err error) { w.WriteHeader(http.StatusInternalServerError) _, _ = w.Write([]byte(text + ": " + err.Error())) } - -func randStr(n int) string { - b := make([]rune, n) - for i := range b { - b[i] = letters[rand.Intn(len(letters))] - } - return string(b) -} From 49293510106cd8f5d1b3ba1e40a8f7c59afd4586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= Date: Mon, 10 Apr 2023 20:31:43 +0200 Subject: [PATCH 048/119] Fix interaction events (#264) * fix events.XInteractionCreate not implementing discord.Interaction anymore * mark Interaction.ChannelID as depreacted * cleanup interaction events --- discord/interaction.go | 1 + events/interaction_events.go | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/discord/interaction.go b/discord/interaction.go index e6835d6a0..39def90e7 100644 --- a/discord/interaction.go +++ b/discord/interaction.go @@ -45,6 +45,7 @@ type Interaction interface { Token() string Version() int GuildID() *snowflake.ID + // Deprecated: Use Interaction.Channel instead ChannelID() snowflake.ID Channel() *PartialChannel Locale() Locale diff --git a/events/interaction_events.go b/events/interaction_events.go index 8dff24499..11fb1293b 100644 --- a/events/interaction_events.go +++ b/events/interaction_events.go @@ -25,10 +25,10 @@ func (e *InteractionCreate) Guild() (discord.Guild, bool) { return discord.Guild{}, false } -// Channel returns the discord.GuildMessageChannel that the interaction happened in. +// MessageChannel returns the discord.GuildMessageChannel that the interaction happened in. // This only returns cached channels. -func (e *InteractionCreate) Channel() (discord.GuildMessageChannel, bool) { - return e.Client().Caches().GuildMessageChannel(e.ChannelID()) +func (e *InteractionCreate) MessageChannel() (discord.GuildMessageChannel, bool) { + return e.Client().Caches().GuildMessageChannel(e.Channel().ID) } // ApplicationCommandInteractionCreate is the base struct for all application command interaction create events. @@ -48,10 +48,10 @@ func (e *ApplicationCommandInteractionCreate) Guild() (discord.Guild, bool) { return discord.Guild{}, false } -// Channel returns the discord.GuildMessageChannel that the interaction happened in. +// MessageChannel returns the discord.GuildMessageChannel that the interaction happened in. // This only returns cached channels. -func (e *ApplicationCommandInteractionCreate) Channel() (discord.GuildMessageChannel, bool) { - return e.Client().Caches().GuildMessageChannel(e.ApplicationCommandInteraction.Channel().ID) +func (e *ApplicationCommandInteractionCreate) MessageChannel() (discord.GuildMessageChannel, bool) { + return e.Client().Caches().GuildMessageChannel(e.Channel().ID) } // CreateMessage responds to the interaction with a new message. @@ -90,10 +90,10 @@ func (e *ComponentInteractionCreate) Guild() (discord.Guild, bool) { return discord.Guild{}, false } -// Channel returns the discord.GuildMessageChannel that the interaction happened in. +// MessageChannel returns the discord.GuildMessageChannel that the interaction happened in. // This only returns cached channels. -func (e *ComponentInteractionCreate) Channel() (discord.GuildMessageChannel, bool) { - return e.Client().Caches().GuildMessageChannel(e.ComponentInteraction.Channel().ID) +func (e *ComponentInteractionCreate) MessageChannel() (discord.GuildMessageChannel, bool) { + return e.Client().Caches().GuildMessageChannel(e.Channel().ID) } // CreateMessage responds to the interaction with a new message. @@ -142,10 +142,10 @@ func (e *AutocompleteInteractionCreate) Guild() (discord.Guild, bool) { return discord.Guild{}, false } -// Channel returns the discord.GuildMessageChannel that the interaction happened in. +// MessageChannel returns the discord.GuildMessageChannel that the interaction happened in. // This only returns cached channels. -func (e *AutocompleteInteractionCreate) Channel() (discord.GuildMessageChannel, bool) { - return e.Client().Caches().GuildMessageChannel(e.AutocompleteInteraction.Channel().ID) +func (e *AutocompleteInteractionCreate) MessageChannel() (discord.GuildMessageChannel, bool) { + return e.Client().Caches().GuildMessageChannel(e.Channel().ID) } // Result responds to the interaction with a slice of choices. @@ -170,10 +170,10 @@ func (e *ModalSubmitInteractionCreate) Guild() (discord.Guild, bool) { return discord.Guild{}, false } -// Channel returns the discord.GuildMessageChannel that the interaction happened in. +// MessageChannel returns the discord.GuildMessageChannel that the interaction happened in. // This only returns cached channels. -func (e *ModalSubmitInteractionCreate) Channel() (discord.GuildMessageChannel, bool) { - return e.Client().Caches().GuildMessageChannel(e.ModalSubmitInteraction.Channel().ID) +func (e *ModalSubmitInteractionCreate) MessageChannel() (discord.GuildMessageChannel, bool) { + return e.Client().Caches().GuildMessageChannel(e.Channel().ID) } // CreateMessage responds to the interaction with a new message. From c02850603df493166cd81c7172529be1aa443024 Mon Sep 17 00:00:00 2001 From: cane Date: Wed, 19 Apr 2023 18:15:46 +0200 Subject: [PATCH 049/119] Add voice messages (#265) --- discord/attachment.go | 22 ++++++++++++---------- discord/message.go | 1 + discord/permissions.go | 5 ++++- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/discord/attachment.go b/discord/attachment.go index 770ce6838..a2cf0f96c 100644 --- a/discord/attachment.go +++ b/discord/attachment.go @@ -8,16 +8,18 @@ import ( // Attachment is used for files sent in a Message type Attachment struct { - ID snowflake.ID `json:"id,omitempty"` - Filename string `json:"filename,omitempty"` - Description *string `json:"description,omitempty"` - ContentType *string `json:"content_type,omitempty"` - Size int `json:"size,omitempty"` - URL string `json:"url,omitempty"` - ProxyURL string `json:"proxy_url,omitempty"` - Height *int `json:"height,omitempty"` - Width *int `json:"width,omitempty"` - Ephemeral bool `json:"ephemeral,omitempty"` + ID snowflake.ID `json:"id,omitempty"` + Filename string `json:"filename,omitempty"` + Description *string `json:"description,omitempty"` + ContentType *string `json:"content_type,omitempty"` + Size int `json:"size,omitempty"` + URL string `json:"url,omitempty"` + ProxyURL string `json:"proxy_url,omitempty"` + Height *int `json:"height,omitempty"` + Width *int `json:"width,omitempty"` + Ephemeral bool `json:"ephemeral,omitempty"` + DurationSecs *float64 `json:"duration_secs,omitempty"` + Waveform *string `json:"waveform,omitempty"` } func (a Attachment) CreatedAt() time.Time { diff --git a/discord/message.go b/discord/message.go index 76aeb4d09..3d513a9f2 100644 --- a/discord/message.go +++ b/discord/message.go @@ -412,6 +412,7 @@ const ( _ _ MessageFlagSuppressNotifications + MessageFlagIsVoiceMessage MessageFlagsNone MessageFlags = 0 ) diff --git a/discord/permissions.go b/discord/permissions.go index 980872afc..9ddce95b7 100644 --- a/discord/permissions.go +++ b/discord/permissions.go @@ -62,6 +62,7 @@ const ( _ _ PermissionUseExternalSounds + PermissionSendVoiceMessages PermissionsAllText = PermissionViewChannel | PermissionSendMessages | @@ -89,7 +90,8 @@ const ( PermissionUseSoundboard | PermissionUseExternalSounds | PermissionRequestToSpeak | - PermissionUseEmbeddedActivities + PermissionUseEmbeddedActivities | + PermissionSendVoiceMessages PermissionsAllChannel = PermissionsAllText | PermissionsAllThread | @@ -165,6 +167,7 @@ var permissions = map[Permissions]string{ PermissionUseExternalSounds: "Use External Sounds", PermissionStream: "Video", PermissionViewGuildInsights: "View Server Insights", + PermissionSendVoiceMessages: "Send Voice Messages", } func (p Permissions) String() string { From f7f72a88b75deb31d65720cf10ecd1ca0199ad3e Mon Sep 17 00:00:00 2001 From: caneleex Date: Wed, 19 Apr 2023 18:25:28 +0200 Subject: [PATCH 050/119] move PermissionSendVoiceMessages to PermissionsAllText --- discord/permissions.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/discord/permissions.go b/discord/permissions.go index 9ddce95b7..fe5a826a6 100644 --- a/discord/permissions.go +++ b/discord/permissions.go @@ -71,7 +71,8 @@ const ( PermissionEmbedLinks | PermissionAttachFiles | PermissionReadMessageHistory | - PermissionMentionEveryone + PermissionMentionEveryone | + PermissionSendVoiceMessages PermissionsAllThread = PermissionManageThreads | PermissionCreatePublicThreads | @@ -90,8 +91,7 @@ const ( PermissionUseSoundboard | PermissionUseExternalSounds | PermissionRequestToSpeak | - PermissionUseEmbeddedActivities | - PermissionSendVoiceMessages + PermissionUseEmbeddedActivities PermissionsAllChannel = PermissionsAllText | PermissionsAllThread | From 7086ffd50605afe5bd9571ab1b6e6156302647fb Mon Sep 17 00:00:00 2001 From: caneleex Date: Thu, 20 Apr 2023 19:57:11 +0200 Subject: [PATCH 051/119] rename automod badge to ApplicationFlagAutoModerationRuleCreateBadge --- discord/application.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/application.go b/discord/application.go index 0c37bd210..d34705b77 100644 --- a/discord/application.go +++ b/discord/application.go @@ -163,7 +163,7 @@ func (t TokenType) Apply(token string) string { type ApplicationFlags int const ( - ApplicationFlagApplicationAutoModerationRuleCreateBadge = 1 << (iota + 6) + ApplicationFlagAutoModerationRuleCreateBadge = 1 << (iota + 6) _ _ _ From 38bc148d39aa00b7d4c5b0c4e51d379b55330960 Mon Sep 17 00:00:00 2001 From: Zen <45705890+ZenShibata@users.noreply.github.com> Date: Fri, 21 Apr 2023 14:41:29 +0700 Subject: [PATCH 052/119] fix(caches): properly calculate permissions' bitfield (#268) * fix(caches): properly calculate permissions' bitfield * fix(caches): leave the perms with view & read msg history when timeout --- cache/caches.go | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/cache/caches.go b/cache/caches.go index d31704afb..0ce1feba6 100644 --- a/cache/caches.go +++ b/cache/caches.go @@ -824,43 +824,38 @@ func (c *cachesImpl) MemberPermissionsInChannel(channel discord.GuildChannel, me } var ( - allowRaw discord.Permissions - denyRaw discord.Permissions + allow discord.Permissions + deny discord.Permissions ) + if overwrite, ok := channel.PermissionOverwrites().Role(channel.GuildID()); ok { - allowRaw = overwrite.Allow - denyRaw = overwrite.Deny + permissions |= overwrite.Allow + permissions &= ^overwrite.Deny } - var ( - allowRole discord.Permissions - denyRole discord.Permissions - ) for _, roleID := range member.RoleIDs { if roleID == channel.GuildID() { continue } if overwrite, ok := channel.PermissionOverwrites().Role(roleID); ok { - allowRole = allowRole.Add(overwrite.Allow) - denyRole = denyRole.Add(overwrite.Deny) + allow |= overwrite.Allow + deny |= overwrite.Deny } } - allowRaw = (allowRaw & (denyRole - 1)) | allowRole - denyRaw = (denyRaw & (allowRole - 1)) | denyRole - if overwrite, ok := channel.PermissionOverwrites().Member(member.User.ID); ok { - allowRaw = (allowRaw & (overwrite.Deny - 1)) | overwrite.Allow - denyRaw = (denyRaw & (overwrite.Allow - 1)) | overwrite.Deny + allow |= overwrite.Allow + deny |= overwrite.Deny } - permissions &= denyRaw - 1 - permissions |= allowRaw + permissions &= ^deny + permissions |= allow if member.CommunicationDisabledUntil != nil { permissions &= discord.PermissionViewChannel | discord.PermissionReadMessageHistory } + return permissions } From 57b63ff2ee1bdb37733cec5e9f4afd2cb540dbc6 Mon Sep 17 00:00:00 2001 From: cane Date: Tue, 25 Apr 2023 18:08:12 +0200 Subject: [PATCH 053/119] Add Reaction() to emojis (#266) --- discord/emoji.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/discord/emoji.go b/discord/emoji.go index 077a3d113..d91b59fe8 100644 --- a/discord/emoji.go +++ b/discord/emoji.go @@ -1,6 +1,7 @@ package discord import ( + "fmt" "time" "github.com/disgoorg/snowflake/v2" @@ -21,6 +22,14 @@ type Emoji struct { Available bool `json:"available,omitempty"` } +// Reaction returns a string used for manipulating with reactions. May be empty if the Name is empty +func (e Emoji) Reaction() string { + if e.Name == "" { + return "" + } + return reaction(e.Name, e.ID) +} + // Mention returns the string used to send the Emoji func (e Emoji) Mention() string { if e.Animated { @@ -61,3 +70,18 @@ type PartialEmoji struct { Name *string `json:"name"` Animated bool `json:"animated"` } + +// Reaction returns a string used for manipulating with reactions. May be empty if the Name is nil +func (e PartialEmoji) Reaction() string { + if e.Name == nil { + return "" + } + if e.ID == nil { + return *e.Name + } + return reaction(*e.Name, *e.ID) +} + +func reaction(name string, id snowflake.ID) string { + return fmt.Sprintf("%s:%s", name, id) +} From ccbf79c87ca93e5972ea67f1dee1919fe32f2673 Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Sun, 30 Apr 2023 00:56:19 +0200 Subject: [PATCH 054/119] reset write & read deadline in voice conn open --- voice/udp_conn.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/voice/udp_conn.go b/voice/udp_conn.go index f8a23ca67..eb050111d 100644 --- a/voice/udp_conn.go +++ b/voice/udp_conn.go @@ -164,6 +164,9 @@ func (u *udpConnImpl) Open(ctx context.Context, ip string, port int, ssrc uint32 if err = u.conn.SetWriteDeadline(time.Now().Add(5 * time.Second)); err != nil { return "", 0, fmt.Errorf("failed to set write deadline on UDPConn connection: %w", err) } + defer func() { + _ = u.conn.SetWriteDeadline(time.Time{}) + }() if _, err = u.conn.Write(sb); err != nil { return "", 0, fmt.Errorf("failed to write ssrc to UDPConn connection: %w", err) } @@ -172,6 +175,9 @@ func (u *udpConnImpl) Open(ctx context.Context, ip string, port int, ssrc uint32 if err = u.conn.SetReadDeadline(time.Now().Add(5 * time.Second)); err != nil { return "", 0, fmt.Errorf("failed to set read deadline on UDPConn connection: %w", err) } + defer func() { + _ = u.conn.SetReadDeadline(time.Time{}) + }() if _, err = u.conn.Read(rb); err != nil { return "", 0, fmt.Errorf("failed to read ip discovery from UDPConn connection: %w", err) } From e9b9dd160582e3c11d8a8445b72d1b4e65cacf4a Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Tue, 2 May 2023 16:26:46 +0200 Subject: [PATCH 055/119] fix missing user in resolved member --- discord/interaction_application_command.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/discord/interaction_application_command.go b/discord/interaction_application_command.go index 3622d05b4..31f7f2984 100644 --- a/discord/interaction_application_command.go +++ b/discord/interaction_application_command.go @@ -506,6 +506,22 @@ type SlashCommandResolved struct { Attachments map[snowflake.ID]Attachment `json:"attachments,omitempty"` } +func (r *SlashCommandResolved) UnmarshalJSON(data []byte) error { + type slashCommandResolved SlashCommandResolved + var v slashCommandResolved + if err := json.Unmarshal(data, &v); err != nil { + return err + } + *r = SlashCommandResolved(v) + for id, member := range r.Members { + if user, ok := r.Users[id]; ok { + member.User = user + r.Members[id] = member + } + } + return nil +} + type ContextCommandInteractionData interface { ApplicationCommandInteractionData TargetID() snowflake.ID From 779858bdba23e4bc9f3a2fc3495e83733b4b32f7 Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Tue, 2 May 2023 22:03:30 +0200 Subject: [PATCH 056/119] fix missing user in target member --- discord/interaction_application_command.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/discord/interaction_application_command.go b/discord/interaction_application_command.go index 31f7f2984..03c9f3aa2 100644 --- a/discord/interaction_application_command.go +++ b/discord/interaction_application_command.go @@ -611,6 +611,22 @@ type UserCommandResolved struct { Members map[snowflake.ID]ResolvedMember `json:"members,omitempty"` } +func (r *UserCommandResolved) UnmarshalJSON(data []byte) error { + type userCommandResolved UserCommandResolved + var v userCommandResolved + if err := json.Unmarshal(data, &v); err != nil { + return err + } + *r = UserCommandResolved(v) + for id, member := range r.Members { + if user, ok := r.Users[id]; ok { + member.User = user + r.Members[id] = member + } + } + return nil +} + var ( _ ApplicationCommandInteractionData = (*MessageCommandInteractionData)(nil) _ ContextCommandInteractionData = (*MessageCommandInteractionData)(nil) From 66a0ba4e04d131ad9312882426a85a2a05246a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= Date: Thu, 4 May 2023 11:45:55 +0200 Subject: [PATCH 057/119] fix nil guild id in message delete event --- handlers/message_handler.go | 1 + 1 file changed, 1 insertion(+) diff --git a/handlers/message_handler.go b/handlers/message_handler.go index 90ff894c7..361be6477 100644 --- a/handlers/message_handler.go +++ b/handlers/message_handler.go @@ -132,6 +132,7 @@ func handleMessageDelete(client bot.Client, sequenceNumber int, shardID int, mes MessageID: messageID, Message: message, ChannelID: channelID, + GuildID: guildID, }, }) From 6cda94940a316e24ab69d5b8ebc1b6486ed36deb Mon Sep 17 00:00:00 2001 From: cane Date: Sat, 6 May 2023 14:19:17 +0200 Subject: [PATCH 058/119] Add raid protection (#223) --- discord/auto_moderation.go | 11 ++++++----- discord/guild.go | 3 +++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/discord/auto_moderation.go b/discord/auto_moderation.go index 3f8a4e020..83a2c77d6 100644 --- a/discord/auto_moderation.go +++ b/discord/auto_moderation.go @@ -23,11 +23,12 @@ const ( ) type AutoModerationTriggerMetadata struct { - KeywordFilter []string `json:"keyword_filter"` - RegexPatterns []string `json:"regex_patterns"` - Presets []AutoModerationKeywordPreset `json:"presets"` - AllowList []string `json:"allow_list"` - MentionTotalLimit int `json:"mention_total_limit"` + KeywordFilter []string `json:"keyword_filter"` + RegexPatterns []string `json:"regex_patterns"` + Presets []AutoModerationKeywordPreset `json:"presets"` + AllowList []string `json:"allow_list"` + MentionTotalLimit int `json:"mention_total_limit"` + MentionRaidProtectionEnabled bool `json:"mention_raid_protection_enabled"` } type AutoModerationKeywordPreset int diff --git a/discord/guild.go b/discord/guild.go index ce58ec000..f88f56b8e 100644 --- a/discord/guild.go +++ b/discord/guild.go @@ -112,6 +112,7 @@ const ( GuildFeatureNews GuildFeature = "NEWS" GuildFeaturePartnered GuildFeature = "PARTNERED" GuildFeaturePreviewEnabled GuildFeature = "PREVIEW_ENABLED" + GuildFeatureRaidAlertsDisabled GuildFeature = "RAID_ALERTS_DISABLED" GuildFeatureRoleIcons GuildFeature = "ROLE_ICONS" GuildFeatureTicketedEventsEnabled GuildFeature = "TICKETED_EVENTS_ENABLED" GuildFeatureVanityURL GuildFeature = "VANITY_URL" @@ -157,6 +158,7 @@ type Guild struct { NSFWLevel NSFWLevel `json:"nsfw_level"` BoostProgressBarEnabled bool `json:"premium_progress_bar_enabled"` JoinedAt time.Time `json:"joined_at"` + SafetyAlertsChannelID *snowflake.ID `json:"safety_alerts_channel_id"` // only over GET /guilds/{guild.id} ApproximateMemberCount int `json:"approximate_member_count"` @@ -321,6 +323,7 @@ type GuildUpdate struct { SystemChannelFlags *SystemChannelFlags `json:"system_channel_flags,omitempty"` RulesChannelID *snowflake.ID `json:"rules_channel_id,omitempty"` PublicUpdatesChannelID *snowflake.ID `json:"public_updates_channel_id,omitempty"` + SafetyAlertsChannelID *snowflake.ID `json:"safety_alerts_channel_id,omitempty"` PreferredLocale *string `json:"preferred_locale,omitempty"` Features *[]GuildFeature `json:"features,omitempty"` Description *string `json:"description,omitempty"` From c3f2beb13f99f714e6fd841ec75d33356a78b821 Mon Sep 17 00:00:00 2001 From: caneleex Date: Sat, 6 May 2023 14:24:24 +0200 Subject: [PATCH 059/119] add Stickers to GuildPreview --- discord/guild.go | 1 + 1 file changed, 1 insertion(+) diff --git a/discord/guild.go b/discord/guild.go index f88f56b8e..81ccf4adf 100644 --- a/discord/guild.go +++ b/discord/guild.go @@ -289,6 +289,7 @@ type GuildPreview struct { ApproximateMemberCount *int `json:"approximate_member_count"` ApproximatePresenceCount *int `json:"approximate_presence_count"` Emojis []Emoji `json:"emojis"` + Stickers []Sticker `json:"stickers"` } // GuildCreate is the payload used to create a Guild From 6b794ba092aea42c3fccedd1f0ae339759c1c5c3 Mon Sep 17 00:00:00 2001 From: caneleex Date: Sat, 6 May 2023 14:42:32 +0200 Subject: [PATCH 060/119] rename BoostProgressBarEnabled to PremiumProgressBarEnabled .. to match the API docs --- discord/guild.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/guild.go b/discord/guild.go index 81ccf4adf..5ca15bc52 100644 --- a/discord/guild.go +++ b/discord/guild.go @@ -156,7 +156,7 @@ type Guild struct { MaxStageVideoChannelUsers int `json:"max_stage_video_channel_users"` WelcomeScreen GuildWelcomeScreen `json:"welcome_screen"` NSFWLevel NSFWLevel `json:"nsfw_level"` - BoostProgressBarEnabled bool `json:"premium_progress_bar_enabled"` + PremiumProgressBarEnabled bool `json:"premium_progress_bar_enabled"` JoinedAt time.Time `json:"joined_at"` SafetyAlertsChannelID *snowflake.ID `json:"safety_alerts_channel_id"` @@ -328,7 +328,7 @@ type GuildUpdate struct { PreferredLocale *string `json:"preferred_locale,omitempty"` Features *[]GuildFeature `json:"features,omitempty"` Description *string `json:"description,omitempty"` - BoostProgressBarEnabled *bool `json:"premium_progress_bar_enabled,omitempty"` + PremiumProgressBarEnabled *bool `json:"premium_progress_bar_enabled,omitempty"` } type NSFWLevel int From 554e2695e24a30138bec743edb9e34cf379f34b4 Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Sun, 7 May 2023 22:43:55 +0200 Subject: [PATCH 061/119] add GatewayMessageDataUnknown & don't error on unknown voice gateway message --- voice/gateway_messages.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/voice/gateway_messages.go b/voice/gateway_messages.go index 5d9d552ab..b2b40e9b2 100644 --- a/voice/gateway_messages.go +++ b/voice/gateway_messages.go @@ -1,8 +1,6 @@ package voice import ( - "errors" - "github.com/disgoorg/json" "github.com/disgoorg/snowflake/v2" ) @@ -86,7 +84,9 @@ func (m *GatewayMessage) UnmarshalJSON(data []byte) error { // ignore this opcode default: - err = errors.New("unknown voicegateway event type") + var d GatewayMessageDataUnknown + err = json.Unmarshal(v.D, &d) + messageData = d } if err != nil { return err @@ -208,3 +208,7 @@ type GatewayMessageDataClientDisconnect struct { } func (GatewayMessageDataClientDisconnect) voiceGatewayMessageData() {} + +type GatewayMessageDataUnknown json.RawMessage + +func (GatewayMessageDataUnknown) voiceGatewayMessageData() {} From 3eb45b6149354c8a217a5cf8d8cd485ac8df46b7 Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Thu, 11 May 2023 22:56:58 +0200 Subject: [PATCH 062/119] fix nil pointer on user leaving voice channel and no audioReceiver being setup --- voice/conn.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/voice/conn.go b/voice/conn.go index 5399e2f37..c10e4ec8f 100644 --- a/voice/conn.go +++ b/voice/conn.go @@ -232,7 +232,9 @@ func (c *connImpl) handleMessage(op Opcode, data GatewayMessageData) { break } } - c.audioReceiver.CleanupUser(d.UserID) + if c.audioReceiver != nil { + c.audioReceiver.CleanupUser(d.UserID) + } } if c.config.EventHandlerFunc != nil { c.config.EventHandlerFunc(op, data) From 67ddd7c0069a0d827c20a13755d0a6380eda6e89 Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Sun, 14 May 2023 23:02:32 +0200 Subject: [PATCH 063/119] change Interaction.Channel from PartialChannel to InteractionChannel --- discord/interaction.go | 20 ++++++++++++-------- discord/interaction_base.go | 4 ++-- discord/interaction_ping.go | 4 ++-- events/interaction_events.go | 30 ------------------------------ 4 files changed, 16 insertions(+), 42 deletions(-) diff --git a/discord/interaction.go b/discord/interaction.go index 39def90e7..9fc60f3fb 100644 --- a/discord/interaction.go +++ b/discord/interaction.go @@ -28,13 +28,13 @@ type rawInteraction struct { Version int `json:"version"` GuildID *snowflake.ID `json:"guild_id,omitempty"` // Deprecated: Use Channel instead - ChannelID snowflake.ID `json:"channel_id,omitempty"` - Channel *PartialChannel `json:"channel,omitempty"` - Locale Locale `json:"locale,omitempty"` - GuildLocale *Locale `json:"guild_locale,omitempty"` - Member *ResolvedMember `json:"member,omitempty"` - User *User `json:"user,omitempty"` - AppPermissions *Permissions `json:"app_permissions,omitempty"` + ChannelID snowflake.ID `json:"channel_id,omitempty"` + Channel InteractionChannel `json:"channel,omitempty"` + Locale Locale `json:"locale,omitempty"` + GuildLocale *Locale `json:"guild_locale,omitempty"` + Member *ResolvedMember `json:"member,omitempty"` + User *User `json:"user,omitempty"` + AppPermissions *Permissions `json:"app_permissions,omitempty"` } // Interaction is used for easier unmarshalling of different Interaction(s) @@ -47,7 +47,7 @@ type Interaction interface { GuildID() *snowflake.ID // Deprecated: Use Interaction.Channel instead ChannelID() snowflake.ID - Channel() *PartialChannel + Channel() InteractionChannel Locale() Locale GuildLocale() *Locale Member() *ResolvedMember @@ -121,4 +121,8 @@ type ( ThreadMetadata ThreadMetadata `json:"thread_metadata"` ParentID snowflake.ID `json:"parent_id"` } + InteractionChannel struct { + MessageChannel + Permissions Permissions `json:"permissions"` + } ) diff --git a/discord/interaction_base.go b/discord/interaction_base.go index 760674783..7f5730ab9 100644 --- a/discord/interaction_base.go +++ b/discord/interaction_base.go @@ -13,7 +13,7 @@ type baseInteraction struct { version int guildID *snowflake.ID channelID snowflake.ID - channel *PartialChannel + channel InteractionChannel locale Locale guildLocale *Locale member *ResolvedMember @@ -41,7 +41,7 @@ func (i baseInteraction) GuildID() *snowflake.ID { func (i baseInteraction) ChannelID() snowflake.ID { return i.channelID } -func (i baseInteraction) Channel() *PartialChannel { +func (i baseInteraction) Channel() InteractionChannel { return i.channel } func (i baseInteraction) Locale() Locale { diff --git a/discord/interaction_ping.go b/discord/interaction_ping.go index 5e23d0343..5d5c15717 100644 --- a/discord/interaction_ping.go +++ b/discord/interaction_ping.go @@ -72,8 +72,8 @@ func (PingInteraction) ChannelID() snowflake.ID { return 0 } -func (PingInteraction) Channel() *PartialChannel { - return nil +func (PingInteraction) Channel() InteractionChannel { + return InteractionChannel{} } func (PingInteraction) Locale() Locale { diff --git a/events/interaction_events.go b/events/interaction_events.go index 11fb1293b..b5453a413 100644 --- a/events/interaction_events.go +++ b/events/interaction_events.go @@ -25,12 +25,6 @@ func (e *InteractionCreate) Guild() (discord.Guild, bool) { return discord.Guild{}, false } -// MessageChannel returns the discord.GuildMessageChannel that the interaction happened in. -// This only returns cached channels. -func (e *InteractionCreate) MessageChannel() (discord.GuildMessageChannel, bool) { - return e.Client().Caches().GuildMessageChannel(e.Channel().ID) -} - // ApplicationCommandInteractionCreate is the base struct for all application command interaction create events. type ApplicationCommandInteractionCreate struct { *GenericEvent @@ -48,12 +42,6 @@ func (e *ApplicationCommandInteractionCreate) Guild() (discord.Guild, bool) { return discord.Guild{}, false } -// MessageChannel returns the discord.GuildMessageChannel that the interaction happened in. -// This only returns cached channels. -func (e *ApplicationCommandInteractionCreate) MessageChannel() (discord.GuildMessageChannel, bool) { - return e.Client().Caches().GuildMessageChannel(e.Channel().ID) -} - // CreateMessage responds to the interaction with a new message. func (e *ApplicationCommandInteractionCreate) CreateMessage(messageCreate discord.MessageCreate, opts ...rest.RequestOpt) error { return e.Respond(discord.InteractionResponseTypeCreateMessage, messageCreate, opts...) @@ -90,12 +78,6 @@ func (e *ComponentInteractionCreate) Guild() (discord.Guild, bool) { return discord.Guild{}, false } -// MessageChannel returns the discord.GuildMessageChannel that the interaction happened in. -// This only returns cached channels. -func (e *ComponentInteractionCreate) MessageChannel() (discord.GuildMessageChannel, bool) { - return e.Client().Caches().GuildMessageChannel(e.Channel().ID) -} - // CreateMessage responds to the interaction with a new message. func (e *ComponentInteractionCreate) CreateMessage(messageCreate discord.MessageCreate, opts ...rest.RequestOpt) error { return e.Respond(discord.InteractionResponseTypeCreateMessage, messageCreate, opts...) @@ -142,12 +124,6 @@ func (e *AutocompleteInteractionCreate) Guild() (discord.Guild, bool) { return discord.Guild{}, false } -// MessageChannel returns the discord.GuildMessageChannel that the interaction happened in. -// This only returns cached channels. -func (e *AutocompleteInteractionCreate) MessageChannel() (discord.GuildMessageChannel, bool) { - return e.Client().Caches().GuildMessageChannel(e.Channel().ID) -} - // Result responds to the interaction with a slice of choices. func (e *AutocompleteInteractionCreate) Result(choices []discord.AutocompleteChoice, opts ...rest.RequestOpt) error { return e.Respond(discord.InteractionResponseTypeApplicationCommandAutocompleteResult, discord.AutocompleteResult{Choices: choices}, opts...) @@ -170,12 +146,6 @@ func (e *ModalSubmitInteractionCreate) Guild() (discord.Guild, bool) { return discord.Guild{}, false } -// MessageChannel returns the discord.GuildMessageChannel that the interaction happened in. -// This only returns cached channels. -func (e *ModalSubmitInteractionCreate) MessageChannel() (discord.GuildMessageChannel, bool) { - return e.Client().Caches().GuildMessageChannel(e.Channel().ID) -} - // CreateMessage responds to the interaction with a new message. func (e *ModalSubmitInteractionCreate) CreateMessage(messageCreate discord.MessageCreate, opts ...rest.RequestOpt) error { return e.Respond(discord.InteractionResponseTypeCreateMessage, messageCreate, opts...) From 125d0727da89d080863c11af0e499798e217f99e Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Mon, 15 May 2023 10:30:34 +0200 Subject: [PATCH 064/119] fix unmarshalling & marshalling of InteractionChannel --- discord/interaction.go | 67 +++++++++++++++++++++++++++++++++--------- go.mod | 2 +- go.sum | 4 +-- 3 files changed, 56 insertions(+), 17 deletions(-) diff --git a/discord/interaction.go b/discord/interaction.go index 9fc60f3fb..3c86fc61b 100644 --- a/discord/interaction.go +++ b/discord/interaction.go @@ -108,21 +108,60 @@ func UnmarshalInteraction(data []byte) (Interaction, error) { return interaction, nil } -type ( - ResolvedMember struct { - Member - Permissions Permissions `json:"permissions,omitempty"` +type ResolvedMember struct { + Member + Permissions Permissions `json:"permissions,omitempty"` +} + +type ResolvedChannel struct { + ID snowflake.ID `json:"id"` + Name string `json:"name"` + Type ChannelType `json:"type"` + Permissions Permissions `json:"permissions"` + ThreadMetadata ThreadMetadata `json:"thread_metadata"` + ParentID snowflake.ID `json:"parent_id"` +} + +type InteractionChannel struct { + MessageChannel + Permissions Permissions `json:"permissions"` +} + +func (c *InteractionChannel) UnmarshalJSON(data []byte) error { + var v struct { + Permissions Permissions `json:"permissions"` + } + if err := json.Unmarshal(data, &v); err != nil { + return err + } + var vc UnmarshalChannel + if err := json.Unmarshal(data, &vc); err != nil { + return err } - ResolvedChannel struct { - ID snowflake.ID `json:"id"` - Name string `json:"name"` - Type ChannelType `json:"type"` - Permissions Permissions `json:"permissions"` - ThreadMetadata ThreadMetadata `json:"thread_metadata"` - ParentID snowflake.ID `json:"parent_id"` + msgChannel, ok := vc.Channel.(MessageChannel) + if !ok { + return fmt.Errorf("unknown channel type: %T", vc.Channel) } - InteractionChannel struct { - MessageChannel + c.MessageChannel = msgChannel + c.Permissions = v.Permissions + + return nil +} + +func (c InteractionChannel) MarshalJSON() ([]byte, error) { + mData, err := json.Marshal(c.MessageChannel) + if err != nil { + return nil, err + } + + pData, err := json.Marshal(struct { Permissions Permissions `json:"permissions"` + }{ + Permissions: c.Permissions, + }) + if err != nil { + return nil, err } -) + + return json.Merge(mData, pData) +} diff --git a/go.mod b/go.mod index 8cca4ea20..110fb8a50 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/disgoorg/disgo go 1.18 require ( - github.com/disgoorg/json v1.0.0 + github.com/disgoorg/json v1.1.0 github.com/disgoorg/log v1.2.0 github.com/disgoorg/snowflake/v2 v2.0.1 github.com/gorilla/websocket v1.5.0 diff --git a/go.sum b/go.sum index b018ce554..012743103 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ 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/disgoorg/json v1.0.0 h1:kDhSM661fgIuNoZF3BO5/odaR5NSq80AWb937DH+Pdo= -github.com/disgoorg/json v1.0.0/go.mod h1:BHDwdde0rpQFDVsRLKhma6Y7fTbQKub/zdGO5O9NqqA= +github.com/disgoorg/json v1.1.0 h1:7xigHvomlVA9PQw9bMGO02PHGJJPqvX5AnwlYg/Tnys= +github.com/disgoorg/json v1.1.0/go.mod h1:BHDwdde0rpQFDVsRLKhma6Y7fTbQKub/zdGO5O9NqqA= github.com/disgoorg/log v1.2.0 h1:sqlXnu/ZKAlIlHV9IO+dbMto7/hCQ474vlIdMWk8QKo= github.com/disgoorg/log v1.2.0/go.mod h1:3x1KDG6DI1CE2pDwi3qlwT3wlXpeHW/5rVay+1qDqOo= github.com/disgoorg/snowflake/v2 v2.0.1 h1:CuUxGLwggUxEswZOmZ+mZ5i0xSumQdXW9tXW7uGqe+0= From a783e34ad0c881cc6e807cef21f17f6708faa41b Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Wed, 24 May 2023 21:10:59 +0200 Subject: [PATCH 065/119] move set into cache package --- cache/cache_config.go | 3 +-- cache/caches.go | 7 +++---- {internal/set => cache}/set.go | 13 +++++++++++-- 3 files changed, 15 insertions(+), 8 deletions(-) rename {internal/set => cache}/set.go (65%) diff --git a/cache/cache_config.go b/cache/cache_config.go index a2208d7bb..dc0395452 100644 --- a/cache/cache_config.go +++ b/cache/cache_config.go @@ -4,7 +4,6 @@ import ( "github.com/disgoorg/snowflake/v2" "github.com/disgoorg/disgo/discord" - "github.com/disgoorg/disgo/internal/set" ) // DefaultConfig returns a Config with sensible defaults. @@ -80,7 +79,7 @@ func (c *Config) Apply(opts []ConfigOpt) { c.SelfUserCache = NewSelfUserCache() } if c.GuildCache == nil { - c.GuildCache = NewGuildCache(NewCache[discord.Guild](c.CacheFlags, FlagGuilds, c.GuildCachePolicy), set.New[snowflake.ID](), set.New[snowflake.ID]()) + c.GuildCache = NewGuildCache(NewCache[discord.Guild](c.CacheFlags, FlagGuilds, c.GuildCachePolicy), NewSet[snowflake.ID](), NewSet[snowflake.ID]()) } if c.ChannelCache == nil { c.ChannelCache = NewChannelCache(NewCache[discord.GuildChannel](c.CacheFlags, FlagChannels, c.ChannelCachePolicy)) diff --git a/cache/caches.go b/cache/caches.go index 0ce1feba6..e3f44dd1d 100644 --- a/cache/caches.go +++ b/cache/caches.go @@ -6,7 +6,6 @@ import ( "github.com/disgoorg/snowflake/v2" "github.com/disgoorg/disgo/discord" - "github.com/disgoorg/disgo/internal/set" ) type SelfUserCache interface { @@ -56,7 +55,7 @@ type GuildCache interface { RemoveGuild(guildID snowflake.ID) (discord.Guild, bool) } -func NewGuildCache(cache Cache[discord.Guild], unreadyGuilds set.Set[snowflake.ID], unavailableGuilds set.Set[snowflake.ID]) GuildCache { +func NewGuildCache(cache Cache[discord.Guild], unreadyGuilds Set[snowflake.ID], unavailableGuilds Set[snowflake.ID]) GuildCache { return &guildCacheImpl{ cache: cache, unreadyGuilds: unreadyGuilds, @@ -66,8 +65,8 @@ func NewGuildCache(cache Cache[discord.Guild], unreadyGuilds set.Set[snowflake.I type guildCacheImpl struct { cache Cache[discord.Guild] - unreadyGuilds set.Set[snowflake.ID] - unavailableGuilds set.Set[snowflake.ID] + unreadyGuilds Set[snowflake.ID] + unavailableGuilds Set[snowflake.ID] } func (c *guildCacheImpl) IsGuildUnready(guildID snowflake.ID) bool { diff --git a/internal/set/set.go b/cache/set.go similarity index 65% rename from internal/set/set.go rename to cache/set.go index ed92f0875..c37af70f4 100644 --- a/internal/set/set.go +++ b/cache/set.go @@ -1,21 +1,30 @@ -package set +package cache import "sync" +// Set is a collection of unique items. It should be thread-safe. type Set[T comparable] interface { + // Add adds an item to the set. Add(item T) + // Remove removes an item from the set. Remove(item T) + // Has returns true if the item is in the set, false otherwise. Has(item T) bool + // Len returns the number of items in the set. Len() int + // Clear removes all items from the set. Clear() + // ForEach iterates over the items in the set. ForEach(f func(item T)) } + type set[T comparable] struct { mu sync.RWMutex items map[T]struct{} } -func New[T comparable]() Set[T] { +// NewSet returns a thread-safe in memory implementation of a set. +func NewSet[T comparable]() Set[T] { return &set[T]{ items: make(map[T]struct{}), } From 3defaebde6da3371d7068460130e729a61140bb6 Mon Sep 17 00:00:00 2001 From: caneleex Date: Thu, 25 May 2023 16:46:56 +0200 Subject: [PATCH 066/119] add SyncID to Activity I hate Discord with my whole soul --- discord/activity.go | 1 + 1 file changed, 1 insertion(+) diff --git a/discord/activity.go b/discord/activity.go index effd4085a..028436cbd 100644 --- a/discord/activity.go +++ b/discord/activity.go @@ -30,6 +30,7 @@ type Activity struct { URL *string `json:"url"` CreatedAt time.Time `json:"created_at"` Timestamps *ActivityTimestamps `json:"timestamps,omitempty"` + SyncID *string `json:"sync_id,omitempty"` ApplicationID snowflake.ID `json:"application_id,omitempty"` Details *string `json:"details,omitempty"` State *string `json:"state,omitempty"` From e0e8d5b1eeeb49104c88197ae184c12f4e9af1c8 Mon Sep 17 00:00:00 2001 From: ikafly144 Date: Fri, 2 Jun 2023 01:27:07 +0900 Subject: [PATCH 067/119] fix get webhooks (#272) --- rest/channels.go | 9 ++++++++- rest/guilds.go | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/rest/channels.go b/rest/channels.go index 1887e49cd..1ae7dee40 100644 --- a/rest/channels.go +++ b/rest/channels.go @@ -76,7 +76,14 @@ func (s *channelImpl) DeleteChannel(channelID snowflake.ID, opts ...RequestOpt) } func (s *channelImpl) GetWebhooks(channelID snowflake.ID, opts ...RequestOpt) (webhooks []discord.Webhook, err error) { - err = s.client.Do(GetChannelWebhooks.Compile(nil, channelID), nil, &webhooks, opts...) + var whs []discord.UnmarshalWebhook + err = s.client.Do(GetChannelWebhooks.Compile(nil, channelID), nil, &whs, opts...) + if err == nil { + webhooks = make([]discord.Webhook, len(whs)) + for i := range whs { + webhooks[i] = whs[i].Webhook + } + } return } diff --git a/rest/guilds.go b/rest/guilds.go index 7a086e2de..28585eb4e 100644 --- a/rest/guilds.go +++ b/rest/guilds.go @@ -239,7 +239,14 @@ func (s *guildImpl) BeginGuildPrune(guildID snowflake.ID, guildPrune discord.Gui } func (s *guildImpl) GetAllWebhooks(guildID snowflake.ID, opts ...RequestOpt) (webhooks []discord.Webhook, err error) { - err = s.client.Do(GetGuildWebhooks.Compile(nil, guildID), nil, &webhooks, opts...) + var whs []discord.UnmarshalWebhook + err = s.client.Do(GetGuildWebhooks.Compile(nil, guildID), nil, &whs, opts...) + if err == nil { + webhooks = make([]discord.Webhook, len(whs)) + for i := range whs { + webhooks[i] = whs[i].Webhook + } + } return } From 0b0b6db253d6a7afef106484f08030a2294a5000 Mon Sep 17 00:00:00 2001 From: caneleex Date: Sat, 3 Jun 2023 22:40:07 +0200 Subject: [PATCH 068/119] remove AllowedMentions from NewMessageUpdateBuilder() this caused issues when modifying message flags for other users since disgo passed allowed mentions as well --- discord/message_update_builder.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/discord/message_update_builder.go b/discord/message_update_builder.go index a8f4102d3..e7d0c8bf4 100644 --- a/discord/message_update_builder.go +++ b/discord/message_update_builder.go @@ -14,11 +14,7 @@ type MessageUpdateBuilder struct { // NewMessageUpdateBuilder creates a new MessageUpdateBuilder to be built later func NewMessageUpdateBuilder() *MessageUpdateBuilder { - return &MessageUpdateBuilder{ - MessageUpdate: MessageUpdate{ - AllowedMentions: &DefaultAllowedMentions, - }, - } + return &MessageUpdateBuilder{} } // SetContent sets content of the Message From ab0ddbc19b0c1518ece9446bb2b57787fc3d7af9 Mon Sep 17 00:00:00 2001 From: cane Date: Thu, 8 Jun 2023 14:46:52 +0200 Subject: [PATCH 069/119] Add support for the new username system (#273) --- discord/cdn_endpoints.go | 2 +- discord/member.go | 4 ++-- discord/mentionable.go | 4 ++++ discord/user.go | 16 +++++++++++++++- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/discord/cdn_endpoints.go b/discord/cdn_endpoints.go index 654e17ecb..a5048c96d 100644 --- a/discord/cdn_endpoints.go +++ b/discord/cdn_endpoints.go @@ -18,7 +18,7 @@ var ( UserBanner = NewCDN("/banners/{user.id}/{user.banner.hash}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP, ImageFormatGIF) UserAvatar = NewCDN("/avatars/{user.id}/{user.avatar.hash}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP, ImageFormatGIF) - DefaultUserAvatar = NewCDN("/embed/avatars/{user.discriminator%5}", ImageFormatPNG) + DefaultUserAvatar = NewCDN("/embed/avatars/{index}", ImageFormatPNG) ChannelIcon = NewCDN("/channel-icons/{channel.id}/{channel.icon.hash}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP) diff --git a/discord/member.go b/discord/member.go index 6e4d3c929..5e912a64d 100644 --- a/discord/member.go +++ b/discord/member.go @@ -37,12 +37,12 @@ func (m Member) Mention() string { return m.String() } -// EffectiveName returns either the nickname or username depending on if the user has a nickname +// EffectiveName returns the nickname of the member if set, falling back to User.EffectiveName() func (m Member) EffectiveName() string { if m.Nick != nil { return *m.Nick } - return m.User.Username + return m.User.EffectiveName() } func (m Member) EffectiveAvatarURL(opts ...CDNOpt) string { diff --git a/discord/mentionable.go b/discord/mentionable.go index 37b22279d..bf2e47b0e 100644 --- a/discord/mentionable.go +++ b/discord/mentionable.go @@ -31,7 +31,11 @@ func ChannelMention(id snowflake.ID) string { return fmt.Sprintf("<#%s>", id) } +// UserTag returns a formatted string of "Username#Discriminator", falling back to the username if discriminator is "0" func UserTag(username string, discriminator string) string { + if discriminator == "0" { + return username + } return fmt.Sprintf("%s#%s", username, discriminator) } diff --git a/discord/user.go b/discord/user.go index 52d65a9b3..300802a6a 100644 --- a/discord/user.go +++ b/discord/user.go @@ -68,6 +68,7 @@ type User struct { ID snowflake.ID `json:"id"` Username string `json:"username"` Discriminator string `json:"discriminator"` + GlobalName *string `json:"global_name"` Avatar *string `json:"avatar"` Banner *string `json:"banner"` AccentColor *int `json:"accent_color"` @@ -84,10 +85,19 @@ func (u User) Mention() string { return u.String() } +// Tag returns a formatted string of "Username#Discriminator", falling back to the username if discriminator is "0" func (u User) Tag() string { return UserTag(u.Username, u.Discriminator) } +// EffectiveName returns the global (display) name of the user if set, falling back to the username +func (u User) EffectiveName() string { + if u.GlobalName != nil { + return *u.GlobalName + } + return u.Username +} + func (u User) EffectiveAvatarURL(opts ...CDNOpt) string { if u.Avatar == nil { return u.DefaultAvatarURL(opts...) @@ -111,7 +121,11 @@ func (u User) DefaultAvatarURL(opts ...CDNOpt) string { if err != nil { return "" } - return formatAssetURL(DefaultUserAvatar, opts, discriminator%5) + index := discriminator % 5 + if index == 0 { // new username system + index = int((u.ID >> 22) % 6) + } + return formatAssetURL(DefaultUserAvatar, opts, index) } func (u User) BannerURL(opts ...CDNOpt) *string { From 9b3d573a109163625b177e69243b5972025fd4ad Mon Sep 17 00:00:00 2001 From: caneleex Date: Thu, 8 Jun 2023 14:54:49 +0200 Subject: [PATCH 070/119] add docs to User and Member funcs --- discord/member.go | 4 ++++ discord/user.go | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/discord/member.go b/discord/member.go index 5e912a64d..126661049 100644 --- a/discord/member.go +++ b/discord/member.go @@ -29,10 +29,12 @@ type Member struct { GuildID snowflake.ID `json:"guild_id"` } +// String returns a mention of the user func (m Member) String() string { return m.User.String() } +// Mention returns a mention of the user func (m Member) Mention() string { return m.String() } @@ -45,6 +47,7 @@ func (m Member) EffectiveName() string { return m.User.EffectiveName() } +// EffectiveAvatarURL returns the guild-specific avatar URL of the user if set, falling back to the effective avatar URL of the user func (m Member) EffectiveAvatarURL(opts ...CDNOpt) string { if m.Avatar == nil { return m.User.EffectiveAvatarURL(opts...) @@ -55,6 +58,7 @@ func (m Member) EffectiveAvatarURL(opts ...CDNOpt) string { return "" } +// AvatarURL returns the guild-specific avatar URL of the user if set or nil func (m Member) AvatarURL(opts ...CDNOpt) *string { if m.Avatar == nil { return nil diff --git a/discord/user.go b/discord/user.go index 300802a6a..47e13d6d6 100644 --- a/discord/user.go +++ b/discord/user.go @@ -77,10 +77,12 @@ type User struct { PublicFlags UserFlags `json:"public_flags"` } +// String returns a mention of the user func (u User) String() string { return UserMention(u.ID) } +// Mention returns a mention of the user func (u User) Mention() string { return u.String() } @@ -98,6 +100,7 @@ func (u User) EffectiveName() string { return u.Username } +// EffectiveAvatarURL returns the avatar URL of the user if set, falling back to the default avatar URL func (u User) EffectiveAvatarURL(opts ...CDNOpt) string { if u.Avatar == nil { return u.DefaultAvatarURL(opts...) @@ -108,6 +111,7 @@ func (u User) EffectiveAvatarURL(opts ...CDNOpt) string { return "" } +// AvatarURL returns the avatar URL of the user if set or nil func (u User) AvatarURL(opts ...CDNOpt) *string { if u.Avatar == nil { return nil @@ -116,6 +120,7 @@ func (u User) AvatarURL(opts ...CDNOpt) *string { return &url } +// DefaultAvatarURL calculates and returns the default avatar URL func (u User) DefaultAvatarURL(opts ...CDNOpt) string { discriminator, err := strconv.Atoi(u.Discriminator) if err != nil { @@ -128,6 +133,7 @@ func (u User) DefaultAvatarURL(opts ...CDNOpt) string { return formatAssetURL(DefaultUserAvatar, opts, index) } +// BannerURL returns the banner URL if set or nil func (u User) BannerURL(opts ...CDNOpt) *string { if u.Banner == nil { return nil From 4547c17ccf8040be70ff54b645376bcab37dcd24 Mon Sep 17 00:00:00 2001 From: caneleex Date: Fri, 9 Jun 2023 22:16:26 +0200 Subject: [PATCH 071/119] fix incorrect type for WithChannelTypes --- discord/select_menu.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/discord/select_menu.go b/discord/select_menu.go index 1f84dd96c..e4db9a9d3 100644 --- a/discord/select_menu.go +++ b/discord/select_menu.go @@ -450,12 +450,12 @@ func NewChannelSelectMenu(customID string, placeholder string) ChannelSelectMenu } type ChannelSelectMenuComponent struct { - CustomID string `json:"custom_id"` - Placeholder string `json:"placeholder,omitempty"` - MinValues *int `json:"min_values,omitempty"` - MaxValues int `json:"max_values,omitempty"` - Disabled bool `json:"disabled,omitempty"` - ChannelTypes []ComponentType `json:"channel_types,omitempty"` + CustomID string `json:"custom_id"` + Placeholder string `json:"placeholder,omitempty"` + MinValues *int `json:"min_values,omitempty"` + MaxValues int `json:"max_values,omitempty"` + Disabled bool `json:"disabled,omitempty"` + ChannelTypes []ChannelType `json:"channel_types,omitempty"` } func (c ChannelSelectMenuComponent) MarshalJSON() ([]byte, error) { @@ -524,7 +524,7 @@ func (c ChannelSelectMenuComponent) WithDisabled(disabled bool) ChannelSelectMen } // WithChannelTypes returns a new ChannelSelectMenuComponent with the provided channelTypes -func (c ChannelSelectMenuComponent) WithChannelTypes(channelTypes ...ComponentType) ChannelSelectMenuComponent { +func (c ChannelSelectMenuComponent) WithChannelTypes(channelTypes ...ChannelType) ChannelSelectMenuComponent { c.ChannelTypes = channelTypes return c } From 844ef78b99b392335da94f9f3caceae97a716bab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= Date: Sat, 10 Jun 2023 14:54:22 +0200 Subject: [PATCH 072/119] remove invalid interaction methods from autocomplete --- handler/autocomplete.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/handler/autocomplete.go b/handler/autocomplete.go index d5397deb3..fafb2e78a 100644 --- a/handler/autocomplete.go +++ b/handler/autocomplete.go @@ -13,18 +13,6 @@ type AutocompleteEvent struct { Variables map[string]string } -func (e *AutocompleteEvent) GetInteractionResponse(opts ...rest.RequestOpt) (*discord.Message, error) { - return e.Client().Rest().GetInteractionResponse(e.ApplicationID(), e.Token(), opts...) -} - -func (e *AutocompleteEvent) UpdateInteractionResponse(messageUpdate discord.MessageUpdate, opts ...rest.RequestOpt) (*discord.Message, error) { - return e.Client().Rest().UpdateInteractionResponse(e.ApplicationID(), e.Token(), messageUpdate, opts...) -} - -func (e *AutocompleteEvent) DeleteInteractionResponse(opts ...rest.RequestOpt) error { - return e.Client().Rest().DeleteInteractionResponse(e.ApplicationID(), e.Token(), opts...) -} - func (e *AutocompleteEvent) GetFollowupMessage(messageID snowflake.ID, opts ...rest.RequestOpt) (*discord.Message, error) { return e.Client().Rest().GetFollowupMessage(e.ApplicationID(), e.Token(), messageID, opts...) } From 863ddf334f6dc466033e9aa3237c6fd04f41106f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= Date: Sat, 10 Jun 2023 15:33:45 +0200 Subject: [PATCH 073/119] fix presence event handler (#275) --- discord/activity.go | 11 +- events/listener_adapter.go | 7 ++ events/user_activity_events.go | 6 + events/user_status_events.go | 2 +- handlers/presence_update_handler.go | 170 ++++++++++++++++++++++++++-- 5 files changed, 175 insertions(+), 21 deletions(-) diff --git a/discord/activity.go b/discord/activity.go index 028436cbd..0f4f019bf 100644 --- a/discord/activity.go +++ b/discord/activity.go @@ -34,7 +34,7 @@ type Activity struct { ApplicationID snowflake.ID `json:"application_id,omitempty"` Details *string `json:"details,omitempty"` State *string `json:"state,omitempty"` - Emoji *ActivityEmoji `json:"emoji,omitempty"` + Emoji *PartialEmoji `json:"emoji,omitempty"` Party *ActivityParty `json:"party,omitempty"` Assets *ActivityAssets `json:"assets,omitempty"` Secrets *ActivitySecrets `json:"secrets,omitempty"` @@ -139,17 +139,10 @@ func (a ActivityTimestamps) MarshalJSON() ([]byte, error) { }) } -// ActivityEmoji is an Emoji object for an Activity -type ActivityEmoji struct { - Name string `json:"name"` - ID *snowflake.ID `json:"id,omitempty"` - Animated *bool `json:"animated,omitempty"` -} - // ActivityParty is information about the party of the player type ActivityParty struct { ID string `json:"id,omitempty"` - Size []int `json:"size,omitempty"` + Size [2]int `json:"size,omitempty"` } // ActivityAssets are the images for the presence and hover texts diff --git a/events/listener_adapter.go b/events/listener_adapter.go index 126d44938..ca82951ef 100644 --- a/events/listener_adapter.go +++ b/events/listener_adapter.go @@ -154,6 +154,8 @@ type ListenerAdapter struct { OnGuildMemberTypingStart func(event *GuildMemberTypingStart) OnDMUserTypingStart func(event *DMUserTypingStart) + OnPresenceUpdate func(event *PresenceUpdate) + // User Activity Events OnUserActivityStart func(event *UserActivityStart) OnUserActivityUpdate func(event *UserActivityUpdate) @@ -592,6 +594,11 @@ func (l *ListenerAdapter) OnEvent(event bot.Event) { listener(e) } + case *PresenceUpdate: + if listener := l.OnPresenceUpdate; listener != nil { + listener(e) + } + // User Activity Events case *UserActivityStart: if listener := l.OnUserActivityStart; listener != nil { diff --git a/events/user_activity_events.go b/events/user_activity_events.go index be751c6d9..8a9e62683 100644 --- a/events/user_activity_events.go +++ b/events/user_activity_events.go @@ -1,11 +1,17 @@ package events import ( + "github.com/disgoorg/disgo/gateway" "github.com/disgoorg/snowflake/v2" "github.com/disgoorg/disgo/discord" ) +type PresenceUpdate struct { + *GenericEvent + gateway.EventPresenceUpdate +} + // GenericUserActivity generic Activity event type GenericUserActivity struct { *GenericEvent diff --git a/events/user_status_events.go b/events/user_status_events.go index 99667722b..f470db3c0 100644 --- a/events/user_status_events.go +++ b/events/user_status_events.go @@ -18,6 +18,6 @@ type UserStatusUpdate struct { type UserClientStatusUpdate struct { *GenericEvent UserID snowflake.ID - OldClientStatus *discord.ClientStatus + OldClientStatus discord.ClientStatus ClientStatus discord.ClientStatus } diff --git a/handlers/presence_update_handler.go b/handlers/presence_update_handler.go index cbeaa3e28..6336e51df 100644 --- a/handlers/presence_update_handler.go +++ b/handlers/presence_update_handler.go @@ -2,28 +2,40 @@ package handlers import ( "github.com/disgoorg/disgo/bot" + "github.com/disgoorg/disgo/cache" + "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/disgo/events" "github.com/disgoorg/disgo/gateway" + "github.com/disgoorg/snowflake/v2" + "golang.org/x/exp/slices" ) func gatewayHandlerPresenceUpdate(client bot.Client, sequenceNumber int, shardID int, event gateway.EventPresenceUpdate) { - /*oldPresence := client.Caches().Presences().GetCopy(event.GuildID, event.PresenceUser.ID) + genericEvent := events.NewGenericEvent(client, sequenceNumber, shardID) - _ = bot.EntityBuilder.CreatePresence(event, core.CacheStrategyYes) + client.EventManager().DispatchEvent(&events.PresenceUpdate{ + GenericEvent: genericEvent, + EventPresenceUpdate: event, + }) - genericEvent := events.NewGenericEvent(client, sequenceNumber, shardID) + if client.Caches().CacheFlags().Missing(cache.FlagPresences) { + return + } var ( oldStatus discord.OnlineStatus - oldClientStatus *discord.ClientStatus + oldClientStatus discord.ClientStatus oldActivities []discord.Activity ) - if oldPresence != nil { + if oldPresence, ok := client.Caches().Presence(event.GuildID, event.PresenceUser.ID); ok { oldStatus = oldPresence.Status - oldClientStatus = &oldPresence.ClientStatus + oldClientStatus = oldPresence.ClientStatus oldActivities = oldPresence.Activities } + client.Caches().AddPresence(event.Presence) + if oldStatus != event.Status { client.EventManager().DispatchEvent(&events.UserStatusUpdate{ GenericEvent: genericEvent, @@ -33,7 +45,7 @@ func gatewayHandlerPresenceUpdate(client bot.Client, sequenceNumber int, shardID }) } - if oldClientStatus == nil || oldClientStatus.Desktop != event.ClientStatus.Desktop || oldClientStatus.Mobile != event.ClientStatus.Mobile || oldClientStatus.Web != event.ClientStatus.Web { + if oldClientStatus.Desktop != event.ClientStatus.Desktop || oldClientStatus.Mobile != event.ClientStatus.Mobile || oldClientStatus.Web != event.ClientStatus.Web { client.EventManager().DispatchEvent(&events.UserClientStatusUpdate{ GenericEvent: genericEvent, UserID: event.PresenceUser.ID, @@ -42,7 +54,7 @@ func gatewayHandlerPresenceUpdate(client bot.Client, sequenceNumber int, shardID }) } - genericUserActivity := events.GenericUserActivity{ + genericUserActivityEvent := events.GenericUserActivity{ GenericEvent: genericEvent, UserID: event.PresenceUser.ID, GuildID: event.GuildID, @@ -88,12 +100,148 @@ func gatewayHandlerPresenceUpdate(client bot.Client, sequenceNumber int, shardID break } } - if oldActivity != nil && !cmp.Equal(*oldActivity, newActivity) { + if oldActivity != nil && isActivityUpdated(*oldActivity, newActivity) { genericUserActivityEvent.Activity = newActivity client.EventManager().DispatchEvent(&events.UserActivityUpdate{ GenericUserActivity: &genericUserActivityEvent, - OldActivity: *oldActivity, + OldActivity: *oldActivity, }) } - }*/ + } +} + +// compare 2 discord.Activity and return true if they are different +func isActivityUpdated(old discord.Activity, new discord.Activity) bool { + if old.Name != new.Name { + return true + } + if old.Type != new.Type { + return true + } + if compareStringPtr(old.URL, new.URL) { + return true + } + if old.CreatedAt.Equal(new.CreatedAt) { + return true + } + if compareActivityTimestampsPtr(old.Timestamps, new.Timestamps) { + return true + } + if compareStringPtr(old.SyncID, new.SyncID) { + return true + } + if old.ApplicationID != new.ApplicationID { + return true + } + if compareStringPtr(old.Details, new.Details) { + return true + } + if compareStringPtr(old.State, new.State) { + return true + } + if comparePartialEmojiPtr(old.Emoji, new.Emoji) { + return true + } + if compareActivityPartyPtr(old.Party, new.Party) { + return true + } + if compareActivityAssetsPtr(old.Assets, new.Assets) { + return true + } + if compareActivitySecretsPtr(old.Secrets, new.Secrets) { + return true + } + if compareBoolPtr(old.Instance, new.Instance) { + return true + } + if old.Flags != new.Flags { + return true + } + return slices.Equal(old.Buttons, new.Buttons) +} + +func compareActivityTimestampsPtr(old *discord.ActivityTimestamps, new *discord.ActivityTimestamps) bool { + if old == nil && new == nil { + return false + } + if old == nil || new == nil { + return true + } + return old.Start.Equal(new.Start) && old.End.Equal(new.End) +} + +func compareBoolPtr(old *bool, new *bool) bool { + if old == nil && new == nil { + return false + } + if old == nil || new == nil { + return true + } + return *old != *new +} + +func compareStringPtr(old *string, new *string) bool { + if old == nil && new == nil { + return false + } + if old == nil || new == nil { + return true + } + return *old != *new +} + +func comparePartialEmojiPtr(old *discord.PartialEmoji, new *discord.PartialEmoji) bool { + if old == nil && new == nil { + return false + } + if old == nil || new == nil { + return true + } + if compareSnowflakePtr(old.ID, new.ID) { + return true + } + if compareStringPtr(old.Name, new.Name) { + return true + } + return old.Animated != new.Animated +} + +func compareSnowflakePtr(old *snowflake.ID, new *snowflake.ID) bool { + if old == nil && new == nil { + return false + } + if old == nil || new == nil { + return true + } + return *old != *new +} + +func compareActivityPartyPtr(old *discord.ActivityParty, new *discord.ActivityParty) bool { + if old == nil && new == nil { + return false + } + if old == nil || new == nil { + return true + } + return old.ID != new.ID || old.Size != new.Size +} + +func compareActivityAssetsPtr(old *discord.ActivityAssets, new *discord.ActivityAssets) bool { + if old == nil && new == nil { + return false + } + if old == nil || new == nil { + return true + } + return old.LargeText != new.LargeText || old.LargeImage != new.LargeImage || old.SmallText != new.SmallText || old.SmallImage != new.SmallImage +} + +func compareActivitySecretsPtr(old *discord.ActivitySecrets, new *discord.ActivitySecrets) bool { + if old == nil && new == nil { + return false + } + if old == nil || new == nil { + return true + } + return old.Join != new.Join || old.Spectate != new.Spectate || old.Match != new.Match } From f810691d7b720c826d594042d781f0da7d85355c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= Date: Sat, 10 Jun 2023 15:54:04 +0200 Subject: [PATCH 074/119] reforamt isActivityUpdated check --- handlers/presence_update_handler.go | 63 ++++++++--------------------- 1 file changed, 16 insertions(+), 47 deletions(-) diff --git a/handlers/presence_update_handler.go b/handlers/presence_update_handler.go index 6336e51df..2267796f5 100644 --- a/handlers/presence_update_handler.go +++ b/handlers/presence_update_handler.go @@ -110,54 +110,23 @@ func gatewayHandlerPresenceUpdate(client bot.Client, sequenceNumber int, shardID } } -// compare 2 discord.Activity and return true if they are different func isActivityUpdated(old discord.Activity, new discord.Activity) bool { - if old.Name != new.Name { - return true - } - if old.Type != new.Type { - return true - } - if compareStringPtr(old.URL, new.URL) { - return true - } - if old.CreatedAt.Equal(new.CreatedAt) { - return true - } - if compareActivityTimestampsPtr(old.Timestamps, new.Timestamps) { - return true - } - if compareStringPtr(old.SyncID, new.SyncID) { - return true - } - if old.ApplicationID != new.ApplicationID { - return true - } - if compareStringPtr(old.Details, new.Details) { - return true - } - if compareStringPtr(old.State, new.State) { - return true - } - if comparePartialEmojiPtr(old.Emoji, new.Emoji) { - return true - } - if compareActivityPartyPtr(old.Party, new.Party) { - return true - } - if compareActivityAssetsPtr(old.Assets, new.Assets) { - return true - } - if compareActivitySecretsPtr(old.Secrets, new.Secrets) { - return true - } - if compareBoolPtr(old.Instance, new.Instance) { - return true - } - if old.Flags != new.Flags { - return true - } - return slices.Equal(old.Buttons, new.Buttons) + return old.Name != new.Name || + old.Type != new.Type || + compareStringPtr(old.URL, new.URL) || + old.CreatedAt.Equal(new.CreatedAt) || + compareActivityTimestampsPtr(old.Timestamps, new.Timestamps) || + compareStringPtr(old.SyncID, new.SyncID) || + old.ApplicationID != new.ApplicationID || + compareStringPtr(old.Details, new.Details) || + compareStringPtr(old.State, new.State) || + comparePartialEmojiPtr(old.Emoji, new.Emoji) || + compareActivityPartyPtr(old.Party, new.Party) || + compareActivityAssetsPtr(old.Assets, new.Assets) || + compareActivitySecretsPtr(old.Secrets, new.Secrets) || + compareBoolPtr(old.Instance, new.Instance) || + old.Flags != new.Flags || + slices.Equal(old.Buttons, new.Buttons) } func compareActivityTimestampsPtr(old *discord.ActivityTimestamps, new *discord.ActivityTimestamps) bool { From f4a652fadc37306a2a952e5dc33ef3f618e17030 Mon Sep 17 00:00:00 2001 From: jkdlgy <65353031+jkdlgy@users.noreply.github.com> Date: Sun, 25 Jun 2023 22:01:48 +0300 Subject: [PATCH 075/119] Fix nil pointer dereference in gatewayHandlerTypingStart (#280) Co-authored-by: jkdlgy --- events/guild_member_events.go | 3 +++ handlers/typing_start_handler.go | 10 ++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/events/guild_member_events.go b/events/guild_member_events.go index 978651c0d..6189a5d79 100644 --- a/events/guild_member_events.go +++ b/events/guild_member_events.go @@ -35,6 +35,9 @@ type GuildMemberLeave struct { } // GuildMemberTypingStart indicates that a discord.Member started typing in a discord.BaseGuildMessageChannel(requires gateway.IntentGuildMessageTyping) +// Member will be empty when event is triggered by [Clyde bot] +// +// [Clyde bot]: https://support.discord.com/hc/en-us/articles/13066317497239-Clyde-Discord-s-AI-Chatbot type GuildMemberTypingStart struct { *GenericEvent ChannelID snowflake.ID diff --git a/handlers/typing_start_handler.go b/handlers/typing_start_handler.go index ea414cea8..c12e9e1b5 100644 --- a/handlers/typing_start_handler.go +++ b/handlers/typing_start_handler.go @@ -2,6 +2,7 @@ package handlers import ( "github.com/disgoorg/disgo/bot" + "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/events" "github.com/disgoorg/disgo/gateway" ) @@ -23,14 +24,19 @@ func gatewayHandlerTypingStart(client bot.Client, sequenceNumber int, shardID in Timestamp: event.Timestamp, }) } else { - client.Caches().AddMember(*event.Member) + var member discord.Member + if event.Member != nil { + member = *event.Member + client.Caches().AddMember(member) + } + client.EventManager().DispatchEvent(&events.GuildMemberTypingStart{ GenericEvent: events.NewGenericEvent(client, sequenceNumber, shardID), ChannelID: event.ChannelID, UserID: event.UserID, GuildID: *event.GuildID, Timestamp: event.Timestamp, - Member: *event.Member, + Member: member, }) } } From 39854144d8576dd118e952beb17d77686a03bec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= Date: Mon, 26 Jun 2023 15:54:32 +0200 Subject: [PATCH 076/119] remove member caching from typing & message reaction events --- handlers/message_reaction_handler.go | 4 ---- handlers/typing_start_handler.go | 2 -- 2 files changed, 6 deletions(-) diff --git a/handlers/message_reaction_handler.go b/handlers/message_reaction_handler.go index 2c563228e..7bed476de 100644 --- a/handlers/message_reaction_handler.go +++ b/handlers/message_reaction_handler.go @@ -9,10 +9,6 @@ import ( func gatewayHandlerMessageReactionAdd(client bot.Client, sequenceNumber int, shardID int, event gateway.EventMessageReactionAdd) { genericEvent := events.NewGenericEvent(client, sequenceNumber, shardID) - if event.Member != nil { - client.Caches().AddMember(*event.Member) - } - client.EventManager().DispatchEvent(&events.MessageReactionAdd{ GenericReaction: &events.GenericReaction{ GenericEvent: genericEvent, diff --git a/handlers/typing_start_handler.go b/handlers/typing_start_handler.go index c12e9e1b5..1a1994ee3 100644 --- a/handlers/typing_start_handler.go +++ b/handlers/typing_start_handler.go @@ -27,9 +27,7 @@ func gatewayHandlerTypingStart(client bot.Client, sequenceNumber int, shardID in var member discord.Member if event.Member != nil { member = *event.Member - client.Caches().AddMember(member) } - client.EventManager().DispatchEvent(&events.GuildMemberTypingStart{ GenericEvent: events.NewGenericEvent(client, sequenceNumber, shardID), ChannelID: event.ChannelID, From 80c3d52022362b04e92d2f751f3c0a73e5a23d90 Mon Sep 17 00:00:00 2001 From: mlnrDev Date: Wed, 28 Jun 2023 19:02:45 +0200 Subject: [PATCH 077/119] rename parameters in StageInstances --- rest/stage_instances.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/rest/stage_instances.go b/rest/stage_instances.go index 6dc1b51ba..2d79b14a5 100644 --- a/rest/stage_instances.go +++ b/rest/stage_instances.go @@ -13,18 +13,18 @@ func NewStageInstances(client Client) StageInstances { } type StageInstances interface { - GetStageInstance(guildID snowflake.ID, opts ...RequestOpt) (*discord.StageInstance, error) + GetStageInstance(channelID snowflake.ID, opts ...RequestOpt) (*discord.StageInstance, error) CreateStageInstance(stageInstanceCreate discord.StageInstanceCreate, opts ...RequestOpt) (*discord.StageInstance, error) - UpdateStageInstance(guildID snowflake.ID, stageInstanceUpdate discord.StageInstanceUpdate, opts ...RequestOpt) (*discord.StageInstance, error) - DeleteStageInstance(guildID snowflake.ID, opts ...RequestOpt) error + UpdateStageInstance(channelID snowflake.ID, stageInstanceUpdate discord.StageInstanceUpdate, opts ...RequestOpt) (*discord.StageInstance, error) + DeleteStageInstance(channelID snowflake.ID, opts ...RequestOpt) error } type stageInstanceImpl struct { client Client } -func (s *stageInstanceImpl) GetStageInstance(guildID snowflake.ID, opts ...RequestOpt) (stageInstance *discord.StageInstance, err error) { - err = s.client.Do(GetStageInstance.Compile(nil, guildID), nil, &stageInstance, opts...) +func (s *stageInstanceImpl) GetStageInstance(channelID snowflake.ID, opts ...RequestOpt) (stageInstance *discord.StageInstance, err error) { + err = s.client.Do(GetStageInstance.Compile(nil, channelID), nil, &stageInstance, opts...) return } @@ -33,11 +33,11 @@ func (s *stageInstanceImpl) CreateStageInstance(stageInstanceCreate discord.Stag return } -func (s *stageInstanceImpl) UpdateStageInstance(guildID snowflake.ID, stageInstanceUpdate discord.StageInstanceUpdate, opts ...RequestOpt) (stageInstance *discord.StageInstance, err error) { - err = s.client.Do(UpdateStageInstance.Compile(nil, guildID), stageInstanceUpdate, &stageInstance, opts...) +func (s *stageInstanceImpl) UpdateStageInstance(channelID snowflake.ID, stageInstanceUpdate discord.StageInstanceUpdate, opts ...RequestOpt) (stageInstance *discord.StageInstance, err error) { + err = s.client.Do(UpdateStageInstance.Compile(nil, channelID), stageInstanceUpdate, &stageInstance, opts...) return } -func (s *stageInstanceImpl) DeleteStageInstance(guildID snowflake.ID, opts ...RequestOpt) error { - return s.client.Do(DeleteStageInstance.Compile(nil, guildID), nil, nil, opts...) +func (s *stageInstanceImpl) DeleteStageInstance(channelID snowflake.ID, opts ...RequestOpt) error { + return s.client.Do(DeleteStageInstance.Compile(nil, channelID), nil, nil, opts...) } From 9bd60df8c47e95113e10352d698a39ba08c7f5c3 Mon Sep 17 00:00:00 2001 From: cane Date: Wed, 28 Jun 2023 23:50:30 +0200 Subject: [PATCH 078/119] Rename ImageFormat to FileFormat (#284) --- discord/cdn_endpoints.go | 84 ++++++++++++++++++++-------------------- discord/sticker.go | 8 ++-- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/discord/cdn_endpoints.go b/discord/cdn_endpoints.go index a5048c96d..a4f5a376a 100644 --- a/discord/cdn_endpoints.go +++ b/discord/cdn_endpoints.go @@ -7,80 +7,80 @@ import ( const CDN = "https://cdn.discordapp.com" var ( - CustomEmoji = NewCDN("/emojis/{emote.id}", ImageFormatPNG, ImageFormatGIF) + CustomEmoji = NewCDN("/emojis/{emote.id}", FileFormatPNG, FileFormatGIF) - GuildIcon = NewCDN("/icons/{guild.id}/{guild.icon.hash}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP, ImageFormatGIF) - GuildSplash = NewCDN("/splashes/{guild.id}/{guild.splash.hash}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP) - GuildDiscoverySplash = NewCDN("/discovery-splashes/{guild.id}/{guild.discovery.splash.hash}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP) - GuildBanner = NewCDN("/banners/{guild.id}/{guild.banner.hash}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP, ImageFormatGIF) + GuildIcon = NewCDN("/icons/{guild.id}/{guild.icon.hash}", FileFormatPNG, FileFormatJPEG, FileFormatWebP, FileFormatGIF) + GuildSplash = NewCDN("/splashes/{guild.id}/{guild.splash.hash}", FileFormatPNG, FileFormatJPEG, FileFormatWebP) + GuildDiscoverySplash = NewCDN("/discovery-splashes/{guild.id}/{guild.discovery.splash.hash}", FileFormatPNG, FileFormatJPEG, FileFormatWebP) + GuildBanner = NewCDN("/banners/{guild.id}/{guild.banner.hash}", FileFormatPNG, FileFormatJPEG, FileFormatWebP, FileFormatGIF) - RoleIcon = NewCDN("/role-icons/{role.id}/{role.icon.hash}", ImageFormatPNG, ImageFormatJPEG) + RoleIcon = NewCDN("/role-icons/{role.id}/{role.icon.hash}", FileFormatPNG, FileFormatJPEG) - UserBanner = NewCDN("/banners/{user.id}/{user.banner.hash}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP, ImageFormatGIF) - UserAvatar = NewCDN("/avatars/{user.id}/{user.avatar.hash}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP, ImageFormatGIF) - DefaultUserAvatar = NewCDN("/embed/avatars/{index}", ImageFormatPNG) + UserBanner = NewCDN("/banners/{user.id}/{user.banner.hash}", FileFormatPNG, FileFormatJPEG, FileFormatWebP, FileFormatGIF) + UserAvatar = NewCDN("/avatars/{user.id}/{user.avatar.hash}", FileFormatPNG, FileFormatJPEG, FileFormatWebP, FileFormatGIF) + DefaultUserAvatar = NewCDN("/embed/avatars/{index}", FileFormatPNG) - ChannelIcon = NewCDN("/channel-icons/{channel.id}/{channel.icon.hash}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP) + ChannelIcon = NewCDN("/channel-icons/{channel.id}/{channel.icon.hash}", FileFormatPNG, FileFormatJPEG, FileFormatWebP) - MemberAvatar = NewCDN("/guilds/{guild.id}/users/{user.id}/avatars/{member.avatar.hash}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP, ImageFormatGIF) + MemberAvatar = NewCDN("/guilds/{guild.id}/users/{user.id}/avatars/{member.avatar.hash}", FileFormatPNG, FileFormatJPEG, FileFormatWebP, FileFormatGIF) - ApplicationIcon = NewCDN("/app-icons/{application.id}/{icon.hash}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP) - ApplicationCover = NewCDN("/app-assets/{application.id}/{cover.image.hash}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP) - ApplicationAsset = NewCDN("/app-assets/{application.id}/{asset.id}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP) + ApplicationIcon = NewCDN("/app-icons/{application.id}/{icon.hash}", FileFormatPNG, FileFormatJPEG, FileFormatWebP) + ApplicationCover = NewCDN("/app-assets/{application.id}/{cover.image.hash}", FileFormatPNG, FileFormatJPEG, FileFormatWebP) + ApplicationAsset = NewCDN("/app-assets/{application.id}/{asset.id}", FileFormatPNG, FileFormatJPEG, FileFormatWebP) - AchievementIcon = NewCDN("/app-assets/{application.id}/achievements/{achievement.id}/icons/{icon.hash}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP) + AchievementIcon = NewCDN("/app-assets/{application.id}/achievements/{achievement.id}/icons/{icon.hash}", FileFormatPNG, FileFormatJPEG, FileFormatWebP) - StorePageAsset = NewCDN("/app-assets/{application.id}/store/{asset.id}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP) + StorePageAsset = NewCDN("/app-assets/{application.id}/store/{asset.id}", FileFormatPNG, FileFormatJPEG, FileFormatWebP) - TeamIcon = NewCDN("/team-icons/{team.id}/{team.icon.hash}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP) + TeamIcon = NewCDN("/team-icons/{team.id}/{team.icon.hash}", FileFormatPNG, FileFormatJPEG, FileFormatWebP) - StickerPackBanner = NewCDN("/app-assets/710982414301790216/store/{banner.asset.id}", ImageFormatPNG, ImageFormatJPEG, ImageFormatWebP) - CustomSticker = NewCDN("/stickers/{sticker.id}", ImageFormatPNG, ImageFormatLottie, ImageFormatGIF) + StickerPackBanner = NewCDN("/app-assets/710982414301790216/store/{banner.asset.id}", FileFormatPNG, FileFormatJPEG, FileFormatWebP) + CustomSticker = NewCDN("/stickers/{sticker.id}", FileFormatPNG, FileFormatLottie, FileFormatGIF) - AttachmentFile = NewCDN("/attachments/{channel.id}/{attachment.id}/{file.name}", ImageFormatNone) + AttachmentFile = NewCDN("/attachments/{channel.id}/{attachment.id}/{file.name}", FileFormatNone) ) -// ImageFormat is the type of image on Discord's CDN (https://discord.com/developers/docs/reference#image-formatting-image-formats) -type ImageFormat string +// FileFormat is the type of file on Discord's CDN (https://discord.com/developers/docs/reference#image-formatting-image-formats) +type FileFormat string -// The available ImageFormat(s) +// The available FileFormat(s) const ( - ImageFormatNone ImageFormat = "" - ImageFormatPNG ImageFormat = "png" - ImageFormatJPEG ImageFormat = "jpg" - ImageFormatWebP ImageFormat = "webp" - ImageFormatGIF ImageFormat = "gif" - ImageFormatLottie ImageFormat = "json" + FileFormatNone FileFormat = "" + FileFormatPNG FileFormat = "png" + FileFormatJPEG FileFormat = "jpg" + FileFormatWebP FileFormat = "webp" + FileFormatGIF FileFormat = "gif" + FileFormatLottie FileFormat = "json" ) -// String returns the string representation of the ImageFormat -func (f ImageFormat) String() string { +// String returns the string representation of the FileFormat +func (f FileFormat) String() string { return string(f) } -// Animated returns true if the ImageFormat is animated -func (f ImageFormat) Animated() bool { +// Animated returns true if the FileFormat is animated +func (f FileFormat) Animated() bool { switch f { - case ImageFormatWebP, ImageFormatGIF: + case FileFormatWebP, FileFormatGIF: return true default: return false } } -func NewCDN(route string, imageFormats ...ImageFormat) *CDNEndpoint { +func NewCDN(route string, fileFormats ...FileFormat) *CDNEndpoint { return &CDNEndpoint{ Route: route, - Formats: imageFormats, + Formats: fileFormats, } } type CDNEndpoint struct { Route string - Formats []ImageFormat + Formats []FileFormat } -func (e CDNEndpoint) URL(format ImageFormat, values QueryValues, params ...any) string { +func (e CDNEndpoint) URL(format FileFormat, values QueryValues, params ...any) string { query := values.Encode() if query != "" { query = "?" + query @@ -90,13 +90,13 @@ func (e CDNEndpoint) URL(format ImageFormat, values QueryValues, params ...any) func DefaultCDNConfig() *CDNConfig { return &CDNConfig{ - Format: ImageFormatPNG, + Format: FileFormatPNG, Values: QueryValues{}, } } type CDNConfig struct { - Format ImageFormat + Format FileFormat Values QueryValues } @@ -115,7 +115,7 @@ func WithSize(size int) CDNOpt { } } -func WithFormat(format ImageFormat) CDNOpt { +func WithFormat(format FileFormat) CDNOpt { return func(config *CDNConfig) { config.Format = format } @@ -140,7 +140,7 @@ func formatAssetURL(cdnRoute *CDNEndpoint, opts []CDNOpt, params ...any) string } if strings.HasPrefix(lastStringParam, "a_") && !config.Format.Animated() { - config.Format = ImageFormatGIF + config.Format = FileFormatGIF } return cdnRoute.URL(config.Format, config.Values, params...) diff --git a/discord/sticker.go b/discord/sticker.go index 309be8619..744932a7e 100644 --- a/discord/sticker.go +++ b/discord/sticker.go @@ -22,14 +22,14 @@ type Sticker struct { } func (s Sticker) URL(opts ...CDNOpt) string { - var format ImageFormat + var format FileFormat switch s.FormatType { case StickerFormatTypeLottie: - format = ImageFormatLottie + format = FileFormatLottie case StickerFormatTypeGIF: - format = ImageFormatGIF + format = FileFormatGIF default: - format = ImageFormatPNG + format = FileFormatPNG } return formatAssetURL(CustomSticker, append(opts, WithFormat(format)), s.ID) } From 79b8080c1b774cda8535af111969ef1adf409e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= Date: Sun, 2 Jul 2023 22:10:49 +0200 Subject: [PATCH 079/119] fix unknown gateway message data unmarshalling & clanup opus reader/writer & audiosender --- voice/audio_sender.go | 23 +++++++++++++---------- voice/gateway_messages.go | 8 ++++++++ voice/opus.go | 37 +++++++++++++++++++++++-------------- 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/voice/audio_sender.go b/voice/audio_sender.go index 9ca5da8a7..6c20aaa3f 100644 --- a/voice/audio_sender.go +++ b/voice/audio_sender.go @@ -10,15 +10,18 @@ import ( "github.com/disgoorg/log" ) -var ( - // SilenceAudioFrame is a 20ms opus frame of silence. - SilenceAudioFrame = []byte{0xF8, 0xFF, 0xFE} +// SilenceAudioFrame is a 20ms opus frame of silence. +var SilenceAudioFrame = []byte{0xF8, 0xFF, 0xFE} - // OpusFrameSize is the size of an opus frame in milliseconds. - OpusFrameSize int64 = 20 +const ( + // OpusFrameSizeMs is the size of an opus frame in milliseconds. + OpusFrameSizeMs int = 20 - // OpusStreamBuffSize is the size of the buffer for receiving one opus frame. - OpusStreamBuffSize int64 = 4000 + // OpusFrameSize is the size of an opus frame in bytes. + OpusFrameSize int = 960 + + // OpusFrameSizeBytes is the size of an opus frame in bytes. + OpusFrameSizeBytes = OpusFrameSize * 2 * 2 ) type ( @@ -76,12 +79,12 @@ loop: default: s.send() - sleepTime := time.Duration(OpusFrameSize - (time.Now().UnixMilli() - lastFrameSent)) + sleepTime := time.Duration(int64(OpusFrameSizeMs) - (time.Now().UnixMilli() - lastFrameSent)) if sleepTime > 0 { time.Sleep(sleepTime * time.Millisecond) } - if time.Now().UnixMilli() < lastFrameSent+OpusFrameSize*3 { - lastFrameSent += OpusFrameSize + if time.Now().UnixMilli() < lastFrameSent+int64(OpusFrameSizeMs)*3 { + lastFrameSent += int64(OpusFrameSizeMs) } else { lastFrameSent = time.Now().UnixMilli() } diff --git a/voice/gateway_messages.go b/voice/gateway_messages.go index b2b40e9b2..e3842dddb 100644 --- a/voice/gateway_messages.go +++ b/voice/gateway_messages.go @@ -212,3 +212,11 @@ func (GatewayMessageDataClientDisconnect) voiceGatewayMessageData() {} type GatewayMessageDataUnknown json.RawMessage func (GatewayMessageDataUnknown) voiceGatewayMessageData() {} + +func (m GatewayMessageDataUnknown) MarshalJSON() ([]byte, error) { + return json.RawMessage(m).MarshalJSON() +} + +func (m *GatewayMessageDataUnknown) UnmarshalJSON(data []byte) error { + return (*json.RawMessage)(m).UnmarshalJSON(data) +} diff --git a/voice/opus.go b/voice/opus.go index c21ff55c0..6887e10b0 100644 --- a/voice/opus.go +++ b/voice/opus.go @@ -9,20 +9,22 @@ import ( ) // NewOpusReader returns a new OpusFrameProvider that reads opus frames from the given io.Reader. -func NewOpusReader(r io.Reader) OpusFrameProvider { - return &opusReader{ - r: r, - buff: make([]byte, OpusStreamBuffSize), +func NewOpusReader(r io.Reader) *OpusReader { + return &OpusReader{ + r: r, } } -type opusReader struct { +// OpusReader is an OpusFrameProvider that reads opus frames from the given io.Reader. +// Each opus frame is prefixed with a 4 byte little endian uint32 that represents the length of the frame. +type OpusReader struct { r io.Reader lenBuff [4]byte - buff []byte + buff [OpusFrameSizeBytes]byte } -func (h *opusReader) ProvideOpusFrame() ([]byte, error) { +// ProvideOpusFrame reads the next opus frame from the underlying io.Reader. +func (h *OpusReader) ProvideOpusFrame() ([]byte, error) { _, err := h.r.Read(h.lenBuff[:]) if err != nil { return nil, fmt.Errorf("error while reading opus frame length: %w", err) @@ -36,22 +38,26 @@ func (h *opusReader) ProvideOpusFrame() ([]byte, error) { return h.buff[:actualLen], nil } -func (*opusReader) Close() {} +// Close is a no-op. +func (*OpusReader) Close() {} // NewOpusWriter returns a new OpusFrameReceiver that writes opus frames to the given io.Writer. -func NewOpusWriter(w io.Writer, userFilter UserFilterFunc) OpusFrameReceiver { - return &opusWriter{ +func NewOpusWriter(w io.Writer, userFilter UserFilterFunc) *OpusWriter { + return &OpusWriter{ w: w, userFilter: userFilter, } } -type opusWriter struct { +// OpusWriter is an OpusFrameReceiver that writes opus frames to the given io.Writer. +// Each opus frame is prefixed with a 4 byte little endian uint32 that represents the length of the frame. +type OpusWriter struct { w io.Writer userFilter UserFilterFunc } -func (r *opusWriter) ReceiveOpusFrame(userID snowflake.ID, packet *Packet) error { +// ReceiveOpusFrame writes the given opus frame to the underlying io.Writer. +func (r *OpusWriter) ReceiveOpusFrame(userID snowflake.ID, packet *Packet) error { if r.userFilter != nil && !r.userFilter(userID) { return nil } @@ -64,5 +70,8 @@ func (r *opusWriter) ReceiveOpusFrame(userID snowflake.ID, packet *Packet) error return nil } -func (*opusWriter) CleanupUser(_ snowflake.ID) {} -func (*opusWriter) Close() {} +// CleanupUser is a no-op. +func (*OpusWriter) CleanupUser(_ snowflake.ID) {} + +// Close is a no-op. +func (*OpusWriter) Close() {} From 07e3134ed4b26c13e82d3224611394364295881a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= Date: Sun, 2 Jul 2023 23:44:00 +0200 Subject: [PATCH 080/119] SetOpusFrameProvider & SetOpusFrameReceiver should not be blocking --- voice/audio_receiver.go | 4 ++++ voice/audio_sender.go | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/voice/audio_receiver.go b/voice/audio_receiver.go index f375330b1..79ec5893b 100644 --- a/voice/audio_receiver.go +++ b/voice/audio_receiver.go @@ -57,6 +57,10 @@ type defaultAudioReceiver struct { } func (s *defaultAudioReceiver) Open() { + go s.open() +} + +func (s *defaultAudioReceiver) open() { defer s.logger.Debugf("closing audio receiver") ctx, cancel := context.WithCancel(context.Background()) s.cancelFunc = cancel diff --git a/voice/audio_sender.go b/voice/audio_sender.go index 6c20aaa3f..b7276e30c 100644 --- a/voice/audio_sender.go +++ b/voice/audio_sender.go @@ -66,6 +66,10 @@ type defaultAudioSender struct { } func (s *defaultAudioSender) Open() { + go s.open() +} + +func (s *defaultAudioSender) open() { defer s.logger.Debug("closing audio sender") lastFrameSent := time.Now().UnixMilli() ctx, cancel := context.WithCancel(context.Background()) From b331ced0cf9231a48690034c5d553f645f3532f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= Date: Thu, 6 Jul 2023 14:22:38 +0200 Subject: [PATCH 081/119] add go & defer interaction middleware --- handler/middleware/defer.go | 36 ++++++++++++++++++++++++++++++++++++ handler/middleware/go.go | 18 ++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 handler/middleware/defer.go create mode 100644 handler/middleware/go.go diff --git a/handler/middleware/defer.go b/handler/middleware/defer.go new file mode 100644 index 000000000..ee5fa4455 --- /dev/null +++ b/handler/middleware/defer.go @@ -0,0 +1,36 @@ +package middleware + +import ( + "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/disgo/events" + "github.com/disgoorg/disgo/handler" +) + +// Defer is a middleware that defers the specified interaction type. +// If updateMessage is true, it will respond with discord.InteractionResponseTypeDeferredUpdateMessage instead of discord.InteractionResponseTypeDeferredCreateMessage. +// If ephemeral is true, it will respond with discord.MessageFlagEphemeral flag in case of a discord.InteractionResponseTypeDeferredCreateMessage. +// Note: You can use this middleware multiple times with different interaction types. +// Note: You can use this middleware in combination with the Go middleware to defer & run in a goroutine. +func Defer(interactionType discord.InteractionType, updateMessage bool, ephemeral bool) handler.Middleware { + return func(next handler.Handler) handler.Handler { + return func(event *events.InteractionCreate) error { + if event.Type() == interactionType { + responseType := discord.InteractionResponseTypeDeferredCreateMessage + if updateMessage { + responseType = discord.InteractionResponseTypeDeferredUpdateMessage + } + + var data discord.InteractionResponseData + if ephemeral && !updateMessage { + data = discord.MessageCreate{ + Flags: discord.MessageFlagEphemeral, + } + } + if err := event.Respond(responseType, data); err != nil { + return err + } + } + return next(event) + } + } +} diff --git a/handler/middleware/go.go b/handler/middleware/go.go new file mode 100644 index 000000000..c67ed1654 --- /dev/null +++ b/handler/middleware/go.go @@ -0,0 +1,18 @@ +package middleware + +import ( + "github.com/disgoorg/disgo/events" + "github.com/disgoorg/disgo/handler" +) + +// Go is a middleware that runs the next handler in a goroutine +var Go handler.Middleware = func(next handler.Handler) handler.Handler { + return func(e *events.InteractionCreate) error { + go func() { + if err := next(e); err != nil { + e.Client().Logger().Errorf("failed to handle interaction: %s\n", err) + } + }() + return nil + } +} From 8f83c4507224581733be167474dc37feb8e25f9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= Date: Sat, 8 Jul 2023 01:43:12 +0200 Subject: [PATCH 082/119] enforce handler patterns to start with / --- handler/mux.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/handler/mux.go b/handler/mux.go index f49160835..74c453098 100644 --- a/handler/mux.go +++ b/handler/mux.go @@ -166,7 +166,7 @@ func (r *Mux) Autocomplete(pattern string, h AutocompleteHandler) { // Component registers the given ComponentHandler to the current Router. func (r *Mux) Component(pattern string, h ComponentHandler) { - checkPatternEmpty(pattern) + checkPattern(pattern) r.handle(&handlerHolder[ComponentHandler]{ pattern: pattern, handler: h, @@ -176,7 +176,7 @@ func (r *Mux) Component(pattern string, h ComponentHandler) { // Modal registers the given ModalHandler to the current Router. func (r *Mux) Modal(pattern string, h ModalHandler) { - checkPatternEmpty(pattern) + checkPattern(pattern) r.handle(&handlerHolder[ModalHandler]{ pattern: pattern, handler: h, @@ -190,14 +190,10 @@ func (r *Mux) NotFound(h NotFoundHandler) { r.notFoundHandler = h } -func checkPatternEmpty(pattern string) { - if pattern == "" { +func checkPattern(pattern string) { + if len(pattern) == 0 { panic("pattern must not be empty") } -} - -func checkPattern(pattern string) { - checkPatternEmpty(pattern) if pattern[0] != '/' { panic("pattern must start with /") } From 089ffe73213c06fcca2ec49038da5d75a11d8818 Mon Sep 17 00:00:00 2001 From: cane Date: Thu, 13 Jul 2023 20:11:17 +0200 Subject: [PATCH 083/119] Add RoleFlags (#287) --- discord/role.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/discord/role.go b/discord/role.go index 2988532b1..ece6958ea 100644 --- a/discord/role.go +++ b/discord/role.go @@ -24,6 +24,7 @@ type Role struct { Emoji *string `json:"unicode_emoji"` Mentionable bool `json:"mentionable"` Tags *RoleTag `json:"tags,omitempty"` + Flags RoleFlags `json:"flags"` } func (r Role) String() string { @@ -54,6 +55,13 @@ type RoleTag struct { GuildConnections bool `json:"guild_connections"` } +type RoleFlags int + +const ( + RoleFlagInPrompt RoleFlags = 1 << iota + RoleFlagsNone RoleFlags = 0 +) + // RoleCreate is the payload to create a Role type RoleCreate struct { Name string `json:"name,omitempty"` From f786e6986652b5873b26a6766bfdac645dbce850 Mon Sep 17 00:00:00 2001 From: cane Date: Thu, 13 Jul 2023 20:13:40 +0200 Subject: [PATCH 084/119] Add onboarding updates (#267) --- discord/guild_onboarding.go | 15 +++++++++++++++ rest/guilds.go | 6 ++++++ rest/rest_endpoints.go | 3 ++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/discord/guild_onboarding.go b/discord/guild_onboarding.go index 6c06e2299..4b397673f 100644 --- a/discord/guild_onboarding.go +++ b/discord/guild_onboarding.go @@ -7,6 +7,7 @@ type GuildOnboarding struct { Prompts []GuildOnboardingPrompt `json:"prompts"` DefaultChannelIDs []snowflake.ID `json:"default_channel_ids"` Enabled bool `json:"enabled"` + Mode GuildOnboardingMode `json:"mode"` } type GuildOnboardingPrompt struct { @@ -34,3 +35,17 @@ const ( GuildOnboardingPromptTypeMultipleChoice GuildOnboardingPromptType = iota GuildOnboardingPromptTypeDropdown ) + +type GuildOnboardingMode int + +const ( + GuildOnboardingModeDefault GuildOnboardingMode = iota + GuildOnboardingModeAdvanced +) + +type GuildOnboardingUpdate struct { + Prompts *[]GuildOnboardingPrompt `json:"prompts,omitempty"` + DefaultChannelIDs *[]snowflake.ID `json:"default_channel_ids,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + Mode *GuildOnboardingMode `json:"mode,omitempty"` +} diff --git a/rest/guilds.go b/rest/guilds.go index 28585eb4e..a382356d3 100644 --- a/rest/guilds.go +++ b/rest/guilds.go @@ -57,6 +57,7 @@ type Guilds interface { UpdateGuildWelcomeScreen(guildID snowflake.ID, screenUpdate discord.GuildWelcomeScreenUpdate, opts ...RequestOpt) (*discord.GuildWelcomeScreen, error) GetGuildOnboarding(guildID snowflake.ID, opts ...RequestOpt) (*discord.GuildOnboarding, error) + UpdateGuildOnboarding(guildID snowflake.ID, onboardingUpdate discord.GuildOnboardingUpdate, opts ...RequestOpt) (*discord.GuildOnboarding, error) } type guildImpl struct { @@ -304,3 +305,8 @@ func (s *guildImpl) GetGuildOnboarding(guildID snowflake.ID, opts ...RequestOpt) err = s.client.Do(GetGuildOnboarding.Compile(nil, guildID), nil, &onboarding, opts...) return } + +func (s *guildImpl) UpdateGuildOnboarding(guildID snowflake.ID, onboardingUpdate discord.GuildOnboardingUpdate, opts ...RequestOpt) (guildOnboarding *discord.GuildOnboarding, err error) { + err = s.client.Do(UpdateGuildOnboarding.Compile(nil, guildID), onboardingUpdate, &guildOnboarding, opts...) + return +} diff --git a/rest/rest_endpoints.go b/rest/rest_endpoints.go index 7160b62cc..1cb2a12d7 100644 --- a/rest/rest_endpoints.go +++ b/rest/rest_endpoints.go @@ -89,7 +89,8 @@ var ( GetGuildWelcomeScreen = NewEndpoint(http.MethodGet, "/guilds/{guild.id}/welcome-screen") UpdateGuildWelcomeScreen = NewEndpoint(http.MethodPatch, "/guilds/{guild.id}/welcome-screen") - GetGuildOnboarding = NewEndpoint(http.MethodGet, "/guilds/{guild.id}/onboarding") + GetGuildOnboarding = NewEndpoint(http.MethodGet, "/guilds/{guild.id}/onboarding") + UpdateGuildOnboarding = NewEndpoint(http.MethodPut, "/guilds/{guild.id}/onboarding") UpdateCurrentUserVoiceState = NewEndpoint(http.MethodPatch, "/guilds/{guild.id}/voice-states/@me") UpdateUserVoiceState = NewEndpoint(http.MethodPatch, "/guilds/{guild.id}/voice-states/{user.id}") From 5e4729f52db89ab5a23007f1480586a8b31268df Mon Sep 17 00:00:00 2001 From: cane Date: Thu, 13 Jul 2023 20:37:58 +0200 Subject: [PATCH 085/119] Add AttachmentFlags (#288) * Add AttachmentFlags * add bitshift --- discord/attachment.go | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/discord/attachment.go b/discord/attachment.go index a2cf0f96c..4a93a5a2e 100644 --- a/discord/attachment.go +++ b/discord/attachment.go @@ -8,24 +8,34 @@ import ( // Attachment is used for files sent in a Message type Attachment struct { - ID snowflake.ID `json:"id,omitempty"` - Filename string `json:"filename,omitempty"` - Description *string `json:"description,omitempty"` - ContentType *string `json:"content_type,omitempty"` - Size int `json:"size,omitempty"` - URL string `json:"url,omitempty"` - ProxyURL string `json:"proxy_url,omitempty"` - Height *int `json:"height,omitempty"` - Width *int `json:"width,omitempty"` - Ephemeral bool `json:"ephemeral,omitempty"` - DurationSecs *float64 `json:"duration_secs,omitempty"` - Waveform *string `json:"waveform,omitempty"` + ID snowflake.ID `json:"id,omitempty"` + Filename string `json:"filename,omitempty"` + Description *string `json:"description,omitempty"` + ContentType *string `json:"content_type,omitempty"` + Size int `json:"size,omitempty"` + URL string `json:"url,omitempty"` + ProxyURL string `json:"proxy_url,omitempty"` + Height *int `json:"height,omitempty"` + Width *int `json:"width,omitempty"` + Ephemeral bool `json:"ephemeral,omitempty"` + DurationSecs *float64 `json:"duration_secs,omitempty"` + Waveform *string `json:"waveform,omitempty"` + Flags AttachmentFlags `json:"flags"` } func (a Attachment) CreatedAt() time.Time { return a.ID.Time() } +type AttachmentFlags int + +const ( + AttachmentFlagIsClip AttachmentFlags = 1 << iota + AttachmentFlagIsThumbnail + AttachmentFlagIsRemix + AttachmentFlagsNone AttachmentFlags = 0 +) + type AttachmentUpdate interface { attachmentUpdate() } From 93dc71036344b3f8451d539fe7ace405854789b8 Mon Sep 17 00:00:00 2001 From: cane Date: Sun, 16 Jul 2023 15:18:33 +0200 Subject: [PATCH 086/119] Add avatar decorations (#217) --- discord/cdn_endpoints.go | 2 ++ discord/user.go | 30 ++++++++++++++++++++---------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/discord/cdn_endpoints.go b/discord/cdn_endpoints.go index a4f5a376a..8186af5a6 100644 --- a/discord/cdn_endpoints.go +++ b/discord/cdn_endpoints.go @@ -24,6 +24,8 @@ var ( MemberAvatar = NewCDN("/guilds/{guild.id}/users/{user.id}/avatars/{member.avatar.hash}", FileFormatPNG, FileFormatJPEG, FileFormatWebP, FileFormatGIF) + UserAvatarDecoration = NewCDN("/avatar-decorations/{user.id}/{user.avatar.decoration.hash}", FileFormatPNG) + ApplicationIcon = NewCDN("/app-icons/{application.id}/{icon.hash}", FileFormatPNG, FileFormatJPEG, FileFormatWebP) ApplicationCover = NewCDN("/app-assets/{application.id}/{cover.image.hash}", FileFormatPNG, FileFormatJPEG, FileFormatWebP) ApplicationAsset = NewCDN("/app-assets/{application.id}/{asset.id}", FileFormatPNG, FileFormatJPEG, FileFormatWebP) diff --git a/discord/user.go b/discord/user.go index 47e13d6d6..caa63b8d5 100644 --- a/discord/user.go +++ b/discord/user.go @@ -65,16 +65,17 @@ var _ Mentionable = (*User)(nil) // User is a struct for interacting with discord's users type User struct { - ID snowflake.ID `json:"id"` - Username string `json:"username"` - Discriminator string `json:"discriminator"` - GlobalName *string `json:"global_name"` - Avatar *string `json:"avatar"` - Banner *string `json:"banner"` - AccentColor *int `json:"accent_color"` - Bot bool `json:"bot"` - System bool `json:"system"` - PublicFlags UserFlags `json:"public_flags"` + ID snowflake.ID `json:"id"` + Username string `json:"username"` + Discriminator string `json:"discriminator"` + GlobalName *string `json:"global_name"` + Avatar *string `json:"avatar"` + Banner *string `json:"banner"` + AccentColor *int `json:"accent_color"` + Bot bool `json:"bot"` + System bool `json:"system"` + PublicFlags UserFlags `json:"public_flags"` + AvatarDecoration *string `json:"avatar_decoration"` } // String returns a mention of the user @@ -142,6 +143,15 @@ func (u User) BannerURL(opts ...CDNOpt) *string { return &url } +// AvatarDecorationURL returns the avatar decoration URL if set or nil +func (u User) AvatarDecorationURL(opts ...CDNOpt) *string { + if u.AvatarDecoration == nil { + return nil + } + url := formatAssetURL(UserAvatarDecoration, opts, u.ID, *u.AvatarDecoration) + return &url +} + func (u User) CreatedAt() time.Time { return u.ID.Time() } From 23637a73400b28da592757351d6dab86b7e5d876 Mon Sep 17 00:00:00 2001 From: mlnrDev Date: Sun, 16 Jul 2023 15:23:02 +0200 Subject: [PATCH 087/119] add MessageAuthorID to EventMessageReactionAdd https://github.com/discord/discord-api-docs/pull/6102 --- events/dm_message_reaction_events.go | 1 + events/guild_message_reaction_events.go | 3 ++- gateway/gateway_events.go | 13 +++++++------ handlers/message_reaction_handler.go | 4 +++- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/events/dm_message_reaction_events.go b/events/dm_message_reaction_events.go index a7540fcfd..b230d605d 100644 --- a/events/dm_message_reaction_events.go +++ b/events/dm_message_reaction_events.go @@ -18,6 +18,7 @@ type GenericDMMessageReaction struct { // DMMessageReactionAdd indicates that a discord.User added a discord.MessageReaction to a discord.Message in a Channel (requires the gateway.IntentDirectMessageReactions) type DMMessageReactionAdd struct { *GenericDMMessageReaction + MessageAuthorID snowflake.ID } // DMMessageReactionRemove indicates that a discord.User removed a discord.MessageReaction from a discord.Message in a Channel (requires the gateway.IntentDirectMessageReactions) diff --git a/events/guild_message_reaction_events.go b/events/guild_message_reaction_events.go index d3ed49d72..738248a33 100644 --- a/events/guild_message_reaction_events.go +++ b/events/guild_message_reaction_events.go @@ -24,7 +24,8 @@ func (e *GenericGuildMessageReaction) Member() (discord.Member, bool) { // GuildMessageReactionAdd indicates that a discord.Member added a discord.PartialEmoji to a discord.Message in a discord.GuildMessageChannel(requires the gateway.IntentGuildMessageReactions) type GuildMessageReactionAdd struct { *GenericGuildMessageReaction - Member discord.Member + Member discord.Member + MessageAuthorID snowflake.ID } // GuildMessageReactionRemove indicates that a discord.Member removed a discord.MessageReaction from a discord.Message in a Channel (requires the gateway.IntentGuildMessageReactions) diff --git a/gateway/gateway_events.go b/gateway/gateway_events.go index 3793f8050..5339e50d9 100644 --- a/gateway/gateway_events.go +++ b/gateway/gateway_events.go @@ -187,12 +187,13 @@ func (EventGuildAuditLogEntryCreate) messageData() {} func (EventGuildAuditLogEntryCreate) eventData() {} type EventMessageReactionAdd struct { - UserID snowflake.ID `json:"user_id"` - ChannelID snowflake.ID `json:"channel_id"` - MessageID snowflake.ID `json:"message_id"` - GuildID *snowflake.ID `json:"guild_id"` - Member *discord.Member `json:"member"` - Emoji discord.PartialEmoji `json:"emoji"` + UserID snowflake.ID `json:"user_id"` + ChannelID snowflake.ID `json:"channel_id"` + MessageID snowflake.ID `json:"message_id"` + GuildID *snowflake.ID `json:"guild_id"` + Member *discord.Member `json:"member"` + Emoji discord.PartialEmoji `json:"emoji"` + MessageAuthorID snowflake.ID `json:"message_author_id"` } func (e *EventMessageReactionAdd) UnmarshalJSON(data []byte) error { diff --git a/handlers/message_reaction_handler.go b/handlers/message_reaction_handler.go index 7bed476de..54cb630db 100644 --- a/handlers/message_reaction_handler.go +++ b/handlers/message_reaction_handler.go @@ -30,6 +30,7 @@ func gatewayHandlerMessageReactionAdd(client bot.Client, sequenceNumber int, sha UserID: event.UserID, Emoji: event.Emoji, }, + MessageAuthorID: event.MessageAuthorID, }) } else { client.EventManager().DispatchEvent(&events.GuildMessageReactionAdd{ @@ -41,7 +42,8 @@ func gatewayHandlerMessageReactionAdd(client bot.Client, sequenceNumber int, sha UserID: event.UserID, Emoji: event.Emoji, }, - Member: *event.Member, + Member: *event.Member, + MessageAuthorID: event.MessageAuthorID, }) } } From 09a5cb21c90abc05b6802185cd5af881e13eef73 Mon Sep 17 00:00:00 2001 From: mlnrDev Date: Sun, 16 Jul 2023 15:26:02 +0200 Subject: [PATCH 088/119] add monetization audit log events https://github.com/discord/discord-api-docs/pull/6071 --- discord/audit_log.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/discord/audit_log.go b/discord/audit_log.go index fc969b5f4..865d9bbc7 100644 --- a/discord/audit_log.go +++ b/discord/audit_log.go @@ -117,6 +117,11 @@ const ( AuditLogAutoModerationUserCommunicationDisabled ) +const ( + AuditLogCreatorMonetizationRequestCreated AuditLogEvent = iota + 150 + AuditLogCreatorMonetizationTermsAccepted +) + // AuditLog (https://discord.com/developers/docs/resources/audit-log) These are logs of events that occurred, accessible via the Discord type AuditLog struct { ApplicationCommands []ApplicationCommand `json:"application_commands"` From 836b4a9882b6270d9ea235c3d474281d613355cb Mon Sep 17 00:00:00 2001 From: mlnrDev Date: Sun, 16 Jul 2023 15:44:45 +0200 Subject: [PATCH 089/119] add with_counts to /users/@me/guilds funcs https://github.com/discord/discord-api-docs/pull/5628 --- discord/guild.go | 14 ++++++++------ rest/oauth2.go | 14 ++++++++------ rest/users.go | 8 +++++--- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/discord/guild.go b/discord/guild.go index 5ca15bc52..a87272ca7 100644 --- a/discord/guild.go +++ b/discord/guild.go @@ -248,12 +248,14 @@ type UnavailableGuild struct { // OAuth2Guild is returned on the GetGuilds route type OAuth2Guild struct { - ID snowflake.ID `json:"id"` - Name string `json:"name"` - Icon *string `json:"icon"` - Owner bool `json:"owner"` - Permissions Permissions `json:"permissions"` - Features []GuildFeature `json:"features"` + ID snowflake.ID `json:"id"` + Name string `json:"name"` + Icon *string `json:"icon"` + Owner bool `json:"owner"` + Permissions Permissions `json:"permissions"` + Features []GuildFeature `json:"features"` + ApproximateMemberCount int `json:"approximate_member_count"` + ApproximatePresenceCount int `json:"approximate_presence_count"` } // GuildWelcomeScreen is the Welcome Screen of a Guild diff --git a/rest/oauth2.go b/rest/oauth2.go index feb2f61ac..a09484307 100644 --- a/rest/oauth2.go +++ b/rest/oauth2.go @@ -20,8 +20,8 @@ type OAuth2 interface { GetCurrentAuthorizationInfo(bearerToken string, opts ...RequestOpt) (*discord.AuthorizationInformation, error) GetCurrentUser(bearerToken string, opts ...RequestOpt) (*discord.OAuth2User, error) GetCurrentMember(bearerToken string, guildID snowflake.ID, opts ...RequestOpt) (*discord.Member, error) - GetCurrentUserGuilds(bearerToken string, before snowflake.ID, after snowflake.ID, limit int, opts ...RequestOpt) ([]discord.OAuth2Guild, error) - GetCurrentUserGuildsPage(bearerToken string, startID snowflake.ID, limit int, opts ...RequestOpt) Page[discord.OAuth2Guild] + GetCurrentUserGuilds(bearerToken string, before snowflake.ID, after snowflake.ID, limit int, withCounts bool, opts ...RequestOpt) ([]discord.OAuth2Guild, error) + GetCurrentUserGuildsPage(bearerToken string, startID snowflake.ID, limit int, withCounts bool, opts ...RequestOpt) Page[discord.OAuth2Guild] GetCurrentUserConnections(bearerToken string, opts ...RequestOpt) ([]discord.Connection, error) SetGuildCommandPermissions(bearerToken string, applicationID snowflake.ID, guildID snowflake.ID, commandID snowflake.ID, commandPermissions []discord.ApplicationCommandPermission, opts ...RequestOpt) (*discord.ApplicationCommandPermissions, error) @@ -64,8 +64,10 @@ func (s *oAuth2Impl) GetCurrentMember(bearerToken string, guildID snowflake.ID, return } -func (s *oAuth2Impl) GetCurrentUserGuilds(bearerToken string, before snowflake.ID, after snowflake.ID, limit int, opts ...RequestOpt) (guilds []discord.OAuth2Guild, err error) { - queryParams := discord.QueryValues{} +func (s *oAuth2Impl) GetCurrentUserGuilds(bearerToken string, before snowflake.ID, after snowflake.ID, limit int, withCounts bool, opts ...RequestOpt) (guilds []discord.OAuth2Guild, err error) { + queryParams := discord.QueryValues{ + "with_counts": withCounts, + } if before != 0 { queryParams["before"] = before } @@ -79,10 +81,10 @@ func (s *oAuth2Impl) GetCurrentUserGuilds(bearerToken string, before snowflake.I return } -func (s *oAuth2Impl) GetCurrentUserGuildsPage(bearerToken string, startID snowflake.ID, limit int, opts ...RequestOpt) Page[discord.OAuth2Guild] { +func (s *oAuth2Impl) GetCurrentUserGuildsPage(bearerToken string, startID snowflake.ID, limit int, withCounts bool, opts ...RequestOpt) Page[discord.OAuth2Guild] { return Page[discord.OAuth2Guild]{ getItemsFunc: func(before snowflake.ID, after snowflake.ID) ([]discord.OAuth2Guild, error) { - return s.GetCurrentUserGuilds(bearerToken, before, after, limit, opts...) + return s.GetCurrentUserGuilds(bearerToken, before, after, limit, withCounts, opts...) }, getIDFunc: func(guild discord.OAuth2Guild) snowflake.ID { return guild.ID diff --git a/rest/users.go b/rest/users.go index 6dd2f50da..ac15783d4 100644 --- a/rest/users.go +++ b/rest/users.go @@ -15,7 +15,7 @@ func NewUsers(client Client) Users { type Users interface { GetUser(userID snowflake.ID, opts ...RequestOpt) (*discord.User, error) UpdateSelfUser(selfUserUpdate discord.SelfUserUpdate, opts ...RequestOpt) (*discord.OAuth2User, error) - GetGuilds(before int, after int, limit int, opts ...RequestOpt) ([]discord.OAuth2Guild, error) + GetGuilds(before int, after int, limit int, withCounts bool, opts ...RequestOpt) ([]discord.OAuth2Guild, error) LeaveGuild(guildID snowflake.ID, opts ...RequestOpt) error GetDMChannels(opts ...RequestOpt) ([]discord.Channel, error) CreateDMChannel(userID snowflake.ID, opts ...RequestOpt) (*discord.DMChannel, error) @@ -35,8 +35,10 @@ func (s *userImpl) UpdateSelfUser(updateSelfUser discord.SelfUserUpdate, opts .. return } -func (s *userImpl) GetGuilds(before int, after int, limit int, opts ...RequestOpt) (guilds []discord.OAuth2Guild, err error) { - queryParams := discord.QueryValues{} +func (s *userImpl) GetGuilds(before int, after int, limit int, withCounts bool, opts ...RequestOpt) (guilds []discord.OAuth2Guild, err error) { + queryParams := discord.QueryValues{ + "with_counts": withCounts, + } if before > 0 { queryParams["before"] = before } From d57b40f1f6baca637833a604e7cd19e8320373fd Mon Sep 17 00:00:00 2001 From: mlnrDev Date: Sun, 16 Jul 2023 15:47:21 +0200 Subject: [PATCH 090/119] i forgor --- oauth2/client_impl.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauth2/client_impl.go b/oauth2/client_impl.go index c535b63b1..080968bfc 100644 --- a/oauth2/client_impl.go +++ b/oauth2/client_impl.go @@ -118,7 +118,7 @@ func (c *clientImpl) GetGuilds(session Session, opts ...rest.RequestOpt) ([]disc if err := checkSession(session, discord.OAuth2ScopeGuilds); err != nil { return nil, err } - return c.Rest().GetCurrentUserGuilds(session.AccessToken, 0, 0, 0, opts...) + return c.Rest().GetCurrentUserGuilds(session.AccessToken, 0, 0, 0, false, opts...) } func (c *clientImpl) GetConnections(session Session, opts ...rest.RequestOpt) ([]discord.Connection, error) { From 201bf30a6feecdcd2a3539b3fd04be9f5280fd3e Mon Sep 17 00:00:00 2001 From: mlnrDev Date: Sun, 16 Jul 2023 17:50:29 +0200 Subject: [PATCH 091/119] make MessageAuthorID a pointer https://github.com/discord/discord-api-docs/pull/6296 --- events/dm_message_reaction_events.go | 2 +- events/guild_message_reaction_events.go | 2 +- gateway/gateway_events.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/events/dm_message_reaction_events.go b/events/dm_message_reaction_events.go index b230d605d..3c8fd7d1d 100644 --- a/events/dm_message_reaction_events.go +++ b/events/dm_message_reaction_events.go @@ -18,7 +18,7 @@ type GenericDMMessageReaction struct { // DMMessageReactionAdd indicates that a discord.User added a discord.MessageReaction to a discord.Message in a Channel (requires the gateway.IntentDirectMessageReactions) type DMMessageReactionAdd struct { *GenericDMMessageReaction - MessageAuthorID snowflake.ID + MessageAuthorID *snowflake.ID } // DMMessageReactionRemove indicates that a discord.User removed a discord.MessageReaction from a discord.Message in a Channel (requires the gateway.IntentDirectMessageReactions) diff --git a/events/guild_message_reaction_events.go b/events/guild_message_reaction_events.go index 738248a33..575e8e97a 100644 --- a/events/guild_message_reaction_events.go +++ b/events/guild_message_reaction_events.go @@ -25,7 +25,7 @@ func (e *GenericGuildMessageReaction) Member() (discord.Member, bool) { type GuildMessageReactionAdd struct { *GenericGuildMessageReaction Member discord.Member - MessageAuthorID snowflake.ID + MessageAuthorID *snowflake.ID } // GuildMessageReactionRemove indicates that a discord.Member removed a discord.MessageReaction from a discord.Message in a Channel (requires the gateway.IntentGuildMessageReactions) diff --git a/gateway/gateway_events.go b/gateway/gateway_events.go index 5339e50d9..7fec6f900 100644 --- a/gateway/gateway_events.go +++ b/gateway/gateway_events.go @@ -193,7 +193,7 @@ type EventMessageReactionAdd struct { GuildID *snowflake.ID `json:"guild_id"` Member *discord.Member `json:"member"` Emoji discord.PartialEmoji `json:"emoji"` - MessageAuthorID snowflake.ID `json:"message_author_id"` + MessageAuthorID *snowflake.ID `json:"message_author_id"` } func (e *EventMessageReactionAdd) UnmarshalJSON(data []byte) error { From e8c9c8b1f685954497cb41486c621d33f38f2450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= Date: Mon, 17 Jul 2023 14:36:14 +0200 Subject: [PATCH 092/119] update disgoorg/log to v1.2.1 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 110fb8a50..274bd5494 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( github.com/disgoorg/json v1.1.0 - github.com/disgoorg/log v1.2.0 + github.com/disgoorg/log v1.2.1 github.com/disgoorg/snowflake/v2 v2.0.1 github.com/gorilla/websocket v1.5.0 github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b diff --git a/go.sum b/go.sum index 012743103..14af0fa8a 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ 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/disgoorg/json v1.1.0 h1:7xigHvomlVA9PQw9bMGO02PHGJJPqvX5AnwlYg/Tnys= github.com/disgoorg/json v1.1.0/go.mod h1:BHDwdde0rpQFDVsRLKhma6Y7fTbQKub/zdGO5O9NqqA= -github.com/disgoorg/log v1.2.0 h1:sqlXnu/ZKAlIlHV9IO+dbMto7/hCQ474vlIdMWk8QKo= -github.com/disgoorg/log v1.2.0/go.mod h1:3x1KDG6DI1CE2pDwi3qlwT3wlXpeHW/5rVay+1qDqOo= +github.com/disgoorg/log v1.2.1 h1:kZYAWkUBcGy4LbZcgYtgYu49xNVLy+xG5Uq3yz5VVQs= +github.com/disgoorg/log v1.2.1/go.mod h1:hhQWYTFTnIGzAuFPZyXJEi11IBm9wq+/TVZt/FEwX0o= github.com/disgoorg/snowflake/v2 v2.0.1 h1:CuUxGLwggUxEswZOmZ+mZ5i0xSumQdXW9tXW7uGqe+0= github.com/disgoorg/snowflake/v2 v2.0.1/go.mod h1:SPU9c2CNn5DSyb86QcKtdZgix9osEtKrHLW4rMhfLCs= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= From b0885debf89ffec5fe52f9586196fd03022a0d63 Mon Sep 17 00:00:00 2001 From: cane Date: Sat, 5 Aug 2023 00:10:13 +0200 Subject: [PATCH 093/119] Add Media Channels (#285) --- cache/caches.go | 12 ++++ discord/channel.go | 133 ++++++++++++++++++++++++++++++++++++-- discord/channel_create.go | 32 ++++++++- discord/channel_update.go | 26 ++++++-- discord/channels_raw.go | 39 ++++++++++- discord/thread.go | 6 +- rest/threads.go | 8 +-- 7 files changed, 236 insertions(+), 20 deletions(-) diff --git a/cache/caches.go b/cache/caches.go index e3f44dd1d..32fe51725 100644 --- a/cache/caches.go +++ b/cache/caches.go @@ -747,6 +747,9 @@ type Caches interface { // GuildForumChannel returns a discord.GuildForumChannel from the ChannelCache and a bool indicating if it exists. GuildForumChannel(channelID snowflake.ID) (discord.GuildForumChannel, bool) + + // GuildMediaChannel returns a discord.GuildMediaChannel from the ChannelCache and a bool indicating if it exists. + GuildMediaChannel(channelID snowflake.ID) (discord.GuildMediaChannel, bool) } // New returns a new default Caches instance with the given ConfigOpt(s) applied. @@ -1008,3 +1011,12 @@ func (c *cachesImpl) GuildForumChannel(channelID snowflake.ID) (discord.GuildFor } return discord.GuildForumChannel{}, false } + +func (c *cachesImpl) GuildMediaChannel(channelID snowflake.ID) (discord.GuildMediaChannel, bool) { + if ch, ok := c.Channel(channelID); ok { + if cCh, ok := ch.(discord.GuildMediaChannel); ok { + return cCh, true + } + } + return discord.GuildMediaChannel{}, false +} diff --git a/discord/channel.go b/discord/channel.go index 30f195f0b..4b0e9caf6 100644 --- a/discord/channel.go +++ b/discord/channel.go @@ -31,6 +31,7 @@ const ( ChannelTypeGuildStageVoice ChannelTypeGuildDirectory ChannelTypeGuildForum + ChannelTypeGuildMedia ) type ChannelFlags int @@ -40,7 +41,8 @@ const ( _ _ ChannelFlagRequireTag - ChannelFlagsNone ChannelFlags = 0 + ChannelFlagHideMediaDownloadOptions ChannelFlags = 1 << 15 + ChannelFlagsNone ChannelFlags = 0 ) // Add allows you to add multiple bits together, producing a new bit @@ -208,6 +210,11 @@ func (u *UnmarshalChannel) UnmarshalJSON(data []byte) error { err = json.Unmarshal(data, &v) channel = v + case ChannelTypeGuildMedia: + var v GuildMediaChannel + err = json.Unmarshal(data, &v) + channel = v + default: err = fmt.Errorf("unknown channel with type %d received", cType.Type) } @@ -1034,12 +1041,12 @@ type GuildForumChannel struct { permissionOverwrites PermissionOverwrites name string parentID *snowflake.ID - LastThreadID *snowflake.ID + LastPostID *snowflake.ID Topic *string NSFW bool RateLimitPerUser int Flags ChannelFlags - AvailableTags []ForumTag + AvailableTags []ChannelTag DefaultReactionEmoji *DefaultReactionEmoji DefaultThreadRateLimitPerUser int DefaultSortOrder *DefaultSortOrder @@ -1058,7 +1065,7 @@ func (c *GuildForumChannel) UnmarshalJSON(data []byte) error { c.permissionOverwrites = v.PermissionOverwrites c.name = v.Name c.parentID = v.ParentID - c.LastThreadID = v.LastThreadID + c.LastPostID = v.LastPostID c.Topic = v.Topic c.NSFW = v.NSFW c.RateLimitPerUser = v.RateLimitPerUser @@ -1080,7 +1087,7 @@ func (c GuildForumChannel) MarshalJSON() ([]byte, error) { PermissionOverwrites: c.permissionOverwrites, Name: c.name, ParentID: c.parentID, - LastThreadID: c.LastThreadID, + LastPostID: c.LastPostID, Topic: c.Topic, NSFW: c.NSFW, RateLimitPerUser: c.RateLimitPerUser, @@ -1136,6 +1143,117 @@ func (c GuildForumChannel) CreatedAt() time.Time { func (GuildForumChannel) channel() {} func (GuildForumChannel) guildChannel() {} +var ( + _ Channel = (*GuildMediaChannel)(nil) + _ GuildChannel = (*GuildMediaChannel)(nil) +) + +type GuildMediaChannel struct { + id snowflake.ID + guildID snowflake.ID + position int + permissionOverwrites PermissionOverwrites + name string + parentID *snowflake.ID + LastPostID *snowflake.ID + Topic *string + NSFW bool + RateLimitPerUser int + Flags ChannelFlags + AvailableTags []ChannelTag + DefaultReactionEmoji *DefaultReactionEmoji + DefaultThreadRateLimitPerUser int + DefaultSortOrder *DefaultSortOrder +} + +func (c *GuildMediaChannel) UnmarshalJSON(data []byte) error { + var v guildMediaChannel + if err := json.Unmarshal(data, &v); err != nil { + return err + } + + c.id = v.ID + c.guildID = v.GuildID + c.position = v.Position + c.permissionOverwrites = v.PermissionOverwrites + c.name = v.Name + c.parentID = v.ParentID + c.LastPostID = v.LastPostID + c.Topic = v.Topic + c.NSFW = v.NSFW + c.RateLimitPerUser = v.RateLimitPerUser + c.Flags = v.Flags + c.AvailableTags = v.AvailableTags + c.DefaultReactionEmoji = v.DefaultReactionEmoji + c.DefaultThreadRateLimitPerUser = v.DefaultThreadRateLimitPerUser + c.DefaultSortOrder = v.DefaultSortOrder + return nil +} + +func (c GuildMediaChannel) MarshalJSON() ([]byte, error) { + return json.Marshal(guildMediaChannel{ + ID: c.id, + Type: c.Type(), + GuildID: c.guildID, + Position: c.position, + PermissionOverwrites: c.permissionOverwrites, + Name: c.name, + ParentID: c.parentID, + LastPostID: c.LastPostID, + Topic: c.Topic, + NSFW: c.NSFW, + RateLimitPerUser: c.RateLimitPerUser, + Flags: c.Flags, + AvailableTags: c.AvailableTags, + DefaultReactionEmoji: c.DefaultReactionEmoji, + DefaultThreadRateLimitPerUser: c.DefaultThreadRateLimitPerUser, + DefaultSortOrder: c.DefaultSortOrder, + }) +} + +func (c GuildMediaChannel) String() string { + return channelString(c) +} + +func (c GuildMediaChannel) Mention() string { + return ChannelMention(c.ID()) +} + +func (GuildMediaChannel) Type() ChannelType { + return ChannelTypeGuildMedia +} + +func (c GuildMediaChannel) ID() snowflake.ID { + return c.id +} + +func (c GuildMediaChannel) Name() string { + return c.name +} + +func (c GuildMediaChannel) GuildID() snowflake.ID { + return c.guildID +} + +func (c GuildMediaChannel) PermissionOverwrites() PermissionOverwrites { + return c.permissionOverwrites +} + +func (c GuildMediaChannel) Position() int { + return c.position +} + +func (c GuildMediaChannel) ParentID() *snowflake.ID { + return c.parentID +} + +func (c GuildMediaChannel) CreatedAt() time.Time { + return c.id.Time() +} + +func (GuildMediaChannel) channel() {} +func (GuildMediaChannel) guildChannel() {} + type FollowedChannel struct { ChannelID snowflake.ID `json:"channel_id"` WebhookID snowflake.ID `json:"webhook_id"` @@ -1167,7 +1285,7 @@ type ThreadMetadata struct { CreateTimestamp time.Time `json:"create_timestamp"` } -type ForumTag struct { +type ChannelTag struct { ID snowflake.ID `json:"id"` Name string `json:"name"` Moderated bool `json:"moderated"` @@ -1236,6 +1354,9 @@ func ApplyGuildIDToChannel(channel GuildChannel, guildID snowflake.ID) GuildChan case GuildForumChannel: c.guildID = guildID return c + case GuildMediaChannel: + c.guildID = guildID + return c default: return channel } diff --git a/discord/channel_create.go b/discord/channel_create.go index edf84eb7a..eb5ac7d21 100644 --- a/discord/channel_create.go +++ b/discord/channel_create.go @@ -188,7 +188,7 @@ type GuildForumChannelCreate struct { ParentID snowflake.ID `json:"parent_id,omitempty"` RateLimitPerUser int `json:"rate_limit_per_user"` DefaultReactionEmoji DefaultReactionEmoji `json:"default_reaction_emoji"` - AvailableTags []ForumTag `json:"available_tags"` + AvailableTags []ChannelTag `json:"available_tags"` DefaultSortOrder DefaultSortOrder `json:"default_sort_order"` DefaultForumLayout DefaultForumLayout `json:"default_forum_layout"` } @@ -211,6 +211,36 @@ func (c GuildForumChannelCreate) MarshalJSON() ([]byte, error) { func (GuildForumChannelCreate) channelCreate() {} func (GuildForumChannelCreate) guildChannelCreate() {} +type GuildMediaChannelCreate struct { + Name string `json:"name"` + Topic string `json:"topic,omitempty"` + Position int `json:"position,omitempty"` + PermissionOverwrites []PermissionOverwrite `json:"permission_overwrites,omitempty"` + ParentID snowflake.ID `json:"parent_id,omitempty"` + RateLimitPerUser int `json:"rate_limit_per_user"` + DefaultReactionEmoji DefaultReactionEmoji `json:"default_reaction_emoji"` + AvailableTags []ChannelTag `json:"available_tags"` + DefaultSortOrder DefaultSortOrder `json:"default_sort_order"` +} + +func (c GuildMediaChannelCreate) Type() ChannelType { + return ChannelTypeGuildMedia +} + +func (c GuildMediaChannelCreate) MarshalJSON() ([]byte, error) { + type guildMediaChannelCreate GuildMediaChannelCreate + return json.Marshal(struct { + Type ChannelType `json:"type"` + guildMediaChannelCreate + }{ + Type: c.Type(), + guildMediaChannelCreate: guildMediaChannelCreate(c), + }) +} + +func (GuildMediaChannelCreate) channelCreate() {} +func (GuildMediaChannelCreate) guildChannelCreate() {} + type DMChannelCreate struct { RecipientID snowflake.ID `json:"recipient_id"` } diff --git a/discord/channel_update.go b/discord/channel_update.go index 82bdf470f..9ddfe4d2e 100644 --- a/discord/channel_update.go +++ b/discord/channel_update.go @@ -104,7 +104,7 @@ type GuildForumChannelUpdate struct { PermissionOverwrites *[]PermissionOverwrite `json:"permission_overwrites,omitempty"` ParentID *snowflake.ID `json:"parent_id,omitempty"` RateLimitPerUser *int `json:"rate_limit_per_user"` - AvailableTags *[]ForumTag `json:"available_tags,omitempty"` + AvailableTags *[]ChannelTag `json:"available_tags,omitempty"` Flags *ChannelFlags `json:"flags,omitempty"` DefaultReactionEmoji *json.Nullable[DefaultReactionEmoji] `json:"default_reaction_emoji,omitempty"` DefaultThreadRateLimitPerUser *int `json:"default_thread_rate_limit_per_user,omitempty"` @@ -115,7 +115,25 @@ type GuildForumChannelUpdate struct { func (GuildForumChannelUpdate) channelUpdate() {} func (GuildForumChannelUpdate) guildChannelUpdate() {} -type GuildForumThreadChannelUpdate struct { +type GuildMediaChannelUpdate struct { + Name *string `json:"name,omitempty"` + Position *int `json:"position,omitempty"` + Topic *string `json:"topic,omitempty"` + NSFW *bool `json:"nsfw,omitempty"` + PermissionOverwrites *[]PermissionOverwrite `json:"permission_overwrites,omitempty"` + ParentID *snowflake.ID `json:"parent_id,omitempty"` + RateLimitPerUser *int `json:"rate_limit_per_user"` + AvailableTags *[]ChannelTag `json:"available_tags,omitempty"` + Flags *ChannelFlags `json:"flags,omitempty"` + DefaultReactionEmoji *json.Nullable[DefaultReactionEmoji] `json:"default_reaction_emoji,omitempty"` + DefaultThreadRateLimitPerUser *int `json:"default_thread_rate_limit_per_user,omitempty"` + DefaultSortOrder *json.Nullable[DefaultSortOrder] `json:"default_sort_order,omitempty"` +} + +func (GuildMediaChannelUpdate) channelUpdate() {} +func (GuildMediaChannelUpdate) guildChannelUpdate() {} + +type GuildPostUpdate struct { Name *string `json:"name,omitempty"` Archived *bool `json:"archived,omitempty"` AutoArchiveDuration *AutoArchiveDuration `json:"auto_archive_duration,omitempty"` @@ -126,8 +144,8 @@ type GuildForumThreadChannelUpdate struct { AppliedTags *[]snowflake.ID `json:"applied_tags,omitempty"` } -func (GuildForumThreadChannelUpdate) channelUpdate() {} -func (GuildForumThreadChannelUpdate) guildChannelUpdate() {} +func (GuildPostUpdate) channelUpdate() {} +func (GuildPostUpdate) guildChannelUpdate() {} type GuildChannelPositionUpdate struct { ID snowflake.ID `json:"id"` diff --git a/discord/channels_raw.go b/discord/channels_raw.go index d2a0667ff..33f75e8a3 100644 --- a/discord/channels_raw.go +++ b/discord/channels_raw.go @@ -188,14 +188,14 @@ type guildForumChannel struct { NSFW bool `json:"nsfw"` RateLimitPerUser int `json:"rate_limit_per_user"` Flags ChannelFlags `json:"flags"` - AvailableTags []ForumTag `json:"available_tags"` + AvailableTags []ChannelTag `json:"available_tags"` DefaultReactionEmoji *DefaultReactionEmoji `json:"default_reaction_emoji"` DefaultThreadRateLimitPerUser int `json:"default_thread_rate_limit_per_user"` DefaultSortOrder *DefaultSortOrder `json:"default_sort_order"` DefaultForumLayout DefaultForumLayout `json:"default_forum_layout"` // idk discord name your shit correctly - LastThreadID *snowflake.ID `json:"last_message_id"` + LastPostID *snowflake.ID `json:"last_message_id"` } func (t *guildForumChannel) UnmarshalJSON(data []byte) error { @@ -212,6 +212,41 @@ func (t *guildForumChannel) UnmarshalJSON(data []byte) error { return nil } +type guildMediaChannel struct { + ID snowflake.ID `json:"id"` + Type ChannelType `json:"type"` + GuildID snowflake.ID `json:"guild_id"` + Position int `json:"position"` + PermissionOverwrites []PermissionOverwrite `json:"permission_overwrites"` + Name string `json:"name"` + ParentID *snowflake.ID `json:"parent_id"` + Topic *string `json:"topic"` + NSFW bool `json:"nsfw"` + RateLimitPerUser int `json:"rate_limit_per_user"` + Flags ChannelFlags `json:"flags"` + AvailableTags []ChannelTag `json:"available_tags"` + DefaultReactionEmoji *DefaultReactionEmoji `json:"default_reaction_emoji"` + DefaultThreadRateLimitPerUser int `json:"default_thread_rate_limit_per_user"` + DefaultSortOrder *DefaultSortOrder `json:"default_sort_order"` + + // idk discord name your shit correctly v2 + LastPostID *snowflake.ID `json:"last_message_id"` +} + +func (t *guildMediaChannel) UnmarshalJSON(data []byte) error { + type guildMediaChannelAlias guildMediaChannel + var v struct { + PermissionOverwrites []UnmarshalPermissionOverwrite `json:"permission_overwrites"` + guildMediaChannelAlias + } + if err := json.Unmarshal(data, &v); err != nil { + return err + } + *t = guildMediaChannel(v.guildMediaChannelAlias) + t.PermissionOverwrites = parsePermissionOverwrites(v.PermissionOverwrites) + return nil +} + func parsePermissionOverwrites(overwrites []UnmarshalPermissionOverwrite) []PermissionOverwrite { if len(overwrites) == 0 { return nil diff --git a/discord/thread.go b/discord/thread.go index 88aa72944..1beae1014 100644 --- a/discord/thread.go +++ b/discord/thread.go @@ -11,7 +11,7 @@ type ThreadCreateFromMessage struct { RateLimitPerUser int `json:"rate_limit_per_user,omitempty"` } -type ForumThreadCreate struct { +type ThreadChannelPostCreate struct { Name string `json:"name"` AutoArchiveDuration AutoArchiveDuration `json:"auto_archive_duration,omitempty"` RateLimitPerUser int `json:"rate_limit_per_user,omitempty"` @@ -19,7 +19,7 @@ type ForumThreadCreate struct { AppliedTags []snowflake.ID `json:"applied_tags,omitempty"` } -func (c ForumThreadCreate) ToBody() (any, error) { +func (c ThreadChannelPostCreate) ToBody() (any, error) { if len(c.Message.Files) > 0 { c.Message.Attachments = parseAttachments(c.Message.Files) return PayloadWithFiles(c, c.Message.Files...) @@ -27,7 +27,7 @@ func (c ForumThreadCreate) ToBody() (any, error) { return c, nil } -type ForumThread struct { +type ThreadChannelPost struct { GuildThread Message Message `json:"message"` } diff --git a/rest/threads.go b/rest/threads.go index 3bd9e8783..573141570 100644 --- a/rest/threads.go +++ b/rest/threads.go @@ -15,9 +15,9 @@ func NewThreads(client Client) Threads { } type Threads interface { - // CreateThreadFromMessage does not work for discord.ChannelTypeGuildForum channels. + // CreateThreadFromMessage does not work for discord.ChannelTypeGuildForum or discord.ChannelTypeGuildMedia channels. CreateThreadFromMessage(channelID snowflake.ID, messageID snowflake.ID, threadCreateFromMessage discord.ThreadCreateFromMessage, opts ...RequestOpt) (thread *discord.GuildThread, err error) - CreateThreadInForum(channelID snowflake.ID, threadCreateInForum discord.ForumThreadCreate, opts ...RequestOpt) (thread *discord.ForumThread, err error) + CreatePostInThreadChannel(channelID snowflake.ID, postCreateInChannel discord.ThreadChannelPostCreate, opts ...RequestOpt) (post *discord.ThreadChannelPost, err error) CreateThread(channelID snowflake.ID, threadCreate discord.ThreadCreate, opts ...RequestOpt) (thread *discord.GuildThread, err error) JoinThread(threadID snowflake.ID, opts ...RequestOpt) error LeaveThread(threadID snowflake.ID, opts ...RequestOpt) error @@ -41,8 +41,8 @@ func (s *threadImpl) CreateThreadFromMessage(channelID snowflake.ID, messageID s return } -func (s *threadImpl) CreateThreadInForum(channelID snowflake.ID, threadCreateInForum discord.ForumThreadCreate, opts ...RequestOpt) (thread *discord.ForumThread, err error) { - body, err := threadCreateInForum.ToBody() +func (s *threadImpl) CreatePostInThreadChannel(channelID snowflake.ID, postCreateInChannel discord.ThreadChannelPostCreate, opts ...RequestOpt) (thread *discord.ThreadChannelPost, err error) { + body, err := postCreateInChannel.ToBody() if err != nil { return } From bb21043a18d5cc4ba64058c1e1130706a33b5b97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= Date: Sat, 5 Aug 2023 23:29:37 +0200 Subject: [PATCH 094/119] replace error casting & comparisons with errors.As & errors.Is --- gateway/gateway_impl.go | 9 +++++---- httpserver/server_impl.go | 3 ++- rest/rest_error.go | 5 +++-- sharding/shard_manager_impl.go | 4 +++- voice/audio_receiver.go | 3 ++- voice/gateway.go | 15 +++++---------- 6 files changed, 20 insertions(+), 19 deletions(-) diff --git a/gateway/gateway_impl.go b/gateway/gateway_impl.go index d73e93d89..d6a0d09a7 100644 --- a/gateway/gateway_impl.go +++ b/gateway/gateway_impl.go @@ -154,7 +154,7 @@ func (g *gatewayImpl) CloseWithCode(ctx context.Context, code int, message strin if g.conn != nil { g.config.RateLimiter.Close(ctx) g.config.Logger.Debug(g.formatLogsf("closing gateway connection with code: %d, message: %s", code, message)) - if err := g.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(code, message)); err != nil && err != websocket.ErrCloseSent { + if err := g.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(code, message)); err != nil && !errors.Is(err, websocket.ErrCloseSent) { g.config.Logger.Debug(g.formatLogs("error writing close code. error: ", err)) } _ = g.conn.Close() @@ -226,7 +226,7 @@ func (g *gatewayImpl) reconnectTry(ctx context.Context, try int) error { } if err := g.open(ctx); err != nil { - if err == discord.ErrGatewayAlreadyConnected { + if errors.Is(err, discord.ErrGatewayAlreadyConnected) { return err } g.config.Logger.Error(g.formatLogs("failed to reconnect gateway. error: ", err)) @@ -268,7 +268,7 @@ func (g *gatewayImpl) sendHeartbeat() { ctx, cancel := context.WithTimeout(context.Background(), g.heartbeatInterval) defer cancel() if err := g.Send(ctx, OpcodeHeartbeat, MessageDataHeartbeat(*g.config.LastSequenceReceived)); err != nil { - if err == discord.ErrShardNotConnected || errors.Is(err, syscall.EPIPE) { + if errors.Is(err, discord.ErrShardNotConnected) || errors.Is(err, syscall.EPIPE) { return } g.config.Logger.Error(g.formatLogs("failed to send heartbeat. error: ", err)) @@ -333,7 +333,8 @@ loop: } reconnect := true - if closeError, ok := err.(*websocket.CloseError); ok { + var closeError *websocket.CloseError + if errors.As(err, &closeError) { closeCode := CloseEventCodeByCode(closeError.Code) reconnect = closeCode.Reconnect diff --git a/httpserver/server_impl.go b/httpserver/server_impl.go index 6063b4f6b..02f12fe51 100644 --- a/httpserver/server_impl.go +++ b/httpserver/server_impl.go @@ -3,6 +3,7 @@ package httpserver import ( "context" "encoding/hex" + "errors" "net/http" ) @@ -43,7 +44,7 @@ func (s *serverImpl) Start() { } else { err = s.config.HTTPServer.ListenAndServe() } - if err != nil && err != http.ErrServerClosed { + if !errors.Is(err, http.ErrServerClosed) { s.config.Logger.Error("error while running http server: ", err) } }() diff --git a/rest/rest_error.go b/rest/rest_error.go index c01e08a79..773712a6d 100644 --- a/rest/rest_error.go +++ b/rest/rest_error.go @@ -1,6 +1,7 @@ package rest import ( + "errors" "fmt" "net/http" @@ -40,8 +41,8 @@ func NewError(rq *http.Request, rqBody []byte, rs *http.Response, rsBody []byte) // Is returns true if the error is a *Error with the same status code as the target error func (e Error) Is(target error) bool { - err, ok := target.(*Error) - if !ok { + var err *Error + if ok := errors.As(target, &err); !ok { return false } if e.Code != 0 && err.Code != 0 { diff --git a/sharding/shard_manager_impl.go b/sharding/shard_manager_impl.go index 3c169949b..e26c0139c 100644 --- a/sharding/shard_manager_impl.go +++ b/sharding/shard_manager_impl.go @@ -2,6 +2,7 @@ package sharding import ( "context" + "errors" "sync" "github.com/disgoorg/snowflake/v2" @@ -35,7 +36,8 @@ type shardManagerImpl struct { } func (m *shardManagerImpl) closeHandler(shard gateway.Gateway, err error) { - if closeError, ok := err.(*websocket.CloseError); !m.config.AutoScaling || !ok || gateway.CloseEventCodeByCode(closeError.Code) != gateway.CloseEventCodeShardingRequired { + var closeError *websocket.CloseError + if !m.config.AutoScaling || !errors.As(err, &closeError) || gateway.CloseEventCodeByCode(closeError.Code) != gateway.CloseEventCodeShardingRequired { return } m.config.Logger.Debugf("shard %d requires re-sharding", shard.ShardID()) diff --git a/voice/audio_receiver.go b/voice/audio_receiver.go index 79ec5893b..57cad5ab1 100644 --- a/voice/audio_receiver.go +++ b/voice/audio_receiver.go @@ -2,6 +2,7 @@ package voice import ( "context" + "errors" "net" "github.com/disgoorg/log" @@ -82,7 +83,7 @@ func (s *defaultAudioReceiver) CleanupUser(userID snowflake.ID) { func (s *defaultAudioReceiver) receive() { packet, err := s.conn.UDP().ReadPacket() - if err == net.ErrClosed { + if errors.Is(err, net.ErrClosed) { s.Close() return } diff --git a/voice/gateway.go b/voice/gateway.go index 3c2dbc803..5e4849224 100644 --- a/voice/gateway.go +++ b/voice/gateway.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "io" - "net" "sync" "syscall" "time" @@ -171,7 +170,7 @@ func (g *gatewayImpl) CloseWithCode(code int, message string) { defer g.connMu.Unlock() if g.conn != nil { g.config.Logger.Debugf("closing voice gateway connection with code: %d, message: %s", code, message) - if err := g.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(code, message)); err != nil && err != websocket.ErrCloseSent { + if err := g.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(code, message)); err != nil && !errors.Is(err, websocket.ErrCloseSent) { g.config.Logger.Debug("error writing close code. error: ", err) } _ = g.conn.Close() @@ -200,7 +199,7 @@ func (g *gatewayImpl) sendHeartbeat() { defer cancel() if err := g.Send(ctx, OpcodeHeartbeat, GatewayMessageDataHeartbeat(g.lastNonce)); err != nil { - if err != ErrGatewayNotConnected || errors.Is(err, syscall.EPIPE) { + if !errors.Is(err, ErrGatewayNotConnected) || errors.Is(err, syscall.EPIPE) { return } g.config.Logger.Error("failed to send heartbeat. error: ", err) @@ -227,14 +226,10 @@ loop: } reconnect := true - if closeError, ok := err.(*websocket.CloseError); ok { + var closeError *websocket.CloseError + if errors.As(err, &closeError) { closeCode := GatewayCloseEventCodeByCode(closeError.Code) reconnect = closeCode.Reconnect - } else if errors.Is(err, net.ErrClosed) { - // we closed the connection ourselves. Don't try to reconnect here - reconnect = false - } else { - g.config.Logger.Debug("failed to read next message from voice gateway. error: ", err) } g.CloseWithCode(websocket.CloseServiceRestart, "listen error") if g.config.AutoReconnect && reconnect { @@ -350,7 +345,7 @@ func (g *gatewayImpl) reconnectTry(ctx context.Context, try int) error { g.config.Logger.Debug("reconnecting voice gateway...") if err := g.Open(ctx, g.state); err != nil { - if err == discord.ErrGatewayAlreadyConnected { + if errors.Is(err, discord.ErrGatewayAlreadyConnected) { return err } g.config.Logger.Error("failed to reconnect voice gateway. error: ", err) From f1062a5f97ea07c2053ee088640bbede9e33289f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= Date: Sat, 5 Aug 2023 23:29:56 +0200 Subject: [PATCH 095/119] fix minor warnings --- discord/embed_builder.go | 2 +- voice/conn.go | 4 ++-- voice/gateway_messages.go | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/discord/embed_builder.go b/discord/embed_builder.go index 53f2b16a4..d8fb73ab5 100644 --- a/discord/embed_builder.go +++ b/discord/embed_builder.go @@ -127,7 +127,7 @@ func (b *EmbedBuilder) SetFooterText(text string) *EmbedBuilder { return b } -// SetFooterText sets the footer text of the EmbedBuilder with format +// SetFooterTextf sets the footer text of the EmbedBuilder with format func (b *EmbedBuilder) SetFooterTextf(text string, a ...any) *EmbedBuilder { return b.SetFooterText(fmt.Sprintf(text, a...)) } diff --git a/voice/conn.go b/voice/conn.go index c10e4ec8f..b90bd9845 100644 --- a/voice/conn.go +++ b/voice/conn.go @@ -204,7 +204,7 @@ func (c *connImpl) handleMessage(op Opcode, data GatewayMessageData) { break } if err = c.Gateway().Send(ctx, OpcodeSelectProtocol, GatewayMessageDataSelectProtocol{ - Protocol: VoiceProtocolUDP, + Protocol: ProtocolUDP, Data: GatewayMessageDataSelectProtocolData{ Address: ourAddress, Port: ourPort, @@ -241,7 +241,7 @@ func (c *connImpl) handleMessage(op Opcode, data GatewayMessageData) { } } -func (c *connImpl) handleGatewayClose(gateway Gateway, err error) { +func (c *connImpl) handleGatewayClose(_ Gateway, _ error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() c.Close(ctx) diff --git a/voice/gateway_messages.go b/voice/gateway_messages.go index e3842dddb..1a80c75f0 100644 --- a/voice/gateway_messages.go +++ b/voice/gateway_messages.go @@ -136,14 +136,14 @@ type GatewayMessageDataSessionDescription struct { func (GatewayMessageDataSessionDescription) voiceGatewayMessageData() {} -type VoiceProtocol string +type Protocol string const ( - VoiceProtocolUDP VoiceProtocol = "udp" + ProtocolUDP Protocol = "udp" ) type GatewayMessageDataSelectProtocol struct { - Protocol VoiceProtocol `json:"protocol"` + Protocol Protocol `json:"protocol"` Data GatewayMessageDataSelectProtocolData `json:"data"` } From 864959c63af3728a4a5181c3e2bf19b9d66558e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= Date: Sat, 5 Aug 2023 23:33:36 +0200 Subject: [PATCH 096/119] update dependencies --- .gitignore | 2 ++ _examples/application_commands/http/go.mod | 16 +++++------ _examples/application_commands/http/go.sum | 33 +++++++++------------- go.mod | 6 ++-- go.sum | 12 ++++---- 5 files changed, 32 insertions(+), 37 deletions(-) diff --git a/.gitignore b/.gitignore index 3ad1afdd8..0658db50f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .idea/ .env +go.work +go.work.sum diff --git a/_examples/application_commands/http/go.mod b/_examples/application_commands/http/go.mod index 6067252ec..a58b722b4 100644 --- a/_examples/application_commands/http/go.mod +++ b/_examples/application_commands/http/go.mod @@ -2,20 +2,18 @@ module github.com/disgoorg/disgo/_examples/application_commands/http go 1.18 -replace github.com/disgoorg/disgo => ../../../ - require ( - github.com/disgoorg/disgo v0.11.5 - github.com/disgoorg/log v1.2.0 + github.com/disgoorg/disgo v0.16.8 + github.com/disgoorg/log v1.2.1 github.com/disgoorg/snowflake/v2 v2.0.1 - github.com/oasisprotocol/curve25519-voi v0.0.0-20220317090546-adb2f9614b17 + github.com/oasisprotocol/curve25519-voi v0.0.0-20230110094441-db37f07504ce ) require ( - github.com/disgoorg/json v1.0.0 // indirect + github.com/disgoorg/json v1.1.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b // indirect - golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect - golang.org/x/exp v0.0.0-20220325121720-054d8573a5d8 // indirect - golang.org/x/sys v0.1.0 // indirect + golang.org/x/crypto v0.12.0 // indirect + golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b // indirect + golang.org/x/sys v0.11.0 // indirect ) diff --git a/_examples/application_commands/http/go.sum b/_examples/application_commands/http/go.sum index 93cb7acc4..dfd5b37d7 100644 --- a/_examples/application_commands/http/go.sum +++ b/_examples/application_commands/http/go.sum @@ -1,29 +1,24 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/disgoorg/json v1.0.0 h1:kDhSM661fgIuNoZF3BO5/odaR5NSq80AWb937DH+Pdo= -github.com/disgoorg/json v1.0.0/go.mod h1:BHDwdde0rpQFDVsRLKhma6Y7fTbQKub/zdGO5O9NqqA= -github.com/disgoorg/log v1.2.0 h1:sqlXnu/ZKAlIlHV9IO+dbMto7/hCQ474vlIdMWk8QKo= -github.com/disgoorg/log v1.2.0/go.mod h1:3x1KDG6DI1CE2pDwi3qlwT3wlXpeHW/5rVay+1qDqOo= +github.com/disgoorg/disgo v0.16.8 h1:tvUeX+3Iu8U6koDc8RAgcQadRciWJwsI95Y7edHqq2g= +github.com/disgoorg/disgo v0.16.8/go.mod h1:5fsaUpfu6Yv0p+PfmsAeQkV395KQskVu/d1bdq8vsNI= +github.com/disgoorg/json v1.1.0 h1:7xigHvomlVA9PQw9bMGO02PHGJJPqvX5AnwlYg/Tnys= +github.com/disgoorg/json v1.1.0/go.mod h1:BHDwdde0rpQFDVsRLKhma6Y7fTbQKub/zdGO5O9NqqA= +github.com/disgoorg/log v1.2.1 h1:kZYAWkUBcGy4LbZcgYtgYu49xNVLy+xG5Uq3yz5VVQs= +github.com/disgoorg/log v1.2.1/go.mod h1:hhQWYTFTnIGzAuFPZyXJEi11IBm9wq+/TVZt/FEwX0o= github.com/disgoorg/snowflake/v2 v2.0.1 h1:CuUxGLwggUxEswZOmZ+mZ5i0xSumQdXW9tXW7uGqe+0= github.com/disgoorg/snowflake/v2 v2.0.1/go.mod h1:SPU9c2CNn5DSyb86QcKtdZgix9osEtKrHLW4rMhfLCs= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/oasisprotocol/curve25519-voi v0.0.0-20220317090546-adb2f9614b17 h1:pxR+aWfo+famermIZvD+SiDQ3qmF7Iy2VPZuEsKTMtA= -github.com/oasisprotocol/curve25519-voi v0.0.0-20220317090546-adb2f9614b17/go.mod h1:WUcXjUd98qaCVFb6j8Xc87MsKeMCXDu9Nk8JRJ9SeC8= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230110094441-db37f07504ce h1:/pEpMk55wH0X+E5zedGEMOdLuWmV8P4+4W3+LZaM6kg= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230110094441-db37f07504ce/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b h1:qYTY2tN72LhgDj2rtWG+LI6TXFl2ygFQQ4YezfVaGQE= github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b/go.mod h1:/pA7k3zsXKdjjAiUhB5CjuKib9KJGCaLvZwtxGC8U0s= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/exp v0.0.0-20220325121720-054d8573a5d8 h1:Xt4/LzbTwfocTk9ZLEu4onjeFucl88iW+v4j4PWbQuE= -golang.org/x/exp v0.0.0-20220325121720-054d8573a5d8/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI= +golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/go.mod b/go.mod index 274bd5494..2517f4d80 100644 --- a/go.mod +++ b/go.mod @@ -9,13 +9,13 @@ require ( github.com/gorilla/websocket v1.5.0 github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b github.com/stretchr/testify v1.8.1 - golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e - golang.org/x/exp v0.0.0-20220325121720-054d8573a5d8 + golang.org/x/crypto v0.12.0 + golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.1.0 // indirect + golang.org/x/sys v0.11.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 14af0fa8a..5f0f4570e 100644 --- a/go.sum +++ b/go.sum @@ -20,12 +20,12 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/exp v0.0.0-20220325121720-054d8573a5d8 h1:Xt4/LzbTwfocTk9ZLEu4onjeFucl88iW+v4j4PWbQuE= -golang.org/x/exp v0.0.0-20220325121720-054d8573a5d8/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI= +golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 76d3e4d6f14b679239142e5d6a0d99c1734c1eda Mon Sep 17 00:00:00 2001 From: cane Date: Thu, 10 Aug 2023 20:53:34 +0200 Subject: [PATCH 097/119] Add support for setting a custom status (#295) --- gateway/gateway_messages.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/gateway/gateway_messages.go b/gateway/gateway_messages.go index 686d6d06f..2c819dd04 100644 --- a/gateway/gateway_messages.go +++ b/gateway/gateway_messages.go @@ -496,6 +496,15 @@ func WithWatchingActivity(name string) PresenceOpt { }) } +// WithCustomActivity creates a new activity of type discord.ActivityTypeCustom +func WithCustomActivity(status string) PresenceOpt { + return withActivity(discord.Activity{ + Name: "Custom Status", + Type: discord.ActivityTypeCustom, + State: &status, + }) +} + // WithCompetingActivity creates a new "Competing in ..." activity of type discord.ActivityTypeCompeting func WithCompetingActivity(name string) PresenceOpt { return withActivity(discord.Activity{ From 49ab66b44e32eae488ddb3b5fa718eaa1daaa562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= Date: Mon, 14 Aug 2023 16:08:30 +0200 Subject: [PATCH 098/119] add activity opts (#297) Co-authored-by: mlnrDev --- _examples/test/examplebot.go | 2 +- gateway/gateway_messages.go | 39 ++++++++++++++++++++++++------------ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/_examples/test/examplebot.go b/_examples/test/examplebot.go index fb060f5d3..cac003bed 100644 --- a/_examples/test/examplebot.go +++ b/_examples/test/examplebot.go @@ -34,7 +34,7 @@ func main() { client, err := disgo.New(token, bot.WithGatewayConfigOpts( gateway.WithIntents(gateway.IntentsNonPrivileged, gateway.IntentMessageContent), - gateway.WithPresenceOpts(gateway.WithListeningActivity("your bullshit"), gateway.WithOnlineStatus(discord.OnlineStatusDND)), + gateway.WithPresenceOpts(gateway.WithListeningActivity("your bullshit", gateway.WithActivityState("lol")), gateway.WithOnlineStatus(discord.OnlineStatusDND)), ), bot.WithCacheConfigOpts( cache.WithCaches(cache.FlagsAll), diff --git a/gateway/gateway_messages.go b/gateway/gateway_messages.go index 2c819dd04..8eca2e0f4 100644 --- a/gateway/gateway_messages.go +++ b/gateway/gateway_messages.go @@ -461,15 +461,15 @@ func (MessageDataPresenceUpdate) messageData() {} type PresenceOpt func(presenceUpdate *MessageDataPresenceUpdate) // WithPlayingActivity creates a new "Playing ..." activity of type discord.ActivityTypeGame -func WithPlayingActivity(name string) PresenceOpt { +func WithPlayingActivity(name string, opts ...ActivityOpt) PresenceOpt { return withActivity(discord.Activity{ Name: name, Type: discord.ActivityTypeGame, - }) + }, opts...) } // WithStreamingActivity creates a new "Streaming ..." activity of type discord.ActivityTypeStreaming -func WithStreamingActivity(name string, url string) PresenceOpt { +func WithStreamingActivity(name string, url string, opts ...ActivityOpt) PresenceOpt { activity := discord.Activity{ Name: name, Type: discord.ActivityTypeStreaming, @@ -477,44 +477,47 @@ func WithStreamingActivity(name string, url string) PresenceOpt { if url != "" { activity.URL = &url } - return withActivity(activity) + return withActivity(activity, opts...) } // WithListeningActivity creates a new "Listening to ..." activity of type discord.ActivityTypeListening -func WithListeningActivity(name string) PresenceOpt { +func WithListeningActivity(name string, opts ...ActivityOpt) PresenceOpt { return withActivity(discord.Activity{ Name: name, Type: discord.ActivityTypeListening, - }) + }, opts...) } // WithWatchingActivity creates a new "Watching ..." activity of type discord.ActivityTypeWatching -func WithWatchingActivity(name string) PresenceOpt { +func WithWatchingActivity(name string, opts ...ActivityOpt) PresenceOpt { return withActivity(discord.Activity{ Name: name, Type: discord.ActivityTypeWatching, - }) + }, opts...) } // WithCustomActivity creates a new activity of type discord.ActivityTypeCustom -func WithCustomActivity(status string) PresenceOpt { +func WithCustomActivity(status string, opts ...ActivityOpt) PresenceOpt { return withActivity(discord.Activity{ Name: "Custom Status", Type: discord.ActivityTypeCustom, State: &status, - }) + }, opts...) } // WithCompetingActivity creates a new "Competing in ..." activity of type discord.ActivityTypeCompeting -func WithCompetingActivity(name string) PresenceOpt { +func WithCompetingActivity(name string, opts ...ActivityOpt) PresenceOpt { return withActivity(discord.Activity{ Name: name, Type: discord.ActivityTypeCompeting, - }) + }, opts...) } -func withActivity(activity discord.Activity) PresenceOpt { +func withActivity(activity discord.Activity, opts ...ActivityOpt) PresenceOpt { return func(presence *MessageDataPresenceUpdate) { + for _, opt := range opts { + opt(activity) + } presence.Activities = []discord.Activity{activity} } } @@ -540,6 +543,16 @@ func WithSince(since *int64) PresenceOpt { } } +// ActivityOpt is a type alias for a function that sets optional data for an Activity +type ActivityOpt func(activity discord.Activity) + +// WithActivityState sets the Activity.State +func WithActivityState(state string) ActivityOpt { + return func(activity discord.Activity) { + activity.State = &state + } +} + // MessageDataVoiceStateUpdate is used for updating the bots voice state in a guild type MessageDataVoiceStateUpdate struct { GuildID snowflake.ID `json:"guild_id"` From 6d891abd10b4d05a37bd5b19b041ae5c99e73892 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= Date: Wed, 16 Aug 2023 23:39:10 +0200 Subject: [PATCH 099/119] set unset member guild id on unmarshal --- discord/message.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/discord/message.go b/discord/message.go index 3d513a9f2..b30a1a322 100644 --- a/discord/message.go +++ b/discord/message.go @@ -134,6 +134,10 @@ func (m *Message) UnmarshalJSON(data []byte) error { } } + if m.Member != nil && m.GuildID != nil { + m.Member.GuildID = *m.GuildID + } + return nil } From de3966110c9e0edbdf1c714d49fe4fd41083bd4d Mon Sep 17 00:00:00 2001 From: mlnrDev Date: Fri, 18 Aug 2023 20:35:04 +0200 Subject: [PATCH 100/119] fix incorrect Type() for YouTubeIntegration --- discord/integration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/integration.go b/discord/integration.go index 58c580cb8..8ced9d141 100644 --- a/discord/integration.go +++ b/discord/integration.go @@ -154,7 +154,7 @@ func (i YouTubeIntegration) MarshalJSON() ([]byte, error) { } func (YouTubeIntegration) Type() IntegrationType { - return IntegrationTypeTwitch + return IntegrationTypeYouTube } func (i YouTubeIntegration) ID() snowflake.ID { From 7bf4cb16be41a2f77dd70c853103a44a8fd8dd11 Mon Sep 17 00:00:00 2001 From: cane Date: Fri, 18 Aug 2023 20:36:26 +0200 Subject: [PATCH 101/119] Add role subscriptions (#218) --- discord/guild.go | 51 ++++++++++++++++-------------- discord/integration.go | 42 ++++++++++++++++++++++-- discord/invite.go | 1 + discord/message.go | 72 +++++++++++++++++++++++------------------- discord/role.go | 10 +++--- 5 files changed, 114 insertions(+), 62 deletions(-) diff --git a/discord/guild.go b/discord/guild.go index a87272ca7..879a25189 100644 --- a/discord/guild.go +++ b/discord/guild.go @@ -29,6 +29,8 @@ const ( SystemChannelFlagSuppressPremiumSubscriptions SystemChannelFlagSuppressGuildReminderNotifications SystemChannelFlagSuppressJoinNotificationReplies + SystemChannelFlagSuppressRoleSubscriptionPurchaseNotifications + SystemChannelFlagSuppressRoleSubscriptionPurchaseNotificationReplies ) // Add allows you to add multiple bits together, producing a new bit @@ -96,29 +98,32 @@ type GuildFeature string // Constants for GuildFeature const ( - GuildFeatureAnimatedBanner GuildFeature = "ANIMATED_BANNER" - GuildFeatureAnimatedIcon GuildFeature = "ANIMATED_ICON" - GuildFeatureAutoModeration GuildFeature = "AUTO_MODERATION" - GuildFeatureBanner GuildFeature = "BANNER" - GuildFeatureCommunity GuildFeature = "COMMUNITY" - GuildFeatureDeveloperSupportServer GuildFeature = "DEVELOPER_SUPPORT_SERVER" - GuildFeatureDiscoverable GuildFeature = "DISCOVERABLE" - GuildFeatureFeaturable GuildFeature = "FEATURABLE" - GuildFeatureInvitesDisabled GuildFeature = "INVITES_DISABLED" - GuildFeatureInviteSplash GuildFeature = "INVITE_SPLASH" - GuildFeatureMemberVerificationGateEnabled GuildFeature = "MEMBER_VERIFICATION_GATE_ENABLED" - GuildFeatureMonetizationEnabled GuildFeature = "MONETIZATION_ENABLED" - GuildFeatureMoreStickers GuildFeature = "MORE_STICKERS" - GuildFeatureNews GuildFeature = "NEWS" - GuildFeaturePartnered GuildFeature = "PARTNERED" - GuildFeaturePreviewEnabled GuildFeature = "PREVIEW_ENABLED" - GuildFeatureRaidAlertsDisabled GuildFeature = "RAID_ALERTS_DISABLED" - GuildFeatureRoleIcons GuildFeature = "ROLE_ICONS" - GuildFeatureTicketedEventsEnabled GuildFeature = "TICKETED_EVENTS_ENABLED" - GuildFeatureVanityURL GuildFeature = "VANITY_URL" - GuildFeatureVerified GuildFeature = "VERIFIED" - GuildFeatureVipRegions GuildFeature = "VIP_REGIONS" - GuildFeatureWelcomeScreenEnabled GuildFeature = "WELCOME_SCREEN_ENABLED" + GuildFeatureAnimatedBanner GuildFeature = "ANIMATED_BANNER" + GuildFeatureAnimatedIcon GuildFeature = "ANIMATED_ICON" + GuildFeatureAutoModeration GuildFeature = "AUTO_MODERATION" + GuildFeatureBanner GuildFeature = "BANNER" + GuildFeatureCommunity GuildFeature = "COMMUNITY" + GuildFeatureCreatorMonetizableProvisional GuildFeature = "CREATOR_MONETIZABLE_PROVISIONAL" + GuildFeatureCreatorStorePage GuildFeature = "CREATOR_STORE_PAGE" + GuildFeatureDeveloperSupportServer GuildFeature = "DEVELOPER_SUPPORT_SERVER" + GuildFeatureDiscoverable GuildFeature = "DISCOVERABLE" + GuildFeatureFeaturable GuildFeature = "FEATURABLE" + GuildFeatureInvitesDisabled GuildFeature = "INVITES_DISABLED" + GuildFeatureInviteSplash GuildFeature = "INVITE_SPLASH" + GuildFeatureMemberVerificationGateEnabled GuildFeature = "MEMBER_VERIFICATION_GATE_ENABLED" + GuildFeatureMoreStickers GuildFeature = "MORE_STICKERS" + GuildFeatureNews GuildFeature = "NEWS" + GuildFeaturePartnered GuildFeature = "PARTNERED" + GuildFeaturePreviewEnabled GuildFeature = "PREVIEW_ENABLED" + GuildFeatureRaidAlertsDisabled GuildFeature = "RAID_ALERTS_DISABLED" + GuildFeatureRoleIcons GuildFeature = "ROLE_ICONS" + GuildFeatureRoleSubscriptionsAvailableForPurchase GuildFeature = "ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE" + GuildFeatureRoleSubscriptionsEnabled GuildFeature = "ROLE_SUBSCRIPTIONS_ENABLED" + GuildFeatureTicketedEventsEnabled GuildFeature = "TICKETED_EVENTS_ENABLED" + GuildFeatureVanityURL GuildFeature = "VANITY_URL" + GuildFeatureVerified GuildFeature = "VERIFIED" + GuildFeatureVipRegions GuildFeature = "VIP_REGIONS" + GuildFeatureWelcomeScreenEnabled GuildFeature = "WELCOME_SCREEN_ENABLED" ) // Guild represents a discord Guild diff --git a/discord/integration.go b/discord/integration.go index 8ced9d141..cb2168203 100644 --- a/discord/integration.go +++ b/discord/integration.go @@ -13,9 +13,10 @@ type IntegrationType string // All IntegrationType(s) const ( - IntegrationTypeTwitch IntegrationType = "twitch" - IntegrationTypeYouTube IntegrationType = "youtube" - IntegrationTypeBot IntegrationType = "discord" + IntegrationTypeTwitch IntegrationType = "twitch" + IntegrationTypeYouTube IntegrationType = "youtube" + IntegrationTypeBot IntegrationType = "discord" + IntegrationTypeGuildSubscription IntegrationType = "guild_subscription" ) // IntegrationAccount (https://discord.com/developers/docs/resources/guild#integration-account-object) @@ -76,6 +77,11 @@ func (i *UnmarshalIntegration) UnmarshalJSON(data []byte) error { err = json.Unmarshal(data, &v) integration = v + case IntegrationTypeGuildSubscription: + var v GuildSubscriptionIntegration + err = json.Unmarshal(data, &v) + integration = v + default: err = fmt.Errorf("unknown integration with type %s received", cType.Type) } @@ -203,3 +209,33 @@ func (i BotIntegration) ID() snowflake.ID { func (i BotIntegration) CreatedAt() time.Time { return i.IntegrationID.Time() } + +type GuildSubscriptionIntegration struct { + IntegrationID snowflake.ID `json:"id"` + Name string `json:"name"` + Enabled bool `json:"enabled"` + Account IntegrationAccount `json:"account"` +} + +func (i GuildSubscriptionIntegration) MarshalJSON() ([]byte, error) { + type subscriptionIntegration GuildSubscriptionIntegration + return json.Marshal(struct { + Type IntegrationType `json:"type"` + subscriptionIntegration + }{ + Type: i.Type(), + subscriptionIntegration: subscriptionIntegration(i), + }) +} + +func (GuildSubscriptionIntegration) Type() IntegrationType { + return IntegrationTypeGuildSubscription +} + +func (i GuildSubscriptionIntegration) ID() snowflake.ID { + return i.IntegrationID +} + +func (i GuildSubscriptionIntegration) CreatedAt() time.Time { + return i.IntegrationID.Time() +} diff --git a/discord/invite.go b/discord/invite.go index b8aaae5bd..37c96324d 100644 --- a/discord/invite.go +++ b/discord/invite.go @@ -13,6 +13,7 @@ type InviteTargetType int const ( InviteTargetTypeStream InviteTargetType = iota + 1 InviteTargetTypeEmbeddedApplication + InviteTargetTypeRoleSubscriptionsPurchase ) // Invite is a partial invite struct diff --git a/discord/message.go b/discord/message.go index b30a1a322..bc16b8f59 100644 --- a/discord/message.go +++ b/discord/message.go @@ -40,7 +40,7 @@ const ( MessageTypeGuildInviteReminder MessageTypeContextMenuCommand MessageTypeAutoModerationAction - _ + MessageTypeRoleSubscriptionPurchase MessageTypeInteractionPremiumUpsell MessageTypeStageStart MessageTypeStageEnd @@ -81,37 +81,38 @@ func MessageURL(guildID snowflake.ID, channelID snowflake.ID, messageID snowflak // Message is a struct for messages sent in discord text-based channels type Message struct { - ID snowflake.ID `json:"id"` - GuildID *snowflake.ID `json:"guild_id"` - Reactions []MessageReaction `json:"reactions"` - Attachments []Attachment `json:"attachments"` - TTS bool `json:"tts"` - Embeds []Embed `json:"embeds,omitempty"` - Components []ContainerComponent `json:"components,omitempty"` - CreatedAt time.Time `json:"timestamp"` - Mentions []User `json:"mentions"` - MentionEveryone bool `json:"mention_everyone"` - MentionRoles []snowflake.ID `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.ID `json:"channel_id"` - Type MessageType `json:"type"` - Flags MessageFlags `json:"flags"` - MessageReference *MessageReference `json:"message_reference,omitempty"` - Interaction *MessageInteraction `json:"interaction,omitempty"` - WebhookID *snowflake.ID `json:"webhook_id,omitempty"` - Activity *MessageActivity `json:"activity,omitempty"` - Application *MessageApplication `json:"application,omitempty"` - ApplicationID *snowflake.ID `json:"application_id,omitempty"` - StickerItems []MessageSticker `json:"sticker_items,omitempty"` - ReferencedMessage *Message `json:"referenced_message,omitempty"` - LastUpdated *time.Time `json:"last_updated,omitempty"` - Thread *MessageThread `json:"thread,omitempty"` - Position *int `json:"position,omitempty"` + ID snowflake.ID `json:"id"` + GuildID *snowflake.ID `json:"guild_id"` + Reactions []MessageReaction `json:"reactions"` + Attachments []Attachment `json:"attachments"` + TTS bool `json:"tts"` + Embeds []Embed `json:"embeds,omitempty"` + Components []ContainerComponent `json:"components,omitempty"` + CreatedAt time.Time `json:"timestamp"` + Mentions []User `json:"mentions"` + MentionEveryone bool `json:"mention_everyone"` + MentionRoles []snowflake.ID `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.ID `json:"channel_id"` + Type MessageType `json:"type"` + Flags MessageFlags `json:"flags"` + MessageReference *MessageReference `json:"message_reference,omitempty"` + Interaction *MessageInteraction `json:"interaction,omitempty"` + WebhookID *snowflake.ID `json:"webhook_id,omitempty"` + Activity *MessageActivity `json:"activity,omitempty"` + Application *MessageApplication `json:"application,omitempty"` + ApplicationID *snowflake.ID `json:"application_id,omitempty"` + StickerItems []MessageSticker `json:"sticker_items,omitempty"` + ReferencedMessage *Message `json:"referenced_message,omitempty"` + LastUpdated *time.Time `json:"last_updated,omitempty"` + Thread *MessageThread `json:"thread,omitempty"` + Position *int `json:"position,omitempty"` + RoleSubscriptionData *RoleSubscriptionData `json:"role_subscription_data,omitempty"` } func (m *Message) UnmarshalJSON(data []byte) error { @@ -439,3 +440,10 @@ func (f MessageFlags) Has(bits ...MessageFlags) bool { func (f MessageFlags) Missing(bits ...MessageFlags) bool { return flags.Missing(f, bits...) } + +type RoleSubscriptionData struct { + RoleSubscriptionListingID snowflake.ID `json:"role_subscription_listing_id"` + TierName string `json:"tier_name"` + TotalMonthsSubscribed int `json:"total_months_subscribed"` + IsRenewal bool `json:"is_renewal"` +} diff --git a/discord/role.go b/discord/role.go index ece6958ea..9e29c35cd 100644 --- a/discord/role.go +++ b/discord/role.go @@ -49,10 +49,12 @@ func (r Role) CreatedAt() time.Time { // RoleTag are tags a Role has type RoleTag struct { - BotID *snowflake.ID `json:"bot_id,omitempty"` - IntegrationID *snowflake.ID `json:"integration_id,omitempty"` - PremiumSubscriber bool `json:"premium_subscriber"` - GuildConnections bool `json:"guild_connections"` + BotID *snowflake.ID `json:"bot_id,omitempty"` + IntegrationID *snowflake.ID `json:"integration_id,omitempty"` + PremiumSubscriber bool `json:"premium_subscriber"` + SubscriptionListingID *snowflake.ID `json:"subscription_listing_id,omitempty"` + AvailableForPurchase bool `json:"available_for_purchase"` + GuildConnections bool `json:"guild_connections"` } type RoleFlags int From ed27e154d0a82afc6e32da2d58a3249241a93eda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= Date: Sun, 20 Aug 2023 00:16:27 +0200 Subject: [PATCH 102/119] add error handler to router (#299) Co-authored-by: cane --- handler/mux.go | 17 ++++++++++++++++- handler/router.go | 3 ++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/handler/mux.go b/handler/mux.go index 74c453098..5577598f6 100644 --- a/handler/mux.go +++ b/handler/mux.go @@ -8,6 +8,10 @@ import ( "github.com/disgoorg/disgo/events" ) +var defaultErrorHandler = func(e *events.InteractionCreate, err error) { + e.Client().Logger().Errorf("error handling interaction: %v\n", err) +} + // New returns a new Router. func New() *Mux { return &Mux{} @@ -27,6 +31,7 @@ type Mux struct { middlewares []Middleware routes []Route notFoundHandler NotFoundHandler + errorHandler ErrorHandler } // OnEvent is called when a new event is received. @@ -53,7 +58,11 @@ func (r *Mux) OnEvent(event bot.Event) { } if err := r.Handle(path, make(map[string]string), e); err != nil { - event.Client().Logger().Errorf("error handling interaction: %v\n", err) + if r.errorHandler != nil { + r.errorHandler(e, err) + return + } + defaultErrorHandler(e, err) } } @@ -190,6 +199,12 @@ func (r *Mux) NotFound(h NotFoundHandler) { r.notFoundHandler = h } +// Error sets the ErrorHandler for this router. +// This handler only works for the root router and will be ignored for sub routers. +func (r *Mux) Error(h ErrorHandler) { + r.errorHandler = h +} + func checkPattern(pattern string) { if len(pattern) == 0 { panic("pattern must not be empty") diff --git a/handler/router.go b/handler/router.go index 04774a445..33cc71319 100644 --- a/handler/router.go +++ b/handler/router.go @@ -11,7 +11,8 @@ type ( AutocompleteHandler func(e *AutocompleteEvent) error ComponentHandler func(e *ComponentEvent) error ModalHandler func(e *ModalEvent) error - NotFoundHandler func(event *events.InteractionCreate) error + NotFoundHandler func(e *events.InteractionCreate) error + ErrorHandler func(e *events.InteractionCreate, err error) ) var ( From 0feca769e651a10c20398c461e81fff8cb48dbe1 Mon Sep 17 00:00:00 2001 From: cane Date: Thu, 24 Aug 2023 19:58:35 +0200 Subject: [PATCH 103/119] Add IntegrationType to OptionalAuditLogEntryInfo (#300) --- discord/audit_log.go | 1 + 1 file changed, 1 insertion(+) diff --git a/discord/audit_log.go b/discord/audit_log.go index 865d9bbc7..4135c5581 100644 --- a/discord/audit_log.go +++ b/discord/audit_log.go @@ -254,4 +254,5 @@ type OptionalAuditLogEntryInfo struct { ApplicationID *snowflake.ID `json:"application_id"` AutoModerationRuleName *string `json:"auto_moderation_rule_name"` AutoModerationRuleTriggerType *AutoModerationTriggerType `json:"auto_moderation_rule_trigger_type,string"` + IntegrationType *IntegrationType `json:"integration_type"` } From 16c4e0a4d847f7bbdff4542592e20ed320fd16b1 Mon Sep 17 00:00:00 2001 From: cane Date: Thu, 24 Aug 2023 22:55:20 +0200 Subject: [PATCH 104/119] Add guild navigation mentions (#286) --- discord/mentionable.go | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/discord/mentionable.go b/discord/mentionable.go index bf2e47b0e..3f8c4087e 100644 --- a/discord/mentionable.go +++ b/discord/mentionable.go @@ -12,14 +12,15 @@ type MentionType struct { } var ( - MentionTypeUser = MentionType{regexp.MustCompile(`<@!?(\d+)>`)} - MentionTypeRole = MentionType{regexp.MustCompile(`<@&(\d+)>`)} - MentionTypeChannel = MentionType{regexp.MustCompile(`<#(\d+)>`)} - MentionTypeEmoji = MentionType{regexp.MustCompile(``)} - MentionTypeTimestamp = MentionType{regexp.MustCompile(`-?\d{1,17})(?::(?P[tTdDfFR]))?>`)} - MentionTypeSlashCommand = MentionType{regexp.MustCompile(``)} - MentionTypeHere = MentionType{regexp.MustCompile(`@here`)} - MentionTypeEveryone = MentionType{regexp.MustCompile(`@everyone`)} + MentionTypeUser = MentionType{regexp.MustCompile(`<@!?(\d+)>`)} + MentionTypeRole = MentionType{regexp.MustCompile(`<@&(\d+)>`)} + MentionTypeChannel = MentionType{regexp.MustCompile(`<#(\d+)>`)} + MentionTypeEmoji = MentionType{regexp.MustCompile(``)} + MentionTypeTimestamp = MentionType{regexp.MustCompile(`-?\d{1,17})(?::(?P[tTdDfFR]))?>`)} + MentionTypeSlashCommand = MentionType{regexp.MustCompile(``)} + MentionTypeHere = MentionType{regexp.MustCompile(`@here`)} + MentionTypeEveryone = MentionType{regexp.MustCompile(`@everyone`)} + MentionTypeGuildNavigation = MentionType{regexp.MustCompile("")} ) type Mentionable interface { @@ -70,3 +71,15 @@ func FormattedTimestampMention(timestamp int64, style TimestampStyle) string { func SlashCommandMention(id snowflake.ID, path string) string { return fmt.Sprintf("", path, id) } + +func NavigationBrowseMention() string { + return "" +} + +func NavigationCustomizeMention() string { + return "" +} + +func NavigationGuideMention() string { + return "" +} From cdef17ea6919f6456cc89e635a239b611e04ebb4 Mon Sep 17 00:00:00 2001 From: cane Date: Thu, 24 Aug 2023 22:56:26 +0200 Subject: [PATCH 105/119] Add super reactions (#291) --- discord/message.go | 18 +++++++++++++----- events/dm_message_reaction_events.go | 10 ++++++---- events/guild_message_reaction_events.go | 12 +++++++----- events/message_reaction_events.go | 12 +++++++----- gateway/gateway_events.go | 14 +++++++++----- handlers/message_reaction_handler.go | 12 ++++++++++++ 6 files changed, 54 insertions(+), 24 deletions(-) diff --git a/discord/message.go b/discord/message.go index bc16b8f59..93ef38181 100644 --- a/discord/message.go +++ b/discord/message.go @@ -345,11 +345,19 @@ type MessageSticker struct { FormatType StickerFormatType `json:"format_type"` } -// MessageReaction contains information about the reactions of a message_events +// MessageReaction contains information about the reactions of a message type MessageReaction struct { - Count int `json:"count"` - Me bool `json:"me"` - Emoji Emoji `json:"emoji"` + Count int `json:"count"` + CountDetails ReactionCountDetails `json:"count_details"` + Me bool `json:"me"` + MeBurst bool `json:"me_burst"` + Emoji Emoji `json:"emoji"` + BurstColors []string `json:"burst_colors"` +} + +type ReactionCountDetails struct { + Burst int `json:"burst"` + Normal int `json:"normal"` } // MessageActivityType is the type of MessageActivity https://com/developers/docs/resources/channel#message-object-message-activity-types @@ -387,7 +395,7 @@ type MessageReference struct { FailIfNotExists bool `json:"fail_if_not_exists,omitempty"` } -// MessageInteraction is sent on the Message object when the message_events is a response to an interaction +// MessageInteraction is sent on the Message object when the message is a response to an interaction type MessageInteraction struct { ID snowflake.ID `json:"id"` Type InteractionType `json:"type"` diff --git a/events/dm_message_reaction_events.go b/events/dm_message_reaction_events.go index 3c8fd7d1d..20de1db55 100644 --- a/events/dm_message_reaction_events.go +++ b/events/dm_message_reaction_events.go @@ -9,10 +9,12 @@ import ( // GenericDMMessageReaction is called upon receiving DMMessageReactionAdd or DMMessageReactionRemove (requires the gateway.IntentDirectMessageReactions) type GenericDMMessageReaction struct { *GenericEvent - UserID snowflake.ID - ChannelID snowflake.ID - MessageID snowflake.ID - Emoji discord.PartialEmoji + UserID snowflake.ID + ChannelID snowflake.ID + MessageID snowflake.ID + Emoji discord.PartialEmoji + BurstColors []string + Burst bool } // DMMessageReactionAdd indicates that a discord.User added a discord.MessageReaction to a discord.Message in a Channel (requires the gateway.IntentDirectMessageReactions) diff --git a/events/guild_message_reaction_events.go b/events/guild_message_reaction_events.go index 575e8e97a..c03773747 100644 --- a/events/guild_message_reaction_events.go +++ b/events/guild_message_reaction_events.go @@ -9,11 +9,13 @@ import ( // GenericGuildMessageReaction is called upon receiving GuildMessageReactionAdd or GuildMessageReactionRemove type GenericGuildMessageReaction struct { *GenericEvent - UserID snowflake.ID - ChannelID snowflake.ID - MessageID snowflake.ID - GuildID snowflake.ID - Emoji discord.PartialEmoji + UserID snowflake.ID + ChannelID snowflake.ID + MessageID snowflake.ID + GuildID snowflake.ID + Emoji discord.PartialEmoji + BurstColors []string + Burst bool } // Member returns the Member that reacted to the discord.Message from the cache. diff --git a/events/message_reaction_events.go b/events/message_reaction_events.go index 583e7243d..6e0070169 100644 --- a/events/message_reaction_events.go +++ b/events/message_reaction_events.go @@ -9,11 +9,13 @@ import ( // GenericReaction is called upon receiving MessageReactionAdd or MessageReactionRemove type GenericReaction struct { *GenericEvent - UserID snowflake.ID - ChannelID snowflake.ID - MessageID snowflake.ID - GuildID *snowflake.ID - Emoji discord.PartialEmoji + UserID snowflake.ID + ChannelID snowflake.ID + MessageID snowflake.ID + GuildID *snowflake.ID + Emoji discord.PartialEmoji + BurstColors []string + Burst bool } // MessageReactionAdd indicates that a discord.User added a discord.MessageReaction to a discord.Message in a discord.Channel(this+++ requires the gateway.IntentGuildMessageReactions and/or gateway.IntentDirectMessageReactions) diff --git a/gateway/gateway_events.go b/gateway/gateway_events.go index 7fec6f900..34a85a6b9 100644 --- a/gateway/gateway_events.go +++ b/gateway/gateway_events.go @@ -194,6 +194,8 @@ type EventMessageReactionAdd struct { Member *discord.Member `json:"member"` Emoji discord.PartialEmoji `json:"emoji"` MessageAuthorID *snowflake.ID `json:"message_author_id"` + BurstColors []string `json:"burst_colors"` + Burst bool `json:"burst"` } func (e *EventMessageReactionAdd) UnmarshalJSON(data []byte) error { @@ -213,11 +215,13 @@ func (EventMessageReactionAdd) messageData() {} func (EventMessageReactionAdd) eventData() {} type EventMessageReactionRemove struct { - UserID snowflake.ID `json:"user_id"` - ChannelID snowflake.ID `json:"channel_id"` - MessageID snowflake.ID `json:"message_id"` - GuildID *snowflake.ID `json:"guild_id"` - Emoji discord.PartialEmoji `json:"emoji"` + UserID snowflake.ID `json:"user_id"` + ChannelID snowflake.ID `json:"channel_id"` + MessageID snowflake.ID `json:"message_id"` + GuildID *snowflake.ID `json:"guild_id"` + Emoji discord.PartialEmoji `json:"emoji"` + BurstColors []string `json:"burst_colors"` + Burst bool `json:"burst"` } func (EventMessageReactionRemove) messageData() {} diff --git a/handlers/message_reaction_handler.go b/handlers/message_reaction_handler.go index 54cb630db..1b1eb6884 100644 --- a/handlers/message_reaction_handler.go +++ b/handlers/message_reaction_handler.go @@ -17,6 +17,8 @@ func gatewayHandlerMessageReactionAdd(client bot.Client, sequenceNumber int, sha GuildID: event.GuildID, UserID: event.UserID, Emoji: event.Emoji, + BurstColors: event.BurstColors, + Burst: event.Burst, }, Member: event.Member, }) @@ -29,6 +31,8 @@ func gatewayHandlerMessageReactionAdd(client bot.Client, sequenceNumber int, sha ChannelID: event.ChannelID, UserID: event.UserID, Emoji: event.Emoji, + BurstColors: event.BurstColors, + Burst: event.Burst, }, MessageAuthorID: event.MessageAuthorID, }) @@ -41,6 +45,8 @@ func gatewayHandlerMessageReactionAdd(client bot.Client, sequenceNumber int, sha GuildID: *event.GuildID, UserID: event.UserID, Emoji: event.Emoji, + BurstColors: event.BurstColors, + Burst: event.Burst, }, Member: *event.Member, MessageAuthorID: event.MessageAuthorID, @@ -59,6 +65,8 @@ func gatewayHandlerMessageReactionRemove(client bot.Client, sequenceNumber int, GuildID: event.GuildID, UserID: event.UserID, Emoji: event.Emoji, + BurstColors: event.BurstColors, + Burst: event.Burst, }, }) @@ -70,6 +78,8 @@ func gatewayHandlerMessageReactionRemove(client bot.Client, sequenceNumber int, ChannelID: event.ChannelID, UserID: event.UserID, Emoji: event.Emoji, + BurstColors: event.BurstColors, + Burst: event.Burst, }, }) } else { @@ -81,6 +91,8 @@ func gatewayHandlerMessageReactionRemove(client bot.Client, sequenceNumber int, GuildID: *event.GuildID, UserID: event.UserID, Emoji: event.Emoji, + BurstColors: event.BurstColors, + Burst: event.Burst, }, }) } From 650fa09b5d702145a3d32bd4655a5be405f4508f Mon Sep 17 00:00:00 2001 From: mlnrDev Date: Thu, 31 Aug 2023 18:28:07 +0200 Subject: [PATCH 106/119] Fix incorrect docs for Channel() funcs --- events/guild_invite_events.go | 2 +- events/guild_member_events.go | 2 +- events/guild_message_events.go | 2 +- events/guild_webhooks_update_events.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/events/guild_invite_events.go b/events/guild_invite_events.go index a733f017e..756bf22c4 100644 --- a/events/guild_invite_events.go +++ b/events/guild_invite_events.go @@ -14,7 +14,7 @@ type GenericInvite struct { Code string } -// Channel returns the Channel the GenericInvite happened in. +// Channel returns the discord.GuildChannel the GenericInvite happened in. func (e *GenericInvite) Channel() (discord.GuildChannel, bool) { return e.Client().Caches().Channel(e.ChannelID) } diff --git a/events/guild_member_events.go b/events/guild_member_events.go index 6189a5d79..4f649d9e3 100644 --- a/events/guild_member_events.go +++ b/events/guild_member_events.go @@ -47,7 +47,7 @@ type GuildMemberTypingStart struct { Member discord.Member } -// Channel returns the discord.BaseGuildMessageChannel the GuildMemberTypingStart happened in +// Channel returns the discord.GuildMessageChannel the GuildMemberTypingStart happened in func (e *GuildMemberTypingStart) Channel() (discord.GuildMessageChannel, bool) { return e.Client().Caches().GuildMessageChannel(e.ChannelID) } diff --git a/events/guild_message_events.go b/events/guild_message_events.go index 67e973e42..184cc68eb 100644 --- a/events/guild_message_events.go +++ b/events/guild_message_events.go @@ -21,7 +21,7 @@ func (e *GenericGuildMessage) Guild() (discord.Guild, bool) { return e.Client().Caches().Guild(e.GuildID) } -// Channel returns the discord.DMChannel where the GenericGuildMessage happened +// Channel returns the discord.GuildMessageChannel where the GenericGuildMessage happened func (e *GenericGuildMessage) Channel() (discord.GuildMessageChannel, bool) { return e.Client().Caches().GuildMessageChannel(e.ChannelID) } diff --git a/events/guild_webhooks_update_events.go b/events/guild_webhooks_update_events.go index e41fbf944..f1f44d95e 100644 --- a/events/guild_webhooks_update_events.go +++ b/events/guild_webhooks_update_events.go @@ -19,7 +19,7 @@ func (e *WebhooksUpdate) Guild() (discord.Guild, bool) { return e.Client().Caches().Guild(e.GuildId) } -// Channel returns the Channel the webhook was updated in. +// Channel returns the discord.GuildMessageChannel webhook was updated in. // This will only return cached channels! func (e *WebhooksUpdate) Channel() (discord.GuildMessageChannel, bool) { return e.Client().Caches().GuildMessageChannel(e.ChannelID) From e8063e47c6d2f84f3785385bdf4bf1a9fa34d99a Mon Sep 17 00:00:00 2001 From: cane Date: Sun, 3 Sep 2023 21:21:37 +0200 Subject: [PATCH 107/119] Fix typo in func name (#307) --- bot/config.go | 8 ++++---- gateway/gateway_config.go | 12 ++++++------ rest/rest_client.go | 2 +- rest/rest_config.go | 22 +++++++++++----------- sharding/shard_manager_config.go | 14 +++++++------- sharding/shard_rate_limiter_impl.go | 4 ++-- 6 files changed, 31 insertions(+), 31 deletions(-) diff --git a/bot/config.go b/bot/config.go index a0575299f..4108ccb4d 100644 --- a/bot/config.go +++ b/bot/config.go @@ -231,7 +231,7 @@ func BuildClient(token string, config Config, gatewayEventHandlerFunc func(clien rest.WithUserAgent(fmt.Sprintf("DiscordBot (%s, %s)", github, version)), rest.WithLogger(client.logger), func(config *rest.Config) { - config.RateRateLimiterConfigOpts = append([]rest.RateLimiterConfigOpt{rest.WithRateLimiterLogger(client.logger)}, config.RateRateLimiterConfigOpts...) + config.RateLimiterConfigOpts = append([]rest.RateLimiterConfigOpt{rest.WithRateLimiterLogger(client.logger)}, config.RateLimiterConfigOpts...) }, }, config.RestClientConfigOpts...) @@ -267,7 +267,7 @@ func BuildClient(token string, config Config, gatewayEventHandlerFunc func(clien gateway.WithBrowser(name), gateway.WithDevice(name), func(config *gateway.Config) { - config.RateRateLimiterConfigOpts = append([]gateway.RateLimiterConfigOpt{gateway.WithRateLimiterLogger(client.logger)}, config.RateRateLimiterConfigOpts...) + config.RateLimiterConfigOpts = append([]gateway.RateLimiterConfigOpt{gateway.WithRateLimiterLogger(client.logger)}, config.RateLimiterConfigOpts...) }, }, config.GatewayConfigOpts...) @@ -297,12 +297,12 @@ func BuildClient(token string, config Config, gatewayEventHandlerFunc func(clien gateway.WithBrowser(name), gateway.WithDevice(name), func(config *gateway.Config) { - config.RateRateLimiterConfigOpts = append([]gateway.RateLimiterConfigOpt{gateway.WithRateLimiterLogger(client.logger)}, config.RateRateLimiterConfigOpts...) + config.RateLimiterConfigOpts = append([]gateway.RateLimiterConfigOpt{gateway.WithRateLimiterLogger(client.logger)}, config.RateLimiterConfigOpts...) }, ), sharding.WithLogger(client.logger), func(config *sharding.Config) { - config.RateRateLimiterConfigOpts = append([]sharding.RateLimiterConfigOpt{sharding.WithRateLimiterLogger(client.logger), sharding.WithMaxConcurrency(gatewayBotRs.SessionStartLimit.MaxConcurrency)}, config.RateRateLimiterConfigOpts...) + config.RateLimiterConfigOpts = append([]sharding.RateLimiterConfigOpt{sharding.WithRateLimiterLogger(client.logger), sharding.WithMaxConcurrency(gatewayBotRs.SessionStartLimit.MaxConcurrency)}, config.RateLimiterConfigOpts...) }, }, config.ShardManagerConfigOpts...) diff --git a/gateway/gateway_config.go b/gateway/gateway_config.go index bb02be4d1..26d17df68 100644 --- a/gateway/gateway_config.go +++ b/gateway/gateway_config.go @@ -54,8 +54,8 @@ type Config struct { EnableResumeURL bool // RateLimiter is the RateLimiter of the Gateway. Defaults to NewRateLimiter(). RateLimiter RateLimiter - // RateRateLimiterConfigOpts is the RateLimiterConfigOpts of the Gateway. Defaults to nil. - RateRateLimiterConfigOpts []RateLimiterConfigOpt + // RateLimiterConfigOpts is the RateLimiterConfigOpts of the Gateway. Defaults to nil. + RateLimiterConfigOpts []RateLimiterConfigOpt // Presence is the presence it should send on login. Defaults to nil. Presence *MessageDataPresenceUpdate // OS is the OS it should send on login. Defaults to runtime.GOOS. @@ -75,7 +75,7 @@ func (c *Config) Apply(opts []ConfigOpt) { opt(c) } if c.RateLimiter == nil { - c.RateLimiter = NewRateLimiter(c.RateRateLimiterConfigOpts...) + c.RateLimiter = NewRateLimiter(c.RateLimiterConfigOpts...) } } @@ -184,10 +184,10 @@ func WithRateLimiter(rateLimiter RateLimiter) ConfigOpt { } } -// WithRateRateLimiterConfigOpts lets you configure the default RateLimiter. -func WithRateRateLimiterConfigOpts(opts ...RateLimiterConfigOpt) ConfigOpt { +// WithRateLimiterConfigOpts lets you configure the default RateLimiter. +func WithRateLimiterConfigOpts(opts ...RateLimiterConfigOpt) ConfigOpt { return func(config *Config) { - config.RateRateLimiterConfigOpts = append(config.RateRateLimiterConfigOpts, opts...) + config.RateLimiterConfigOpts = append(config.RateLimiterConfigOpts, opts...) } } diff --git a/rest/rest_client.go b/rest/rest_client.go index 8330178b9..e50edf6d3 100644 --- a/rest/rest_client.go +++ b/rest/rest_client.go @@ -32,7 +32,7 @@ type Client interface { // HTTPClient returns the http.Client the rest client uses HTTPClient() *http.Client - // RateLimiter returns the rrate.RateLimiter the rest client uses + // RateLimiter returns the RateLimiter the rest client uses RateLimiter() RateLimiter // Close closes the rest client and awaits all pending requests to finish. You can use a cancelling context to abort the waiting diff --git a/rest/rest_config.go b/rest/rest_config.go index 799b49037..65de4cc15 100644 --- a/rest/rest_config.go +++ b/rest/rest_config.go @@ -19,12 +19,12 @@ func DefaultConfig() *Config { // Config is the configuration for the rest client type Config struct { - Logger log.Logger - HTTPClient *http.Client - RateLimiter RateLimiter - RateRateLimiterConfigOpts []RateLimiterConfigOpt - URL string - UserAgent string + Logger log.Logger + HTTPClient *http.Client + RateLimiter RateLimiter + RateLimiterConfigOpts []RateLimiterConfigOpt + URL string + UserAgent string } // ConfigOpt can be used to supply optional parameters to NewClient @@ -36,7 +36,7 @@ func (c *Config) Apply(opts []ConfigOpt) { opt(c) } if c.RateLimiter == nil { - c.RateLimiter = NewRateLimiter(c.RateRateLimiterConfigOpts...) + c.RateLimiter = NewRateLimiter(c.RateLimiterConfigOpts...) } } @@ -54,17 +54,17 @@ func WithHTTPClient(httpClient *http.Client) ConfigOpt { } } -// WithRateLimiter applies a custom rrate.RateLimiter to the rest client +// WithRateLimiter applies a custom RateLimiter to the rest client func WithRateLimiter(rateLimiter RateLimiter) ConfigOpt { return func(config *Config) { config.RateLimiter = rateLimiter } } -// WithRateRateLimiterConfigOpts applies rrate.ConfigOpt for the rrate.RateLimiter to the rest rate limiter -func WithRateRateLimiterConfigOpts(opts ...RateLimiterConfigOpt) ConfigOpt { +// WithRateLimiterConfigOpts applies RateLimiterConfigOpt to the RateLimiter +func WithRateLimiterConfigOpts(opts ...RateLimiterConfigOpt) ConfigOpt { return func(config *Config) { - config.RateRateLimiterConfigOpts = append(config.RateRateLimiterConfigOpts, opts...) + config.RateLimiterConfigOpts = append(config.RateLimiterConfigOpts, opts...) } } diff --git a/sharding/shard_manager_config.go b/sharding/shard_manager_config.go index bb2ecb3d4..36adb92cf 100644 --- a/sharding/shard_manager_config.go +++ b/sharding/shard_manager_config.go @@ -33,8 +33,8 @@ type Config struct { GatewayConfigOpts []gateway.ConfigOpt // RateLimiter is the RateLimiter which is used by the ShardManager. Defaults to NewRateLimiter() RateLimiter RateLimiter - // RateRateLimiterConfigOpts are the RateLimiterConfigOpt(s) which are applied to the RateLimiter. - RateRateLimiterConfigOpts []RateLimiterConfigOpt + // RateLimiterConfigOpts are the RateLimiterConfigOpt(s) which are applied to the RateLimiter. + RateLimiterConfigOpts []RateLimiterConfigOpt } // ConfigOpt is a type alias for a function that takes a Config and is used to configure your Server. @@ -46,7 +46,7 @@ func (c *Config) Apply(opts []ConfigOpt) { opt(c) } if c.RateLimiter == nil { - c.RateLimiter = NewRateLimiter(c.RateRateLimiterConfigOpts...) + c.RateLimiter = NewRateLimiter(c.RateLimiterConfigOpts...) } } @@ -105,16 +105,16 @@ func WithGatewayConfigOpts(opts ...gateway.ConfigOpt) ConfigOpt { } } -// WithRateLimiter lets you inject your own srate.RateLimiter into the ShardManager. +// WithRateLimiter lets you inject your own RateLimiter into the ShardManager. func WithRateLimiter(rateLimiter RateLimiter) ConfigOpt { return func(config *Config) { config.RateLimiter = rateLimiter } } -// WithRateRateLimiterConfigOpt lets you configure the default srate.RateLimiter used by the ShardManager. -func WithRateRateLimiterConfigOpt(opts ...RateLimiterConfigOpt) ConfigOpt { +// WithRateLimiterConfigOpt lets you configure the default RateLimiter used by the ShardManager. +func WithRateLimiterConfigOpt(opts ...RateLimiterConfigOpt) ConfigOpt { return func(config *Config) { - config.RateRateLimiterConfigOpts = append(config.RateRateLimiterConfigOpts, opts...) + config.RateLimiterConfigOpts = append(config.RateLimiterConfigOpts, opts...) } } diff --git a/sharding/shard_rate_limiter_impl.go b/sharding/shard_rate_limiter_impl.go index ed4305275..315487538 100644 --- a/sharding/shard_rate_limiter_impl.go +++ b/sharding/shard_rate_limiter_impl.go @@ -46,10 +46,10 @@ func (r *rateLimiterImpl) Close(ctx context.Context) { } func (r *rateLimiterImpl) getBucket(shardID int, create bool) *bucket { - r.config.Logger.Debug("locking shard srate limiter") + r.config.Logger.Debug("locking shard rate limiter") r.mu.Lock() defer func() { - r.config.Logger.Debug("unlocking shard srate limiter") + r.config.Logger.Debug("unlocking shard rate limiter") r.mu.Unlock() }() key := ShardMaxConcurrencyKey(shardID, r.config.MaxConcurrency) From 3bab561e449ae690185fd01fb954b53c2472c6f4 Mon Sep 17 00:00:00 2001 From: mlnrDev Date: Sun, 3 Sep 2023 21:31:35 +0200 Subject: [PATCH 108/119] I made a typo while fixing one --- events/guild_webhooks_update_events.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/events/guild_webhooks_update_events.go b/events/guild_webhooks_update_events.go index f1f44d95e..25857a76d 100644 --- a/events/guild_webhooks_update_events.go +++ b/events/guild_webhooks_update_events.go @@ -19,7 +19,7 @@ func (e *WebhooksUpdate) Guild() (discord.Guild, bool) { return e.Client().Caches().Guild(e.GuildId) } -// Channel returns the discord.GuildMessageChannel webhook was updated in. +// Channel returns the discord.GuildMessageChannel the webhook was updated in. // This will only return cached channels! func (e *WebhooksUpdate) Channel() (discord.GuildMessageChannel, bool) { return e.Client().Caches().GuildMessageChannel(e.ChannelID) From 9880cbcca44fc4caa2afd6dfee0bb56af0a2bec2 Mon Sep 17 00:00:00 2001 From: mlnrDev Date: Sun, 3 Sep 2023 22:41:50 +0200 Subject: [PATCH 109/119] Add missing func to interface --- rest/channels.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest/channels.go b/rest/channels.go index 1ae7dee40..3afd45565 100644 --- a/rest/channels.go +++ b/rest/channels.go @@ -46,7 +46,7 @@ type Channels interface { GetPinnedMessages(channelID snowflake.ID, opts ...RequestOpt) ([]discord.Message, error) PinMessage(channelID snowflake.ID, messageID snowflake.ID, opts ...RequestOpt) error UnpinMessage(channelID snowflake.ID, messageID snowflake.ID, opts ...RequestOpt) error - // TODO: add missing endpoints + Follow(channelID snowflake.ID, targetChannelID snowflake.ID, opts ...RequestOpt) (*discord.FollowedChannel, error) } type channelImpl struct { From 16e4552c54c391bbc8e970007f8c6dcc401997cd Mon Sep 17 00:00:00 2001 From: cane Date: Mon, 11 Sep 2023 21:34:53 +0200 Subject: [PATCH 110/119] Fix voice and stage channel structure (#310) --- discord/channel.go | 129 ++++++++++++++++++++++++-------------- discord/channel_create.go | 10 ++- discord/channel_update.go | 3 +- discord/channels_raw.go | 38 +++++------ 4 files changed, 111 insertions(+), 69 deletions(-) diff --git a/discord/channel.go b/discord/channel.go index 4b0e9caf6..3abec1d1b 100644 --- a/discord/channel.go +++ b/discord/channel.go @@ -431,22 +431,19 @@ var ( ) type GuildVoiceChannel struct { - id snowflake.ID - guildID snowflake.ID - position int - permissionOverwrites []PermissionOverwrite - name string - bitrate int - UserLimit int - parentID *snowflake.ID - rtcRegion string - VideoQualityMode VideoQualityMode - lastMessageID *snowflake.ID - lastPinTimestamp *time.Time - topic *string - nsfw bool - defaultAutoArchiveDuration AutoArchiveDuration - rateLimitPerUser int + id snowflake.ID + guildID snowflake.ID + position int + permissionOverwrites []PermissionOverwrite + name string + bitrate int + UserLimit int + parentID *snowflake.ID + rtcRegion string + VideoQualityMode VideoQualityMode + lastMessageID *snowflake.ID + nsfw bool + rateLimitPerUser int } func (c *GuildVoiceChannel) UnmarshalJSON(data []byte) error { @@ -466,33 +463,27 @@ func (c *GuildVoiceChannel) UnmarshalJSON(data []byte) error { c.rtcRegion = v.RTCRegion c.VideoQualityMode = v.VideoQualityMode c.lastMessageID = v.LastMessageID - c.lastPinTimestamp = v.LastPinTimestamp - c.topic = v.Topic c.nsfw = v.NSFW - c.defaultAutoArchiveDuration = v.DefaultAutoArchiveDuration c.rateLimitPerUser = v.RateLimitPerUser return nil } func (c GuildVoiceChannel) MarshalJSON() ([]byte, error) { return json.Marshal(guildVoiceChannel{ - ID: c.id, - Type: c.Type(), - GuildID: c.guildID, - Position: c.position, - PermissionOverwrites: c.permissionOverwrites, - Name: c.name, - Bitrate: c.bitrate, - UserLimit: c.UserLimit, - ParentID: c.parentID, - RTCRegion: c.rtcRegion, - VideoQualityMode: c.VideoQualityMode, - LastMessageID: c.lastMessageID, - LastPinTimestamp: c.lastPinTimestamp, - Topic: c.topic, - NSFW: c.nsfw, - DefaultAutoArchiveDuration: c.defaultAutoArchiveDuration, - RateLimitPerUser: c.rateLimitPerUser, + ID: c.id, + Type: c.Type(), + GuildID: c.guildID, + Position: c.position, + PermissionOverwrites: c.permissionOverwrites, + Name: c.name, + Bitrate: c.bitrate, + UserLimit: c.UserLimit, + ParentID: c.parentID, + RTCRegion: c.rtcRegion, + VideoQualityMode: c.VideoQualityMode, + LastMessageID: c.lastMessageID, + NSFW: c.nsfw, + RateLimitPerUser: c.rateLimitPerUser, }) } @@ -544,20 +535,23 @@ func (c GuildVoiceChannel) LastMessageID() *snowflake.ID { return c.lastMessageID } +// LastPinTimestamp always returns nil for GuildVoiceChannel(s) as they cannot have pinned messages. func (c GuildVoiceChannel) LastPinTimestamp() *time.Time { - return c.lastPinTimestamp + return nil } +// Topic always returns nil for GuildVoiceChannel(s) as they do not have their own topic. func (c GuildVoiceChannel) Topic() *string { - return c.topic + return nil } func (c GuildVoiceChannel) NSFW() bool { return c.nsfw } +// DefaultAutoArchiveDuration is always 0 for GuildVoiceChannel(s) as they do not have their own AutoArchiveDuration. func (c GuildVoiceChannel) DefaultAutoArchiveDuration() AutoArchiveDuration { - return c.defaultAutoArchiveDuration + return 0 } func (c GuildVoiceChannel) RateLimitPerUser() int { @@ -930,9 +924,10 @@ func (GuildThread) messageChannel() {} func (GuildThread) guildMessageChannel() {} var ( - _ Channel = (*GuildStageVoiceChannel)(nil) - _ GuildChannel = (*GuildStageVoiceChannel)(nil) - _ GuildAudioChannel = (*GuildStageVoiceChannel)(nil) + _ Channel = (*GuildStageVoiceChannel)(nil) + _ GuildChannel = (*GuildStageVoiceChannel)(nil) + _ GuildAudioChannel = (*GuildStageVoiceChannel)(nil) + _ GuildMessageChannel = (*GuildStageVoiceChannel)(nil) ) type GuildStageVoiceChannel struct { @@ -944,6 +939,10 @@ type GuildStageVoiceChannel struct { bitrate int parentID *snowflake.ID rtcRegion string + VideoQualityMode VideoQualityMode + lastMessageID *snowflake.ID + nsfw bool + rateLimitPerUser int } func (c *GuildStageVoiceChannel) UnmarshalJSON(data []byte) error { @@ -960,6 +959,10 @@ func (c *GuildStageVoiceChannel) UnmarshalJSON(data []byte) error { c.bitrate = v.Bitrate c.parentID = v.ParentID c.rtcRegion = v.RTCRegion + c.VideoQualityMode = v.VideoQualityMode + c.lastMessageID = v.LastMessageID + c.nsfw = v.NSFW + c.rateLimitPerUser = v.RateLimitPerUser return nil } @@ -974,6 +977,10 @@ func (c GuildStageVoiceChannel) MarshalJSON() ([]byte, error) { Bitrate: c.bitrate, ParentID: c.parentID, RTCRegion: c.rtcRegion, + VideoQualityMode: c.VideoQualityMode, + LastMessageID: c.lastMessageID, + NSFW: c.nsfw, + RateLimitPerUser: c.rateLimitPerUser, }) } @@ -1021,13 +1028,42 @@ func (c GuildStageVoiceChannel) ParentID() *snowflake.ID { return c.parentID } +func (c GuildStageVoiceChannel) LastMessageID() *snowflake.ID { + return c.lastMessageID +} + +// LastPinTimestamp always returns nil for GuildStageVoiceChannel(s) as they cannot have pinned messages. +func (c GuildStageVoiceChannel) LastPinTimestamp() *time.Time { + return nil +} + +// Topic always returns nil for GuildStageVoiceChannel(s) as they do not have their own topic. +func (c GuildStageVoiceChannel) Topic() *string { + return nil +} + +func (c GuildStageVoiceChannel) NSFW() bool { + return c.nsfw +} + +// DefaultAutoArchiveDuration is always 0 for GuildStageVoiceChannel(s) as they do not have their own AutoArchiveDuration. +func (c GuildStageVoiceChannel) DefaultAutoArchiveDuration() AutoArchiveDuration { + return 0 +} + +func (c GuildStageVoiceChannel) RateLimitPerUser() int { + return c.rateLimitPerUser +} + func (c GuildStageVoiceChannel) CreatedAt() time.Time { return c.id.Time() } -func (GuildStageVoiceChannel) channel() {} -func (GuildStageVoiceChannel) guildChannel() {} -func (GuildStageVoiceChannel) guildAudioChannel() {} +func (GuildStageVoiceChannel) channel() {} +func (GuildStageVoiceChannel) messageChannel() {} +func (GuildStageVoiceChannel) guildChannel() {} +func (GuildStageVoiceChannel) guildAudioChannel() {} +func (GuildStageVoiceChannel) guildMessageChannel() {} var ( _ Channel = (*GuildForumChannel)(nil) @@ -1386,9 +1422,6 @@ func ApplyLastPinTimestampToChannel(channel GuildMessageChannel, lastPinTimestam case GuildTextChannel: c.lastPinTimestamp = lastPinTimestamp return c - case GuildVoiceChannel: - c.lastPinTimestamp = lastPinTimestamp - return c case GuildNewsChannel: c.lastPinTimestamp = lastPinTimestamp return c diff --git a/discord/channel_create.go b/discord/channel_create.go index eb5ac7d21..cc78bce16 100644 --- a/discord/channel_create.go +++ b/discord/channel_create.go @@ -57,12 +57,15 @@ var ( type GuildVoiceChannelCreate struct { Name string `json:"name"` - Topic string `json:"topic,omitempty"` Bitrate int `json:"bitrate,omitempty"` UserLimit int `json:"user_limit,omitempty"` + RateLimitPerUser int `json:"rate_limit_per_user,omitempty"` Position int `json:"position,omitempty"` PermissionOverwrites []PermissionOverwrite `json:"permission_overwrites,omitempty"` ParentID snowflake.ID `json:"parent_id,omitempty"` + NSFW bool `json:"nsfw,omitempty"` + RTCRegion string `json:"rtc_region,omitempty"` + VideoQualityMode VideoQualityMode `json:"video_quality_mode,omitempty"` } func (c GuildVoiceChannelCreate) Type() ChannelType { @@ -154,12 +157,15 @@ var ( type GuildStageVoiceChannelCreate struct { Name string `json:"name"` - Topic string `json:"topic,omitempty"` Bitrate int `json:"bitrate,omitempty"` UserLimit int `json:"user_limit,omitempty"` + RateLimitPerUser int `json:"rate_limit_per_user,omitempty"` Position int `json:"position,omitempty"` PermissionOverwrites []PermissionOverwrite `json:"permission_overwrites,omitempty"` ParentID snowflake.ID `json:"parent_id,omitempty"` + NSFW bool `json:"nsfw,omitempty"` + RTCRegion string `json:"rtc_region,omitempty"` + VideoQualityMode VideoQualityMode `json:"video_quality_mode,omitempty"` } func (c GuildStageVoiceChannelCreate) Type() ChannelType { diff --git a/discord/channel_update.go b/discord/channel_update.go index 9ddfe4d2e..975c78d0f 100644 --- a/discord/channel_update.go +++ b/discord/channel_update.go @@ -39,6 +39,7 @@ type GuildVoiceChannelUpdate struct { PermissionOverwrites *[]PermissionOverwrite `json:"permission_overwrites,omitempty"` ParentID *snowflake.ID `json:"parent_id,omitempty"` RTCRegion *string `json:"rtc_region,omitempty"` + NSFW *bool `json:"nsfw,omitempty"` VideoQualityMode *VideoQualityMode `json:"video_quality_mode,omitempty"` } @@ -83,7 +84,7 @@ func (GuildThreadUpdate) guildChannelUpdate() {} type GuildStageVoiceChannelUpdate struct { Name *string `json:"name,omitempty"` Position *int `json:"position,omitempty"` - Topic *string `json:"topic,omitempty"` + RateLimitPerUser *int `json:"rate_limit_per_user,omitempty"` Bitrate *int `json:"bitrate,omitempty"` UserLimit *int `json:"user_limit,omitempty"` PermissionOverwrites *[]PermissionOverwrite `json:"permission_overwrites,omitempty"` diff --git a/discord/channels_raw.go b/discord/channels_raw.go index 33f75e8a3..ac66f8d5d 100644 --- a/discord/channels_raw.go +++ b/discord/channels_raw.go @@ -117,23 +117,20 @@ func (t *guildCategoryChannel) UnmarshalJSON(data []byte) error { } type guildVoiceChannel struct { - ID snowflake.ID `json:"id"` - Type ChannelType `json:"type"` - GuildID snowflake.ID `json:"guild_id"` - Position int `json:"position"` - PermissionOverwrites []PermissionOverwrite `json:"permission_overwrites"` - Name string `json:"name"` - Bitrate int `json:"bitrate"` - UserLimit int `json:"user_limit"` - ParentID *snowflake.ID `json:"parent_id"` - RTCRegion string `json:"rtc_region"` - VideoQualityMode VideoQualityMode `json:"video_quality_mode"` - LastMessageID *snowflake.ID `json:"last_message_id"` - LastPinTimestamp *time.Time `json:"last_pin_timestamp"` - Topic *string `json:"topic"` - NSFW bool `json:"nsfw"` - DefaultAutoArchiveDuration AutoArchiveDuration `json:"default_auto_archive_duration"` - RateLimitPerUser int `json:"rate_limit_per_user"` + ID snowflake.ID `json:"id"` + Type ChannelType `json:"type"` + GuildID snowflake.ID `json:"guild_id"` + Position int `json:"position"` + PermissionOverwrites []PermissionOverwrite `json:"permission_overwrites"` + Name string `json:"name"` + Bitrate int `json:"bitrate"` + UserLimit int `json:"user_limit"` + ParentID *snowflake.ID `json:"parent_id"` + RTCRegion string `json:"rtc_region"` + VideoQualityMode VideoQualityMode `json:"video_quality_mode"` + LastMessageID *snowflake.ID `json:"last_message_id"` + NSFW bool `json:"nsfw"` + RateLimitPerUser int `json:"rate_limit_per_user"` } func (t *guildVoiceChannel) UnmarshalJSON(data []byte) error { @@ -157,9 +154,14 @@ type guildStageVoiceChannel struct { Position int `json:"position"` PermissionOverwrites []PermissionOverwrite `json:"permission_overwrites"` Name string `json:"name"` - Bitrate int `json:"bitrate,"` + Bitrate int `json:"bitrate"` + UserLimit int `json:"user_limit"` ParentID *snowflake.ID `json:"parent_id"` RTCRegion string `json:"rtc_region"` + VideoQualityMode VideoQualityMode `json:"video_quality_mode"` + LastMessageID *snowflake.ID `json:"last_message_id"` + NSFW bool `json:"nsfw"` + RateLimitPerUser int `json:"rate_limit_per_user"` } func (t *guildStageVoiceChannel) UnmarshalJSON(data []byte) error { From 3129dd50ea4bf347f2737b21a8fc01233cd188e9 Mon Sep 17 00:00:00 2001 From: cane Date: Tue, 19 Sep 2023 20:17:28 +0200 Subject: [PATCH 111/119] Add DefaultThreadRateLimitPerUser to channel creates (#314) --- discord/channel_create.go | 74 +++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/discord/channel_create.go b/discord/channel_create.go index cc78bce16..63e99b2f3 100644 --- a/discord/channel_create.go +++ b/discord/channel_create.go @@ -22,14 +22,15 @@ var ( ) type GuildTextChannelCreate struct { - Name string `json:"name"` - Topic string `json:"topic,omitempty"` - RateLimitPerUser int `json:"rate_limit_per_user,omitempty"` - Position int `json:"position,omitempty"` - PermissionOverwrites []PermissionOverwrite `json:"permission_overwrites,omitempty"` - ParentID snowflake.ID `json:"parent_id,omitempty"` - NSFW bool `json:"nsfw,omitempty"` - DefaultAutoArchiveDuration AutoArchiveDuration `json:"default_auto_archive_days,omitempty"` + Name string `json:"name"` + Topic string `json:"topic,omitempty"` + RateLimitPerUser int `json:"rate_limit_per_user,omitempty"` + Position int `json:"position,omitempty"` + PermissionOverwrites []PermissionOverwrite `json:"permission_overwrites,omitempty"` + ParentID snowflake.ID `json:"parent_id,omitempty"` + NSFW bool `json:"nsfw,omitempty"` + DefaultAutoArchiveDuration AutoArchiveDuration `json:"default_auto_archive_days,omitempty"` + DefaultThreadRateLimitPerUser int `json:"default_thread_rate_limit_per_user,omitempty"` } func (c GuildTextChannelCreate) Type() ChannelType { @@ -122,14 +123,15 @@ var ( ) type GuildNewsChannelCreate struct { - Name string `json:"name"` - Topic string `json:"topic,omitempty"` - RateLimitPerUser int `json:"rate_limit_per_user,omitempty"` - Position int `json:"position,omitempty"` - PermissionOverwrites []PermissionOverwrite `json:"permission_overwrites,omitempty"` - ParentID snowflake.ID `json:"parent_id,omitempty"` - NSFW bool `json:"nsfw,omitempty"` - DefaultAutoArchiveDuration AutoArchiveDuration `json:"default_auto_archive_days,omitempty"` + Name string `json:"name"` + Topic string `json:"topic,omitempty"` + RateLimitPerUser int `json:"rate_limit_per_user,omitempty"` + Position int `json:"position,omitempty"` + PermissionOverwrites []PermissionOverwrite `json:"permission_overwrites,omitempty"` + ParentID snowflake.ID `json:"parent_id,omitempty"` + NSFW bool `json:"nsfw,omitempty"` + DefaultAutoArchiveDuration AutoArchiveDuration `json:"default_auto_archive_days,omitempty"` + DefaultThreadRateLimitPerUser int `json:"default_thread_rate_limit_per_user,omitempty"` } func (c GuildNewsChannelCreate) Type() ChannelType { @@ -187,16 +189,17 @@ func (GuildStageVoiceChannelCreate) channelCreate() {} func (GuildStageVoiceChannelCreate) guildChannelCreate() {} type GuildForumChannelCreate struct { - Name string `json:"name"` - Topic string `json:"topic,omitempty"` - Position int `json:"position,omitempty"` - PermissionOverwrites []PermissionOverwrite `json:"permission_overwrites,omitempty"` - ParentID snowflake.ID `json:"parent_id,omitempty"` - RateLimitPerUser int `json:"rate_limit_per_user"` - DefaultReactionEmoji DefaultReactionEmoji `json:"default_reaction_emoji"` - AvailableTags []ChannelTag `json:"available_tags"` - DefaultSortOrder DefaultSortOrder `json:"default_sort_order"` - DefaultForumLayout DefaultForumLayout `json:"default_forum_layout"` + Name string `json:"name"` + Topic string `json:"topic,omitempty"` + Position int `json:"position,omitempty"` + PermissionOverwrites []PermissionOverwrite `json:"permission_overwrites,omitempty"` + ParentID snowflake.ID `json:"parent_id,omitempty"` + RateLimitPerUser int `json:"rate_limit_per_user,omitempty"` + DefaultReactionEmoji DefaultReactionEmoji `json:"default_reaction_emoji"` + AvailableTags []ChannelTag `json:"available_tags"` + DefaultSortOrder DefaultSortOrder `json:"default_sort_order"` + DefaultForumLayout DefaultForumLayout `json:"default_forum_layout"` + DefaultThreadRateLimitPerUser int `json:"default_thread_rate_limit_per_user,omitempty"` } func (c GuildForumChannelCreate) Type() ChannelType { @@ -218,15 +221,16 @@ func (GuildForumChannelCreate) channelCreate() {} func (GuildForumChannelCreate) guildChannelCreate() {} type GuildMediaChannelCreate struct { - Name string `json:"name"` - Topic string `json:"topic,omitempty"` - Position int `json:"position,omitempty"` - PermissionOverwrites []PermissionOverwrite `json:"permission_overwrites,omitempty"` - ParentID snowflake.ID `json:"parent_id,omitempty"` - RateLimitPerUser int `json:"rate_limit_per_user"` - DefaultReactionEmoji DefaultReactionEmoji `json:"default_reaction_emoji"` - AvailableTags []ChannelTag `json:"available_tags"` - DefaultSortOrder DefaultSortOrder `json:"default_sort_order"` + Name string `json:"name"` + Topic string `json:"topic,omitempty"` + Position int `json:"position,omitempty"` + PermissionOverwrites []PermissionOverwrite `json:"permission_overwrites,omitempty"` + ParentID snowflake.ID `json:"parent_id,omitempty"` + RateLimitPerUser int `json:"rate_limit_per_user,omitempty"` + DefaultReactionEmoji DefaultReactionEmoji `json:"default_reaction_emoji"` + AvailableTags []ChannelTag `json:"available_tags"` + DefaultSortOrder DefaultSortOrder `json:"default_sort_order"` + DefaultThreadRateLimitPerUser int `json:"default_thread_rate_limit_per_user,omitempty"` } func (c GuildMediaChannelCreate) Type() ChannelType { From c9986e891d60fb848297678d3af03f7fbf74366f Mon Sep 17 00:00:00 2001 From: cane Date: Wed, 27 Sep 2023 16:54:59 +0200 Subject: [PATCH 112/119] Add default values to select menus (#315) --- discord/interaction.go | 24 +++ discord/interaction_application_command.go | 28 +--- discord/message.go | 1 + discord/select_menu.go | 181 ++++++++++++++++++--- 4 files changed, 186 insertions(+), 48 deletions(-) diff --git a/discord/interaction.go b/discord/interaction.go index 3c86fc61b..e12d66ecb 100644 --- a/discord/interaction.go +++ b/discord/interaction.go @@ -108,6 +108,30 @@ func UnmarshalInteraction(data []byte) (Interaction, error) { return interaction, nil } +type ResolvedData struct { + Users map[snowflake.ID]User `json:"users,omitempty"` + Members map[snowflake.ID]ResolvedMember `json:"members,omitempty"` + Roles map[snowflake.ID]Role `json:"roles,omitempty"` + Channels map[snowflake.ID]ResolvedChannel `json:"channels,omitempty"` + Attachments map[snowflake.ID]Attachment `json:"attachments,omitempty"` +} + +func (r *ResolvedData) UnmarshalJSON(data []byte) error { + type resolvedData ResolvedData + var v resolvedData + if err := json.Unmarshal(data, &v); err != nil { + return err + } + *r = ResolvedData(v) + for id, member := range r.Members { + if user, ok := r.Users[id]; ok { + member.User = user + r.Members[id] = member + } + } + return nil +} + type ResolvedMember struct { Member Permissions Permissions `json:"permissions,omitempty"` diff --git a/discord/interaction_application_command.go b/discord/interaction_application_command.go index 03c9f3aa2..afa88f977 100644 --- a/discord/interaction_application_command.go +++ b/discord/interaction_application_command.go @@ -139,7 +139,7 @@ type rawSlashCommandInteractionData struct { Name string `json:"name"` Type ApplicationCommandType `json:"type"` GuildID *snowflake.ID `json:"guild_id,omitempty"` - Resolved SlashCommandResolved `json:"resolved"` + Resolved ResolvedData `json:"resolved"` Options []internalSlashCommandOption `json:"options"` } @@ -170,7 +170,7 @@ type SlashCommandInteractionData struct { guildID *snowflake.ID SubCommandName *string SubCommandGroupName *string - Resolved SlashCommandResolved + Resolved ResolvedData Options map[string]SlashCommandOption } @@ -498,30 +498,6 @@ func (d SlashCommandInteractionData) FindAll(optionFindFunc func(option SlashCom func (SlashCommandInteractionData) applicationCommandInteractionData() {} -type SlashCommandResolved struct { - Users map[snowflake.ID]User `json:"users,omitempty"` - Members map[snowflake.ID]ResolvedMember `json:"members,omitempty"` - Roles map[snowflake.ID]Role `json:"roles,omitempty"` - Channels map[snowflake.ID]ResolvedChannel `json:"channels,omitempty"` - Attachments map[snowflake.ID]Attachment `json:"attachments,omitempty"` -} - -func (r *SlashCommandResolved) UnmarshalJSON(data []byte) error { - type slashCommandResolved SlashCommandResolved - var v slashCommandResolved - if err := json.Unmarshal(data, &v); err != nil { - return err - } - *r = SlashCommandResolved(v) - for id, member := range r.Members { - if user, ok := r.Users[id]; ok { - member.User = user - r.Members[id] = member - } - } - return nil -} - type ContextCommandInteractionData interface { ApplicationCommandInteractionData TargetID() snowflake.ID diff --git a/discord/message.go b/discord/message.go index 93ef38181..d6c0a0653 100644 --- a/discord/message.go +++ b/discord/message.go @@ -113,6 +113,7 @@ type Message struct { Thread *MessageThread `json:"thread,omitempty"` Position *int `json:"position,omitempty"` RoleSubscriptionData *RoleSubscriptionData `json:"role_subscription_data,omitempty"` + Resolved *ResolvedData `json:"resolved,omitempty"` } func (m *Message) UnmarshalJSON(data []byte) error { diff --git a/discord/select_menu.go b/discord/select_menu.go index e4db9a9d3..6f7105dee 100644 --- a/discord/select_menu.go +++ b/discord/select_menu.go @@ -1,6 +1,9 @@ package discord -import "github.com/disgoorg/json" +import ( + "github.com/disgoorg/json" + "github.com/disgoorg/snowflake/v2" +) type SelectMenuComponent interface { InteractiveComponent @@ -189,11 +192,12 @@ func NewUserSelectMenu(customID string, placeholder string) UserSelectMenuCompon } type UserSelectMenuComponent struct { - CustomID string `json:"custom_id"` - Placeholder string `json:"placeholder,omitempty"` - MinValues *int `json:"min_values,omitempty"` - MaxValues int `json:"max_values,omitempty"` - Disabled bool `json:"disabled,omitempty"` + CustomID string `json:"custom_id"` + Placeholder string `json:"placeholder,omitempty"` + DefaultValues []SelectMenuDefaultValue `json:"default_values,omitempty"` + MinValues *int `json:"min_values,omitempty"` + MaxValues int `json:"max_values,omitempty"` + Disabled bool `json:"disabled,omitempty"` } func (c UserSelectMenuComponent) MarshalJSON() ([]byte, error) { @@ -261,6 +265,30 @@ func (c UserSelectMenuComponent) WithDisabled(disabled bool) UserSelectMenuCompo return c } +// SetDefaultValues returns a new UserSelectMenuComponent with the provided default values +func (c UserSelectMenuComponent) SetDefaultValues(defaultValues ...snowflake.ID) UserSelectMenuComponent { + values := make([]SelectMenuDefaultValue, 0, len(defaultValues)) + for _, value := range defaultValues { + values = append(values, NewSelectMenuDefaultUser(value)) + } + c.DefaultValues = values + return c +} + +// AddDefaultValue returns a new UserSelectMenuComponent with the provided default value added +func (c UserSelectMenuComponent) AddDefaultValue(defaultValue snowflake.ID) UserSelectMenuComponent { + c.DefaultValues = append(c.DefaultValues, NewSelectMenuDefaultUser(defaultValue)) + return c +} + +// RemoveDefaultValue returns a new UserSelectMenuComponent with the provided default value at the index removed +func (c UserSelectMenuComponent) RemoveDefaultValue(index int) UserSelectMenuComponent { + if len(c.DefaultValues) > index { + c.DefaultValues = append(c.DefaultValues[:index], c.DefaultValues[index+1:]...) + } + return c +} + var ( _ Component = (*UserSelectMenuComponent)(nil) _ InteractiveComponent = (*UserSelectMenuComponent)(nil) @@ -276,11 +304,12 @@ func NewRoleSelectMenu(customID string, placeholder string) RoleSelectMenuCompon } type RoleSelectMenuComponent struct { - CustomID string `json:"custom_id"` - Placeholder string `json:"placeholder,omitempty"` - MinValues *int `json:"min_values,omitempty"` - MaxValues int `json:"max_values,omitempty"` - Disabled bool `json:"disabled,omitempty"` + CustomID string `json:"custom_id"` + Placeholder string `json:"placeholder,omitempty"` + DefaultValues []SelectMenuDefaultValue `json:"default_values,omitempty"` + MinValues *int `json:"min_values,omitempty"` + MaxValues int `json:"max_values,omitempty"` + Disabled bool `json:"disabled,omitempty"` } func (c RoleSelectMenuComponent) MarshalJSON() ([]byte, error) { @@ -348,6 +377,30 @@ func (c RoleSelectMenuComponent) WithDisabled(disabled bool) RoleSelectMenuCompo return c } +// SetDefaultValues returns a new RoleSelectMenuComponent with the provided default values +func (c RoleSelectMenuComponent) SetDefaultValues(defaultValues ...snowflake.ID) RoleSelectMenuComponent { + values := make([]SelectMenuDefaultValue, 0, len(defaultValues)) + for _, value := range defaultValues { + values = append(values, NewSelectMenuDefaultRole(value)) + } + c.DefaultValues = values + return c +} + +// AddDefaultValue returns a new RoleSelectMenuComponent with the provided default value added +func (c RoleSelectMenuComponent) AddDefaultValue(defaultValue snowflake.ID) RoleSelectMenuComponent { + c.DefaultValues = append(c.DefaultValues, NewSelectMenuDefaultRole(defaultValue)) + return c +} + +// RemoveDefaultValue returns a new RoleSelectMenuComponent with the provided default value at the index removed +func (c RoleSelectMenuComponent) RemoveDefaultValue(index int) RoleSelectMenuComponent { + if len(c.DefaultValues) > index { + c.DefaultValues = append(c.DefaultValues[:index], c.DefaultValues[index+1:]...) + } + return c +} + var ( _ Component = (*MentionableSelectMenuComponent)(nil) _ InteractiveComponent = (*MentionableSelectMenuComponent)(nil) @@ -363,11 +416,12 @@ func NewMentionableSelectMenu(customID string, placeholder string) MentionableSe } type MentionableSelectMenuComponent struct { - CustomID string `json:"custom_id"` - Placeholder string `json:"placeholder,omitempty"` - MinValues *int `json:"min_values,omitempty"` - MaxValues int `json:"max_values,omitempty"` - Disabled bool `json:"disabled,omitempty"` + CustomID string `json:"custom_id"` + Placeholder string `json:"placeholder,omitempty"` + DefaultValues []SelectMenuDefaultValue `json:"default_values,omitempty"` + MinValues *int `json:"min_values,omitempty"` + MaxValues int `json:"max_values,omitempty"` + Disabled bool `json:"disabled,omitempty"` } func (c MentionableSelectMenuComponent) MarshalJSON() ([]byte, error) { @@ -435,6 +489,27 @@ func (c MentionableSelectMenuComponent) WithDisabled(disabled bool) MentionableS return c } +// SetDefaultValues returns a new MentionableSelectMenuComponent with the provided default values +func (c MentionableSelectMenuComponent) SetDefaultValues(defaultValues ...SelectMenuDefaultValue) MentionableSelectMenuComponent { + c.DefaultValues = defaultValues + return c +} + +// AddDefaultValue returns a new MentionableSelectMenuComponent with the provided default value added. +// SelectMenuDefaultValue can easily be constructed using helpers like NewSelectMenuDefaultUser or NewSelectMenuDefaultRole +func (c MentionableSelectMenuComponent) AddDefaultValue(value SelectMenuDefaultValue) MentionableSelectMenuComponent { + c.DefaultValues = append(c.DefaultValues, value) + return c +} + +// RemoveDefaultValue returns a new MentionableSelectMenuComponent with the provided default value at the index removed +func (c MentionableSelectMenuComponent) RemoveDefaultValue(index int) MentionableSelectMenuComponent { + if len(c.DefaultValues) > index { + c.DefaultValues = append(c.DefaultValues[:index], c.DefaultValues[index+1:]...) + } + return c +} + var ( _ Component = (*ChannelSelectMenuComponent)(nil) _ InteractiveComponent = (*ChannelSelectMenuComponent)(nil) @@ -450,12 +525,13 @@ func NewChannelSelectMenu(customID string, placeholder string) ChannelSelectMenu } type ChannelSelectMenuComponent struct { - CustomID string `json:"custom_id"` - Placeholder string `json:"placeholder,omitempty"` - MinValues *int `json:"min_values,omitempty"` - MaxValues int `json:"max_values,omitempty"` - Disabled bool `json:"disabled,omitempty"` - ChannelTypes []ChannelType `json:"channel_types,omitempty"` + CustomID string `json:"custom_id"` + Placeholder string `json:"placeholder,omitempty"` + DefaultValues []SelectMenuDefaultValue `json:"default_values,omitempty"` + MinValues *int `json:"min_values,omitempty"` + MaxValues int `json:"max_values,omitempty"` + Disabled bool `json:"disabled,omitempty"` + ChannelTypes []ChannelType `json:"channel_types,omitempty"` } func (c ChannelSelectMenuComponent) MarshalJSON() ([]byte, error) { @@ -528,3 +604,64 @@ func (c ChannelSelectMenuComponent) WithChannelTypes(channelTypes ...ChannelType c.ChannelTypes = channelTypes return c } + +// SetDefaultValues returns a new ChannelSelectMenuComponent with the provided default values +func (c ChannelSelectMenuComponent) SetDefaultValues(defaultValues ...snowflake.ID) ChannelSelectMenuComponent { + values := make([]SelectMenuDefaultValue, 0, len(defaultValues)) + for _, value := range defaultValues { + values = append(values, NewSelectMenuDefaultChannel(value)) + } + c.DefaultValues = values + return c +} + +// AddDefaultValue returns a new ChannelSelectMenuComponent with the provided default value added +func (c ChannelSelectMenuComponent) AddDefaultValue(defaultValue snowflake.ID) ChannelSelectMenuComponent { + c.DefaultValues = append(c.DefaultValues, NewSelectMenuDefaultChannel(defaultValue)) + return c +} + +// RemoveDefaultValue returns a new ChannelSelectMenuComponent with the provided default value at the index removed +func (c ChannelSelectMenuComponent) RemoveDefaultValue(index int) ChannelSelectMenuComponent { + if len(c.DefaultValues) > index { + c.DefaultValues = append(c.DefaultValues[:index], c.DefaultValues[index+1:]...) + } + return c +} + +type SelectMenuDefaultValue struct { + ID snowflake.ID `json:"id"` + Type SelectMenuDefaultValueType `json:"type"` +} + +type SelectMenuDefaultValueType string + +const ( + SelectMenuDefaultValueTypeUser SelectMenuDefaultValueType = "user" + SelectMenuDefaultValueTypeRole SelectMenuDefaultValueType = "role" + SelectMenuDefaultValueTypeChannel SelectMenuDefaultValueType = "channel" +) + +// NewSelectMenuDefaultUser returns a new SelectMenuDefaultValue of type SelectMenuDefaultValueTypeUser +func NewSelectMenuDefaultUser(id snowflake.ID) SelectMenuDefaultValue { + return SelectMenuDefaultValue{ + ID: id, + Type: SelectMenuDefaultValueTypeUser, + } +} + +// NewSelectMenuDefaultRole returns a new SelectMenuDefaultValue of type SelectMenuDefaultValueTypeRole +func NewSelectMenuDefaultRole(id snowflake.ID) SelectMenuDefaultValue { + return SelectMenuDefaultValue{ + ID: id, + Type: SelectMenuDefaultValueTypeRole, + } +} + +// NewSelectMenuDefaultChannel returns a new SelectMenuDefaultValue of type SelectMenuDefaultValueTypeChannel +func NewSelectMenuDefaultChannel(id snowflake.ID) SelectMenuDefaultValue { + return SelectMenuDefaultValue{ + ID: id, + Type: SelectMenuDefaultValueTypeChannel, + } +} From 012c4f50d6a1999f9e039d664d0fed73b0611c16 Mon Sep 17 00:00:00 2001 From: cane Date: Thu, 28 Sep 2023 19:16:34 +0200 Subject: [PATCH 113/119] Add GuildScheduledEventID to StageInstanceCreate (#215) --- discord/stage_instance.go | 1 + 1 file changed, 1 insertion(+) diff --git a/discord/stage_instance.go b/discord/stage_instance.go index 4566fc72f..588c80b62 100644 --- a/discord/stage_instance.go +++ b/discord/stage_instance.go @@ -31,6 +31,7 @@ type StageInstanceCreate struct { Topic string `json:"topic,omitempty"` PrivacyLevel StagePrivacyLevel `json:"privacy_level,omitempty"` SendStartNotification bool `json:"send_start_notification,omitempty"` + GuildScheduledEventID snowflake.ID `json:"guild_scheduled_event_id,omitempty"` } type StageInstanceUpdate struct { From 86c1d267060165c8810f790679269708b72f4ad0 Mon Sep 17 00:00:00 2001 From: cane Date: Thu, 28 Sep 2023 20:11:53 +0200 Subject: [PATCH 114/119] Update Applications (#290) --- discord/application.go | 17 +++++++++++++++++ rest/applications.go | 13 +++++++++++++ rest/rest_endpoints.go | 3 +++ 3 files changed, 33 insertions(+) diff --git a/discord/application.go b/discord/application.go index d34705b77..d3045bae8 100644 --- a/discord/application.go +++ b/discord/application.go @@ -5,6 +5,7 @@ import ( "strings" "time" + "github.com/disgoorg/json" "github.com/disgoorg/snowflake/v2" "github.com/disgoorg/disgo/internal/flags" @@ -18,9 +19,11 @@ type Application struct { RPCOrigins []string `json:"rpc_origins"` BotPublic bool `json:"bot_public"` BotRequireCodeGrant bool `json:"bot_require_code_grant"` + Bot *User `json:"bot,omitempty"` TermsOfServiceURL *string `json:"terms_of_service_url,omitempty"` PrivacyPolicyURL *string `json:"privacy_policy_url,omitempty"` CustomInstallURL *string `json:"custom_install_url,omitempty"` + InteractionsEndpointURL *string `json:"interactions_endpoint_url,omitempty"` RoleConnectionsVerificationURL *string `json:"role_connections_verification_url"` InstallParams *InstallParams `json:"install_params"` Tags []string `json:"tags"` @@ -29,10 +32,12 @@ type Application struct { VerifyKey string `json:"verify_key"` Team *Team `json:"team,omitempty"` GuildID *snowflake.ID `json:"guild_id,omitempty"` + Guild *Guild `json:"guild,omitempty"` PrimarySkuID *snowflake.ID `json:"primary_sku_id,omitempty"` Slug *string `json:"slug,omitempty"` CoverImage *string `json:"cover_image,omitempty"` Flags ApplicationFlags `json:"flags,omitempty"` + ApproximateGuildCount *int `json:"approximate_guild_count,omitempty"` } func (a Application) IconURL(opts ...CDNOpt) *string { @@ -55,6 +60,18 @@ func (a Application) CreatedAt() time.Time { return a.ID.Time() } +type ApplicationUpdate struct { + CustomInstallURL *string `json:"custom_install_url,omitempty"` + Description *string `json:"description,omitempty"` + RoleConnectionsVerificationURL *string `json:"role_connections_verification_url,omitempty"` + InstallParams *InstallParams `json:"install_params,omitempty"` + Flags *ApplicationFlags `json:"flags,omitempty"` + Icon *json.Nullable[Icon] `json:"icon,omitempty"` + CoverImage *json.Nullable[Icon] `json:"cover_image,omitempty"` + InteractionsEndpointURL *string `json:"interactions_endpoint_url,omitempty"` + Tags []string `json:"tags,omitempty"` +} + type PartialApplication struct { ID snowflake.ID `json:"id"` Flags ApplicationFlags `json:"flags"` diff --git a/rest/applications.go b/rest/applications.go index d68acd361..742278cd6 100644 --- a/rest/applications.go +++ b/rest/applications.go @@ -13,6 +13,9 @@ func NewApplications(client Client) Applications { } type Applications interface { + GetCurrentApplication(opts ...RequestOpt) (*discord.Application, error) + UpdateCurrentApplication(applicationUpdate discord.ApplicationUpdate, opts ...RequestOpt) (*discord.Application, error) + GetGlobalCommands(applicationID snowflake.ID, withLocalizations bool, opts ...RequestOpt) ([]discord.ApplicationCommand, error) GetGlobalCommand(applicationID snowflake.ID, commandID snowflake.ID, opts ...RequestOpt) (discord.ApplicationCommand, error) CreateGlobalCommand(applicationID snowflake.ID, commandCreate discord.ApplicationCommandCreate, opts ...RequestOpt) (discord.ApplicationCommand, error) @@ -38,6 +41,16 @@ type applicationsImpl struct { client Client } +func (s *applicationsImpl) GetCurrentApplication(opts ...RequestOpt) (application *discord.Application, err error) { + err = s.client.Do(GetCurrentApplication.Compile(nil), nil, &application, opts...) + return +} + +func (s *applicationsImpl) UpdateCurrentApplication(applicationUpdate discord.ApplicationUpdate, opts ...RequestOpt) (application *discord.Application, err error) { + err = s.client.Do(UpdateCurrentApplication.Compile(nil), applicationUpdate, &application, opts...) + return +} + func (s *applicationsImpl) GetGlobalCommands(applicationID snowflake.ID, withLocalizations bool, opts ...RequestOpt) (commands []discord.ApplicationCommand, err error) { var unmarshalCommands []discord.UnmarshalApplicationCommand err = s.client.Do(GetGlobalCommands.Compile(discord.QueryValues{"with_localizations": withLocalizations}, applicationID), nil, &unmarshalCommands, opts...) diff --git a/rest/rest_endpoints.go b/rest/rest_endpoints.go index 1cb2a12d7..a56099dc7 100644 --- a/rest/rest_endpoints.go +++ b/rest/rest_endpoints.go @@ -259,6 +259,9 @@ var ( // Applications var ( + GetCurrentApplication = NewEndpoint(http.MethodGet, "/applications/@me") + UpdateCurrentApplication = NewEndpoint(http.MethodPatch, "/applications/@me") + GetGlobalCommands = NewEndpoint(http.MethodGet, "/applications/{application.id}/commands") GetGlobalCommand = NewEndpoint(http.MethodGet, "/applications/{application.id}/command/{command.id}") CreateGlobalCommand = NewEndpoint(http.MethodPost, "/applications/{application.id}/commands") From 4d0bbb2a4508ca8e0e31150d013f1f9329856d30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= Date: Thu, 28 Sep 2023 20:13:39 +0200 Subject: [PATCH 115/119] fix voice resuming sending OpcodeIdentify --- voice/gateway.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/voice/gateway.go b/voice/gateway.go index 5e4849224..76b171de1 100644 --- a/voice/gateway.go +++ b/voice/gateway.go @@ -264,7 +264,7 @@ loop: }) } else { g.status = StatusResuming - err = g.Send(ctx, OpcodeIdentify, GatewayMessageDataResume{ + err = g.Send(ctx, OpcodeResume, GatewayMessageDataResume{ GuildID: g.state.GuildID, SessionID: g.state.SessionID, Token: g.state.Token, From 2570fd3a42ad8ad311c1df500c3c0fe5f26e8d38 Mon Sep 17 00:00:00 2001 From: cane Date: Fri, 29 Sep 2023 14:52:35 +0200 Subject: [PATCH 116/119] Add app subscriptions (#319) --- discord/entitlement.go | 42 ++++++++++++++++++++ discord/interaction.go | 2 + discord/interaction_application_command.go | 2 + discord/interaction_autocomplete.go | 2 + discord/interaction_base.go | 5 +++ discord/interaction_component.go | 2 + discord/interaction_modal_submit.go | 2 + discord/interaction_ping.go | 4 ++ discord/interaction_response.go | 1 + discord/sku.go | 36 +++++++++++++++++ events/entitlement_events.go | 20 ++++++++++ events/interaction_events.go | 15 ++++++++ events/listener_adapter.go | 19 +++++++++ gateway/gateway_event_type.go | 3 ++ gateway/gateway_events.go | 21 ++++++++++ gateway/gateway_messages.go | 15 ++++++++ handlers/all_handlers.go | 4 ++ handlers/entitlement_handlers.go | 34 ++++++++++++++++ internal/slicehelper/slice_helper.go | 14 +++++++ rest/applications.go | 45 ++++++++++++++++++++++ rest/guilds.go | 12 ++---- rest/rest_endpoints.go | 6 +++ 22 files changed, 297 insertions(+), 9 deletions(-) create mode 100644 discord/entitlement.go create mode 100644 discord/sku.go create mode 100644 events/entitlement_events.go create mode 100644 handlers/entitlement_handlers.go create mode 100644 internal/slicehelper/slice_helper.go diff --git a/discord/entitlement.go b/discord/entitlement.go new file mode 100644 index 000000000..fb0e763e2 --- /dev/null +++ b/discord/entitlement.go @@ -0,0 +1,42 @@ +package discord + +import ( + "time" + + "github.com/disgoorg/snowflake/v2" +) + +type Entitlement struct { + ID snowflake.ID `json:"id"` + SkuID snowflake.ID `json:"sku_id"` + UserID *snowflake.ID `json:"user_id"` + GuildID *snowflake.ID `json:"guild_id"` + ApplicationID snowflake.ID `json:"application_id"` + Type EntitlementType `json:"type"` + Consumed bool `json:"consumed"` + StartsAt *time.Time `json:"starts_at"` + EndsAt *time.Time `json:"ends_at"` + PromotionID *snowflake.ID `json:"promotion_id"` + Deleted bool `json:"deleted"` + GiftCodeFlags int `json:"gift_code_flags"` + SubscriptionID *snowflake.ID `json:"subscription_id"` +} + +type EntitlementType int + +const ( + EntitlementTypeApplicationSubscription EntitlementType = 8 +) + +type TestEntitlementCreate struct { + SkuID snowflake.ID `json:"sku_id"` + OwnerID snowflake.ID `json:"owner_id"` + OwnerType EntitlementOwnerType `json:"owner_type"` +} + +type EntitlementOwnerType int + +const ( + EntitlementOwnerTypeGuild EntitlementOwnerType = iota + 1 + EntitlementOwnerTypeUser +) diff --git a/discord/interaction.go b/discord/interaction.go index e12d66ecb..0833985c5 100644 --- a/discord/interaction.go +++ b/discord/interaction.go @@ -35,6 +35,7 @@ type rawInteraction struct { Member *ResolvedMember `json:"member,omitempty"` User *User `json:"user,omitempty"` AppPermissions *Permissions `json:"app_permissions,omitempty"` + Entitlements []Entitlement `json:"entitlements"` } // Interaction is used for easier unmarshalling of different Interaction(s) @@ -53,6 +54,7 @@ type Interaction interface { Member() *ResolvedMember User() User AppPermissions() *Permissions + Entitlements() []Entitlement CreatedAt() time.Time interaction() diff --git a/discord/interaction_application_command.go b/discord/interaction_application_command.go index afa88f977..bda984936 100644 --- a/discord/interaction_application_command.go +++ b/discord/interaction_application_command.go @@ -78,6 +78,7 @@ func (i *ApplicationCommandInteraction) UnmarshalJSON(data []byte) error { i.baseInteraction.member = interaction.Member i.baseInteraction.user = interaction.User i.baseInteraction.appPermissions = interaction.AppPermissions + i.baseInteraction.entitlements = interaction.Entitlements i.Data = interactionData return nil @@ -102,6 +103,7 @@ func (i ApplicationCommandInteraction) MarshalJSON() ([]byte, error) { Member: i.member, User: i.user, AppPermissions: i.appPermissions, + Entitlements: i.entitlements, }, Data: i.Data, }) diff --git a/discord/interaction_autocomplete.go b/discord/interaction_autocomplete.go index 3d52f8729..0da5c2ac2 100644 --- a/discord/interaction_autocomplete.go +++ b/discord/interaction_autocomplete.go @@ -35,6 +35,7 @@ func (i *AutocompleteInteraction) UnmarshalJSON(data []byte) error { i.baseInteraction.member = interaction.Member i.baseInteraction.user = interaction.User i.baseInteraction.appPermissions = interaction.AppPermissions + i.baseInteraction.entitlements = interaction.Entitlements i.Data = interaction.Data return nil @@ -59,6 +60,7 @@ func (i AutocompleteInteraction) MarshalJSON() ([]byte, error) { Member: i.member, User: i.user, AppPermissions: i.appPermissions, + Entitlements: i.entitlements, }, Data: i.Data, }) diff --git a/discord/interaction_base.go b/discord/interaction_base.go index 7f5730ab9..f9d8046a6 100644 --- a/discord/interaction_base.go +++ b/discord/interaction_base.go @@ -19,6 +19,7 @@ type baseInteraction struct { member *ResolvedMember user *User appPermissions *Permissions + entitlements []Entitlement } func (i baseInteraction) ID() snowflake.ID { @@ -64,6 +65,10 @@ func (i baseInteraction) AppPermissions() *Permissions { return i.appPermissions } +func (i baseInteraction) Entitlements() []Entitlement { + return i.entitlements +} + func (i baseInteraction) CreatedAt() time.Time { return i.id.Time() } diff --git a/discord/interaction_component.go b/discord/interaction_component.go index 12164e83b..850530dfd 100644 --- a/discord/interaction_component.go +++ b/discord/interaction_component.go @@ -88,6 +88,7 @@ func (i *ComponentInteraction) UnmarshalJSON(data []byte) error { i.baseInteraction.member = interaction.Member i.baseInteraction.user = interaction.User i.baseInteraction.appPermissions = interaction.AppPermissions + i.baseInteraction.entitlements = interaction.Entitlements i.Data = interactionData i.Message = interaction.Message @@ -115,6 +116,7 @@ func (i ComponentInteraction) MarshalJSON() ([]byte, error) { Member: i.member, User: i.user, AppPermissions: i.appPermissions, + Entitlements: i.entitlements, }, Data: i.Data, Message: i.Message, diff --git a/discord/interaction_modal_submit.go b/discord/interaction_modal_submit.go index fe96e274a..031268205 100644 --- a/discord/interaction_modal_submit.go +++ b/discord/interaction_modal_submit.go @@ -32,6 +32,7 @@ func (i *ModalSubmitInteraction) UnmarshalJSON(data []byte) error { i.baseInteraction.member = interaction.Member i.baseInteraction.user = interaction.User i.baseInteraction.appPermissions = interaction.AppPermissions + i.baseInteraction.entitlements = interaction.Entitlements i.Data = interaction.Data return nil @@ -56,6 +57,7 @@ func (i ModalSubmitInteraction) MarshalJSON() ([]byte, error) { Member: i.member, User: i.user, AppPermissions: i.appPermissions, + Entitlements: i.entitlements, }, Data: i.Data, }) diff --git a/discord/interaction_ping.go b/discord/interaction_ping.go index 5d5c15717..08e1c890a 100644 --- a/discord/interaction_ping.go +++ b/discord/interaction_ping.go @@ -96,4 +96,8 @@ func (PingInteraction) AppPermissions() *Permissions { return nil } +func (PingInteraction) Entitlements() []Entitlement { + return nil +} + func (PingInteraction) interaction() {} diff --git a/discord/interaction_response.go b/discord/interaction_response.go index c786c16b8..5b8c0c1c0 100644 --- a/discord/interaction_response.go +++ b/discord/interaction_response.go @@ -14,6 +14,7 @@ const ( InteractionResponseTypeUpdateMessage InteractionResponseTypeApplicationCommandAutocompleteResult InteractionResponseTypeModal + InteractionResponseTypePremiumRequired ) // InteractionResponse is how you answer interactions. If an answer is not sent within 3 seconds of receiving it, the interaction is failed, and you will be unable to respond to it. diff --git a/discord/sku.go b/discord/sku.go new file mode 100644 index 000000000..20366a45f --- /dev/null +++ b/discord/sku.go @@ -0,0 +1,36 @@ +package discord + +import ( + "time" + + "github.com/disgoorg/snowflake/v2" +) + +type SKU struct { + ID snowflake.ID `json:"id"` + Type SKUType `json:"type"` + ApplicationID snowflake.ID `json:"application_id"` + Name string `json:"name"` + Slug string `json:"slug"` + DependentSkuID *snowflake.ID `json:"dependent_sku_id"` + AccessType int `json:"access_type"` + Features []string `json:"features"` + ReleaseDate *time.Time `json:"release_date"` + Premium bool `json:"premium"` + Flags SKUFlags `json:"flags"` + ShowAgeGate bool `json:"show_age_gate"` +} + +type SKUType int + +const ( + SKUTypeSubscription SKUType = iota + 5 + SKUTypeSubscriptionGroup +) + +type SKUFlags int + +const ( + SKUFlagGuildSubscription SKUFlags = 1 << (iota + 7) + SKUFlagUserSubscription +) diff --git a/events/entitlement_events.go b/events/entitlement_events.go new file mode 100644 index 000000000..68d79931b --- /dev/null +++ b/events/entitlement_events.go @@ -0,0 +1,20 @@ +package events + +import "github.com/disgoorg/disgo/discord" + +type GenericEntitlementEvent struct { + *GenericEvent + discord.Entitlement +} + +type EntitlementCreate struct { + *GenericEntitlementEvent +} + +type EntitlementUpdate struct { + *GenericEntitlementEvent +} + +type EntitlementDelete struct { + *GenericEntitlementEvent +} diff --git a/events/interaction_events.go b/events/interaction_events.go index b5453a413..c72d58ecc 100644 --- a/events/interaction_events.go +++ b/events/interaction_events.go @@ -61,6 +61,11 @@ func (e *ApplicationCommandInteractionCreate) CreateModal(modalCreate discord.Mo return e.Respond(discord.InteractionResponseTypeModal, modalCreate, opts...) } +// PremiumRequired responds to the interaction with an upgrade button if available. +func (e *ApplicationCommandInteractionCreate) PremiumRequired(opts ...rest.RequestOpt) error { + return e.Respond(discord.InteractionResponseTypePremiumRequired, nil, opts...) +} + // ComponentInteractionCreate indicates that a new component interaction has been created. type ComponentInteractionCreate struct { *GenericEvent @@ -107,6 +112,11 @@ func (e *ComponentInteractionCreate) CreateModal(modalCreate discord.ModalCreate return e.Respond(discord.InteractionResponseTypeModal, modalCreate, opts...) } +// PremiumRequired responds to the interaction with an upgrade button if available. +func (e *ComponentInteractionCreate) PremiumRequired(opts ...rest.RequestOpt) error { + return e.Respond(discord.InteractionResponseTypePremiumRequired, nil, opts...) +} + // AutocompleteInteractionCreate indicates that a new autocomplete interaction has been created. type AutocompleteInteractionCreate struct { *GenericEvent @@ -169,3 +179,8 @@ func (e *ModalSubmitInteractionCreate) UpdateMessage(messageUpdate discord.Messa func (e *ModalSubmitInteractionCreate) DeferUpdateMessage(opts ...rest.RequestOpt) error { return e.Respond(discord.InteractionResponseTypeDeferredUpdateMessage, nil, opts...) } + +// PremiumRequired responds to the interaction with an upgrade button if available. +func (e *ModalSubmitInteractionCreate) PremiumRequired(opts ...rest.RequestOpt) error { + return e.Respond(discord.InteractionResponseTypePremiumRequired, nil, opts...) +} diff --git a/events/listener_adapter.go b/events/listener_adapter.go index ca82951ef..79a73edbb 100644 --- a/events/listener_adapter.go +++ b/events/listener_adapter.go @@ -61,6 +61,11 @@ type ListenerAdapter struct { OnEmojiUpdate func(event *EmojiUpdate) OnEmojiDelete func(event *EmojiDelete) + // Entitlement Events + OnEntitlementCreate func(event *EntitlementCreate) + OnEntitlementUpdate func(event *EntitlementUpdate) + OnEntitlementDelete func(event *EntitlementDelete) + // Sticker Events OnStickersUpdate func(event *StickersUpdate) OnStickerCreate func(event *StickerCreate) @@ -318,6 +323,20 @@ func (l *ListenerAdapter) OnEvent(event bot.Event) { listener(e) } + // Entitlement Events + case *EntitlementCreate: + if listener := l.OnEntitlementCreate; listener != nil { + listener(e) + } + case *EntitlementUpdate: + if listener := l.OnEntitlementUpdate; listener != nil { + listener(e) + } + case *EntitlementDelete: + if listener := l.OnEntitlementDelete; listener != nil { + listener(e) + } + // Sticker Events case *StickersUpdate: if listener := l.OnStickersUpdate; listener != nil { diff --git a/gateway/gateway_event_type.go b/gateway/gateway_event_type.go index 88ebd49c4..0cb54fd01 100644 --- a/gateway/gateway_event_type.go +++ b/gateway/gateway_event_type.go @@ -19,6 +19,9 @@ const ( EventTypeChannelUpdate EventType = "CHANNEL_UPDATE" EventTypeChannelDelete EventType = "CHANNEL_DELETE" EventTypeChannelPinsUpdate EventType = "CHANNEL_PINS_UPDATE" + EventTypeEntitlementCreate EventType = "ENTITLEMENT_CREATE" + EventTypeEntitlementUpdate EventType = "ENTITLEMENT_UPDATE" + EventTypeEntitlementDelete EventType = "ENTITLEMENT_DELETE" EventTypeThreadCreate EventType = "THREAD_CREATE" EventTypeThreadUpdate EventType = "THREAD_UPDATE" EventTypeThreadDelete EventType = "THREAD_DELETE" diff --git a/gateway/gateway_events.go b/gateway/gateway_events.go index 34a85a6b9..a2f717d58 100644 --- a/gateway/gateway_events.go +++ b/gateway/gateway_events.go @@ -705,3 +705,24 @@ type EventHeartbeatAck struct { func (EventHeartbeatAck) messageData() {} func (EventHeartbeatAck) eventData() {} + +type EventEntitlementCreate struct { + discord.Entitlement +} + +func (EventEntitlementCreate) messageData() {} +func (EventEntitlementCreate) eventData() {} + +type EventEntitlementUpdate struct { + discord.Entitlement +} + +func (EventEntitlementUpdate) messageData() {} +func (EventEntitlementUpdate) eventData() {} + +type EventEntitlementDelete struct { + discord.Entitlement +} + +func (EventEntitlementDelete) messageData() {} +func (EventEntitlementDelete) eventData() {} diff --git a/gateway/gateway_messages.go b/gateway/gateway_messages.go index 8eca2e0f4..301f8fa61 100644 --- a/gateway/gateway_messages.go +++ b/gateway/gateway_messages.go @@ -159,6 +159,21 @@ func UnmarshalEventData(data []byte, eventType EventType) (EventData, error) { err = json.Unmarshal(data, &d) eventData = d + case EventTypeEntitlementCreate: + var d EventEntitlementCreate + err = json.Unmarshal(data, &d) + eventData = d + + case EventTypeEntitlementUpdate: + var d EventEntitlementUpdate + err = json.Unmarshal(data, &d) + eventData = d + + case EventTypeEntitlementDelete: + var d EventEntitlementDelete + err = json.Unmarshal(data, &d) + eventData = d + case EventTypeThreadCreate: var d EventThreadCreate err = json.Unmarshal(data, &d) diff --git a/handlers/all_handlers.go b/handlers/all_handlers.go index 5e1f2b5b0..51a6466a1 100644 --- a/handlers/all_handlers.go +++ b/handlers/all_handlers.go @@ -48,6 +48,10 @@ var allEventHandlers = []bot.GatewayEventHandler{ bot.NewGatewayEventHandler(gateway.EventTypeChannelDelete, gatewayHandlerChannelDelete), bot.NewGatewayEventHandler(gateway.EventTypeChannelPinsUpdate, gatewayHandlerChannelPinsUpdate), + bot.NewGatewayEventHandler(gateway.EventTypeEntitlementCreate, gatewayHandlerEntitlementCreate), + bot.NewGatewayEventHandler(gateway.EventTypeEntitlementUpdate, gatewayHandlerEntitlementUpdate), + bot.NewGatewayEventHandler(gateway.EventTypeEntitlementDelete, gatewayHandlerEntitlementDelete), + bot.NewGatewayEventHandler(gateway.EventTypeThreadCreate, gatewayHandlerThreadCreate), bot.NewGatewayEventHandler(gateway.EventTypeThreadUpdate, gatewayHandlerThreadUpdate), bot.NewGatewayEventHandler(gateway.EventTypeThreadDelete, gatewayHandlerThreadDelete), diff --git a/handlers/entitlement_handlers.go b/handlers/entitlement_handlers.go new file mode 100644 index 000000000..68662a093 --- /dev/null +++ b/handlers/entitlement_handlers.go @@ -0,0 +1,34 @@ +package handlers + +import ( + "github.com/disgoorg/disgo/bot" + "github.com/disgoorg/disgo/events" + "github.com/disgoorg/disgo/gateway" +) + +func gatewayHandlerEntitlementCreate(client bot.Client, sequenceNumber int, shardID int, event gateway.EventEntitlementCreate) { + client.EventManager().DispatchEvent(&events.EntitlementCreate{ + GenericEntitlementEvent: &events.GenericEntitlementEvent{ + GenericEvent: events.NewGenericEvent(client, sequenceNumber, shardID), + Entitlement: event.Entitlement, + }, + }) +} + +func gatewayHandlerEntitlementUpdate(client bot.Client, sequenceNumber int, shardID int, event gateway.EventEntitlementUpdate) { + client.EventManager().DispatchEvent(&events.EntitlementUpdate{ + GenericEntitlementEvent: &events.GenericEntitlementEvent{ + GenericEvent: events.NewGenericEvent(client, sequenceNumber, shardID), + Entitlement: event.Entitlement, + }, + }) +} + +func gatewayHandlerEntitlementDelete(client bot.Client, sequenceNumber int, shardID int, event gateway.EventEntitlementDelete) { + client.EventManager().DispatchEvent(&events.EntitlementDelete{ + GenericEntitlementEvent: &events.GenericEntitlementEvent{ + GenericEvent: events.NewGenericEvent(client, sequenceNumber, shardID), + Entitlement: event.Entitlement, + }, + }) +} diff --git a/internal/slicehelper/slice_helper.go b/internal/slicehelper/slice_helper.go new file mode 100644 index 000000000..b2e755736 --- /dev/null +++ b/internal/slicehelper/slice_helper.go @@ -0,0 +1,14 @@ +package slicehelper + +import "github.com/disgoorg/snowflake/v2" + +func JoinSnowflakes(snowflakes []snowflake.ID) string { + var str string + for i, s := range snowflakes { + str += s.String() + if i != len(str)-1 { + str += "," + } + } + return str +} diff --git a/rest/applications.go b/rest/applications.go index 742278cd6..cb46bc912 100644 --- a/rest/applications.go +++ b/rest/applications.go @@ -1,6 +1,7 @@ package rest import ( + "github.com/disgoorg/disgo/internal/slicehelper" "github.com/disgoorg/snowflake/v2" "github.com/disgoorg/disgo/discord" @@ -35,6 +36,12 @@ type Applications interface { GetApplicationRoleConnectionMetadata(applicationID snowflake.ID, opts ...RequestOpt) ([]discord.ApplicationRoleConnectionMetadata, error) UpdateApplicationRoleConnectionMetadata(applicationID snowflake.ID, newRecords []discord.ApplicationRoleConnectionMetadata, opts ...RequestOpt) ([]discord.ApplicationRoleConnectionMetadata, error) + + GetEntitlements(applicationID snowflake.ID, userID snowflake.ID, guildID snowflake.ID, before snowflake.ID, after snowflake.ID, limit int, excludeEnded bool, skuIDs []snowflake.ID, opts ...RequestOpt) ([]discord.Entitlement, error) + CreateTestEntitlement(applicationID snowflake.ID, entitlementCreate discord.TestEntitlementCreate, opts ...RequestOpt) (*discord.Entitlement, error) + DeleteTestEntitlement(applicationID snowflake.ID, entitlementID snowflake.ID, opts ...RequestOpt) error + + GetSKUs(applicationID snowflake.ID, opts ...RequestOpt) ([]discord.SKU, error) } type applicationsImpl struct { @@ -169,6 +176,44 @@ func (s *applicationsImpl) UpdateApplicationRoleConnectionMetadata(applicationID return } +func (s *applicationsImpl) GetEntitlements(applicationID snowflake.ID, userID snowflake.ID, guildID snowflake.ID, before snowflake.ID, after snowflake.ID, limit int, excludeEnded bool, skuIDs []snowflake.ID, opts ...RequestOpt) (entitlements []discord.Entitlement, err error) { + queryValues := discord.QueryValues{ + "exclude_ended": excludeEnded, + "sku_ids": slicehelper.JoinSnowflakes(skuIDs), + } + if userID != 0 { + queryValues["user_id"] = userID + } + if guildID != 0 { + queryValues["guild_id"] = guildID + } + if before != 0 { + queryValues["before"] = before + } + if after != 0 { + queryValues["after"] = after + } + if limit != 0 { + queryValues["limit"] = limit + } + err = s.client.Do(GetEntitlements.Compile(queryValues, applicationID), nil, &entitlements, opts...) + return +} + +func (s *applicationsImpl) CreateTestEntitlement(applicationID snowflake.ID, entitlementCreate discord.TestEntitlementCreate, opts ...RequestOpt) (entitlement *discord.Entitlement, err error) { + err = s.client.Do(CreateTestEntitlement.Compile(nil, applicationID), entitlementCreate, &entitlement, opts...) + return +} + +func (s *applicationsImpl) DeleteTestEntitlement(applicationID snowflake.ID, entitlementID snowflake.ID, opts ...RequestOpt) error { + return s.client.Do(DeleteTestEntitlement.Compile(nil, applicationID, entitlementID), nil, nil, opts...) +} + +func (s *applicationsImpl) GetSKUs(applicationID snowflake.ID, opts ...RequestOpt) (skus []discord.SKU, err error) { + err = s.client.Do(GetSKUs.Compile(nil, applicationID), nil, &skus, opts...) + return +} + func unmarshalApplicationCommandsToApplicationCommands(unmarshalCommands []discord.UnmarshalApplicationCommand) []discord.ApplicationCommand { commands := make([]discord.ApplicationCommand, len(unmarshalCommands)) for i := range unmarshalCommands { diff --git a/rest/guilds.go b/rest/guilds.go index a382356d3..43d71b140 100644 --- a/rest/guilds.go +++ b/rest/guilds.go @@ -3,6 +3,7 @@ package rest import ( "time" + "github.com/disgoorg/disgo/internal/slicehelper" "github.com/disgoorg/snowflake/v2" "github.com/disgoorg/disgo/discord" @@ -220,16 +221,9 @@ func (s *guildImpl) DeleteIntegration(guildID snowflake.ID, integrationID snowfl func (s *guildImpl) GetGuildPruneCount(guildID snowflake.ID, days int, includeRoles []snowflake.ID, opts ...RequestOpt) (result *discord.GuildPruneResult, err error) { values := discord.QueryValues{ - "days": days, + "days": days, + "include_roles": slicehelper.JoinSnowflakes(includeRoles), } - var joinedRoles string - for i, roleID := range includeRoles { - joinedRoles += roleID.String() - if i != len(includeRoles)-1 { - joinedRoles += "," - } - } - values["include_roles"] = joinedRoles err = s.client.Do(GetGuildPruneCount.Compile(values, guildID), nil, &result, opts...) return } diff --git a/rest/rest_endpoints.go b/rest/rest_endpoints.go index a56099dc7..e06bd0e59 100644 --- a/rest/rest_endpoints.go +++ b/rest/rest_endpoints.go @@ -292,6 +292,12 @@ var ( GetApplicationRoleConnectionMetadata = NewEndpoint(http.MethodGet, "/applications/{application.id}/role-connections/metadata") UpdateApplicationRoleConnectionMetadata = NewEndpoint(http.MethodPut, "/applications/{application.id}/role-connections/metadata") + + GetEntitlements = NewEndpoint(http.MethodGet, "/applications/{application.id}/entitlements") + CreateTestEntitlement = NewEndpoint(http.MethodPost, "/applications/{application.id}/entitlements") + DeleteTestEntitlement = NewEndpoint(http.MethodDelete, "/applications/{application.id}/entitlements/{entitlement.id}") + + GetSKUs = NewEndpoint(http.MethodGet, "/applications/{application.id}/skus") ) // NewEndpoint returns a new Endpoint which requires bot auth with the given http method & route. From d0d82cecaae2390b150f169402c8915190c6f7fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= Date: Fri, 29 Sep 2023 15:01:46 +0200 Subject: [PATCH 117/119] fix inconsistent interaction response types & response method naming --- discord/interaction_response.go | 2 +- events/interaction_events.go | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/discord/interaction_response.go b/discord/interaction_response.go index 5b8c0c1c0..dd4664ccf 100644 --- a/discord/interaction_response.go +++ b/discord/interaction_response.go @@ -12,7 +12,7 @@ const ( InteractionResponseTypeDeferredCreateMessage InteractionResponseTypeDeferredUpdateMessage InteractionResponseTypeUpdateMessage - InteractionResponseTypeApplicationCommandAutocompleteResult + InteractionResponseTypeAutocompleteResult InteractionResponseTypeModal InteractionResponseTypePremiumRequired ) diff --git a/events/interaction_events.go b/events/interaction_events.go index c72d58ecc..2f04e2508 100644 --- a/events/interaction_events.go +++ b/events/interaction_events.go @@ -56,8 +56,8 @@ func (e *ApplicationCommandInteractionCreate) DeferCreateMessage(ephemeral bool, return e.Respond(discord.InteractionResponseTypeDeferredCreateMessage, data, opts...) } -// CreateModal responds to the interaction with a new modal. -func (e *ApplicationCommandInteractionCreate) CreateModal(modalCreate discord.ModalCreate, opts ...rest.RequestOpt) error { +// Modal responds to the interaction with a new modal. +func (e *ApplicationCommandInteractionCreate) Modal(modalCreate discord.ModalCreate, opts ...rest.RequestOpt) error { return e.Respond(discord.InteractionResponseTypeModal, modalCreate, opts...) } @@ -107,8 +107,8 @@ func (e *ComponentInteractionCreate) DeferUpdateMessage(opts ...rest.RequestOpt) return e.Respond(discord.InteractionResponseTypeDeferredUpdateMessage, nil, opts...) } -// CreateModal responds to the interaction with a new modal. -func (e *ComponentInteractionCreate) CreateModal(modalCreate discord.ModalCreate, opts ...rest.RequestOpt) error { +// Modal responds to the interaction with a new modal. +func (e *ComponentInteractionCreate) Modal(modalCreate discord.ModalCreate, opts ...rest.RequestOpt) error { return e.Respond(discord.InteractionResponseTypeModal, modalCreate, opts...) } @@ -134,9 +134,9 @@ func (e *AutocompleteInteractionCreate) Guild() (discord.Guild, bool) { return discord.Guild{}, false } -// Result responds to the interaction with a slice of choices. -func (e *AutocompleteInteractionCreate) Result(choices []discord.AutocompleteChoice, opts ...rest.RequestOpt) error { - return e.Respond(discord.InteractionResponseTypeApplicationCommandAutocompleteResult, discord.AutocompleteResult{Choices: choices}, opts...) +// AutocompleteResult responds to the interaction with a slice of choices. +func (e *AutocompleteInteractionCreate) AutocompleteResult(choices []discord.AutocompleteChoice, opts ...rest.RequestOpt) error { + return e.Respond(discord.InteractionResponseTypeAutocompleteResult, discord.AutocompleteResult{Choices: choices}, opts...) } // ModalSubmitInteractionCreate indicates that a new modal submit interaction has been created. From 44dfd8e2f65aebf70b723562ca8fad136a8b6946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= Date: Fri, 29 Sep 2023 15:10:22 +0200 Subject: [PATCH 118/119] fix test example --- _examples/test/listeners.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_examples/test/listeners.go b/_examples/test/listeners.go index 716b4bfbd..d883d432e 100644 --- a/_examples/test/listeners.go +++ b/_examples/test/listeners.go @@ -48,7 +48,7 @@ func componentListener(event *events.ComponentInteractionCreate) { ids := strings.Split(data.CustomID(), ":") switch ids[0] { case "modal": - _ = event.CreateModal(discord.ModalCreate{ + _ = event.Modal(discord.ModalCreate{ CustomID: "test" + ids[1], Title: "Test" + ids[1] + " Modal", Components: []discord.ContainerComponent{ @@ -159,7 +159,7 @@ func applicationCommandListener(event *events.ApplicationCommandInteractionCreat func autocompleteListener(event *events.AutocompleteInteractionCreate) { switch event.Data.CommandName { case "test2": - if err := event.Result([]discord.AutocompleteChoice{ + if err := event.AutocompleteResult([]discord.AutocompleteChoice{ discord.AutocompleteChoiceInt{ Name: "test1", Value: 1, From 5a9ed6695be52e20a45a2c0aec21d3d8dde064e1 Mon Sep 17 00:00:00 2001 From: cane Date: Sat, 14 Oct 2023 11:10:36 +0200 Subject: [PATCH 119/119] Update subscription objects to match the docs (#322) --- discord/entitlement.go | 22 +++++++++------------- discord/sku.go | 7 ++++++- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/discord/entitlement.go b/discord/entitlement.go index fb0e763e2..756aad761 100644 --- a/discord/entitlement.go +++ b/discord/entitlement.go @@ -7,19 +7,15 @@ import ( ) type Entitlement struct { - ID snowflake.ID `json:"id"` - SkuID snowflake.ID `json:"sku_id"` - UserID *snowflake.ID `json:"user_id"` - GuildID *snowflake.ID `json:"guild_id"` - ApplicationID snowflake.ID `json:"application_id"` - Type EntitlementType `json:"type"` - Consumed bool `json:"consumed"` - StartsAt *time.Time `json:"starts_at"` - EndsAt *time.Time `json:"ends_at"` - PromotionID *snowflake.ID `json:"promotion_id"` - Deleted bool `json:"deleted"` - GiftCodeFlags int `json:"gift_code_flags"` - SubscriptionID *snowflake.ID `json:"subscription_id"` + ID snowflake.ID `json:"id"` + SkuID snowflake.ID `json:"sku_id"` + ApplicationID snowflake.ID `json:"application_id"` + UserID *snowflake.ID `json:"user_id"` + Type EntitlementType `json:"type"` + Deleted bool `json:"deleted"` + StartsAt *time.Time `json:"starts_at"` + EndsAt *time.Time `json:"ends_at"` + GuildID *snowflake.ID `json:"guild_id"` } type EntitlementType int diff --git a/discord/sku.go b/discord/sku.go index 20366a45f..6e3858e64 100644 --- a/discord/sku.go +++ b/discord/sku.go @@ -31,6 +31,11 @@ const ( type SKUFlags int const ( - SKUFlagGuildSubscription SKUFlags = 1 << (iota + 7) + SKUFlagAvailable SKUFlags = 1 << (iota + 2) + _ + _ + _ + _ + SKUFlagGuildSubscription SKUFlagUserSubscription )