From c4a08de52513a9057d4ebf127a23ba8f4160f8d8 Mon Sep 17 00:00:00 2001 From: Nick Dumas Date: Thu, 7 Sep 2017 16:46:40 -0400 Subject: [PATCH 01/41] Remove a misspelled copy of EndpointGuildInvites. Issue #440 (#441) Reference Issue: https://github.com/bwmarrin/discordgo/issues/440 This endpoint was declard twice in endpoints.go, and used in the GuildInvites() method on Session values. I replaced the misspelled invocation. All tests pass. --- endpoints.go | 1 - restapi.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/endpoints.go b/endpoints.go index b10f958..db91d81 100644 --- a/endpoints.go +++ b/endpoints.go @@ -71,7 +71,6 @@ var ( EndpointUserNotes = func(uID string) string { return EndpointUsers + "@me/notes/" + uID } EndpointGuild = func(gID string) string { return EndpointGuilds + gID } - EndpointGuildInivtes = func(gID string) string { return EndpointGuilds + gID + "/invites" } EndpointGuildChannels = func(gID string) string { return EndpointGuilds + gID + "/channels" } EndpointGuildMembers = func(gID string) string { return EndpointGuilds + gID + "/members" } EndpointGuildMember = func(gID, uID string) string { return EndpointGuilds + gID + "/members/" + uID } diff --git a/restapi.go b/restapi.go index 836e4a4..1a682fa 100644 --- a/restapi.go +++ b/restapi.go @@ -907,7 +907,7 @@ func (s *Session) GuildChannelsReorder(guildID string, channels []*Channel) (err // GuildInvites returns an array of Invite structures for the given guild // guildID : The ID of a Guild. func (s *Session) GuildInvites(guildID string) (st []*Invite, err error) { - body, err := s.RequestWithBucketID("GET", EndpointGuildInvites(guildID), nil, EndpointGuildInivtes(guildID)) + body, err := s.RequestWithBucketID("GET", EndpointGuildInvites(guildID), nil, EndpointGuildInvites(guildID)) if err != nil { return } From 24dcabd55d14ac1a5a8c86892c1b62e5e6541211 Mon Sep 17 00:00:00 2001 From: robbix1206 Date: Thu, 21 Sep 2017 00:32:49 +0200 Subject: [PATCH 02/41] Fix #443 --- structs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/structs.go b/structs.go index c3e3956..35da213 100644 --- a/structs.go +++ b/structs.go @@ -171,7 +171,7 @@ type Channel struct { NSFW bool `json:"nsfw"` Position int `json:"position"` Bitrate int `json:"bitrate"` - Recipients []*User `json:"recipient"` + Recipients []*User `json:"recipients"` Messages []*Message `json:"-"` PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites"` } From 7b6a91ac0457d16474690189ee030e2fc252dddd Mon Sep 17 00:00:00 2001 From: Sebastian Winkler Date: Fri, 22 Sep 2017 16:43:00 +0200 Subject: [PATCH 03/41] adds parent_id to channel struct (#448) --- structs.go | 1 + 1 file changed, 1 insertion(+) diff --git a/structs.go b/structs.go index 35da213..cbe4a09 100644 --- a/structs.go +++ b/structs.go @@ -174,6 +174,7 @@ type Channel struct { Recipients []*User `json:"recipients"` Messages []*Message `json:"-"` PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites"` + ParentID string `json:"parent_id"` } // A PermissionOverwrite holds permission overwrite data for a Channel From 9da2c9e76a2a932afda4afb313572cb8696f2cce Mon Sep 17 00:00:00 2001 From: jD91mZM2 Date: Sat, 7 Oct 2017 19:54:04 +0200 Subject: [PATCH 04/41] Added GameType (#435) * Added GameType * Delete useless function --- structs.go | 48 +++++++++++------------------------------------- wsapi.go | 4 ++-- 2 files changed, 13 insertions(+), 39 deletions(-) diff --git a/structs.go b/structs.go index cbe4a09..80928a8 100644 --- a/structs.go +++ b/structs.go @@ -12,9 +12,7 @@ package discordgo import ( - "encoding/json" "net/http" - "strconv" "sync" "time" @@ -315,43 +313,19 @@ type Presence struct { Since *int `json:"since"` } +// A game type +type GameType int + +const ( + GameTypeGame GameType = iota + GameTypeStreaming +) + // A Game struct holds the name of the "playing .." game for a user type Game struct { - Name string `json:"name"` - Type int `json:"type"` - URL string `json:"url,omitempty"` -} - -// UnmarshalJSON unmarshals json to Game struct -func (g *Game) UnmarshalJSON(bytes []byte) error { - temp := &struct { - Name json.Number `json:"name"` - Type json.RawMessage `json:"type"` - URL string `json:"url"` - }{} - err := json.Unmarshal(bytes, temp) - if err != nil { - return err - } - g.URL = temp.URL - g.Name = temp.Name.String() - - if temp.Type != nil { - err = json.Unmarshal(temp.Type, &g.Type) - if err == nil { - return nil - } - - s := "" - err = json.Unmarshal(temp.Type, &s) - if err == nil { - g.Type, err = strconv.Atoi(s) - } - - return err - } - - return nil + Name string `json:"name"` + Type GameType `json:"type"` + URL string `json:"url,omitempty"` } // A Member stores user information for Guild members. diff --git a/wsapi.go b/wsapi.go index df87092..59dc532 100644 --- a/wsapi.go +++ b/wsapi.go @@ -285,9 +285,9 @@ func (s *Session) UpdateStreamingStatus(idle int, game string, url string) (err } if game != "" { - gameType := 0 + gameType := GameTypeGame if url != "" { - gameType = 1 + gameType = GameTypeStreaming } usd.Game = &Game{ Name: game, From 97a510ca0a97c1dcbabe15df991c61617e6554d5 Mon Sep 17 00:00:00 2001 From: Erik McClure Date: Sat, 7 Oct 2017 10:54:46 -0700 Subject: [PATCH 05/41] Add rate limit buffering support (#430) * Fix #406: reconnect() can be called while still connected * Add memberMap to speed up member queries * Fix error return value and remove deletion * Fix GuildAdd member map initialization edge case * Add rate limit buffering support - Break request into requestInner - Break LockBucket into LockBucketObject - Change getBucket to GetBucket so it can be externally accessed - Add RequestBuffer interface - Add RequestBuffer pointer to Bucket struct - Add RequestPostWithBuffer() function to Session * Remove internal implementation, export ratelimiter instead --- discord.go | 2 +- ratelimit.go | 49 +++++++++++++++++++++++++++++-------------------- restapi.go | 10 ++++++---- structs.go | 6 +++--- 4 files changed, 39 insertions(+), 28 deletions(-) diff --git a/discord.go b/discord.go index 04d4719..6c098fd 100644 --- a/discord.go +++ b/discord.go @@ -50,7 +50,7 @@ func New(args ...interface{}) (s *Session, err error) { // Create an empty Session interface. s = &Session{ State: NewState(), - ratelimiter: NewRatelimiter(), + Ratelimiter: NewRatelimiter(), StateEnabled: true, Compress: true, ShouldReconnectOnError: true, diff --git a/ratelimit.go b/ratelimit.go index 223c0d0..c3d553c 100644 --- a/ratelimit.go +++ b/ratelimit.go @@ -41,8 +41,8 @@ func NewRatelimiter() *RateLimiter { } } -// getBucket retrieves or creates a bucket -func (r *RateLimiter) getBucket(key string) *Bucket { +// GetBucket retrieves or creates a bucket +func (r *RateLimiter) GetBucket(key string) *Bucket { r.Lock() defer r.Unlock() @@ -51,7 +51,7 @@ func (r *RateLimiter) getBucket(key string) *Bucket { } b := &Bucket{ - remaining: 1, + Remaining: 1, Key: key, global: r.global, } @@ -67,28 +67,36 @@ func (r *RateLimiter) getBucket(key string) *Bucket { r.buckets[key] = b return b } - -// LockBucket Locks until a request can be made -func (r *RateLimiter) LockBucket(bucketID string) *Bucket { - - b := r.getBucket(bucketID) - - b.Lock() - +func (r *RateLimiter) GetWaitTime(b *Bucket, minRemaining int) time.Duration { // If we ran out of calls and the reset time is still ahead of us // then we need to take it easy and relax a little - if b.remaining < 1 && b.reset.After(time.Now()) { - time.Sleep(b.reset.Sub(time.Now())) - + if b.Remaining < minRemaining && b.reset.After(time.Now()) { + return b.reset.Sub(time.Now()) } // Check for global ratelimits sleepTo := time.Unix(0, atomic.LoadInt64(r.global)) if now := time.Now(); now.Before(sleepTo) { - time.Sleep(sleepTo.Sub(now)) + return sleepTo.Sub(now) } - b.remaining-- + return 0 +} + +// LockBucket Locks until a request can be made +func (r *RateLimiter) LockBucket(bucketID string) *Bucket { + return r.LockBucketObject(r.GetBucket(bucketID)) +} + +// LockBucketObject Locks an already resolved bucket until a request can be made +func (r *RateLimiter) LockBucketObject(b *Bucket) *Bucket { + b.Lock() + + if wait := r.GetWaitTime(b, 1); wait > 0 { + time.Sleep(wait) + } + + b.Remaining-- return b } @@ -96,13 +104,14 @@ func (r *RateLimiter) LockBucket(bucketID string) *Bucket { type Bucket struct { sync.Mutex Key string - remaining int + Remaining int limit int reset time.Time global *int64 lastReset time.Time customRateLimit *customRateLimit + Userdata interface{} } // Release unlocks the bucket and reads the headers to update the buckets ratelimit info @@ -113,10 +122,10 @@ func (b *Bucket) Release(headers http.Header) error { // Check if the bucket uses a custom ratelimiter if rl := b.customRateLimit; rl != nil { if time.Now().Sub(b.lastReset) >= rl.reset { - b.remaining = rl.requests - 1 + b.Remaining = rl.requests - 1 b.lastReset = time.Now() } - if b.remaining < 1 { + if b.Remaining < 1 { b.reset = time.Now().Add(rl.reset) } return nil @@ -176,7 +185,7 @@ func (b *Bucket) Release(headers http.Header) error { if err != nil { return err } - b.remaining = int(parsedRemaining) + b.Remaining = int(parsedRemaining) } return nil diff --git a/restapi.go b/restapi.go index 1a682fa..9ba59c7 100644 --- a/restapi.go +++ b/restapi.go @@ -65,9 +65,11 @@ func (s *Session) request(method, urlStr, contentType string, b []byte, bucketID if bucketID == "" { bucketID = strings.SplitN(urlStr, "?", 2)[0] } + return s.RequestWithLockedBucket(method, urlStr, contentType, b, s.Ratelimiter.LockBucket(bucketID), sequence) +} - bucket := s.ratelimiter.LockBucket(bucketID) - +// RequestWithLockedBucket makes a request using a bucket that's already been locked +func (s *Session) RequestWithLockedBucket(method, urlStr, contentType string, b []byte, bucket *Bucket, sequence int) (response []byte, err error) { if s.Debug { log.Printf("API REQUEST %8s :: %s\n", method, urlStr) log.Printf("API REQUEST PAYLOAD :: [%s]\n", string(b)) @@ -139,7 +141,7 @@ func (s *Session) request(method, urlStr, contentType string, b []byte, bucketID if sequence < s.MaxRestRetries { s.log(LogInformational, "%s Failed (%s), Retrying...", urlStr, resp.Status) - response, err = s.request(method, urlStr, contentType, b, bucketID, sequence+1) + response, err = s.RequestWithLockedBucket(method, urlStr, contentType, b, s.Ratelimiter.LockBucketObject(bucket), sequence+1) } else { err = fmt.Errorf("Exceeded Max retries HTTP %s, %s", resp.Status, response) } @@ -158,7 +160,7 @@ func (s *Session) request(method, urlStr, contentType string, b []byte, bucketID // we can make the above smarter // this method can cause longer delays than required - response, err = s.request(method, urlStr, contentType, b, bucketID, sequence) + response, err = s.RequestWithLockedBucket(method, urlStr, contentType, b, s.Ratelimiter.LockBucketObject(bucket), sequence) default: // Error condition err = newRestError(req, resp, response) diff --git a/structs.go b/structs.go index 80928a8..c5dbdae 100644 --- a/structs.go +++ b/structs.go @@ -83,6 +83,9 @@ type Session struct { // Stores the last HeartbeatAck that was recieved (in UTC) LastHeartbeatAck time.Time + // used to deal with rate limits + Ratelimiter *RateLimiter + // Event handlers handlersMu sync.RWMutex handlers map[string][]*eventHandlerInstance @@ -94,9 +97,6 @@ type Session struct { // When nil, the session is not listening. listening chan interface{} - // used to deal with rate limits - ratelimiter *RateLimiter - // sequence tracks the current gateway api websocket sequence number sequence *int64 From aeda5eb7b12e2f62912caace58f03fc6ad4ce15e Mon Sep 17 00:00:00 2001 From: Necroforger Date: Sun, 8 Oct 2017 18:37:24 -0400 Subject: [PATCH 06/41] Update bulk-delete endpoint (#451) --- endpoints.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/endpoints.go b/endpoints.go index db91d81..74337ca 100644 --- a/endpoints.go +++ b/endpoints.go @@ -97,7 +97,7 @@ var ( EndpointChannelMessages = func(cID string) string { return EndpointChannels + cID + "/messages" } EndpointChannelMessage = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID } EndpointChannelMessageAck = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID + "/ack" } - EndpointChannelMessagesBulkDelete = func(cID string) string { return EndpointChannel(cID) + "/messages/bulk_delete" } + EndpointChannelMessagesBulkDelete = func(cID string) string { return EndpointChannel(cID) + "/messages/bulk-delete" } EndpointChannelMessagesPins = func(cID string) string { return EndpointChannel(cID) + "/pins" } EndpointChannelMessagePin = func(cID, mID string) string { return EndpointChannel(cID) + "/pins/" + mID } From 8f1a335d2545bba0adb0035bf6311fc6801529b6 Mon Sep 17 00:00:00 2001 From: Carson Hoffman Date: Sun, 8 Oct 2017 20:54:06 -0400 Subject: [PATCH 07/41] Added fix for #437 (#452) --- endpoints.go | 2 ++ restapi.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/endpoints.go b/endpoints.go index 74337ca..335e224 100644 --- a/endpoints.go +++ b/endpoints.go @@ -121,6 +121,8 @@ var ( EndpointRelationship = func(uID string) string { return EndpointRelationships() + "/" + uID } EndpointRelationshipsMutual = func(uID string) string { return EndpointUsers + uID + "/relationships" } + EndpointGuildCreate = EndpointAPI + "guilds" + EndpointInvite = func(iID string) string { return EndpointAPI + "invite/" + iID } EndpointIntegrationsJoin = func(iID string) string { return EndpointAPI + "integrations/" + iID + "/join" } diff --git a/restapi.go b/restapi.go index 9ba59c7..52ca3f6 100644 --- a/restapi.go +++ b/restapi.go @@ -587,7 +587,7 @@ func (s *Session) GuildCreate(name string) (st *Guild, err error) { Name string `json:"name"` }{name} - body, err := s.RequestWithBucketID("POST", EndpointGuilds, data, EndpointGuilds) + body, err := s.RequestWithBucketID("POST", EndpointGuildCreate, data, EndpointGuildCreate) if err != nil { return } From 28dc6f6f338f2345b4534dbb2225f0955a4bdebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20Bo=C3=9Fe?= Date: Tue, 10 Oct 2017 19:35:07 +0200 Subject: [PATCH 08/41] Add members from GuildMembersChunk to state (#456) --- state.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/state.go b/state.go index 35a8e75..0cc1f73 100644 --- a/state.go +++ b/state.go @@ -816,6 +816,13 @@ func (s *State) OnInterface(se *Session, i interface{}) (err error) { if s.TrackMembers { err = s.MemberRemove(t.Member) } + case *GuildMembersChunk: + if s.TrackMembers { + for i := range t.Members { + t.Members[i].GuildID = t.GuildID + err = s.MemberAdd(t.Members[i]) + } + } case *GuildRoleCreate: if s.TrackRoles { err = s.RoleAdd(t.GuildID, t.Role) From 8737777ce72bb218154b5d5c9619bc14c52c1602 Mon Sep 17 00:00:00 2001 From: Cory Ory Date: Sun, 22 Oct 2017 22:08:08 +0100 Subject: [PATCH 09/41] Implement Raw sending of status (Rich Presence and Online Status) (#462) * Added ability to change the online status * Add structs for rick presence implementation * Refractor and publicise UpdateStatusData * Add UpdateStatusComplex for raw status data sending * Case gameType to int, stopped it compiling * Might want to gofmt. Doesn't do it on save because Gogland removed it and their new thing I can't make sense of. * Revert "Added ability to change the online status" This reverts commit 235cd15a8eebbec070cb95a5853295387bceae1c. * Change gametypeto match * Move RLock to UpdateStatusComplex --- structs.go | 27 ++++++++++++++++++++++++--- wsapi.go | 24 +++++++++++++++--------- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/structs.go b/structs.go index c5dbdae..83e5089 100644 --- a/structs.go +++ b/structs.go @@ -323,9 +323,30 @@ const ( // 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"` + 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) +} + +// A TimeStamps struct contains start and end times used in the rich presence "playing .." Game +type TimeStamps struct { + EndTimestamp uint `json:"end,omitempty"` + StartTimestamp uint `json:"start,omitempty"` +} + +// An Assets struct contains assets and labels used in the rich presence "playing .." Game +type Assets struct { + LargeImageID string `json:"large_image,omitempty"` + SmallImageID string `json:"small_image,omitempty"` + LargeText string `json:"large_text,omitempty"` + SmallText string `json:"small_text,omitempty"` } // A Member stores user information for Guild members. diff --git a/wsapi.go b/wsapi.go index 59dc532..94281b0 100644 --- a/wsapi.go +++ b/wsapi.go @@ -249,7 +249,7 @@ func (s *Session) heartbeat(wsConn *websocket.Conn, listening <-chan interface{} } } -type updateStatusData struct { +type UpdateStatusData struct { IdleSince *int `json:"since"` Game *Game `json:"game"` AFK bool `json:"afk"` @@ -258,7 +258,7 @@ type updateStatusData struct { type updateStatusOp struct { Op int `json:"op"` - Data updateStatusData `json:"d"` + Data UpdateStatusData `json:"d"` } // UpdateStreamingStatus is used to update the user's streaming status. @@ -270,13 +270,7 @@ func (s *Session) UpdateStreamingStatus(idle int, game string, url string) (err s.log(LogInformational, "called") - s.RLock() - defer s.RUnlock() - if s.wsConn == nil { - return ErrWSNotFound - } - - usd := updateStatusData{ + usd := UpdateStatusData{ Status: "online", } @@ -296,6 +290,18 @@ func (s *Session) UpdateStreamingStatus(idle int, game string, url string) (err } } + return s.UpdateStatusComplex(usd) +} + +// UpdateStatusComplex allows for sending the raw status update data untouched by discordgo. +func (s *Session) UpdateStatusComplex(usd UpdateStatusData) (err error) { + + s.RLock() + defer s.RUnlock() + if s.wsConn == nil { + return ErrWSNotFound + } + s.wsMutex.Lock() err = s.wsConn.WriteJSON(updateStatusOp{3, usd}) s.wsMutex.Unlock() From 70c6c583e4affc3ca38d0d986edaa1ce170e4e6a Mon Sep 17 00:00:00 2001 From: NamedKitten Date: Mon, 23 Oct 2017 19:20:07 +0100 Subject: [PATCH 10/41] Use v.log rather than log for consistency. (#464) Change made to keep the use of logging consistent. --- voice.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/voice.go b/voice.go index 8f033aa..d68d0e2 100644 --- a/voice.go +++ b/voice.go @@ -13,7 +13,6 @@ import ( "encoding/binary" "encoding/json" "fmt" - "log" "net" "strings" "sync" @@ -104,7 +103,7 @@ func (v *VoiceConnection) Speaking(b bool) (err error) { defer v.Unlock() if err != nil { v.speaking = false - log.Println("Speaking() write json error:", err) + v.log(LogError, "Speaking() write json error:", err) return } @@ -181,7 +180,7 @@ func (v *VoiceConnection) Close() { v.log(LogInformational, "closing udp") err := v.udpConn.Close() if err != nil { - log.Println("error closing udp connection: ", err) + v.log(LogError, "error closing udp connection: ", err) } v.udpConn = nil } From d30b33abfa0c657d783d7c9c025836f123a08a0e Mon Sep 17 00:00:00 2001 From: NamedKitten Date: Mon, 23 Oct 2017 21:08:14 +0100 Subject: [PATCH 11/41] Add support for custom logging behaviour. (#465) --- discord.go | 2 +- logging.go | 24 ++++++++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/discord.go b/discord.go index 6c098fd..df4c499 100644 --- a/discord.go +++ b/discord.go @@ -21,7 +21,7 @@ import ( ) // VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/) -const VERSION = "0.17.0-dev" +const VERSION = "0.17.0" // ErrMFA will be risen by New when the user has 2FA. var ErrMFA = errors.New("account has 2FA enabled") diff --git a/logging.go b/logging.go index 70d78d6..5626eba 100644 --- a/logging.go +++ b/logging.go @@ -34,6 +34,9 @@ const ( LogDebug ) +// Logger can be used to replace the standard logging for discordgo +var Logger func(msgL, caller int, format string, a ...interface{}) + // msglog provides package wide logging consistancy for discordgo // the format, a... portion this command follows that of fmt.Printf // msgL : LogLevel of the message @@ -42,18 +45,23 @@ const ( // a ... : comma seperated list of values to pass func msglog(msgL, caller int, format string, a ...interface{}) { - pc, file, line, _ := runtime.Caller(caller) + if Logger != nil { + Logger(msgL, caller, format, a) + } else { - files := strings.Split(file, "/") - file = files[len(files)-1] + pc, file, line, _ := runtime.Caller(caller) - name := runtime.FuncForPC(pc).Name() - fns := strings.Split(name, ".") - name = fns[len(fns)-1] + files := strings.Split(file, "/") + file = files[len(files)-1] - msg := fmt.Sprintf(format, a...) + name := runtime.FuncForPC(pc).Name() + fns := strings.Split(name, ".") + name = fns[len(fns)-1] - log.Printf("[DG%d] %s:%d:%s() %s\n", msgL, file, line, name, msg) + msg := fmt.Sprintf(format, a...) + + log.Printf("[DG%d] %s:%d:%s() %s\n", msgL, file, line, name, msg) + } } // helper function that wraps msglog for the Session struct From 7373a96b717eb8b16d9df756a31edc209d8697dc Mon Sep 17 00:00:00 2001 From: Chris Rhodes Date: Mon, 23 Oct 2017 13:35:53 -0700 Subject: [PATCH 12/41] Remove trailing space. --- logging.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logging.go b/logging.go index 5626eba..4272060 100644 --- a/logging.go +++ b/logging.go @@ -47,7 +47,7 @@ func msglog(msgL, caller int, format string, a ...interface{}) { if Logger != nil { Logger(msgL, caller, format, a) - } else { + } else { pc, file, line, _ := runtime.Caller(caller) From 287add05a5ea8c01a57b69fb9c59d335126cae43 Mon Sep 17 00:00:00 2001 From: NamedKitten Date: Tue, 24 Oct 2017 16:09:51 +0100 Subject: [PATCH 13/41] Fix typo. (#466) --- logging.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logging.go b/logging.go index 4272060..71bcd91 100644 --- a/logging.go +++ b/logging.go @@ -23,7 +23,7 @@ const ( LogError int = iota // LogWarning level is used for very abnormal events and errors that are - // also returend to a calling function. + // also returned to a calling function. LogWarning // LogInformational level is used for normal non-error activity From 48eecda67e0b44a247ac898198abcae57cb2945b Mon Sep 17 00:00:00 2001 From: NamedKitten Date: Wed, 8 Nov 2017 15:45:27 +0000 Subject: [PATCH 14/41] Forgot ... (#473) --- logging.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logging.go b/logging.go index 71bcd91..270299e 100644 --- a/logging.go +++ b/logging.go @@ -46,7 +46,7 @@ var Logger func(msgL, caller int, format string, a ...interface{}) func msglog(msgL, caller int, format string, a ...interface{}) { if Logger != nil { - Logger(msgL, caller, format, a) + Logger(msgL, caller, format, a...) } else { pc, file, line, _ := runtime.Caller(caller) From d5542701362a2a85b30c7cde376de56df8598d4d Mon Sep 17 00:00:00 2001 From: Necroforger Date: Wed, 8 Nov 2017 10:55:51 -0500 Subject: [PATCH 15/41] Allow size parameter to be omitted (#468) --- user.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/user.go b/user.go index 76abdd1..d708518 100644 --- a/user.go +++ b/user.go @@ -29,7 +29,9 @@ func (u *User) Mention() string { } // AvatarURL returns a URL to the user's avatar. -// size: The size of the user's avatar as a power of two +// size: The size of the user's avatar as a power of two +// if size is an empty string, no size parameter will +// be added to the URL. func (u *User) AvatarURL(size string) string { var URL string if strings.HasPrefix(u.Avatar, "a_") { @@ -37,6 +39,9 @@ func (u *User) AvatarURL(size string) string { } else { URL = EndpointUserAvatar(u.ID, u.Avatar) } - - return URL + "?size=" + size + + if size != "" { + return URL + "?size=" + size + } + return URL } From 1c4ea6df0454a34fd69ac66a93762fe226e9ec7a Mon Sep 17 00:00:00 2001 From: Bruce Marriner Date: Wed, 8 Nov 2017 19:36:26 +0000 Subject: [PATCH 16/41] gofmt :) --- user.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/user.go b/user.go index d708518..a710f28 100644 --- a/user.go +++ b/user.go @@ -39,9 +39,9 @@ func (u *User) AvatarURL(size string) string { } else { URL = EndpointUserAvatar(u.ID, u.Avatar) } - + if size != "" { - return URL + "?size=" + size + return URL + "?size=" + size } return URL } From 277bbbebeeadfeb1199572ecf2a045977f175352 Mon Sep 17 00:00:00 2001 From: Bruce Marriner Date: Wed, 8 Nov 2017 19:41:40 +0000 Subject: [PATCH 17/41] I don't want your non-linted code. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 92beb2e..440159d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,5 +10,5 @@ install: script: - diff <(gofmt -d .) <(echo -n) - go vet -x ./... - - golint ./... + - golint -set_exit_status ./... - go test -v -race ./... From 8c52d97c1ed8c729ec96f8f8db6278e7fb59153c Mon Sep 17 00:00:00 2001 From: Bruce Marriner Date: Wed, 8 Nov 2017 19:54:04 +0000 Subject: [PATCH 18/41] Linting. --- ratelimit.go | 2 ++ structs.go | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ratelimit.go b/ratelimit.go index c3d553c..dc48c92 100644 --- a/ratelimit.go +++ b/ratelimit.go @@ -67,6 +67,8 @@ func (r *RateLimiter) GetBucket(key string) *Bucket { r.buckets[key] = b return b } + +// GetWaitTime returns the duration you should wait for a Bucket func (r *RateLimiter) GetWaitTime(b *Bucket, minRemaining int) time.Duration { // If we ran out of calls and the reset time is still ahead of us // then we need to take it easy and relax a little diff --git a/structs.go b/structs.go index 83e5089..87ebe1d 100644 --- a/structs.go +++ b/structs.go @@ -313,9 +313,10 @@ type Presence struct { Since *int `json:"since"` } -// A game type +// GameType is the type of "game" (see GameType* consts) in the Game struct type GameType int +// Valid GameType values const ( GameTypeGame GameType = iota GameTypeStreaming From a948670657735bf6a4c927b2bfdc9c8b967d7c72 Mon Sep 17 00:00:00 2001 From: Bruce Marriner Date: Wed, 8 Nov 2017 19:59:54 +0000 Subject: [PATCH 19/41] Add comments --- wsapi.go | 1 + 1 file changed, 1 insertion(+) diff --git a/wsapi.go b/wsapi.go index 94281b0..85fe700 100644 --- a/wsapi.go +++ b/wsapi.go @@ -249,6 +249,7 @@ 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"` From 43bf6cf0d0aa67abd3479fcc066766f174245bb4 Mon Sep 17 00:00:00 2001 From: Nicholas Hanley Date: Wed, 8 Nov 2017 21:39:26 -0500 Subject: [PATCH 20/41] Fix TimeStamps unmarshalling (#474) (#475) --- structs.go | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/structs.go b/structs.go index 87ebe1d..198ff79 100644 --- a/structs.go +++ b/structs.go @@ -12,6 +12,7 @@ package discordgo import ( + "encoding/json" "net/http" "sync" "time" @@ -338,8 +339,23 @@ type Game struct { // A TimeStamps struct contains start and end times used in the rich presence "playing .." Game type TimeStamps struct { - EndTimestamp uint `json:"end,omitempty"` - StartTimestamp uint `json:"start,omitempty"` + EndTimestamp int64 `json:"end,omitempty"` + StartTimestamp int64 `json:"start,omitempty"` +} + +// UnmarshalJSON unmarshals JSON into TimeStamps struct +func (t *TimeStamps) UnmarshalJSON(b []byte) error { + temp := struct { + End float64 `json:"end,omitempty"` + Start float64 `json:"start,omitempty"` + }{} + err := json.Unmarshal(b, &temp) + if err != nil { + return err + } + t.EndTimestamp = int64(temp.End) + t.StartTimestamp = int64(temp.Start) + return nil } // An Assets struct contains assets and labels used in the rich presence "playing .." Game From 7d1657e59b7d4ab2c5e2ea7aa85feb33d87548a2 Mon Sep 17 00:00:00 2001 From: Bruce Marriner Date: Sat, 11 Nov 2017 15:04:56 +0000 Subject: [PATCH 21/41] Open() func now validates connection, fixed #198 Now the open function will follow through a bit more and insure that the proper sequence of events happens during the Open call. This required some refactoring and a few mild changes in the onEvent func. --- wsapi.go | 224 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 140 insertions(+), 84 deletions(-) diff --git a/wsapi.go b/wsapi.go index 85fe700..904f24a 100644 --- a/wsapi.go +++ b/wsapi.go @@ -15,6 +15,7 @@ import ( "compress/zlib" "encoding/json" "errors" + "fmt" "io" "net/http" "runtime" @@ -45,19 +46,114 @@ type resumePacket struct { } `json:"d"` } -// Open opens a websocket connection to Discord. -func (s *Session) Open() (err error) { - +// Open creates a websocket connection to Discord. +// See: https://discordapp.com/developers/docs/topics/gateway#connecting +func (s *Session) Open() error { s.log(LogInformational, "called") + var err error + + // Prevent Open or other major Session functions from + // being called while Open is still running. s.Lock() - defer func() { + defer s.Unlock() + + // If the websock is already open, bail out here. + if s.wsConn != nil { + return ErrWSAlreadyOpen + } + + // Get the gateway to use for the Websocket connection + if s.gateway == "" { + s.gateway, err = s.Gateway() if err != nil { - s.Unlock() + return err + } + + // Add the version and encoding to the URL + s.gateway = s.gateway + "?v=" + APIVersion + "&encoding=json" + } + + // Connect to the Gateway + s.log(LogInformational, "connecting to gateway %s", s.gateway) + header := http.Header{} + header.Add("accept-encoding", "zlib") + s.wsConn, _, err = websocket.DefaultDialer.Dial(s.gateway, header) + if err != nil { + s.log(LogWarning, "error connecting to gateway %s, %s", s.gateway, err) + s.gateway = "" // clear cached gateway + s.wsConn = nil // Just to be safe. + return err + } + + defer func() { + // because of this, all code below must set err to the error + // when exiting with an error :) Maybe someone has a better + // way :) + if err != nil { + s.wsConn.Close() + s.wsConn = nil } }() + // The first response from Discord should be an Op 10 (Hello) Packet. + // When processed by onEvent the heartbeat goroutine will be started. + mt, m, err := s.wsConn.ReadMessage() + if err != nil { + return err + } + e, err := s.onEvent(mt, m) + if err != nil { + return err + } + if e.Operation != 10 { + err = fmt.Errorf("Expecting Op 10, got Op %d instead.", e.Operation) + return err + } + s.log(LogInformational, "Op 10 Hello Packet received from Discord") + s.LastHeartbeatAck = time.Now().UTC() + var h helloOp + if err = json.Unmarshal(e.RawData, &h); err != nil { + err = fmt.Errorf("error unmarshalling helloOp, %s", err) + return err + } + + // Now we send either an Op 2 Identity if this is a brand new + // connection or Op 6 Resume if we are resuming an existing connection. + sequence := atomic.LoadInt64(s.sequence) + if s.sessionID == "" && sequence == 0 { + + // Send Op 2 Identity Packet + err = s.identify() + if err != nil { + err = fmt.Errorf("error sending identify packet to gateway, %s, %s", s.gateway, err) + return err + } + + } else { + + // Send Op 6 Resume Packet + p := resumePacket{} + p.Op = 6 + p.Data.Token = s.Token + p.Data.SessionID = s.sessionID + p.Data.Sequence = sequence + + s.log(LogInformational, "sending resume packet to gateway") + s.wsMutex.Lock() + err = s.wsConn.WriteJSON(p) + s.wsMutex.Unlock() + if err != nil { + err = fmt.Errorf("error sending gateway resume packet, %s, %s", s.gateway, err) + return err + } + + } + // A basic state is a hard requirement for Voice. + // We create it here so the below READY/RESUMED packet can populate + // the state :) + // XXX: Move to New() func? if s.State == nil { state := NewState() state.TrackChannels = false @@ -68,77 +164,42 @@ func (s *Session) Open() (err error) { s.State = state } - if s.wsConn != nil { - err = ErrWSAlreadyOpen - return + // Now Discord should send us a READY or RESUMED packet. + mt, m, err = s.wsConn.ReadMessage() + if err != nil { + return err } + e, err = s.onEvent(mt, m) + if err != nil { + return err + } + if e.Type != `READY` && e.Type != `RESUMED` { + // This is not fatal, but it does not follow their API documentation. + s.log(LogWarning, "Expected READY/RESUMED, instead got:\n%#v\n", e) + } + s.log(LogInformational, "First Packet:\n%#v\n", e) + s.log(LogInformational, "We are now connected to Discord, emitting connect event") + s.handleEvent(connectEventType, &Connect{}) + + // A VoiceConnections map is a hard requirement for Voice. + // XXX: can this be moved to when opening a voice connection? if s.VoiceConnections == nil { s.log(LogInformational, "creating new VoiceConnections map") s.VoiceConnections = make(map[string]*VoiceConnection) } - // Get the gateway to use for the Websocket connection - if s.gateway == "" { - s.gateway, err = s.Gateway() - if err != nil { - return - } - - // Add the version and encoding to the URL - s.gateway = s.gateway + "?v=" + APIVersion + "&encoding=json" - } - - header := http.Header{} - header.Add("accept-encoding", "zlib") - - s.log(LogInformational, "connecting to gateway %s", s.gateway) - s.wsConn, _, err = websocket.DefaultDialer.Dial(s.gateway, header) - if err != nil { - s.log(LogWarning, "error connecting to gateway %s, %s", s.gateway, err) - s.gateway = "" // clear cached gateway - // TODO: should we add a retry block here? - return - } - - sequence := atomic.LoadInt64(s.sequence) - if s.sessionID != "" && sequence > 0 { - - p := resumePacket{} - p.Op = 6 - p.Data.Token = s.Token - p.Data.SessionID = s.sessionID - p.Data.Sequence = sequence - - s.log(LogInformational, "sending resume packet to gateway") - err = s.wsConn.WriteJSON(p) - if err != nil { - s.log(LogWarning, "error sending gateway resume packet, %s, %s", s.gateway, err) - return - } - - } else { - - err = s.identify() - if err != nil { - s.log(LogWarning, "error sending gateway identify packet, %s, %s", s.gateway, err) - return - } - } - - // Create listening outside of listen, as it needs to happen inside the mutex - // lock. + // Create listening chan outside of listen, as it needs to happen inside the + // mutex lock and needs to exist before calling heartbeat and listen + // go rountines. s.listening = make(chan interface{}) + + // Start sending heartbeats and reading messages from Discord. + go s.heartbeat(s.wsConn, s.listening, h.HeartbeatInterval) go s.listen(s.wsConn, s.listening) - s.LastHeartbeatAck = time.Now().UTC() - - s.Unlock() - - s.log(LogInformational, "emit connect event") - s.handleEvent(connectEventType, &Connect{}) s.log(LogInformational, "exiting") - return + return nil } // listen polls the websocket connection for events, it will stop when the @@ -364,9 +425,7 @@ func (s *Session) RequestGuildMembers(guildID, query string, limit int) (err err // // If you use the AddHandler() function to register a handler for the // "OnEvent" event then all events will be passed to that handler. -// -// TODO: You may also register a custom event handler entirely using... -func (s *Session) onEvent(messageType int, message []byte) { +func (s *Session) onEvent(messageType int, message []byte) (*Event, error) { var err error var reader io.Reader @@ -378,7 +437,7 @@ func (s *Session) onEvent(messageType int, message []byte) { z, err2 := zlib.NewReader(reader) if err2 != nil { s.log(LogError, "error uncompressing websocket message, %s", err) - return + return nil, err2 } defer func() { @@ -396,7 +455,7 @@ func (s *Session) onEvent(messageType int, message []byte) { decoder := json.NewDecoder(reader) if err = decoder.Decode(&e); err != nil { s.log(LogError, "error decoding websocket message, %s", err) - return + return e, err } s.log(LogDebug, "Op: %d, Seq: %d, Type: %s, Data: %s\n\n", e.Operation, e.Sequence, e.Type, string(e.RawData)) @@ -410,10 +469,10 @@ func (s *Session) onEvent(messageType int, message []byte) { s.wsMutex.Unlock() if err != nil { s.log(LogError, "error sending heartbeat in response to Op1") - return + return e, err } - return + return e, nil } // Reconnect @@ -422,7 +481,7 @@ func (s *Session) onEvent(messageType int, message []byte) { s.log(LogInformational, "Closing and reconnecting in response to Op7") s.Close() s.reconnect() - return + return e, nil } // Invalid Session @@ -434,20 +493,15 @@ func (s *Session) onEvent(messageType int, message []byte) { err = s.identify() if err != nil { s.log(LogWarning, "error sending gateway identify packet, %s, %s", s.gateway, err) - return + return e, err } - return + return e, nil } if e.Operation == 10 { - var h helloOp - if err = json.Unmarshal(e.RawData, &h); err != nil { - s.log(LogError, "error unmarshalling helloOp, %s", err) - } else { - go s.heartbeat(s.wsConn, s.listening, h.HeartbeatInterval) - } - return + // Op10 is handled by Open() + return e, nil } if e.Operation == 11 { @@ -455,7 +509,7 @@ func (s *Session) onEvent(messageType int, message []byte) { s.LastHeartbeatAck = time.Now().UTC() s.Unlock() s.log(LogInformational, "got heartbeat ACK") - return + return e, nil } // Do not try to Dispatch a non-Dispatch Message @@ -463,7 +517,7 @@ func (s *Session) onEvent(messageType int, message []byte) { // But we probably should be doing something with them. // TEMP s.log(LogWarning, "unknown Op: %d, Seq: %d, Type: %s, Data: %s, message: %s", e.Operation, e.Sequence, e.Type, string(e.RawData), string(message)) - return + return e, nil } // Store the message sequence @@ -492,6 +546,8 @@ func (s *Session) onEvent(messageType int, message []byte) { // For legacy reasons, we send the raw event also, this could be useful for handling unknown events. s.handleEvent(eventEventType, e) + + return e, nil } // ------------------------------------------------------------------------------------------------ From 7ed70a416e00827c2c60109a026b97e2e036c5f1 Mon Sep 17 00:00:00 2001 From: Bruce Marriner Date: Sat, 11 Nov 2017 15:07:55 +0000 Subject: [PATCH 22/41] Change version to v0.18.0-alpha --- discord.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord.go b/discord.go index df4c499..df911cb 100644 --- a/discord.go +++ b/discord.go @@ -21,7 +21,7 @@ import ( ) // VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/) -const VERSION = "0.17.0" +const VERSION = "0.18.0-alpha" // ErrMFA will be risen by New when the user has 2FA. var ErrMFA = errors.New("account has 2FA enabled") From d45cbf73f0cc9fc385440f9de4ec05fbf37c9d50 Mon Sep 17 00:00:00 2001 From: Bruce Marriner Date: Sat, 11 Nov 2017 15:08:49 +0000 Subject: [PATCH 23/41] Changed test Nickname, for fun! --- restapi_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/restapi_test.go b/restapi_test.go index 7aa4e60..a2da634 100644 --- a/restapi_test.go +++ b/restapi_test.go @@ -227,7 +227,7 @@ func TestGuildMemberNickname(t *testing.T) { t.Skip("Skipping, dg not set.") } - err := dg.GuildMemberNickname(envGuild, "@me/nick", "testnickname") + err := dg.GuildMemberNickname(envGuild, "@me/nick", "B1nzyRocks") if err != nil { t.Errorf("GuildNickname returned error: %+v", err) } From 9e1220d82bf1e3319924e2950515abdbe283df3e Mon Sep 17 00:00:00 2001 From: Bruce Marriner Date: Sat, 11 Nov 2017 15:18:37 +0000 Subject: [PATCH 24/41] Lint :) --- wsapi.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wsapi.go b/wsapi.go index 904f24a..425dbf3 100644 --- a/wsapi.go +++ b/wsapi.go @@ -107,7 +107,7 @@ func (s *Session) Open() error { return err } if e.Operation != 10 { - err = fmt.Errorf("Expecting Op 10, got Op %d instead.", e.Operation) + err = fmt.Errorf("expecting Op 10, got Op %d instead", e.Operation) return err } s.log(LogInformational, "Op 10 Hello Packet received from Discord") From a838552b223cc33c4a975da39298e8dc3e6698e4 Mon Sep 17 00:00:00 2001 From: Lukas Breuer Date: Sat, 11 Nov 2017 18:02:17 +0100 Subject: [PATCH 25/41] Remove XKCDPass Invites This feature has been removed from the API quite some time ago. --- restapi.go | 12 +++++------- structs.go | 1 - 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/restapi.go b/restapi.go index 52ca3f6..ebf3f18 100644 --- a/restapi.go +++ b/restapi.go @@ -1571,16 +1571,14 @@ func (s *Session) ChannelInvites(channelID string) (st []*Invite, err error) { // ChannelInviteCreate creates a new invite for the given channel. // channelID : The ID of a Channel -// i : An Invite struct with the values MaxAge, MaxUses, Temporary, -// and XkcdPass defined. +// i : An Invite struct with the values MaxAge, MaxUses and Temporary defined. func (s *Session) ChannelInviteCreate(channelID string, i Invite) (st *Invite, err error) { data := struct { MaxAge int `json:"max_age"` MaxUses int `json:"max_uses"` Temporary bool `json:"temporary"` - XKCDPass string `json:"xkcdpass"` - }{i.MaxAge, i.MaxUses, i.Temporary, i.XkcdPass} + }{i.MaxAge, i.MaxUses, i.Temporary} body, err := s.RequestWithBucketID("POST", EndpointChannelInvites(channelID), data, EndpointChannelInvites(channelID)) if err != nil { @@ -1620,7 +1618,7 @@ func (s *Session) ChannelPermissionDelete(channelID, targetID string) (err error // ------------------------------------------------------------------------------------------------ // Invite returns an Invite structure of the given invite -// inviteID : The invite code (or maybe xkcdpass?) +// inviteID : The invite code func (s *Session) Invite(inviteID string) (st *Invite, err error) { body, err := s.RequestWithBucketID("GET", EndpointInvite(inviteID), nil, EndpointInvite("")) @@ -1633,7 +1631,7 @@ func (s *Session) Invite(inviteID string) (st *Invite, err error) { } // InviteDelete deletes an existing invite -// inviteID : the code (or maybe xkcdpass?) of an invite +// inviteID : the code of an invite func (s *Session) InviteDelete(inviteID string) (st *Invite, err error) { body, err := s.RequestWithBucketID("DELETE", EndpointInvite(inviteID), nil, EndpointInvite("")) @@ -1646,7 +1644,7 @@ func (s *Session) InviteDelete(inviteID string) (st *Invite, err error) { } // InviteAccept accepts an Invite to a Guild or Channel -// inviteID : The invite code (or maybe xkcdpass?) +// inviteID : The invite code func (s *Session) InviteAccept(inviteID string) (st *Invite, err error) { body, err := s.RequestWithBucketID("POST", EndpointInvite(inviteID), nil, EndpointInvite("")) diff --git a/structs.go b/structs.go index 198ff79..e24c9a8 100644 --- a/structs.go +++ b/structs.go @@ -142,7 +142,6 @@ type Invite struct { MaxAge int `json:"max_age"` Uses int `json:"uses"` MaxUses int `json:"max_uses"` - XkcdPass string `json:"xkcdpass"` Revoked bool `json:"revoked"` Temporary bool `json:"temporary"` } From 1b69ba4a5fc39d7a46cd7d6a171015c89c9a2a2c Mon Sep 17 00:00:00 2001 From: Lukas Breuer Date: Sat, 11 Nov 2017 19:48:26 +0100 Subject: [PATCH 26/41] Format restapi.go with gofmt --- restapi.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/restapi.go b/restapi.go index ebf3f18..3ab9164 100644 --- a/restapi.go +++ b/restapi.go @@ -1575,9 +1575,9 @@ func (s *Session) ChannelInvites(channelID string) (st []*Invite, err error) { func (s *Session) ChannelInviteCreate(channelID string, i Invite) (st *Invite, err error) { data := struct { - MaxAge int `json:"max_age"` - MaxUses int `json:"max_uses"` - Temporary bool `json:"temporary"` + MaxAge int `json:"max_age"` + MaxUses int `json:"max_uses"` + Temporary bool `json:"temporary"` }{i.MaxAge, i.MaxUses, i.Temporary} body, err := s.RequestWithBucketID("POST", EndpointChannelInvites(channelID), data, EndpointChannelInvites(channelID)) From ca1b9186f261c52bcbfa3996db59faab030b9971 Mon Sep 17 00:00:00 2001 From: vim2meta Date: Sun, 19 Nov 2017 10:10:17 -0500 Subject: [PATCH 27/41] Update event.go --- event.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/event.go b/event.go index 3a03f46..bba396c 100644 --- a/event.go +++ b/event.go @@ -6,7 +6,7 @@ type EventHandler interface { Type() string // Handle is called whenever an event of Type() happens. - // It is the recievers responsibility to type assert that the interface + // It is the receivers responsibility to type assert that the interface // is the expected struct. Handle(*Session, interface{}) } From 7086a1f2a9ed9770683af980447d0058709de68e Mon Sep 17 00:00:00 2001 From: vim2meta Date: Sun, 19 Nov 2017 10:11:09 -0500 Subject: [PATCH 28/41] Update logging.go --- logging.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logging.go b/logging.go index 270299e..6460b35 100644 --- a/logging.go +++ b/logging.go @@ -42,7 +42,7 @@ var Logger func(msgL, caller int, format string, a ...interface{}) // msgL : LogLevel of the message // caller : 1 + the number of callers away from the message source // format : Printf style message format -// a ... : comma seperated list of values to pass +// a ... : comma separated list of values to pass func msglog(msgL, caller int, format string, a ...interface{}) { if Logger != nil { From d4e2b50ce4f7738c213ab80d50309933c4061385 Mon Sep 17 00:00:00 2001 From: vim2meta Date: Sun, 19 Nov 2017 10:12:21 -0500 Subject: [PATCH 29/41] Update restapi.go --- restapi.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/restapi.go b/restapi.go index 3ab9164..276ab7f 100644 --- a/restapi.go +++ b/restapi.go @@ -1206,7 +1206,7 @@ func (s *Session) GuildEmbedEdit(guildID string, enabled bool, channelID string) // Functions specific to Discord Channels // ------------------------------------------------------------------------------------------------ -// Channel returns a Channel strucutre of a specific Channel. +// Channel returns a Channel structure of a specific Channel. // channelID : The ID of the Channel you want returned. func (s *Session) Channel(channelID string) (st *Channel, err error) { body, err := s.RequestWithBucketID("GET", EndpointChannel(channelID), nil, EndpointChannel(channelID)) From 74a0038b630f29f84926ca82e781c66440ee16bd Mon Sep 17 00:00:00 2001 From: vim2meta Date: Sun, 19 Nov 2017 10:13:04 -0500 Subject: [PATCH 30/41] Update restapi.go --- restapi.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/restapi.go b/restapi.go index 276ab7f..a16a743 100644 --- a/restapi.go +++ b/restapi.go @@ -1478,7 +1478,7 @@ func (s *Session) ChannelMessageDelete(channelID, messageID string) (err error) } // ChannelMessagesBulkDelete bulk deletes the messages from the channel for the provided messageIDs. -// If only one messageID is in the slice call channelMessageDelete funciton. +// If only one messageID is in the slice call channelMessageDelete function. // If the slice is empty do nothing. // channelID : The ID of the channel for the messages to delete. // messages : The IDs of the messages to be deleted. A slice of string IDs. A maximum of 100 messages. From 0e42acc5f52be00891fbb431142c3ce355a274f5 Mon Sep 17 00:00:00 2001 From: vim2meta Date: Sun, 19 Nov 2017 10:13:54 -0500 Subject: [PATCH 31/41] Update structs.go --- structs.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/structs.go b/structs.go index e24c9a8..03974cb 100644 --- a/structs.go +++ b/structs.go @@ -203,7 +203,7 @@ func (e *Emoji) APIName() string { return e.ID } -// VerificationLevel type defination +// VerificationLevel type definition type VerificationLevel int // Constants for VerificationLevel levels from 0 to 3 inclusive @@ -395,7 +395,7 @@ type Settings struct { DeveloperMode bool `json:"developer_mode"` } -// Status type defination +// Status type definition type Status string // Constants for Status with the different current available status From 86557619ba85bf24c8935198f73af842d9e160bb Mon Sep 17 00:00:00 2001 From: vim2meta Date: Sun, 19 Nov 2017 10:14:45 -0500 Subject: [PATCH 32/41] Update voice.go --- voice.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/voice.go b/voice.go index d68d0e2..3bbf621 100644 --- a/voice.go +++ b/voice.go @@ -68,7 +68,7 @@ type VoiceConnection struct { voiceSpeakingUpdateHandlers []VoiceSpeakingUpdateHandler } -// VoiceSpeakingUpdateHandler type provides a function defination for the +// VoiceSpeakingUpdateHandler type provides a function definition for the // VoiceSpeakingUpdate event type VoiceSpeakingUpdateHandler func(vc *VoiceConnection, vs *VoiceSpeakingUpdate) @@ -246,7 +246,7 @@ type voiceOP2 struct { } // WaitUntilConnected waits for the Voice Connection to -// become ready, if it does not become ready it retuns an err +// become ready, if it does not become ready it returns an err func (v *VoiceConnection) waitUntilConnected() error { v.log(LogInformational, "called") @@ -857,7 +857,7 @@ func (v *VoiceConnection) reconnect() { } if v.session.DataReady == false || v.session.wsConn == nil { - v.log(LogInformational, "cannot reconenct to channel %s with unready session", v.ChannelID) + v.log(LogInformational, "cannot reconnect to channel %s with unready session", v.ChannelID) continue } From 1033558fcf19894f107d22b596bf03f08e38260d Mon Sep 17 00:00:00 2001 From: vim2meta Date: Sun, 19 Nov 2017 10:15:13 -0500 Subject: [PATCH 33/41] Update wsapi.go --- wsapi.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wsapi.go b/wsapi.go index 425dbf3..de66f69 100644 --- a/wsapi.go +++ b/wsapi.go @@ -673,7 +673,7 @@ func (s *Session) onVoiceServerUpdate(st *VoiceServerUpdate) { voice.GuildID = st.GuildID voice.Unlock() - // Open a conenction to the voice server + // Open a connection to the voice server err := voice.open() if err != nil { s.log(LogError, "onVoiceServerUpdate voice.open, %s", err) From 396e868b36f7f92da671b87cd4c165591210d487 Mon Sep 17 00:00:00 2001 From: vim2meta Date: Sun, 19 Nov 2017 10:15:52 -0500 Subject: [PATCH 34/41] Update main.go --- examples/appmaker/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/appmaker/main.go b/examples/appmaker/main.go index 286fe16..5581dd9 100644 --- a/examples/appmaker/main.go +++ b/examples/appmaker/main.go @@ -79,7 +79,7 @@ func main() { ap.Name = Name ap, err = dg.ApplicationCreate(ap) if err != nil { - fmt.Println("error creating new applicaiton,", err) + fmt.Println("error creating new application,", err) return } From 294ab42061d771af38b7286eba376fe4ce239c11 Mon Sep 17 00:00:00 2001 From: MrJohnCoder Date: Sat, 2 Dec 2017 22:16:49 -0500 Subject: [PATCH 35/41] Fix typo --- state.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/state.go b/state.go index 0cc1f73..8158708 100644 --- a/state.go +++ b/state.go @@ -531,7 +531,7 @@ func (s *State) PrivateChannel(channelID string) (*Channel, error) { return s.Channel(channelID) } -// Channel gets a channel by ID, it will look in all guilds an private channels. +// Channel gets a channel by ID, it will look in all guilds and private channels. func (s *State) Channel(channelID string) (*Channel, error) { if s == nil { return nil, ErrNilState From 3c792b297c42eff2e8b269ee5f5c479d30fb3ce1 Mon Sep 17 00:00:00 2001 From: Sebastian Winkler Date: Sun, 3 Dec 2017 09:38:01 +0100 Subject: [PATCH 36/41] adds unique field to invite struct --- structs.go | 1 + 1 file changed, 1 insertion(+) diff --git a/structs.go b/structs.go index 03974cb..e60092b 100644 --- a/structs.go +++ b/structs.go @@ -144,6 +144,7 @@ type Invite struct { MaxUses int `json:"max_uses"` Revoked bool `json:"revoked"` Temporary bool `json:"temporary"` + Unique bool `json:"unique"` } // ChannelType is the type of a Channel From e024d5f0323de5478479d7568c87d69763f4537b Mon Sep 17 00:00:00 2001 From: psheets <4058236+psheets@users.noreply.github.com> Date: Fri, 15 Dec 2017 00:59:07 -0500 Subject: [PATCH 37/41] Add ChannelEditComplex (#493) * Add ChannelEditComplex * Fixed comment format * gofmt * Reverted permissions and fixed ChannelEditComplex * Reverted Perms * Delete discordgo - Shortcut.lnk removed link * Added ChannelID param to ChannelEditComplex * gofmt --- restapi.go | 14 +++++++++----- structs.go | 12 ++++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/restapi.go b/restapi.go index a16a743..285898b 100644 --- a/restapi.go +++ b/restapi.go @@ -1221,12 +1221,16 @@ func (s *Session) Channel(channelID string) (st *Channel, err error) { // ChannelEdit edits the given channel // channelID : The ID of a Channel // name : The new name to assign the channel. -func (s *Session) ChannelEdit(channelID, name string) (st *Channel, err error) { - - data := struct { - Name string `json:"name"` - }{name} +func (s *Session) ChannelEdit(channelID, name string) (*Channel, error) { + return s.ChannelEditComplex(channelID, &ChannelEdit{ + Name: name, + }) +} +// ChannelEditComplex edits an existing channel, replacing the parameters entirely with ChannelEdit struct +// channelID : The ID of a Channel +// data : The channel struct to send +func (s *Session) ChannelEditComplex(channelID string, data *ChannelEdit) (st *Channel, err error) { body, err := s.RequestWithBucketID("PATCH", EndpointChannel(channelID), data, EndpointChannel(channelID)) if err != nil { return diff --git a/structs.go b/structs.go index e60092b..04efd75 100644 --- a/structs.go +++ b/structs.go @@ -176,6 +176,18 @@ type Channel struct { ParentID string `json:"parent_id"` } +// A ChannelEdit holds Channel Feild data for a channel edit. +type ChannelEdit struct { + Name string `json:"name,omitempty"` + Topic string `json:"topic,omitempty"` + NSFW bool `json:"nsfw,omitempty"` + Position int `json:"position"` + Bitrate int `json:"bitrate,omitempty"` + UserLimit int `json:"user_limit,omitempty"` + PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites,omitempty"` + ParentID string `json:"parent_id,omitempty"` +} + // A PermissionOverwrite holds permission overwrite data for a Channel type PermissionOverwrite struct { ID string `json:"id"` From e6ed3d579b4a6d5e972f83583ff512813d07020d Mon Sep 17 00:00:00 2001 From: Sebastian Winkler Date: Fri, 22 Dec 2017 17:35:44 +0100 Subject: [PATCH 38/41] adds support for animated emojis (#496) --- structs.go | 1 + 1 file changed, 1 insertion(+) diff --git a/structs.go b/structs.go index 04efd75..19d2bad 100644 --- a/structs.go +++ b/structs.go @@ -203,6 +203,7 @@ type Emoji struct { Roles []string `json:"roles"` Managed bool `json:"managed"` RequireColons bool `json:"require_colons"` + Animated bool `json:"animated"` } // APIName returns an correctly formatted API name for use in the MessageReactions endpoints. From 0d2878bac48fde623aa912da2804944b1ba572a2 Mon Sep 17 00:00:00 2001 From: Bruce Marriner Date: Wed, 27 Dec 2017 20:03:15 +0000 Subject: [PATCH 39/41] Remove tests against email/password. --- discord_test.go | 99 +++++++------------------------------------------ oauth2_test.go | 2 +- 2 files changed, 14 insertions(+), 87 deletions(-) diff --git a/discord_test.go b/discord_test.go index ca4472a..318bed0 100644 --- a/discord_test.go +++ b/discord_test.go @@ -1,6 +1,7 @@ package discordgo import ( + "fmt" "os" "runtime" "sync/atomic" @@ -14,29 +15,25 @@ var ( dg *Session // Stores a global discordgo user session dgBot *Session // Stores a global discordgo bot session - envToken = os.Getenv("DG_TOKEN") // Token to use when authenticating the user account - envBotToken = os.Getenv("DGB_TOKEN") // Token to use when authenticating the bot account - envEmail = os.Getenv("DG_EMAIL") // Email to use when authenticating - envPassword = os.Getenv("DG_PASSWORD") // Password to use when authenticating - envGuild = os.Getenv("DG_GUILD") // Guild ID to use for tests - envChannel = os.Getenv("DG_CHANNEL") // Channel ID to use for tests - // envUser = os.Getenv("DG_USER") // User ID to use for tests - envAdmin = os.Getenv("DG_ADMIN") // User ID of admin user to use for tests + envToken = os.Getenv("DGU_TOKEN") // Token to use when authenticating the user account + envBotToken = os.Getenv("DGB_TOKEN") // Token to use when authenticating the bot account + envGuild = os.Getenv("DG_GUILD") // Guild ID to use for tests + envChannel = os.Getenv("DG_CHANNEL") // Channel ID to use for tests + envAdmin = os.Getenv("DG_ADMIN") // User ID of admin user to use for tests ) func init() { + fmt.Println("Init is being called.") if envBotToken != "" { if d, err := New(envBotToken); err == nil { dgBot = d } } - if envEmail == "" || envPassword == "" || envToken == "" { - return - } - - if d, err := New(envEmail, envPassword, envToken); err == nil { + if d, err := New(envToken); err == nil { dg = d + } else { + fmt.Println("dg is nil, error", err) } } @@ -67,58 +64,11 @@ func TestInvalidToken(t *testing.T) { } } -// TestInvalidUserPass tests the New() function with an invalid Email and Pass -func TestInvalidEmailPass(t *testing.T) { - - _, err := New("invalidemail", "invalidpassword") - if err == nil { - t.Errorf("New(InvalidEmail, InvalidPass) returned nil error.") - } - -} - -// TestInvalidPass tests the New() function with an invalid Password -func TestInvalidPass(t *testing.T) { - - if envEmail == "" { - t.Skip("Skipping New(username,InvalidPass), DG_EMAIL not set") - return - } - _, err := New(envEmail, "invalidpassword") - if err == nil { - t.Errorf("New(Email, InvalidPass) returned nil error.") - } -} - -// TestNewUserPass tests the New() function with a username and password. -// This should return a valid Session{}, a valid Session.Token. -func TestNewUserPass(t *testing.T) { - - if envEmail == "" || envPassword == "" { - t.Skip("Skipping New(username,password), DG_EMAIL or DG_PASSWORD not set") - return - } - - d, err := New(envEmail, envPassword) - if err != nil { - t.Fatalf("New(user,pass) returned error: %+v", err) - } - - if d == nil { - t.Fatal("New(user,pass), d is nil, should be Session{}") - } - - if d.Token == "" { - t.Fatal("New(user,pass), d.Token is empty, should be a valid Token.") - } -} - -// TestNewToken tests the New() function with a Token. This should return -// the same as the TestNewUserPass function. +// TestNewToken tests the New() function with a Token. func TestNewToken(t *testing.T) { if envToken == "" { - t.Skip("Skipping New(token), DG_TOKEN not set") + t.Skip("Skipping New(token), DGU_TOKEN not set") } d, err := New(envToken) @@ -135,32 +85,9 @@ func TestNewToken(t *testing.T) { } } -// TestNewUserPassToken tests the New() function with a username, password and token. -// This should return the same as the TestNewUserPass function. -func TestNewUserPassToken(t *testing.T) { - - if envEmail == "" || envPassword == "" || envToken == "" { - t.Skip("Skipping New(username,password,token), DG_EMAIL, DG_PASSWORD or DG_TOKEN not set") - return - } - - d, err := New(envEmail, envPassword, envToken) - if err != nil { - t.Fatalf("New(user,pass,token) returned error: %+v", err) - } - - if d == nil { - t.Fatal("New(user,pass,token), d is nil, should be Session{}") - } - - if d.Token == "" { - t.Fatal("New(user,pass,token), d.Token is empty, should be a valid Token.") - } -} - func TestOpenClose(t *testing.T) { if envToken == "" { - t.Skip("Skipping TestClose, DG_TOKEN not set") + t.Skip("Skipping TestClose, DGU_TOKEN not set") } d, err := New(envToken) diff --git a/oauth2_test.go b/oauth2_test.go index 30526eb..0ff0ca0 100644 --- a/oauth2_test.go +++ b/oauth2_test.go @@ -10,7 +10,7 @@ import ( func ExampleApplication() { // Authentication Token pulled from environment variable DG_TOKEN - Token := os.Getenv("DG_TOKEN") + Token := os.Getenv("DGU_TOKEN") if Token == "" { return } From 2c2b0166af4a74515e40dfcbf1be35c8bcb3c5b0 Mon Sep 17 00:00:00 2001 From: Bruce Marriner Date: Wed, 27 Dec 2017 20:04:55 +0000 Subject: [PATCH 40/41] Run tests vs Go 1.7, 1.8, and 1.9 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 440159d..c181dda 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: go go: - - 1.6 - 1.7 - 1.8 + - 1.9 install: - go get github.com/bwmarrin/discordgo - go get -v . From b58212ae336d123c4191b621c71fd8e3af08b18e Mon Sep 17 00:00:00 2001 From: Bruce Marriner Date: Wed, 27 Dec 2017 21:13:27 +0000 Subject: [PATCH 41/41] If there's an err, return it :) --- restapi.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/restapi.go b/restapi.go index 285898b..5dc0467 100644 --- a/restapi.go +++ b/restapi.go @@ -959,6 +959,7 @@ func (s *Session) GuildRoleEdit(guildID, roleID, name string, color int, hoist b // Prevent sending a color int that is too big. if color > 0xFFFFFF { err = fmt.Errorf("color value cannot be larger than 0xFFFFFF") + return nil, err } data := struct { @@ -1022,6 +1023,9 @@ func (s *Session) GuildPruneCount(guildID string, days uint32) (count uint32, er uri := EndpointGuildPrune(guildID) + fmt.Sprintf("?days=%d", days) body, err := s.RequestWithBucketID("GET", uri, nil, EndpointGuildPrune(guildID)) + if err != nil { + return + } err = unmarshal(body, &p) if err != nil {