bot.*Twitch.Update   F
last analyzed

Complexity

Conditions 16

Size

Total Lines 85
Code Lines 69

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 16
eloc 69
nop 0
dl 0
loc 85
rs 2.4
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like bot.*Twitch.Update often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
package bot
2
3
import (
4
	"context"
5
	"encoding/json"
6
	"errors"
7
	"fmt"
8
	"github.com/bwmarrin/discordgo"
9
	"log"
10
	"net/http"
11
	"net/url"
12
	"strings"
13
	"time"
14
15
	"golang.org/x/oauth2/clientcredentials"
16
	"golang.org/x/oauth2/twitch"
17
)
18
19
// Twitch contains streams
20
type Twitch struct {
21
	Guilds  map[string]*TwitchGuild
22
	DB      *DBWorker
23
	Conf    *Config
24
	Discord *discordgo.Session
25
}
26
27
// TwitchGuild contains streams from specified guild
28
type TwitchGuild struct {
29
	ID      string
30
	Streams map[string]*TwitchStream
31
}
32
33
// TwitchStream contains stream data
34
type TwitchStream struct {
35
	Login           string
36
	UserID          string
37
	Name            string
38
	Guild           string
39
	Channel         string
40
	ProfileImageURL string
41
	IsOnline        bool
42
	IsCustom        bool
43
	CustomMessage   string
44
	CustomImageURL  string
45
}
46
47
// TwitchStreamResult contains response of Twitch API for streams
48
type TwitchStreamResult struct {
49
	Data []TwitchStreamData `json:"data"`
50
}
51
52
// TwitchStreamData Twitch API response struct
53
type TwitchStreamData struct {
54
	ID           string `json:"id"`
55
	UserID       string `json:"user_id"`
56
	UserName     string `json:"user_name"`
57
	GameID       string `json:"game_id"`
58
	Type         string `json:"type"`
59
	Title        string `json:"title"`
60
	Viewers      int    `json:"viewer_count"`
61
	Language     string `json:"language"`
62
	ThumbnailURL string `json:"thumbnail_url"`
63
}
64
65
// TwitchUserResult contains response of Twitch API for users
66
type TwitchUserResult struct {
67
	Data []TwitchUserData `json:"data"`
68
}
69
70
// TwitchUserData Twitch API response struct
71
type TwitchUserData struct {
72
	ID              string `json:"id"`
73
	Login           string `json:"login"`
74
	Name            string `json:"display_name"`
75
	Type            string `json:"type"`
76
	BroadcasterType string `json:"broadcaster_type"`
77
	Description     string `json:"description"`
78
	ProfileImgURL   string `json:"profile_image_url"`
79
	OfflineImgURL   string `json:"offline_image_url"`
80
	Views           int    `json:"view_count"`
81
}
82
83
// TwitchGameResult contains response of Twitch API for games
84
type TwitchGameResult struct {
85
	Data []TwitchGameData `json:"data"`
86
}
87
88
// TwitchGameData Twitch API response struct
89
type TwitchGameData struct {
90
	ID     string `json:"id"`
91
	Name   string `json:"name"`
92
	ArtURL string `json:"box_art_url"`
93
}
94
95
// TwitchInit makes new instance of twitch api worker
96
func TwitchInit(session *discordgo.Session, conf *Config, db *DBWorker) *Twitch {
97
	guilds := make(map[string]*TwitchGuild)
98
	var counter int
99
	for _, g := range session.State.Guilds {
100
		guildStreams := db.GetTwitchStreams(g.ID)
101
		counter += len(guildStreams)
102
		guilds[g.ID] = &TwitchGuild{g.ID, guildStreams}
103
	}
104
	fmt.Printf("Loaded [%v] streamers\n", counter)
105
	return &Twitch{guilds, db, conf, session}
106
}
107
108
func (t *Twitch) OAuthToken() string {
0 ignored issues
show
introduced by
exported method Twitch.OAuthToken should have comment or be unexported
Loading history...
109
	dbt := t.DB.GetTwitchToken()
110
	if dbt.Expire.Unix() < time.Now().Unix() {
111
		var oauth2Config *clientcredentials.Config
112
		oauth2Config = &clientcredentials.Config{
113
			ClientID:     t.Conf.Twitch.ClientID,
114
			ClientSecret: t.Conf.Twitch.ClientSecret,
115
			TokenURL:     twitch.Endpoint.TokenURL,
116
		}
117
118
		token, err := oauth2Config.Token(context.Background())
119
		if err != nil {
120
			log.Println("[Twitch] Getting token error: ", err)
121
		}
122
		t.DB.UpdateTwitchToken(token.AccessToken, token.Expiry)
123
		return token.AccessToken
124
	}
125
	return dbt.Token
126
}
127
128
// Update updates status of streamers and notify
129
func (t *Twitch) Update() {
130
	var gameResult TwitchGameResult
131
	var streamResult TwitchStreamResult
132
	var streams = make(map[string]*TwitchStreamData)
133
	var games = make(map[string]*TwitchGameData)
134
	var oauthToken = t.OAuthToken()
135
	timeout := time.Duration(time.Duration(1) * time.Second)
136
	client := &http.Client{
137
		Timeout: time.Duration(timeout),
138
	}
139
	streamQuery := url.Values{}
140
	gameQuery := url.Values{}
141
	for _, g := range t.Guilds {
142
		for _, s := range g.Streams {
143
			streamQuery.Add("user_login", s.Login)
144
		}
145
	}
146
	// Streams
147
	tsreq, _ := http.NewRequest("GET", fmt.Sprintf("https://api.twitch.tv/helix/streams?%v", streamQuery.Encode()), nil)
148
	tsreq.Header.Add("Client-ID", t.Conf.Twitch.ClientID)
149
	tsreq.Header.Add("Authorization", "Bearer "+oauthToken)
150
	tsresp, tserr := client.Do(tsreq)
151
	if tserr == nil {
152
		jerr := json.NewDecoder(tsresp.Body).Decode(&streamResult)
153
		if jerr != nil {
154
			t.DB.Log("Twitch", "", "Parsing Twitch API stream error")
155
		}
156
	} else {
157
		t.DB.Log("Twitch", "", fmt.Sprintf("Getting Twitch API stream error: %v", tserr.Error()))
158
		return
159
	}
160
	for i, s := range streamResult.Data {
161
		gameQuery.Add("id", s.GameID)
162
		streams[s.UserID] = &streamResult.Data[i]
163
	}
164
165
	// Games
166
	tgreq, _ := http.NewRequest("GET", fmt.Sprintf("https://api.twitch.tv/helix/games?%v", gameQuery.Encode()), nil)
167
	tgreq.Header.Add("Client-ID", t.Conf.Twitch.ClientID)
168
	tgreq.Header.Add("Authorization", "Bearer "+oauthToken)
169
	tgresp, tgerr := client.Do(tgreq)
170
	if tgerr == nil {
171
		jerr := json.NewDecoder(tgresp.Body).Decode(&gameResult)
172
		if jerr != nil {
173
			t.DB.Log("Twitch", "", "Parsing Twitch API game error")
174
			return
175
		}
176
	} else {
177
		t.DB.Log("Twitch", "", fmt.Sprintf("Getting Twitch API game error: %v", tgerr.Error()))
178
		return
179
	}
180
	for i, g := range gameResult.Data {
181
		games[g.ID] = &gameResult.Data[i]
182
	}
183
184
	for _, g := range t.Guilds {
185
		for _, s := range g.Streams {
186
			if stream, ok := streams[s.UserID]; ok {
187
				if !s.IsOnline {
188
					gameName := "Unknown"
189
					if _, ok := games[stream.GameID]; ok {
190
						gameName = games[stream.GameID].Name
191
					}
192
					t.Guilds[s.Guild].Streams[s.Login].IsOnline = true
193
					t.DB.UpdateStream(s)
194
					imgURL := strings.Replace(stream.ThumbnailURL, "{width}", "320", -1)
195
					imgURL = strings.Replace(imgURL, "{height}", "180", -1)
196
					emb := NewEmbed(stream.Title).
197
						URL(fmt.Sprintf("http://www.twitch.tv/%v", s.Login)).
198
						Author(s.Name, "", s.ProfileImageURL).
199
						Field("Viewers", fmt.Sprintf("%v", stream.Viewers), true).
200
						Field("Game", gameName, true).
201
						AttachImgURL(imgURL).
202
						Color(t.Conf.General.EmbedColor)
203
					if s.CustomMessage != "" {
204
						emb.Content = s.CustomMessage
205
					} else {
206
						emb.Content = fmt.Sprintf(t.Conf.GetLocaleLang("twitch_online", stream.Language), s.Name, s.Login)
0 ignored issues
show
introduced by
can't check non-constant format in call to Sprintf
Loading history...
207
					}
208
					_, _ = t.Discord.ChannelMessageSendComplex(s.Channel, emb.MessageSend)
209
				}
210
			} else {
211
				if s.IsOnline == true {
212
					t.Guilds[s.Guild].Streams[s.Login].IsOnline = false
213
					t.DB.UpdateStream(s)
214
				}
215
			}
216
		}
217
	}
218
}
219
220
// AddStreamer adds new streamer to list
221
func (t *Twitch) AddStreamer(guild, channel, login, message string) (string, error) {
222
	if g, ok := t.Guilds[guild]; ok {
223
		if g.Streams == nil {
224
			t.Guilds[guild].Streams = make(map[string]*TwitchStream)
225
		}
226
		for _, s := range g.Streams {
227
			if s.Guild == guild && s.Login == login {
228
				return "", errors.New("streamer already exists")
229
			}
230
		}
231
		var oauthToken = t.OAuthToken()
232
		timeout := time.Duration(time.Duration(1) * time.Second)
233
		client := &http.Client{
234
			Timeout: time.Duration(timeout),
235
		}
236
		req, _ := http.NewRequest("GET", fmt.Sprintf("https://api.twitch.tv/helix/users?login=%v", login), nil)
237
		req.Header.Add("Client-ID", t.Conf.Twitch.ClientID)
238
		req.Header.Add("Authorization", "Bearer "+oauthToken)
239
		resp, err := client.Do(req)
240
		var result TwitchUserResult
241
		if err == nil {
242
			err = json.NewDecoder(resp.Body).Decode(&result)
243
			if err != nil {
244
				return "", errors.New("parsing streamer error")
245
			}
246
			if len(result.Data) > 0 {
247
				stream := TwitchStream{}
248
				stream.Login = login
249
				stream.Channel = channel
250
				stream.Guild = guild
251
				stream.UserID = result.Data[0].ID
252
				if result.Data[0].Name == "" {
253
					stream.Name = login
254
				} else {
255
					stream.Name = result.Data[0].Name
256
				}
257
				stream.ProfileImageURL = result.Data[0].ProfileImgURL
258
				stream.CustomMessage = message
259
				t.Guilds[guild].Streams[login] = &stream
260
				t.DB.AddStream(&stream)
261
			} else {
262
				return "", errors.New("streamer not found")
263
			}
264
		} else {
265
			return "", errors.New("getting streamer error")
266
		}
267
		return result.Data[0].Name, nil
268
	}
269
	return "", errors.New("guild not found")
270
}
271
272
// RemoveStreamer removes streamer from list
273
func (t *Twitch) RemoveStreamer(login, guild string) error {
274
	complete := false
275
	if g, ok := t.Guilds[guild]; ok {
276
		if g.Streams != nil {
277
			if t.Guilds[guild].Streams[login] != nil {
278
				if g.Streams[login].Login == login && g.Streams[login].Guild == guild {
279
					t.DB.RemoveStream(g.Streams[login])
280
					delete(t.Guilds[guild].Streams, login)
281
					complete = true
282
				}
283
			}
284
		}
285
	} else {
286
		return errors.New("guild not found")
287
	}
288
	if !complete {
289
		return errors.New("streamer not found")
290
	}
291
	return nil
292
}
293