mirror of
https://github.com/Luzifer/twitch-bot.git
synced 2024-12-20 20:01:17 +00:00
Add category search and channel update
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
9451354d30
commit
2b67673e95
1 changed files with 103 additions and 5 deletions
108
twitch/twitch.go
108
twitch/twitch.go
|
@ -1,11 +1,13 @@
|
||||||
package twitch
|
package twitch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Luzifer/go_helpers/v2/backoff"
|
"github.com/Luzifer/go_helpers/v2/backoff"
|
||||||
|
@ -22,12 +24,20 @@ const (
|
||||||
twitchRequestTimeout = 2 * time.Second
|
twitchRequestTimeout = 2 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type (
|
||||||
clientID string
|
Category struct {
|
||||||
token string
|
BoxArtURL string `json:"box_art_url"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
apiCache *APICache
|
Client struct {
|
||||||
}
|
clientID string
|
||||||
|
token string
|
||||||
|
|
||||||
|
apiCache *APICache
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
func New(clientID, token string) *Client {
|
func New(clientID, token string) *Client {
|
||||||
return &Client{
|
return &Client{
|
||||||
|
@ -140,6 +150,38 @@ func (c Client) GetFollowDate(from, to string) (time.Time, error) {
|
||||||
return payload.Data[0].FollowedAt, nil
|
return payload.Data[0].FollowedAt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c Client) SearchCategories(ctx context.Context, name string) ([]Category, error) {
|
||||||
|
var out []Category
|
||||||
|
|
||||||
|
params := make(url.Values)
|
||||||
|
params.Set("query", name)
|
||||||
|
params.Set("first", "100")
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Data []Category `json:"data"`
|
||||||
|
Pagination struct {
|
||||||
|
Cursor string `json:"cursor"`
|
||||||
|
} `json:"pagination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if err := c.request(ctx, http.MethodGet, fmt.Sprintf("https://api.twitch.tv/helix/search/categories?%s", params.Encode()), nil, &resp); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "executing request")
|
||||||
|
}
|
||||||
|
|
||||||
|
out = append(out, resp.Data...)
|
||||||
|
|
||||||
|
if resp.Pagination.Cursor == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
params.Set("after", resp.Pagination.Cursor)
|
||||||
|
resp.Pagination.Cursor = "" // Clear from struct as struct is reused
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c Client) HasLiveStream(username string) (bool, error) {
|
func (c Client) HasLiveStream(username string) (bool, error) {
|
||||||
cacheKey := []string{"hasLiveStream", username}
|
cacheKey := []string{"hasLiveStream", username}
|
||||||
if d := c.apiCache.Get(cacheKey); d != nil {
|
if d := c.apiCache.Get(cacheKey); d != nil {
|
||||||
|
@ -243,6 +285,58 @@ func (c Client) GetRecentStreamInfo(username string) (string, string, error) {
|
||||||
return payload.Data[0].GameName, payload.Data[0].Title, nil
|
return payload.Data[0].GameName, payload.Data[0].Title, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c Client) ModifyChannelInformation(ctx context.Context, broadcaster string, game, title *string) error {
|
||||||
|
if game == nil && title == nil {
|
||||||
|
return errors.New("netiher game nor title provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
data := struct {
|
||||||
|
GameID *string `json:"game_id,omitempty"`
|
||||||
|
Title *string `json:"title,omitempty"`
|
||||||
|
}{
|
||||||
|
Title: title,
|
||||||
|
}
|
||||||
|
|
||||||
|
if game != nil {
|
||||||
|
categories, err := c.SearchCategories(ctx, *game)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "searching for game")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(categories) {
|
||||||
|
case 0:
|
||||||
|
return errors.New("no matching game found")
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
data.GameID = &categories[0].ID
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Multiple matches: Search for exact one
|
||||||
|
for _, c := range categories {
|
||||||
|
if c.Name == *game {
|
||||||
|
data.GameID = &c.ID
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.GameID == nil {
|
||||||
|
// No exact match found: This is an error
|
||||||
|
return errors.New("no exact game match found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body := new(bytes.Buffer)
|
||||||
|
if err := json.NewEncoder(body).Encode(data); err != nil {
|
||||||
|
return errors.Wrap(err, "encoding payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Wrap(
|
||||||
|
c.request(ctx, http.MethodPost, fmt.Sprintf("https://api.twitch.tv/helix/channels?broadcaster_id=%s", broadcaster), body, nil),
|
||||||
|
"executing request",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func (c Client) request(ctx context.Context, method, url string, body io.Reader, out interface{}) error {
|
func (c Client) request(ctx context.Context, method, url string, body io.Reader, out interface{}) error {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"method": method,
|
"method": method,
|
||||||
|
@ -267,6 +361,10 @@ func (c Client) request(ctx context.Context, method, url string, body io.Reader,
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if out == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return errors.Wrap(
|
return errors.Wrap(
|
||||||
json.NewDecoder(resp.Body).Decode(out),
|
json.NewDecoder(resp.Body).Decode(out),
|
||||||
"parse user info",
|
"parse user info",
|
||||||
|
|
Loading…
Reference in a new issue