Passed
Push — master ( 7d1d80...2df032 )
by Viktor
01:55
created

bot.*Twitch.Update   F

Complexity

Conditions 20

Size

Total Lines 74
Code Lines 60

Duplication

Lines 0
Ratio 0 %

Importance

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