Skip to content

Commit

Permalink
twitch: get users api
Browse files Browse the repository at this point in the history
  • Loading branch information
zephyrtronium committed Apr 17, 2024
1 parent 2b45724 commit b59ef40
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 1 deletion.
2 changes: 1 addition & 1 deletion twitch/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func Validate(ctx context.Context, client *http.Client, tok *oauth2.Token) (*Val
if err != nil {
return nil, fmt.Errorf("couldn't make validate request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+tok.AccessToken)
tok.SetAuthHeader(req)
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("couldn't validate access token: %w", err)
Expand Down
60 changes: 60 additions & 0 deletions twitch/twitch.go
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()
}
56 changes: 56 additions & 0 deletions twitch/twitch_test.go
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)
}
}
44 changes: 44 additions & 0 deletions twitch/user.go
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
}

0 comments on commit b59ef40

Please sign in to comment.