2022-10-25 16:47:30 +00:00
|
|
|
package twitch
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"strconv"
|
2023-08-14 13:34:02 +00:00
|
|
|
"strings"
|
2022-10-25 16:47:30 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
|
|
|
User struct {
|
|
|
|
DisplayName string `json:"display_name"`
|
|
|
|
ID string `json:"id"`
|
|
|
|
Login string `json:"login"`
|
|
|
|
ProfileImageURL string `json:"profile_image_url"`
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2022-12-26 21:38:14 +00:00
|
|
|
var ErrUserDoesNotFollow = errors.New("no follow-relation found")
|
|
|
|
|
2022-10-25 16:47:30 +00:00
|
|
|
func (c *Client) GetAuthorizedUser() (userID string, userName string, err error) {
|
|
|
|
var payload struct {
|
|
|
|
Data []User `json:"data"`
|
|
|
|
}
|
|
|
|
|
2023-07-01 14:48:21 +00:00
|
|
|
if err := c.Request(ClientRequestOpts{
|
|
|
|
AuthType: AuthTypeBearerToken,
|
2022-10-25 16:47:30 +00:00
|
|
|
Context: context.Background(),
|
|
|
|
Method: http.MethodGet,
|
|
|
|
OKStatus: http.StatusOK,
|
|
|
|
Out: &payload,
|
|
|
|
URL: "https://api.twitch.tv/helix/users",
|
|
|
|
}); err != nil {
|
|
|
|
return "", "", errors.Wrap(err, "request channel info")
|
|
|
|
}
|
|
|
|
|
|
|
|
if l := len(payload.Data); l != 1 {
|
|
|
|
return "", "", errors.Errorf("unexpected number of users returned: %d", l)
|
|
|
|
}
|
|
|
|
|
|
|
|
return payload.Data[0].ID, payload.Data[0].Login, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) GetDisplayNameForUser(username string) (string, error) {
|
2023-08-14 13:34:02 +00:00
|
|
|
username = strings.TrimLeft(username, "#@")
|
|
|
|
|
2022-10-25 16:47:30 +00:00
|
|
|
cacheKey := []string{"displayNameForUsername", username}
|
|
|
|
if d := c.apiCache.Get(cacheKey); d != nil {
|
|
|
|
return d.(string), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var payload struct {
|
|
|
|
Data []User `json:"data"`
|
|
|
|
}
|
|
|
|
|
2023-07-01 14:48:21 +00:00
|
|
|
if err := c.Request(ClientRequestOpts{
|
|
|
|
AuthType: AuthTypeAppAccessToken,
|
2022-10-25 16:47:30 +00:00
|
|
|
Context: context.Background(),
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Out: &payload,
|
|
|
|
URL: fmt.Sprintf("https://api.twitch.tv/helix/users?login=%s", username),
|
|
|
|
}); err != nil {
|
|
|
|
return "", errors.Wrap(err, "request channel info")
|
|
|
|
}
|
|
|
|
|
|
|
|
if l := len(payload.Data); l != 1 {
|
|
|
|
return "", errors.Errorf("unexpected number of users returned: %d", l)
|
|
|
|
}
|
|
|
|
|
|
|
|
// The DisplayName for an username will not change (often), cache for a decent time
|
|
|
|
c.apiCache.Set(cacheKey, time.Hour, payload.Data[0].DisplayName)
|
|
|
|
|
|
|
|
return payload.Data[0].DisplayName, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) GetFollowDate(from, to string) (time.Time, error) {
|
|
|
|
cacheKey := []string{"followDate", from, to}
|
|
|
|
if d := c.apiCache.Get(cacheKey); d != nil {
|
|
|
|
return d.(time.Time), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
fromID, err := c.GetIDForUsername(from)
|
|
|
|
if err != nil {
|
|
|
|
return time.Time{}, errors.Wrap(err, "getting id for 'from' user")
|
|
|
|
}
|
|
|
|
toID, err := c.GetIDForUsername(to)
|
|
|
|
if err != nil {
|
|
|
|
return time.Time{}, errors.Wrap(err, "getting id for 'to' user")
|
|
|
|
}
|
|
|
|
|
|
|
|
var payload struct {
|
|
|
|
Data []struct {
|
|
|
|
FollowedAt time.Time `json:"followed_at"`
|
|
|
|
} `json:"data"`
|
|
|
|
}
|
|
|
|
|
2023-07-01 14:48:21 +00:00
|
|
|
if err := c.Request(ClientRequestOpts{
|
|
|
|
AuthType: AuthTypeAppAccessToken,
|
2022-10-25 16:47:30 +00:00
|
|
|
Context: context.Background(),
|
|
|
|
Method: http.MethodGet,
|
|
|
|
OKStatus: http.StatusOK,
|
|
|
|
Out: &payload,
|
2023-09-29 12:54:24 +00:00
|
|
|
URL: fmt.Sprintf("https://api.twitch.tv/helix/channels/followers?broadcaster_id=%s&user_id=%s", toID, fromID),
|
2022-10-25 16:47:30 +00:00
|
|
|
}); err != nil {
|
|
|
|
return time.Time{}, errors.Wrap(err, "request follow info")
|
|
|
|
}
|
|
|
|
|
2022-12-26 21:38:14 +00:00
|
|
|
switch len(payload.Data) {
|
|
|
|
case 0:
|
|
|
|
return time.Time{}, ErrUserDoesNotFollow
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
// Handled below, no error
|
|
|
|
|
|
|
|
default:
|
|
|
|
return time.Time{}, errors.Errorf("unexpected number of records returned: %d", len(payload.Data))
|
2022-10-25 16:47:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Follow date will not change that often, cache for a long time
|
|
|
|
c.apiCache.Set(cacheKey, timeDay, payload.Data[0].FollowedAt)
|
|
|
|
|
|
|
|
return payload.Data[0].FollowedAt, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) GetIDForUsername(username string) (string, error) {
|
2023-08-14 13:34:02 +00:00
|
|
|
username = strings.TrimLeft(username, "#@")
|
|
|
|
|
2022-10-25 16:47:30 +00:00
|
|
|
cacheKey := []string{"idForUsername", username}
|
|
|
|
if d := c.apiCache.Get(cacheKey); d != nil {
|
|
|
|
return d.(string), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var payload struct {
|
|
|
|
Data []User `json:"data"`
|
|
|
|
}
|
|
|
|
|
2023-07-01 14:48:21 +00:00
|
|
|
if err := c.Request(ClientRequestOpts{
|
|
|
|
AuthType: AuthTypeAppAccessToken,
|
2022-10-25 16:47:30 +00:00
|
|
|
Context: context.Background(),
|
|
|
|
Method: http.MethodGet,
|
|
|
|
OKStatus: http.StatusOK,
|
|
|
|
Out: &payload,
|
|
|
|
URL: fmt.Sprintf("https://api.twitch.tv/helix/users?login=%s", username),
|
|
|
|
}); err != nil {
|
|
|
|
return "", errors.Wrap(err, "request channel info")
|
|
|
|
}
|
|
|
|
|
|
|
|
if l := len(payload.Data); l != 1 {
|
|
|
|
return "", errors.Errorf("unexpected number of users returned: %d", l)
|
|
|
|
}
|
|
|
|
|
|
|
|
// The ID for an username will not change (often), cache for a long time
|
|
|
|
c.apiCache.Set(cacheKey, timeDay, payload.Data[0].ID)
|
|
|
|
|
|
|
|
return payload.Data[0].ID, nil
|
|
|
|
}
|
|
|
|
|
2023-09-03 10:09:33 +00:00
|
|
|
// GetUsernameForID retrieves the login name (not the display name)
|
|
|
|
// for the given user ID
|
|
|
|
func (c *Client) GetUsernameForID(ctx context.Context, id string) (string, error) {
|
|
|
|
cacheKey := []string{"usernameForID", id}
|
|
|
|
if d := c.apiCache.Get(cacheKey); d != nil {
|
|
|
|
return d.(string), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var payload struct {
|
|
|
|
Data []User `json:"data"`
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := c.Request(ClientRequestOpts{
|
|
|
|
AuthType: AuthTypeAppAccessToken,
|
|
|
|
Context: ctx,
|
|
|
|
Method: http.MethodGet,
|
|
|
|
OKStatus: http.StatusOK,
|
|
|
|
Out: &payload,
|
|
|
|
URL: fmt.Sprintf("https://api.twitch.tv/helix/users?id=%s", id),
|
|
|
|
}); err != nil {
|
|
|
|
return "", errors.Wrap(err, "request channel info")
|
|
|
|
}
|
|
|
|
|
|
|
|
if l := len(payload.Data); l != 1 {
|
|
|
|
return "", errors.Errorf("unexpected number of users returned: %d", l)
|
|
|
|
}
|
|
|
|
|
|
|
|
// The username for an ID will not change (often), cache for a long time
|
|
|
|
c.apiCache.Set(cacheKey, timeDay, payload.Data[0].Login)
|
|
|
|
|
|
|
|
return payload.Data[0].Login, nil
|
|
|
|
}
|
|
|
|
|
2022-10-25 16:47:30 +00:00
|
|
|
func (c *Client) GetUserInformation(user string) (*User, error) {
|
2023-08-14 13:34:02 +00:00
|
|
|
user = strings.TrimLeft(user, "#@")
|
|
|
|
|
2022-10-25 16:47:30 +00:00
|
|
|
var (
|
|
|
|
out User
|
|
|
|
param = "login"
|
|
|
|
payload struct {
|
|
|
|
Data []User `json:"data"`
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
cacheKey := []string{"userInformation", user}
|
|
|
|
if d := c.apiCache.Get(cacheKey); d != nil {
|
|
|
|
out = d.(User)
|
|
|
|
return &out, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := strconv.ParseInt(user, 10, 64); err == nil {
|
|
|
|
param = "id"
|
|
|
|
}
|
|
|
|
|
2023-07-01 14:48:21 +00:00
|
|
|
if err := c.Request(ClientRequestOpts{
|
|
|
|
AuthType: AuthTypeAppAccessToken,
|
2022-10-25 16:47:30 +00:00
|
|
|
Context: context.Background(),
|
|
|
|
Method: http.MethodGet,
|
|
|
|
OKStatus: http.StatusOK,
|
|
|
|
Out: &payload,
|
|
|
|
URL: fmt.Sprintf("https://api.twitch.tv/helix/users?%s=%s", param, user),
|
|
|
|
}); err != nil {
|
|
|
|
return nil, errors.Wrap(err, "request user info")
|
|
|
|
}
|
|
|
|
|
|
|
|
if l := len(payload.Data); l != 1 {
|
|
|
|
return nil, errors.Errorf("unexpected number of records returned: %d", l)
|
|
|
|
}
|
|
|
|
|
2023-09-29 12:54:24 +00:00
|
|
|
// User info will not change that often, cache for a long time
|
2022-10-25 16:47:30 +00:00
|
|
|
c.apiCache.Set(cacheKey, timeDay, payload.Data[0])
|
|
|
|
out = payload.Data[0]
|
|
|
|
|
|
|
|
return &out, nil
|
|
|
|
}
|