From bca349d46d25fc0a24b74a1e102588b541dde669 Mon Sep 17 00:00:00 2001 From: Carson Hoffman Date: Wed, 20 Jan 2021 18:05:27 -0500 Subject: [PATCH 01/15] Change library version to alpha for v8 --- discord.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord.go b/discord.go index f508fa8..3bbcfa1 100644 --- a/discord.go +++ b/discord.go @@ -22,7 +22,7 @@ import ( ) // VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/) -const VERSION = "0.22.0" +const VERSION = "0.23.0-v8alpha" // ErrMFA will be risen by New when the user has 2FA. var ErrMFA = errors.New("account has 2FA enabled") From 9eb033c0db4c931577c91e0f9083546e6dbaacd1 Mon Sep 17 00:00:00 2001 From: Carson Hoffman Date: Wed, 20 Jan 2021 18:05:39 -0500 Subject: [PATCH 02/15] Bump API version to v8 --- endpoints.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/endpoints.go b/endpoints.go index 88663fe..f4f2b3b 100644 --- a/endpoints.go +++ b/endpoints.go @@ -14,7 +14,7 @@ package discordgo import "strconv" // APIVersion is the Discord API version used for the REST and Websocket API. -var APIVersion = "6" +var APIVersion = "8" // Known Discord API Endpoints. var ( From 3773e286e69ae1dc4e1fcdd13599408cb9dcbfd4 Mon Sep 17 00:00:00 2001 From: Carson Hoffman Date: Wed, 20 Jan 2021 18:11:20 -0500 Subject: [PATCH 03/15] Add v8 permissions changes --- restapi.go | 22 ++++++++++------------ state.go | 4 ++-- structs.go | 22 ++++++++++++++++------ 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/restapi.go b/restapi.go index 53970f2..4c11e2d 100644 --- a/restapi.go +++ b/restapi.go @@ -465,7 +465,7 @@ func (s *Session) UserGuildSettingsEdit(guildID string, settings *UserGuildSetti // // NOTE: This function is now deprecated and will be removed in the future. // Please see the same function inside state.go -func (s *Session) UserChannelPermissions(userID, channelID string) (apermissions int, err error) { +func (s *Session) UserChannelPermissions(userID, channelID string) (apermissions int64, err error) { // Try to just get permissions from state. apermissions, err = s.State.UserChannelPermissions(userID, channelID) if err == nil { @@ -507,7 +507,7 @@ func (s *Session) UserChannelPermissions(userID, channelID string) (apermissions // Calculates the permissions for a member. // https://support.discord.com/hc/en-us/articles/206141927-How-is-the-permission-hierarchy-structured- -func memberPermissions(guild *Guild, channel *Channel, userID string, roles []string) (apermissions int) { +func memberPermissions(guild *Guild, channel *Channel, userID string, roles []string) (apermissions int64) { if userID == guild.OwnerID { apermissions = PermissionAll return @@ -542,13 +542,11 @@ func memberPermissions(guild *Guild, channel *Channel, userID string, roles []st } } - denies := 0 - allows := 0 - + var denies, allows int64 // Member overwrites can override role overrides, so do two passes for _, overwrite := range channel.PermissionOverwrites { for _, roleID := range roles { - if overwrite.Type == "role" && roleID == overwrite.ID { + if overwrite.Type == PermissionOverwriteTypeRole && roleID == overwrite.ID { denies |= overwrite.Deny allows |= overwrite.Allow break @@ -560,7 +558,7 @@ func memberPermissions(guild *Guild, channel *Channel, userID string, roles []st apermissions |= allows for _, overwrite := range channel.PermissionOverwrites { - if overwrite.Type == "member" && overwrite.ID == userID { + if overwrite.Type == PermissionOverwriteTypeMember && overwrite.ID == userID { apermissions &= ^overwrite.Deny apermissions |= overwrite.Allow break @@ -1809,13 +1807,13 @@ func (s *Session) ChannelInviteCreate(channelID string, i Invite) (st *Invite, e // ChannelPermissionSet creates a Permission Override for the given channel. // NOTE: This func name may changed. Using Set instead of Create because // you can both create a new override or update an override with this function. -func (s *Session) ChannelPermissionSet(channelID, targetID, targetType string, allow, deny int) (err error) { +func (s *Session) ChannelPermissionSet(channelID, targetID string, targetType PermissionOverwriteType, allow, deny int) (err error) { data := struct { - ID string `json:"id"` - Type string `json:"type"` - Allow int `json:"allow"` - Deny int `json:"deny"` + ID string `json:"id"` + Type PermissionOverwriteType `json:"type"` + Allow int `json:"allow"` + Deny int `json:"deny"` }{targetID, targetType, allow, deny} _, err = s.RequestWithBucketID("PUT", EndpointChannelPermission(channelID, targetID), data, EndpointChannelPermission(channelID, "")) diff --git a/state.go b/state.go index c89f8bb..4f6479b 100644 --- a/state.go +++ b/state.go @@ -997,7 +997,7 @@ func (s *State) OnInterface(se *Session, i interface{}) (err error) { // UserChannelPermissions returns the permission of a user in a channel. // userID : The ID of the user to calculate permissions for. // channelID : The ID of the channel to calculate permission for. -func (s *State) UserChannelPermissions(userID, channelID string) (apermissions int, err error) { +func (s *State) UserChannelPermissions(userID, channelID string) (apermissions int64, err error) { if s == nil { return 0, ErrNilState } @@ -1022,7 +1022,7 @@ func (s *State) UserChannelPermissions(userID, channelID string) (apermissions i // MessagePermissions returns the permissions of the author of the message // in the channel in which it was sent. -func (s *State) MessagePermissions(message *Message) (apermissions int, err error) { +func (s *State) MessagePermissions(message *Message) (apermissions int64, err error) { if s == nil { return 0, ErrNilState } diff --git a/structs.go b/structs.go index 24e245f..fc4fd9a 100644 --- a/structs.go +++ b/structs.go @@ -322,12 +322,22 @@ type ChannelFollow struct { WebhookID string `json:"webhook_id"` } +// PermissionOverwriteType represents the type of resource on which +// a permission overwrite acts. +type PermissionOverwriteType int + +// The possible permission overwrite types. +const ( + PermissionOverwriteTypeRole PermissionOverwriteType = iota + PermissionOverwriteTypeMember +) + // A PermissionOverwrite holds permission overwrite data for a Channel type PermissionOverwrite struct { - ID string `json:"id"` - Type string `json:"type"` - Deny int `json:"deny"` - Allow int `json:"allow"` + ID string `json:"id"` + Type PermissionOverwriteType `json:"type"` + Deny int64 `json:"deny,string"` + Allow int64 `json:"allow,string"` } // Emoji struct holds data related to Emoji's @@ -564,7 +574,7 @@ type Guild struct { ApproximatePresenceCount int `json:"approximate_presence_count"` // Permissions of our user - Permissions int `json:"permissions"` + Permissions int64 `json:"permissions,string"` } // MessageNotifications is the notification level for a guild @@ -650,7 +660,7 @@ type Role struct { // The permissions of the role on the guild (doesn't include channel overrides). // This is a combination of bit masks; the presence of a certain permission can // be checked by performing a bitwise AND between this int and the permission. - Permissions int `json:"permissions"` + Permissions int64 `json:"permissions,string"` } // Mention returns a string which mentions the role From 93d5c5947beef4f3906dbe1ebbb36b2722f5df13 Mon Sep 17 00:00:00 2001 From: Carson Hoffman Date: Wed, 20 Jan 2021 18:13:18 -0500 Subject: [PATCH 04/15] Correct gap in message types --- message.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/message.go b/message.go index 64866a3..6c6b48f 100644 --- a/message.go +++ b/message.go @@ -34,7 +34,7 @@ const ( MessageTypeUserPremiumGuildSubscriptionTierTwo MessageTypeUserPremiumGuildSubscriptionTierThree MessageTypeChannelFollowAdd - MessageTypeGuildDiscoveryDisqualified + MessageTypeGuildDiscoveryDisqualified = iota + 1 MessageTypeGuildDiscoveryRequalified ) From 9fc2793e83fbe9376936c4973c5e3920cb5c6615 Mon Sep 17 00:00:00 2001 From: Carson Hoffman Date: Wed, 20 Jan 2021 18:14:50 -0500 Subject: [PATCH 05/15] Add reply and application command message types --- message.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/message.go b/message.go index 6c6b48f..61cd0d9 100644 --- a/message.go +++ b/message.go @@ -36,6 +36,8 @@ const ( MessageTypeChannelFollowAdd MessageTypeGuildDiscoveryDisqualified = iota + 1 MessageTypeGuildDiscoveryRequalified + MessageTypeReply = iota + 4 + MessageTypeApplicationCommand ) // A Message stores all data related to a specific Discord message. From 167b649902726e8e263ab4318a75340f4f978ac8 Mon Sep 17 00:00:00 2001 From: Carson Hoffman Date: Wed, 20 Jan 2021 18:18:34 -0500 Subject: [PATCH 06/15] Remove support for optional intents --- examples/airhorn/main.go | 2 +- examples/pingpong/main.go | 2 +- examples/voice_receive/main.go | 2 +- structs.go | 10 ++++++---- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/examples/airhorn/main.go b/examples/airhorn/main.go index 9a37afa..0955d64 100644 --- a/examples/airhorn/main.go +++ b/examples/airhorn/main.go @@ -55,7 +55,7 @@ func main() { // We need information about guilds (which includes their channels), // messages and voice states. - dg.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsGuilds | discordgo.IntentsGuildMessages | discordgo.IntentsGuildVoiceStates) + dg.Identify.Intents = discordgo.IntentsGuilds | discordgo.IntentsGuildMessages | discordgo.IntentsGuildVoiceStates // Open the websocket and begin listening. err = dg.Open() diff --git a/examples/pingpong/main.go b/examples/pingpong/main.go index e42eb40..1a23140 100644 --- a/examples/pingpong/main.go +++ b/examples/pingpong/main.go @@ -34,7 +34,7 @@ func main() { dg.AddHandler(messageCreate) // In this example, we only care about receiving message events. - dg.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsGuildMessages) + dg.Identify.Intents = discordgo.IntentsGuildMessages // Open a websocket connection to Discord and begin listening. err = dg.Open() diff --git a/examples/voice_receive/main.go b/examples/voice_receive/main.go index e5a9252..c480b9e 100644 --- a/examples/voice_receive/main.go +++ b/examples/voice_receive/main.go @@ -75,7 +75,7 @@ func main() { defer s.Close() // We only really care about receiving voice state updates. - s.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsGuildVoiceStates) + s.Identify.Intents = discordgo.IntentsGuildVoiceStates err = s.Open() if err != nil { diff --git a/structs.go b/structs.go index fc4fd9a..3975b2e 100644 --- a/structs.go +++ b/structs.go @@ -1158,7 +1158,7 @@ type Identify struct { Shard *[2]int `json:"shard,omitempty"` Presence GatewayStatusUpdate `json:"presence,omitempty"` GuildSubscriptions bool `json:"guild_subscriptions"` - Intents *Intent `json:"intents,omitempty"` + Intents Intent `json:"intents"` } // IdentifyProperties contains the "properties" portion of an Identify packet @@ -1345,7 +1345,9 @@ const ( IntentsNone Intent = 0 ) -// MakeIntent helps convert a gateway intent value for use in the Identify structure. -func MakeIntent(intents Intent) *Intent { - return &intents +// MakeIntent used to help convert a gateway intent value for use in the Identify structure; +// this was useful to help support the use of a pointer type when intents were optional. +// This is now a no-op, and is not necessary to use. +func MakeIntent(intents Intent) Intent { + return intents } From daaafb5a7fb6d38b43b83b9b56791fdd5342a15e Mon Sep 17 00:00:00 2001 From: Carson Hoffman Date: Wed, 20 Jan 2021 18:22:39 -0500 Subject: [PATCH 07/15] Rename fields for v8 --- endpoints.go | 3 ++- restapi.go | 2 +- structs.go | 6 ------ 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/endpoints.go b/endpoints.go index f4f2b3b..89d56ed 100644 --- a/endpoints.go +++ b/endpoints.go @@ -90,7 +90,8 @@ var ( EndpointGuildRoles = func(gID string) string { return EndpointGuilds + gID + "/roles" } EndpointGuildRole = func(gID, rID string) string { return EndpointGuilds + gID + "/roles/" + rID } EndpointGuildInvites = func(gID string) string { return EndpointGuilds + gID + "/invites" } - EndpointGuildEmbed = func(gID string) string { return EndpointGuilds + gID + "/embed" } + EndpointGuildWidget = func(gID string) string { return EndpointGuilds + gID + "/widget" } + EndpointGuildEmbed = EndpointGuildWidget EndpointGuildPrune = func(gID string) string { return EndpointGuilds + gID + "/prune" } EndpointGuildIcon = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".png" } EndpointGuildIconAnimated = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".gif" } diff --git a/restapi.go b/restapi.go index 4c11e2d..4c26aa8 100644 --- a/restapi.go +++ b/restapi.go @@ -715,7 +715,7 @@ func (s *Session) GuildBanCreateWithReason(guildID, userID, reason string, days queryParams := url.Values{} if days > 0 { - queryParams.Set("delete-message-days", strconv.Itoa(days)) + queryParams.Set("delete_message_days", strconv.Itoa(days)) } if reason != "" { queryParams.Set("reason", reason) diff --git a/structs.go b/structs.go index 3975b2e..c6e63b1 100644 --- a/structs.go +++ b/structs.go @@ -437,9 +437,6 @@ type Guild struct { // The ID of the AFK voice channel. AfkChannelID string `json:"afk_channel_id"` - // The ID of the embed channel ID, used for embed widgets. - EmbedChannelID string `json:"embed_channel_id"` - // The user ID of the owner of the guild. OwnerID string `json:"owner_id"` @@ -468,9 +465,6 @@ type Guild struct { // The verification level required for the guild. VerificationLevel VerificationLevel `json:"verification_level"` - // Whether the guild has embedding enabled. - EmbedEnabled bool `json:"embed_enabled"` - // Whether the guild is considered large. This is // determined by a member threshold in the identify packet, // and is currently hard-coded at 250 members in the library. From 866ecccb2ea25a981211edc6a0e6a538d2e0c98f Mon Sep 17 00:00:00 2001 From: Carson Hoffman Date: Wed, 20 Jan 2021 18:33:32 -0500 Subject: [PATCH 08/15] Support millisecond precision in rate limits --- ratelimit.go | 15 +++++++++------ ratelimit_test.go | 5 +++-- structs.go | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/ratelimit.go b/ratelimit.go index dc48c92..cd96ead 100644 --- a/ratelimit.go +++ b/ratelimit.go @@ -1,6 +1,7 @@ package discordgo import ( + "math" "net/http" "strconv" "strings" @@ -140,20 +141,21 @@ func (b *Bucket) Release(headers http.Header) error { remaining := headers.Get("X-RateLimit-Remaining") reset := headers.Get("X-RateLimit-Reset") global := headers.Get("X-RateLimit-Global") - retryAfter := headers.Get("Retry-After") + resetAfter := headers.Get("X-RateLimit-Reset-After") // Update global and per bucket reset time if the proper headers are available // If global is set, then it will block all buckets until after Retry-After // If Retry-After without global is provided it will use that for the new reset // time since it's more accurate than X-RateLimit-Reset. // If Retry-After after is not proided, it will update the reset time from X-RateLimit-Reset - if retryAfter != "" { - parsedAfter, err := strconv.ParseInt(retryAfter, 10, 64) + if resetAfter != "" { + parsedAfter, err := strconv.ParseFloat(resetAfter, 64) if err != nil { return err } - resetAt := time.Now().Add(time.Duration(parsedAfter) * time.Millisecond) + whole, frac := math.Modf(parsedAfter) + resetAt := time.Now().Add(time.Duration(whole) * time.Second).Add(time.Duration(frac*1000) * time.Millisecond) // Lock either this single bucket or all buckets if global != "" { @@ -168,7 +170,7 @@ func (b *Bucket) Release(headers http.Header) error { return err } - unix, err := strconv.ParseInt(reset, 10, 64) + unix, err := strconv.ParseFloat(reset, 64) if err != nil { return err } @@ -177,7 +179,8 @@ func (b *Bucket) Release(headers http.Header) error { // some extra time is added because without it i still encountered 429's. // The added amount is the lowest amount that gave no 429's // in 1k requests - delta := time.Unix(unix, 0).Sub(discordTime) + time.Millisecond*250 + whole, frac := math.Modf(unix) + delta := time.Unix(int64(whole), 0).Add(time.Duration(frac*1000)*time.Millisecond).Sub(discordTime) + time.Millisecond*250 b.reset = time.Now().Add(delta) } diff --git a/ratelimit_test.go b/ratelimit_test.go index db18211..95dc704 100644 --- a/ratelimit_test.go +++ b/ratelimit_test.go @@ -1,6 +1,7 @@ package discordgo import ( + "fmt" "net/http" "strconv" "testing" @@ -18,7 +19,7 @@ func TestRatelimitReset(t *testing.T) { headers.Set("X-RateLimit-Remaining", "0") // Reset for approx 2 seconds from now - headers.Set("X-RateLimit-Reset", strconv.FormatInt(time.Now().Add(time.Second*2).Unix(), 10)) + headers.Set("X-RateLimit-Reset", fmt.Sprint(float64(time.Now().Add(time.Second*2).UnixNano())/1e6)) headers.Set("Date", time.Now().Format(time.RFC850)) err := bucket.Release(headers) @@ -105,7 +106,7 @@ func sendBenchReq(endpoint string, rl *RateLimiter) { headers := http.Header(make(map[string][]string)) headers.Set("X-RateLimit-Remaining", "10") - headers.Set("X-RateLimit-Reset", strconv.FormatInt(time.Now().Unix(), 10)) + headers.Set("X-RateLimit-Reset", fmt.Sprint(float64(time.Now().UnixNano())/1e6)) headers.Set("Date", time.Now().Format(time.RFC850)) bucket.Release(headers) diff --git a/structs.go b/structs.go index c6e63b1..09bb163 100644 --- a/structs.go +++ b/structs.go @@ -845,6 +845,24 @@ type TooManyRequests struct { RetryAfter time.Duration `json:"retry_after"` } +func (t *TooManyRequests) UnmarshalJSON(b []byte) error { + u := struct { + Bucket string `json:"bucket"` + Message string `json:"message"` + RetryAfter float64 `json:"retry_after"` + }{} + err := json.Unmarshal(b, &u) + if err != nil { + return err + } + + t.Bucket = u.Bucket + t.Message = u.Message + whole, frac := math.Modf(u.RetryAfter) + t.RetryAfter = time.Duration(whole)*time.Second + time.Duration(frac*1000)*time.Millisecond + return nil +} + // A ReadState stores data on the read state of channels. type ReadState struct { MentionCount int `json:"mention_count"` From c41dc15a10e0e72c58b6c0a8a57e03a275b93ffe Mon Sep 17 00:00:00 2001 From: Carson Hoffman Date: Wed, 20 Jan 2021 18:54:38 -0500 Subject: [PATCH 09/15] Add changes to presences, remove Game type --- discord_test.go | 2 +- events.go | 3 +-- state.go | 18 +----------------- structs.go | 40 ++++++---------------------------------- wsapi.go | 48 ++++++++++++++++++++++++------------------------ 5 files changed, 33 insertions(+), 78 deletions(-) diff --git a/discord_test.go b/discord_test.go index 318bed0..30d2c75 100644 --- a/discord_test.go +++ b/discord_test.go @@ -125,7 +125,7 @@ func TestOpenClose(t *testing.T) { // UpdateStatus - maybe we move this into wsapi_test.go but the websocket // created here is needed. This helps tests that the websocket was setup // and it is working. - if err = d.UpdateStatus(0, time.Now().String()); err != nil { + if err = d.UpdateGameStatus(0, time.Now().String()); err != nil { t.Errorf("UpdateStatus error: %+v", err) } diff --git a/events.go b/events.go index dd0e3d8..7488dcc 100644 --- a/events.go +++ b/events.go @@ -196,8 +196,7 @@ type PresencesReplace []*Presence // PresenceUpdate is the data for a PresenceUpdate event. type PresenceUpdate struct { Presence - GuildID string `json:"guild_id"` - Roles []string `json:"roles"` + GuildID string `json:"guild_id"` } // Resumed is the data for a Resumed event. diff --git a/state.go b/state.go index 4f6479b..2eeabd8 100644 --- a/state.go +++ b/state.go @@ -200,14 +200,10 @@ func (s *State) PresenceAdd(guildID string, presence *Presence) error { //guild.Presences[i] = presence //Update status - guild.Presences[i].Game = presence.Game - guild.Presences[i].Roles = presence.Roles + guild.Presences[i].Activities = presence.Activities if presence.Status != "" { guild.Presences[i].Status = presence.Status } - if presence.Nick != "" { - guild.Presences[i].Nick = presence.Nick - } //Update the optionally sent user information //ID Is a mandatory field so you should not need to check if it is empty @@ -966,24 +962,12 @@ func (s *State) OnInterface(se *Session, i interface{}) (err error) { // Member not found; this is a user coming online m = &Member{ GuildID: t.GuildID, - Nick: t.Nick, User: t.User, - Roles: t.Roles, } - } else { - - if t.Nick != "" { - m.Nick = t.Nick - } - if t.User.Username != "" { m.User.Username = t.User.Username } - - // PresenceUpdates always contain a list of roles, so there's no need to check for an empty list here - m.Roles = t.Roles - } err = s.MemberAdd(m) diff --git a/structs.go b/structs.go index 09bb163..3267072 100644 --- a/structs.go +++ b/structs.go @@ -14,6 +14,7 @@ package discordgo import ( "encoding/json" "fmt" + "math" "net/http" "strings" "sync" @@ -692,39 +693,10 @@ type VoiceState struct { // A Presence stores the online, offline, or idle and game status of Guild members. type Presence struct { - User *User `json:"user"` - Status Status `json:"status"` - Game *Game `json:"game"` - Activities []*Game `json:"activities"` - Nick string `json:"nick"` - Roles []string `json:"roles"` - Since *int `json:"since"` -} - -// GameType is the type of "game" (see GameType* consts) in the Game struct -type GameType int - -// Valid GameType values -const ( - GameTypeGame GameType = iota - GameTypeStreaming - GameTypeListening - GameTypeWatching - GameTypeCustom -) - -// A Game struct holds the name of the "playing .." game for a user -type Game struct { - Name string `json:"name"` - Type GameType `json:"type"` - URL string `json:"url,omitempty"` - Details string `json:"details,omitempty"` - State string `json:"state,omitempty"` - TimeStamps TimeStamps `json:"timestamps,omitempty"` - Assets Assets `json:"assets,omitempty"` - ApplicationID string `json:"application_id,omitempty"` - Instance int8 `json:"instance,omitempty"` - // TODO: Party and Secrets (unknown structure) + User *User `json:"user"` + Status Status `json:"status"` + Activities []*Activity `json:"activities"` + Since *int `json:"since"` } // A TimeStamps struct contains start and end times used in the rich presence "playing .." Game @@ -1153,7 +1125,7 @@ type ActivityType int // Valid ActivityType values const ( - ActivityTypeGame GameType = iota + ActivityTypeGame ActivityType = iota ActivityTypeStreaming ActivityTypeListening // ActivityTypeWatching // not valid in this use case? diff --git a/wsapi.go b/wsapi.go index 1cf1598..55bc695 100644 --- a/wsapi.go +++ b/wsapi.go @@ -322,10 +322,10 @@ func (s *Session) heartbeat(wsConn *websocket.Conn, listening <-chan interface{} // UpdateStatusData ia provided to UpdateStatusComplex() type UpdateStatusData struct { - IdleSince *int `json:"since"` - Game *Game `json:"game"` - AFK bool `json:"afk"` - Status string `json:"status"` + IdleSince *int `json:"since"` + Activities []*Activity `json:"activities"` + AFK bool `json:"afk"` + Status string `json:"status"` } type updateStatusOp struct { @@ -333,7 +333,7 @@ type updateStatusOp struct { Data UpdateStatusData `json:"d"` } -func newUpdateStatusData(idle int, gameType GameType, game, url string) *UpdateStatusData { +func newUpdateStatusData(idle int, activityType ActivityType, name, url string) *UpdateStatusData { usd := &UpdateStatusData{ Status: "online", } @@ -342,12 +342,12 @@ func newUpdateStatusData(idle int, gameType GameType, game, url string) *UpdateS usd.IdleSince = &idle } - if game != "" { - usd.Game = &Game{ - Name: game, - Type: gameType, + if name != "" { + usd.Activities = []*Activity{{ + Name: name, + Type: activityType, URL: url, - } + }} } return usd @@ -355,30 +355,30 @@ func newUpdateStatusData(idle int, gameType GameType, game, url string) *UpdateS // UpdateStatus is used to update the user's status. // If idle>0 then set status to idle. -// If game!="" then set game. -// if otherwise, set status to active, and no game. -func (s *Session) UpdateStatus(idle int, game string) (err error) { - return s.UpdateStatusComplex(*newUpdateStatusData(idle, GameTypeGame, game, "")) +// If name!="" then set game. +// if otherwise, set status to active, and no activity. +func (s *Session) UpdateGameStatus(idle int, name string) (err error) { + return s.UpdateStatusComplex(*newUpdateStatusData(idle, ActivityTypeGame, name, "")) } // UpdateStreamingStatus is used to update the user's streaming status. // If idle>0 then set status to idle. -// If game!="" then set game. -// If game!="" and url!="" then set the status type to streaming with the URL set. +// If name!="" then set game. +// If name!="" and url!="" then set the status type to streaming with the URL set. // if otherwise, set status to active, and no game. -func (s *Session) UpdateStreamingStatus(idle int, game string, url string) (err error) { - gameType := GameTypeGame +func (s *Session) UpdateStreamingStatus(idle int, name string, url string) (err error) { + gameType := ActivityTypeGame if url != "" { - gameType = GameTypeStreaming + gameType = ActivityTypeStreaming } - return s.UpdateStatusComplex(*newUpdateStatusData(idle, gameType, game, url)) + return s.UpdateStatusComplex(*newUpdateStatusData(idle, gameType, name, url)) } // UpdateListeningStatus is used to set the user to "Listening to..." -// If game!="" then set to what user is listening to -// Else, set user to active and no game. -func (s *Session) UpdateListeningStatus(game string) (err error) { - return s.UpdateStatusComplex(*newUpdateStatusData(0, GameTypeListening, game, "")) +// If name!="" then set to what user is listening to +// Else, set user to active and no activity. +func (s *Session) UpdateListeningStatus(name string) (err error) { + return s.UpdateStatusComplex(*newUpdateStatusData(0, ActivityTypeListening, name, "")) } // UpdateStatusComplex allows for sending the raw status update data untouched by discordgo. From 657ac048fe60e332fb93351f26a439145930ba89 Mon Sep 17 00:00:00 2001 From: Carson Hoffman Date: Wed, 20 Jan 2021 19:33:43 -0500 Subject: [PATCH 10/15] Switch example to use UpdateGameStatus --- examples/airhorn/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/airhorn/main.go b/examples/airhorn/main.go index 0955d64..45aaa8e 100644 --- a/examples/airhorn/main.go +++ b/examples/airhorn/main.go @@ -78,7 +78,7 @@ func main() { func ready(s *discordgo.Session, event *discordgo.Ready) { // Set the playing status. - s.UpdateStatus(0, "!airhorn") + s.UpdateGameStatus(0, "!airhorn") } // This function will be called (due to AddHandler above) every time a new From 9548146f695087825b34af9d4d06d2f0de57600a Mon Sep 17 00:00:00 2001 From: Carson Hoffman Date: Wed, 20 Jan 2021 19:34:05 -0500 Subject: [PATCH 11/15] Fix comment on UpdateGameStatus --- wsapi.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wsapi.go b/wsapi.go index 55bc695..29a4f61 100644 --- a/wsapi.go +++ b/wsapi.go @@ -353,7 +353,7 @@ func newUpdateStatusData(idle int, activityType ActivityType, name, url string) return usd } -// UpdateStatus is used to update the user's status. +// UpdateGameStatus is used to update the user's status. // If idle>0 then set status to idle. // If name!="" then set game. // if otherwise, set status to active, and no activity. From b7a2a4b7fafb577866e59530ad87baa7dfe4f248 Mon Sep 17 00:00:00 2001 From: Carson Hoffman Date: Wed, 20 Jan 2021 19:42:53 -0500 Subject: [PATCH 12/15] Correct rate limit test magnitude --- ratelimit_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ratelimit_test.go b/ratelimit_test.go index 95dc704..44a0120 100644 --- a/ratelimit_test.go +++ b/ratelimit_test.go @@ -19,7 +19,7 @@ func TestRatelimitReset(t *testing.T) { headers.Set("X-RateLimit-Remaining", "0") // Reset for approx 2 seconds from now - headers.Set("X-RateLimit-Reset", fmt.Sprint(float64(time.Now().Add(time.Second*2).UnixNano())/1e6)) + headers.Set("X-RateLimit-Reset", fmt.Sprint(float64(time.Now().Add(time.Second*2).UnixNano())/1e9)) headers.Set("Date", time.Now().Format(time.RFC850)) err := bucket.Release(headers) @@ -106,7 +106,7 @@ func sendBenchReq(endpoint string, rl *RateLimiter) { headers := http.Header(make(map[string][]string)) headers.Set("X-RateLimit-Remaining", "10") - headers.Set("X-RateLimit-Reset", fmt.Sprint(float64(time.Now().UnixNano())/1e6)) + headers.Set("X-RateLimit-Reset", fmt.Sprint(float64(time.Now().UnixNano())/1e9)) headers.Set("Date", time.Now().Format(time.RFC850)) bucket.Release(headers) From bd34c3c66dc65043e724aa797400ccb9c5973849 Mon Sep 17 00:00:00 2001 From: Carson Hoffman Date: Wed, 20 Jan 2021 19:43:00 -0500 Subject: [PATCH 13/15] Add comment for UnmarshalJSON --- structs.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/structs.go b/structs.go index 3267072..067bab2 100644 --- a/structs.go +++ b/structs.go @@ -817,6 +817,8 @@ type TooManyRequests struct { RetryAfter time.Duration `json:"retry_after"` } +// UnmarshalJSON helps support translation of a milliseconds-based float +// into a time.Duration on TooManyRequests. func (t *TooManyRequests) UnmarshalJSON(b []byte) error { u := struct { Bucket string `json:"bucket"` From 0f0aaad0115d19936000eeb481b733672eef6b8b Mon Sep 17 00:00:00 2001 From: Carson Hoffman Date: Wed, 20 Jan 2021 19:48:09 -0500 Subject: [PATCH 14/15] Correct header in rate limit test --- ratelimit_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ratelimit_test.go b/ratelimit_test.go index 44a0120..cefa57d 100644 --- a/ratelimit_test.go +++ b/ratelimit_test.go @@ -57,7 +57,7 @@ func TestRatelimitGlobal(t *testing.T) { headers.Set("X-RateLimit-Global", "1") // Reset for approx 1 seconds from now - headers.Set("Retry-After", "1000") + headers.Set("X-RateLimit-Reset-After", "1") err := bucket.Release(headers) if err != nil { From 90531dc71378b24f8e1eebf9c3f67579c0aee305 Mon Sep 17 00:00:00 2001 From: Carson Hoffman Date: Wed, 20 Jan 2021 20:19:51 -0500 Subject: [PATCH 15/15] Fix permissions type for UserGuild --- structs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/structs.go b/structs.go index 067bab2..44557ad 100644 --- a/structs.go +++ b/structs.go @@ -611,7 +611,7 @@ type UserGuild struct { Name string `json:"name"` Icon string `json:"icon"` Owner bool `json:"owner"` - Permissions int `json:"permissions"` + Permissions int64 `json:"permissions,string"` } // A GuildParams stores all the data needed to update discord guild settings