Skip to content

Commit

Permalink
Since FollowupMessageCreate doesn't bound to global rate limit, use R…
Browse files Browse the repository at this point in the history
…equestWithoutBucket instead.

Try to fix bwmarrin#1333
  • Loading branch information
chenshengwu committed Feb 22, 2023
1 parent 9fa5d09 commit 8032d03
Showing 1 changed file with 125 additions and 9 deletions.
134 changes: 125 additions & 9 deletions restapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,6 @@ func (s *Session) RequestWithLockedBucket(method, urlStr, contentType string, b
}

if s.Debug {

log.Printf("API RESPONSE STATUS :: %s\n", resp.Status)
for k, v := range resp.Header {
log.Printf("API RESPONSE HEADER :: [%s] = %+v\n", k, v)
Expand Down Expand Up @@ -307,6 +306,114 @@ func (s *Session) RequestWithLockedBucket(method, urlStr, contentType string, b
return
}

// RequestWithoutBucket make a request that doesn't bound to rate limit
func (s *Session) RequestWithoutBucket(method, urlStr, contentType string, b []byte, sequence int, options ...RequestOption) (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))
}

req, err := http.NewRequest(method, urlStr, bytes.NewBuffer(b))
if err != nil {
return
}

// Not used on initial login..
// TODO: Verify if a login, otherwise complain about no-token
if s.Token != "" {
req.Header.Set("authorization", s.Token)
}

// Discord's API returns a 400 Bad Request is Content-Type is set, but the
// request body is empty.
if b != nil {
req.Header.Set("Content-Type", contentType)
}

// TODO: Make a configurable static variable.
req.Header.Set("User-Agent", s.UserAgent)

cfg := newRequestConfig(s, req)
for _, opt := range options {
opt(cfg)
}

if s.Debug {
for k, v := range req.Header {
log.Printf("API REQUEST HEADER :: [%s] = %+v\n", k, v)
}
}

resp, err := cfg.Client.Do(req)
if err != nil {
return
}
defer func() {
err2 := resp.Body.Close()
if s.Debug && err2 != nil {
log.Println("error closing resp body")
}
}()

response, err = ioutil.ReadAll(resp.Body)
if err != nil {
return
}

if s.Debug {
log.Printf("API RESPONSE STATUS :: %s\n", resp.Status)
for k, v := range resp.Header {
log.Printf("API RESPONSE HEADER :: [%s] = %+v\n", k, v)
}
log.Printf("API RESPONSE BODY :: [%s]\n\n\n", response)
}

switch resp.StatusCode {
case http.StatusOK:
case http.StatusCreated:
case http.StatusNoContent:
case http.StatusBadGateway:
// Retry sending request if possible
if sequence < cfg.MaxRestRetries {

s.log(LogInformational, "%s Failed (%s), Retrying...", urlStr, resp.Status)
response, err = s.RequestWithoutBucket(method, urlStr, contentType, b, sequence+1, options...)
} else {
err = fmt.Errorf("Exceeded Max retries HTTP %s, %s", resp.Status, response)
}
case 429: // TOO MANY REQUESTS - Rate limiting
rl := TooManyRequests{}
err = Unmarshal(response, &rl)
if err != nil {
s.log(LogError, "rate limit unmarshal error, %s", err)
return
}

if cfg.ShouldRetryOnRateLimit {
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

response, err = s.RequestWithoutBucket(method, urlStr, contentType, b, sequence, options...)
} else {
err = &RateLimitError{&RateLimit{TooManyRequests: &rl, URL: urlStr}}
}
case http.StatusUnauthorized:
if strings.Index(s.Token, "Bot ") != 0 {
s.log(LogInformational, ErrUnauthorized.Error())
err = ErrUnauthorized
}
fallthrough
default: // Error condition
err = newRestError(req, resp, response)
}

return
}

func unmarshal(data []byte, v interface{}) error {
err := Unmarshal(data, v)
if err != nil {
Expand Down Expand Up @@ -3066,24 +3173,33 @@ func (s *Session) InteractionResponseDelete(interaction *Interaction, options ..
// interaction : Interaction instance.
// data : Data of the message to send.
func (s *Session) FollowupMessageCreate(interaction *Interaction, data *WebhookParams, options ...RequestOption) (st *Message, err error) {
if data == nil {
return nil, errors.New("data is nil")
}

uri := EndpointWebhookToken(interaction.AppID, interaction.Token)

v := url.Values{}
// wait is always true for FollowupMessageCreate as mentioned in
// https://discord.com/developers/docs/interactions/receiving-and-responding#endpoints
v.Set("wait", "true")
uri += "?" + v.Encode()

var response []byte
var body []byte
contentType := "application/json"
if len(data.Files) > 0 {
contentType, body, encodeErr := MultipartBodyWithJSON(data, data.Files)
if encodeErr != nil {
return st, encodeErr
if contentType, body, err = MultipartBodyWithJSON(data, data.Files); err != nil {
return st, err
}

response, err = s.request("POST", uri, contentType, body, EndpointWebhookToken(interaction.AppID, "followup"), 0, options...)
} else {
response, err = s.RequestWithBucketID("POST", uri, data, EndpointWebhookToken(interaction.AppID, "followup"), options...)
if body, err = Marshal(data); err != nil {
return st, err
}
}
if err != nil {

var response []byte
// FollowupMessageCreate not bound to global rate limit
if response, err = s.RequestWithoutBucket("POST", uri, contentType, body, 0, options...); err != nil {
return
}

Expand Down

0 comments on commit 8032d03

Please sign in to comment.