-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
service_twitch.go
158 lines (130 loc) · 4.38 KB
/
service_twitch.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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"time"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
)
const (
// #configStore twitch.client_id - string - ID of a Twitch application
configKeyTwitchClientID = "twitch.client_id"
// #configStore twitch.client_secret - string - Secret of the Twitch application identified by twitch.client_id
configKeyTwitchClientSecret = "twitch.client_secret"
)
func init() {
registerServiceHandler("twitch", &twitchServiceHandler{})
}
type twitchServiceHandler struct {
accessToken string
accessTokenExpiry time.Time
}
func (twitchServiceHandler) GetDocumentation() serviceHandlerDocumentationList {
return serviceHandlerDocumentationList{
{
ServiceName: "Twitch views",
DemoPath: "/twitch/views/luziferus",
Arguments: []string{"views", "<user login/id>"},
},
}
}
func (twitchServiceHandler) IsEnabled() bool {
return configStore[configKeyTwitchClientID] != nil && configStore[configKeyTwitchClientSecret] != nil
}
func (t *twitchServiceHandler) Handle(ctx context.Context, params []string) (title, text, color string, err error) {
if len(params) < 2 { //nolint:gomnd
err = errors.New("No service-command / parameters were given")
return title, text, color, err
}
switch params[0] {
case "views":
title, text, color, err = t.handleViews(ctx, params[1:])
default:
err = errors.New("An unknown service command was called")
}
return title, text, color, err
}
func (t *twitchServiceHandler) handleViews(ctx context.Context, params []string) (title, text, color string, err error) {
var respData struct {
Data []struct {
Login string `json:"login"`
ViewCount int64 `json:"view_count"`
} `json:"data"`
}
field := "login"
if _, err := strconv.ParseInt(params[0], 10, 64); err == nil {
field = "id"
}
if err := t.doTwitchRequest(ctx, http.MethodGet, fmt.Sprintf("https://api.twitch.tv/helix/users?%s=%s", field, params[0]), nil, &respData); err != nil {
return "", "", "", errors.Wrap(err, "requesting user list")
}
if len(respData.Data) != 1 {
return "", "", "", errors.New("unexpected number of users returned")
}
text = strconv.FormatInt(respData.Data[0].ViewCount, 10)
title = "views"
color = "9146FF"
return title, text, color, err
}
func (t *twitchServiceHandler) getAccessToken(ctx context.Context) (string, error) {
if time.Now().Before(t.accessTokenExpiry) && t.accessToken != "" {
return t.accessToken, nil
}
params := url.Values{}
params.Set("client_id", configStore.Str(configKeyTwitchClientID))
params.Set("client_secret", configStore.Str(configKeyTwitchClientSecret))
params.Set("grant_type", "client_credentials")
req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("https://id.twitch.tv/oauth2/token?%s", params.Encode()), nil)
if err != nil {
return "", errors.Wrap(err, "creating access token request")
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", errors.Wrap(err, "executing access token request")
}
defer func() {
if err := resp.Body.Close(); err != nil {
logrus.WithError(err).Error("closing response body (leaked fd)")
}
}()
var respData struct {
AccessToken string `json:"access_token"`
ExpiresIn int64 `json:"expires_in"`
}
if err = json.NewDecoder(resp.Body).Decode(&respData); err != nil {
return "", errors.Wrap(err, "reading access token")
}
t.accessToken = respData.AccessToken
t.accessTokenExpiry = time.Now().Add(time.Duration(respData.ExpiresIn) * time.Second)
return t.accessToken, nil
}
func (t *twitchServiceHandler) doTwitchRequest(ctx context.Context, method, reqURL string, body io.Reader, out any) error {
at, err := t.getAccessToken(ctx)
if err != nil {
return errors.Wrap(err, "getting access token")
}
req, err := http.NewRequestWithContext(ctx, method, reqURL, body)
if err != nil {
return errors.Wrap(err, "creating request")
}
req.Header.Set("Client-Id", configStore.Str(configKeyTwitchClientID))
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", at))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return errors.Wrap(err, "executing request")
}
defer func() {
if err := resp.Body.Close(); err != nil {
logrus.WithError(err).Error("closing response body (leaked fd)")
}
}()
if err = json.NewDecoder(resp.Body).Decode(out); err != nil {
return errors.Wrap(err, "reading response")
}
return nil
}