diff --git a/discord.go b/discord.go index bdcb8c48f..d83d891bb 100644 --- a/discord.go +++ b/discord.go @@ -36,6 +36,7 @@ func New(token string) (s *Session, err error) { StateEnabled: true, Compress: true, ShouldReconnectOnError: true, + ShouldRetryOnRateLimit: true, ShardID: 0, ShardCount: 1, MaxRestRetries: 3, diff --git a/restapi.go b/restapi.go index adfbeb926..9e5d83a3c 100644 --- a/restapi.go +++ b/restapi.go @@ -73,6 +73,18 @@ func (r RESTError) Error() string { return "HTTP " + r.Response.Status + ", " + string(r.ResponseBody) } +// RateLimitError is returned when a request exceeds a rate limit +// and ShouldRetryOnRateLimit is false. The request may be manually +// retried after waiting the duration specified by RetryAfter. +type RateLimitError struct { + *RateLimit +} + +// Error returns a rate limit error with rate limited endpoint and retry time. +func (e RateLimitError) Error() string { + return "Rate limit exceeded on " + e.URL + ", retry after " + e.RetryAfter.String() +} + // Request is the same as RequestWithBucketID but the bucket id is the same as the urlStr func (s *Session) Request(method, urlStr string, data interface{}) (response []byte, err error) { return s.RequestWithBucketID(method, urlStr, data, strings.SplitN(urlStr, "?", 2)[0]) @@ -186,14 +198,19 @@ func (s *Session) RequestWithLockedBucket(method, urlStr, contentType string, b s.log(LogError, "rate limit unmarshal error, %s", err) return } - s.log(LogInformational, "Rate Limiting %s, retry in %v", urlStr, rl.RetryAfter) - s.handleEvent(rateLimitEventType, &RateLimit{TooManyRequests: &rl, URL: urlStr}) - time.Sleep(rl.RetryAfter) - // we can make the above smarter - // this method can cause longer delays than required + if s.ShouldRetryOnRateLimit { + s.log(LogInformational, "Rate Limiting %s, retry in %v", urlStr, rl.RetryAfter) + s.handleEvent(rateLimitEventType, &RateLimit{TooManyRequests: &rl, URL: urlStr}) - response, err = s.RequestWithLockedBucket(method, urlStr, contentType, b, s.Ratelimiter.LockBucketObject(bucket), sequence) + time.Sleep(rl.RetryAfter) + // we can make the above smarter + // this method can cause longer delays than required + + response, err = s.RequestWithLockedBucket(method, urlStr, contentType, b, s.Ratelimiter.LockBucketObject(bucket), sequence) + } else { + err = &RateLimitError{&RateLimit{TooManyRequests: &rl, URL: urlStr}} + } case http.StatusUnauthorized: if strings.Index(s.Token, "Bot ") != 0 { s.log(LogInformational, ErrUnauthorized.Error()) diff --git a/structs.go b/structs.go index ab79af6ab..752aafe4d 100644 --- a/structs.go +++ b/structs.go @@ -43,6 +43,9 @@ type Session struct { // Should the session reconnect the websocket on errors. ShouldReconnectOnError bool + // Should the session retry requests when rate limited. + ShouldRetryOnRateLimit bool + // Identify is sent during initial handshake with the discord gateway. // https://discord.com/developers/docs/topics/gateway#identify Identify Identify