This repository was archived by the owner on Aug 20, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclient.go
executable file
·98 lines (84 loc) · 2.95 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package http
import (
"bytes"
"errors"
"net/http"
"time"
"github.com/streamcord/http/objects"
"github.com/streamcord/http/ratelimit"
json "github.com/json-iterator/go"
)
type Client struct {
HTTPClient http.Client
Token string // This should also include the token's prefix, e.g. "Bot <token>"
URL string // This is the URL of the API
}
func (c *Client) MakeRequest(r objects.Request) (*http.Response, error) {
body, err := json.Marshal(r.Payload)
if err != nil {
return nil, err
}
req, err := http.NewRequest(r.Method, c.URL+r.Endpoint, bytes.NewBuffer(body))
if err != nil {
return nil, err
}
// Set request headers - individual routes shouldn't need their own headers
req.Header.Set("Authorization", c.Token)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "Streamcord (https://github.com/streamcord/http, 1.0.0)")
bucket := ratelimit.GetBucket(r.RatelimitBucket)
if bucket != nil {
if bucket.Remaining == 0 {
wait := time.Duration(bucket.Reset - float64(time.Now().Unix()))
// If wait is below 0 then that means it's already reset and we don't have to wait
if wait > 0 {
time.Sleep(wait * time.Second)
}
}
}
res, err := c.HTTPClient.Do(req)
if err != nil {
return nil, err
}
// If we get a 500/502 error for some reason, we can try again a minute later
// In this case, ratelimits shouldn't really be a problem for long-lasting problems since Discord likely won't be receiving the request
// Therefore a minute delay should not cause any interference.
if res.StatusCode == http.StatusInternalServerError || res.StatusCode == http.StatusBadGateway {
time.Sleep(time.Minute)
return c.MakeRequest(r)
} else if res.StatusCode < http.StatusInternalServerError {
// Update ratelimit state using the response headers.
// We don't want to be calling this if we get a 5xx error since there won't be any ratelimit headers to handle.
err := ratelimit.UpdateBucket(r.RatelimitBucket, res)
if err != nil {
return nil, err
}
}
// If we've got here then we've hit a ratelimit. Oh dear.
// So, we'll retry the request when we can.
// We don't need to change anything if the ratelimit is global as the reset header will refer to the global ratelimit.
if res.StatusCode == http.StatusTooManyRequests {
bucket = ratelimit.GetBucket(r.RatelimitBucket)
if bucket != nil {
wait := time.Duration(bucket.Reset - float64(time.Now().Unix()))
if wait > 0 {
time.Sleep(wait * time.Second)
return c.MakeRequest(r)
}
}
}
return res, nil
}
func NewClient(timeout time.Duration, token string) (*Client, error) {
// If timeout is 0 then failed connections (e.g. an outage on Discord's end), will cause our connections to hang leading an abundance of problems.
if timeout == 0 {
return nil, errors.New("timeout cannot be 0 for safety reasons")
}
return &Client{
HTTPClient: http.Client{
Timeout: timeout,
},
Token: token,
URL: "https://discord.com/api/v9",
}, nil
}