From 610e64e9f01ca8b48fc5f143643680c270b68c21 Mon Sep 17 00:00:00 2001 From: switchupcb <81384235+switchupcb@users.noreply.github.com> Date: Tue, 18 Feb 2025 21:42:26 -0600 Subject: [PATCH] fix #75 --- disgo.go | 78 +++++++++++++++++++++--------------- wrapper/.golangci.yml | 2 +- wrapper/client.go | 10 +++-- wrapper/dasgo.go | 34 ++++++++-------- wrapper/session.go | 6 +-- wrapper/session_heartbeat.go | 2 +- wrapper/session_listener.go | 4 +- wrapper/session_manager.go | 22 +++++++--- 8 files changed, 91 insertions(+), 67 deletions(-) diff --git a/disgo.go b/disgo.go index 8ebf640..7af7b29 100644 --- a/disgo.go +++ b/disgo.go @@ -295,21 +295,23 @@ func DefaultGateway() Gateway { // Use the Gateway.IntentSet to check whether the intent is already enabled. // // DISCLAIMER. Bots that use `DefaultGateway()` or `DefaultConfig()` to -// initialize the Client have privileged intents = `true` in the IntentSet by default. +// initialize the Client have privileged intents = `true` in the IntentSet by default, +// which disables the privileged intent. func (g *Gateway) EnableIntent(intent BitFlag) { g.IntentSet[intent] = true g.Intents |= intent } -// EnableIntentPrivileged enables all privileged intents. +// EnableIntentsPrivileged enables all privileged intents. // https://discord.com/developers/docs/topics/gateway#privileged-intents // // This function does NOT check whether the intent is already enabled. // Use the Gateway.IntentSet to check whether the intent is already enabled. // // DISCLAIMER. Bots that use `DefaultGateway()` or `DefaultConfig()` to -// initialize the Client have privileged intents = `true` in the IntentSet by default. -func (g *Gateway) EnableIntentPrivileged(intent BitFlag) { +// initialize the Client have privileged intents = `true` in the IntentSet by default, +// which disables the privileged intent. +func (g *Gateway) EnableIntentsPrivileged() { for privilegedIntent := range PrivilegedIntents { g.EnableIntent(privilegedIntent) } @@ -2515,7 +2517,7 @@ type GetChannelMessages struct { Around *string `url:"around,omitempty"` Before *string `url:"before,omitempty"` After *string `url:"after,omitempty"` - Limit *Flag `url:"limit,omitempty"` + Limit *int `url:"limit,omitempty"` ChannelID string `url:"-"` } @@ -2541,7 +2543,7 @@ type CreateMessage struct { ChannelID string `json:"-"` Embeds []*Embed `json:"embeds,omitempty"` Components []Component `json:"components,omitempty"` - StickerIDS []*string `json:"sticker_ids,omitempty"` + StickerIDS []string `json:"sticker_ids,omitempty"` Files []*File `json:"-" dasgo:"files,omitempty"` Attachments []*Attachment `json:"attachments,omitempty"` } @@ -2638,8 +2640,8 @@ type DeleteMessage struct { // POST /channels/{channel.id}/messages/bulk-delete // https://discord.com/developers/docs/resources/channel#bulk-delete-messages type BulkDeleteMessages struct { - ChannelID string `json:"-"` - Messages []*string `json:"messages"` + ChannelID string `json:"-"` + Messages []string `json:"messages"` } // Get Answer Voters @@ -2868,7 +2870,7 @@ type ForumAndMediaThreadMessageParams struct { Flags *BitFlag `json:"flags,omitempty"` Embeds []*Embed `json:"embeds,omitempty"` Components []Component `json:"components,omitempty"` - StickerIDS []*string `json:"sticker_ids,omitempty"` + StickerIDS []string `json:"sticker_ids,omitempty"` Attachments []*Attachment `json:"attachments,omitempty"` } @@ -2967,20 +2969,20 @@ type GetGuildEmoji struct { // POST /guilds/{guild.id}/emojis // https://discord.com/developers/docs/resources/emoji#create-guild-emoji type CreateGuildEmoji struct { - GuildID string `json:"-"` - Name string `json:"name"` - Image string `json:"image"` - Roles []*string `json:"roles"` + GuildID string `json:"-"` + Name string `json:"name"` + Image string `json:"image"` + Roles []string `json:"roles"` } // Modify Guild Emoji // PATCH /guilds/{guild.id}/emojis/{emoji.id} // https://discord.com/developers/docs/resources/emoji#modify-guild-emoji type ModifyGuildEmoji struct { - Name *string `json:"name,omitempty"` - Roles *[]*string `json:"roles,omitempty"` - GuildID string `json:"-"` - EmojiID string `json:"-"` + Name *string `json:"name,omitempty"` + Roles *[]string `json:"roles"` + GuildID string `json:"-"` + EmojiID string `json:"-"` } // Delete Guild Emoji @@ -4944,7 +4946,7 @@ type Message struct { StickerItems []*StickerItem `json:"sticker_items"` Attachments []*Attachment `json:"attachments"` MentionChannels []*ChannelMention `json:"mention_channels,omitempty"` - MentionRoles []*string `json:"mention_roles"` + MentionRoles []string `json:"mention_roles"` Mentions []*User `json:"mentions"` Pinned bool `json:"pinned"` MentionEveryone bool `json:"mention_everyone"` @@ -5290,8 +5292,8 @@ type ChannelMention struct { // https://discord.com/developers/docs/resources/channel#allowed-mentions-object-allowed-mentions-structure type AllowedMentions struct { Parse []*string `json:"parse"` - Roles []*string `json:"roles"` - Users []*string `json:"users"` + Roles []string `json:"roles"` + Users []string `json:"users"` RepliedUser bool `json:"replied_user"` } @@ -5656,7 +5658,7 @@ type GuildMember struct { Permissions *string `json:"permissions,omitempty"` CommunicationDisabledUntil **time.Time `json:"communication_disabled_until,omitempty"` AvatarDecorationData **AvatarDecorationData `json:"avatar_decoration_data,omitempty"` - Roles []*string `json:"roles"` + Roles []string `json:"roles"` Flags BitFlag `json:"flags"` Deaf bool `json:"deaf"` Mute bool `json:"mute"` @@ -17394,7 +17396,7 @@ func (s *Session) connect(bot *Client) error { // spawn the manager goroutine. s.manager.routines.Add(1) - go s.manage() + go s.manage(bot) // ensure that the Session's goroutines are spawned. s.manager.routines.Wait() @@ -17519,7 +17521,7 @@ func (s *Session) initial(bot *Client, attempt int) error { // the session does NOT reconnect in time, the Discord Gateway send an Opcode 9 Invalid Session. case FlagGatewayOpcodeInvalidSession: // Remove the session from the session manager. - s.client_manager.Gateway.Store(s.ID, nil) + s.client_manager.RemoveGatewaySession(s.ID) if attempt < 1 { // wait for Discord to close the session, then complete a fresh connect. @@ -17598,7 +17600,7 @@ func (s *Session) disconnect(code int) error { // Reconnect reconnects an already connected session to the Discord Gateway // by disconnecting the session, then connecting again. func (s *Session) Reconnect(bot *Client) error { - s.reconnect("reconnecting") + s.reconnect(bot, "reconnecting") if err := <-s.manager.err; err != nil { return err @@ -20932,7 +20934,7 @@ func (s *Session) beat(bot *Client) error { if atomic.LoadUint32(&s.heartbeat.acks) == 0 { s.Unlock() - s.reconnect("attempting to reconnect session due to no HeartbeatACK") + s.reconnect(bot, "attempting to reconnect session due to no HeartbeatACK") return nil } @@ -21113,14 +21115,14 @@ func (s *Session) onPayload(bot *Client, payload GatewayPayload) error { // occurs when the Discord Gateway is shutting down the connection, while signalling the client to reconnect. case FlagGatewayOpcodeReconnect: - s.reconnect("reconnecting session due to Opcode 7 Reconnect") + s.reconnect(bot, "reconnecting session due to Opcode 7 Reconnect") return nil // in the context of onPayload, an Invalid Session occurs when an active session is invalidated. case FlagGatewayOpcodeInvalidSession: // Remove the session from the session manager. - s.client_manager.Gateway.Store(s.ID, nil) + s.client_manager.RemoveGatewaySession(s.ID) // wait for Discord to close the session, then complete a fresh connect. <-time.NewTimer(invalidSessionWaitTime).C @@ -21179,7 +21181,7 @@ func (s *Session) logClose(routine string) { // reconnect spawns a goroutine for reconnection which prompts the manager // to reconnect upon a disconnection. -func (s *Session) reconnect(reason string) { +func (s *Session) reconnect(bot *Client, reason string) { s.manager.Go(func() error { s.Lock() defer s.logClose("reconnect") @@ -21192,12 +21194,18 @@ func (s *Session) reconnect(reason string) { return fmt.Errorf("reconnect: %w", err) } + // connect to the Discord Gateway again. + s.Context = nil + if err := s.connect(bot); err != nil { + return fmt.Errorf("reconnect: %w", err) + } + return nil }) } // manage manages a Session's goroutines. -func (s *Session) manage() { +func (s *Session) manage(bot *Client) { s.manager.routines.Done() defer func() { s.Lock() @@ -21247,7 +21255,11 @@ func (s *Session) manage() { // when an error occurs from a WebSocket Close Error. case errors.As(err, closeErr): - s.manager.err <- s.handleGatewayCloseError(closeErr) + if bot == nil { + s.manager.err <- fmt.Errorf("gateway websocket close error, but unable to reconnect: %w", err) + } + + s.manager.err <- s.handleGatewayCloseError(bot, closeErr) default: if cErr := s.Conn.Close(websocket.StatusCode(FlagClientCloseEventCodeAway), ""); cErr != nil { @@ -21270,7 +21282,7 @@ func (s *Session) manage() { } // handleGatewayCloseError handles a WebSocket CloseError. -func (s *Session) handleGatewayCloseError(closeErr *websocket.CloseError) error { +func (s *Session) handleGatewayCloseError(bot *Client, closeErr *websocket.CloseError) error { code, ok := GatewayCloseEventCodes[int(closeErr.Code)] switch ok { // Gateway Close Event Code is known. @@ -21281,7 +21293,7 @@ func (s *Session) handleGatewayCloseError(closeErr *websocket.CloseError) error ) if code.Reconnect { - s.reconnect(fmt.Sprintf("reconnecting due to Gateway Close Event Code %d", code.Code)) + s.reconnect(bot, fmt.Sprintf("reconnecting due to Gateway Close Event Code %d", code.Code)) return nil } @@ -21379,7 +21391,7 @@ func (s *Session) Wait() (int, error) { // when an error occurs from a WebSocket Close Error. case errors.As(err, closeErr): - return SignalError, s.handleGatewayCloseError(closeErr) + return SignalError, s.handleGatewayCloseError(nil, closeErr) } return SignalError, err //nolint:wrapcheck diff --git a/wrapper/.golangci.yml b/wrapper/.golangci.yml index 74ca94b..90e9525 100644 --- a/wrapper/.golangci.yml +++ b/wrapper/.golangci.yml @@ -31,7 +31,7 @@ linters: - dupl - forbidigo - funlen - - goerr113 + - err113 - gofumpt - interfacebloat - nilnil diff --git a/wrapper/client.go b/wrapper/client.go index 88295ad..0da3001 100644 --- a/wrapper/client.go +++ b/wrapper/client.go @@ -265,21 +265,23 @@ func DefaultGateway() Gateway { // Use the Gateway.IntentSet to check whether the intent is already enabled. // // DISCLAIMER. Bots that use `DefaultGateway()` or `DefaultConfig()` to -// initialize the Client have privileged intents = `true` in the IntentSet by default. +// initialize the Client have privileged intents = `true` in the IntentSet by default, +// which disables the privileged intent. func (g *Gateway) EnableIntent(intent BitFlag) { g.IntentSet[intent] = true g.Intents |= intent } -// EnableIntentPrivileged enables all privileged intents. +// EnableIntentsPrivileged enables all privileged intents. // https://discord.com/developers/docs/topics/gateway#privileged-intents // // This function does NOT check whether the intent is already enabled. // Use the Gateway.IntentSet to check whether the intent is already enabled. // // DISCLAIMER. Bots that use `DefaultGateway()` or `DefaultConfig()` to -// initialize the Client have privileged intents = `true` in the IntentSet by default. -func (g *Gateway) EnableIntentPrivileged(intent BitFlag) { +// initialize the Client have privileged intents = `true` in the IntentSet by default, +// which disables the privileged intent. +func (g *Gateway) EnableIntentsPrivileged() { for privilegedIntent := range PrivilegedIntents { g.EnableIntent(privilegedIntent) } diff --git a/wrapper/dasgo.go b/wrapper/dasgo.go index 38337e8..52c6506 100644 --- a/wrapper/dasgo.go +++ b/wrapper/dasgo.go @@ -2101,7 +2101,7 @@ type GetChannelMessages struct { Around *string `url:"around,omitempty"` Before *string `url:"before,omitempty"` After *string `url:"after,omitempty"` - Limit *Flag `url:"limit,omitempty"` + Limit *int `url:"limit,omitempty"` } // Get Channel Message @@ -2124,7 +2124,7 @@ type CreateMessage struct { AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"` MessageReference *MessageReference `json:"message_reference,omitempty"` Components []Component `json:"components,omitempty"` - StickerIDS []*string `json:"sticker_ids,omitempty"` + StickerIDS []string `json:"sticker_ids,omitempty"` Files []*File `json:"-" dasgo:"files,omitempty"` Attachments []*Attachment `json:"attachments,omitempty"` Flags *BitFlag `json:"flags,omitempty"` @@ -2223,8 +2223,8 @@ type DeleteMessage struct { // POST /channels/{channel.id}/messages/bulk-delete // https://discord.com/developers/docs/resources/channel#bulk-delete-messages type BulkDeleteMessages struct { - ChannelID string `json:"-"` - Messages []*string `json:"messages"` + ChannelID string `json:"-"` + Messages []string `json:"messages"` } // Get Answer Voters @@ -2452,7 +2452,7 @@ type ForumAndMediaThreadMessageParams struct { Embeds []*Embed `json:"embeds,omitempty"` AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"` Components []Component `json:"components,omitempty"` - StickerIDS []*string `json:"sticker_ids,omitempty"` + StickerIDS []string `json:"sticker_ids,omitempty"` Attachments []*Attachment `json:"attachments,omitempty"` Flags *BitFlag `json:"flags,omitempty"` } @@ -2552,20 +2552,20 @@ type GetGuildEmoji struct { // POST /guilds/{guild.id}/emojis // https://discord.com/developers/docs/resources/emoji#create-guild-emoji type CreateGuildEmoji struct { - GuildID string `json:"-"` - Name string `json:"name"` - Image string `json:"image"` - Roles []*string `json:"roles"` + GuildID string `json:"-"` + Name string `json:"name"` + Image string `json:"image"` + Roles []string `json:"roles"` } // Modify Guild Emoji // PATCH /guilds/{guild.id}/emojis/{emoji.id} // https://discord.com/developers/docs/resources/emoji#modify-guild-emoji type ModifyGuildEmoji struct { - GuildID string `json:"-"` - EmojiID string `json:"-"` - Name *string `json:"name,omitempty"` - Roles *[]*string `json:"roles,omitempty"` + GuildID string `json:"-"` + EmojiID string `json:"-"` + Name *string `json:"name,omitempty"` + Roles *[]string `json:"roles"` } // Delete Guild Emoji @@ -4507,7 +4507,7 @@ type Message struct { TTS bool `json:"tts"` MentionEveryone bool `json:"mention_everyone"` Mentions []*User `json:"mentions"` - MentionRoles []*string `json:"mention_roles"` + MentionRoles []string `json:"mention_roles"` MentionChannels []*ChannelMention `json:"mention_channels,omitempty"` Attachments []*Attachment `json:"attachments"` Embeds []*Embed `json:"embeds"` @@ -4878,8 +4878,8 @@ type ChannelMention struct { // https://discord.com/developers/docs/resources/channel#allowed-mentions-object-allowed-mentions-structure type AllowedMentions struct { Parse []*string `json:"parse"` - Roles []*string `json:"roles"` - Users []*string `json:"users"` + Roles []string `json:"roles"` + Users []string `json:"users"` RepliedUser bool `json:"replied_user"` } @@ -5241,7 +5241,7 @@ type GuildMember struct { Nick **string `json:"nick,omitempty"` Avatar **string `json:"avatar,omitempty"` Banner **string `json:"banner,omitempty"` - Roles []*string `json:"roles"` + Roles []string `json:"roles"` JoinedAt time.Time `json:"joined_at"` PremiumSince **time.Time `json:"premium_since,omitempty"` Deaf bool `json:"deaf"` diff --git a/wrapper/session.go b/wrapper/session.go index 5f04526..a51c312 100644 --- a/wrapper/session.go +++ b/wrapper/session.go @@ -261,7 +261,7 @@ func (s *Session) connect(bot *Client) error { // spawn the manager goroutine. s.manager.routines.Add(1) - go s.manage() + go s.manage(bot) // ensure that the Session's goroutines are spawned. s.manager.routines.Wait() @@ -386,7 +386,7 @@ func (s *Session) initial(bot *Client, attempt int) error { // the session does NOT reconnect in time, the Discord Gateway send an Opcode 9 Invalid Session. case FlagGatewayOpcodeInvalidSession: // Remove the session from the session manager. - s.client_manager.Gateway.Store(s.ID, nil) + s.client_manager.RemoveGatewaySession(s.ID) if attempt < 1 { // wait for Discord to close the session, then complete a fresh connect. @@ -465,7 +465,7 @@ func (s *Session) disconnect(code int) error { // Reconnect reconnects an already connected session to the Discord Gateway // by disconnecting the session, then connecting again. func (s *Session) Reconnect(bot *Client) error { - s.reconnect("reconnecting") + s.reconnect(bot, "reconnecting") if err := <-s.manager.err; err != nil { return err diff --git a/wrapper/session_heartbeat.go b/wrapper/session_heartbeat.go index c9a0179..f9eb23c 100644 --- a/wrapper/session_heartbeat.go +++ b/wrapper/session_heartbeat.go @@ -62,7 +62,7 @@ func (s *Session) beat(bot *Client) error { if atomic.LoadUint32(&s.heartbeat.acks) == 0 { s.Unlock() - s.reconnect("attempting to reconnect session due to no HeartbeatACK") + s.reconnect(bot, "attempting to reconnect session due to no HeartbeatACK") return nil } diff --git a/wrapper/session_listener.go b/wrapper/session_listener.go index 8968b4d..4c85f9a 100644 --- a/wrapper/session_listener.go +++ b/wrapper/session_listener.go @@ -73,14 +73,14 @@ func (s *Session) onPayload(bot *Client, payload GatewayPayload) error { // occurs when the Discord Gateway is shutting down the connection, while signalling the client to reconnect. case FlagGatewayOpcodeReconnect: - s.reconnect("reconnecting session due to Opcode 7 Reconnect") + s.reconnect(bot, "reconnecting session due to Opcode 7 Reconnect") return nil // in the context of onPayload, an Invalid Session occurs when an active session is invalidated. case FlagGatewayOpcodeInvalidSession: // Remove the session from the session manager. - s.client_manager.Gateway.Store(s.ID, nil) + s.client_manager.RemoveGatewaySession(s.ID) // wait for Discord to close the session, then complete a fresh connect. <-time.NewTimer(invalidSessionWaitTime).C diff --git a/wrapper/session_manager.go b/wrapper/session_manager.go index f0a735c..d78271b 100644 --- a/wrapper/session_manager.go +++ b/wrapper/session_manager.go @@ -107,7 +107,7 @@ func (s *Session) logClose(routine string) { // reconnect spawns a goroutine for reconnection which prompts the manager // to reconnect upon a disconnection. -func (s *Session) reconnect(reason string) { +func (s *Session) reconnect(bot *Client, reason string) { s.manager.Go(func() error { s.Lock() defer s.logClose("reconnect") @@ -120,12 +120,18 @@ func (s *Session) reconnect(reason string) { return fmt.Errorf("reconnect: %w", err) } + // connect to the Discord Gateway again. + s.Context = nil + if err := s.connect(bot); err != nil { + return fmt.Errorf("reconnect: %w", err) + } + return nil }) } // manage manages a Session's goroutines. -func (s *Session) manage() { +func (s *Session) manage(bot *Client) { s.manager.routines.Done() defer func() { s.Lock() @@ -175,7 +181,11 @@ func (s *Session) manage() { // when an error occurs from a WebSocket Close Error. case errors.As(err, closeErr): - s.manager.err <- s.handleGatewayCloseError(closeErr) + if bot == nil { + s.manager.err <- fmt.Errorf("gateway websocket close error, but unable to reconnect: %w", err) + } + + s.manager.err <- s.handleGatewayCloseError(bot, closeErr) default: if cErr := s.Conn.Close(websocket.StatusCode(FlagClientCloseEventCodeAway), ""); cErr != nil { @@ -198,7 +208,7 @@ func (s *Session) manage() { } // handleGatewayCloseError handles a WebSocket CloseError. -func (s *Session) handleGatewayCloseError(closeErr *websocket.CloseError) error { +func (s *Session) handleGatewayCloseError(bot *Client, closeErr *websocket.CloseError) error { code, ok := GatewayCloseEventCodes[int(closeErr.Code)] switch ok { // Gateway Close Event Code is known. @@ -209,7 +219,7 @@ func (s *Session) handleGatewayCloseError(closeErr *websocket.CloseError) error ) if code.Reconnect { - s.reconnect(fmt.Sprintf("reconnecting due to Gateway Close Event Code %d", code.Code)) + s.reconnect(bot, fmt.Sprintf("reconnecting due to Gateway Close Event Code %d", code.Code)) return nil } @@ -307,7 +317,7 @@ func (s *Session) Wait() (int, error) { // when an error occurs from a WebSocket Close Error. case errors.As(err, closeErr): - return SignalError, s.handleGatewayCloseError(closeErr) + return SignalError, s.handleGatewayCloseError(nil, closeErr) } return SignalError, err //nolint:wrapcheck