-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2b45724
commit b59ef40
Showing
4 changed files
with
161 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package twitch | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"net/url" | ||
|
||
"golang.org/x/oauth2" | ||
) | ||
|
||
// Client holds the context for requests to the Twitch API. | ||
type Client struct { | ||
HTTP *http.Client | ||
Token *oauth2.Token | ||
} | ||
|
||
// reqjson performs an HTTP request and decodes the response as JSON. | ||
// The response body is truncated to 2 MB. | ||
func reqjson[Resp any](ctx context.Context, client Client, method, url string, body io.Reader, u *Resp) error { | ||
req, err := http.NewRequestWithContext(ctx, method, url, body) | ||
if err != nil { | ||
return fmt.Errorf("couldn't make request: %w", err) | ||
} | ||
client.Token.SetAuthHeader(req) | ||
resp, err := client.HTTP.Do(req) | ||
if err != nil { | ||
return fmt.Errorf("couldn't %s: %w", method, err) | ||
} | ||
b, err := io.ReadAll(io.LimitReader(resp.Body, 2<<20)) | ||
if err != nil { | ||
return fmt.Errorf("couldn't read response: %w", err) | ||
} | ||
resp.Body.Close() | ||
if resp.StatusCode != http.StatusOK { | ||
return fmt.Errorf("request failed: %s (%s)", b, resp.Status) | ||
} | ||
r := struct { | ||
Data *Resp `json:"data"` | ||
}{u} | ||
if err := json.Unmarshal(b, &r); err != nil { | ||
return fmt.Errorf("couldn't decode JSON response: %w", err) | ||
} | ||
return nil | ||
} | ||
|
||
// apiurl creates an api.twitch.tv URL for the given endpoint and with the | ||
// given URL parameters. | ||
func apiurl(ep string, values url.Values) string { | ||
u, err := url.JoinPath("https://api.twitch.tv/", ep) | ||
if err != nil { | ||
panic("twitch: bad url join with " + ep) | ||
} | ||
if len(values) == 0 { | ||
return u | ||
} | ||
return u + "?" + values.Encode() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package twitch | ||
|
||
import ( | ||
"context" | ||
_ "embed" | ||
"errors" | ||
"io" | ||
"net/http" | ||
"strings" | ||
"testing" | ||
|
||
"golang.org/x/oauth2" | ||
) | ||
|
||
type reqspy struct { | ||
// got is the first request the round tripper received. | ||
got *http.Request | ||
// respond is the response the round tripper returns. | ||
respond *http.Response | ||
} | ||
|
||
func (r *reqspy) RoundTrip(req *http.Request) (*http.Response, error) { | ||
if r.got != nil { | ||
return nil, errors.New("already have a request") | ||
} | ||
r.got = req | ||
return r.respond, nil | ||
} | ||
|
||
func TestReqJSON(t *testing.T) { | ||
spy := &reqspy{ | ||
respond: &http.Response{ | ||
StatusCode: 200, | ||
Body: io.NopCloser(strings.NewReader(`{"data":1}`)), | ||
}, | ||
} | ||
cl := Client{ | ||
HTTP: &http.Client{ | ||
Transport: spy, | ||
}, | ||
Token: &oauth2.Token{ | ||
AccessToken: "bocchi", | ||
}, | ||
} | ||
var u int | ||
err := reqjson(context.Background(), cl, "GET", "https://bocchi.rocks/bocchi", nil, &u) | ||
if err != nil { | ||
t.Errorf("failed to request: %v", err) | ||
} | ||
if u != 1 { | ||
t.Errorf("didn't get the result: want 1, got %d", u) | ||
} | ||
if spy.got.URL.String() != "https://bocchi.rocks/bocchi" { | ||
t.Errorf("request went to the wrong place: want https://bocchi.rocks/bocchi, got %v", spy.got.URL) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package twitch | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/url" | ||
) | ||
|
||
// User is the response type from https://dev.twitch.tv/docs/api/reference/#get-users. | ||
type User struct { | ||
ID string `json:"id"` | ||
Login string `json:"login"` | ||
DisplayName string `json:"display_name"` | ||
Type string `json:"type"` | ||
BroadcasterType string `json:"broadcaster_type"` | ||
Description string `json:"description"` | ||
ProfileImageURL string `json:"profile_image_url"` | ||
OfflineImageURL string `json:"offline_image_url"` | ||
ViewCount string `json:"view_count"` | ||
Email string `json:"email"` | ||
CreatedAt string `json:"created_at"` | ||
} | ||
|
||
func UsersByLogin(ctx context.Context, client Client, names ...string) ([]User, error) { | ||
v := url.Values{"login": names} | ||
u := make([]User, 0, 100) | ||
url := apiurl("/helix/users", v) | ||
err := reqjson(ctx, client, "GET", url, nil, &u) | ||
if err != nil { | ||
return nil, fmt.Errorf("couldn't get users info: %w", err) | ||
} | ||
return u, nil | ||
} | ||
|
||
func UsersByID(ctx context.Context, client Client, ids ...string) ([]User, error) { | ||
v := url.Values{"id": ids} | ||
u := make([]User, 0, 100) | ||
url := apiurl("/helix/users", v) | ||
err := reqjson(ctx, client, "GET", url, nil, &u) | ||
if err != nil { | ||
return nil, fmt.Errorf("couldn't get users info: %w", err) | ||
} | ||
return u, nil | ||
} |