Passed
Push — master ( 77df28...2905b9 )
by Viktor
01:32
created

bot.*Twitch.Update   C

Complexity

Conditions 11

Size

Total Lines 44
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 40
nop 0
dl 0
loc 44
rs 5.4
c 0
b 0
f 0

How to fix   Complexity   

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
	"strings"
10
	"time"
11
)
12
13
// Twitch contains streams
14
type Twitch struct {
15
	Guilds  map[string]*TwitchGuild
16
	DB      *DBWorker
17
	Conf    *Config
18
	Discord *discordgo.Session
19
}
20
21
// TwitchGuild contains streams from specified guild
22
type TwitchGuild struct {
23
	ID      string
24
	Streams map[string]*TwitchStream
25
}
26
27
// TwitchStream contains stream data
28
type TwitchStream struct {
29
	Login          string
30
	Guild          string
31
	Channel        string
32
	IsOnline       bool
33
	IsCustom       bool
34
	CustomMessage  string
35
	CustomImageURI string
36
}
37
38
// TwitchStreamResult contains response of Twitch API for streams
39
type TwitchStreamResult struct {
40
	Data []TwitchStreamData `json:"data"`
41
}
42
43
// TwitchStreamData Twitch API response struct
44
type TwitchStreamData struct {
45
	ID           string `json:"id"`
46
	UserID       string `json:"user_id"`
47
	UserName     string `json:"user_name"`
48
	GameID       string `json:"game_id"`
49
	Type         string `json:"type"`
50
	Title        string `json:"title"`
51
	Viewers      int    `json:"viewer_count"`
52
	Language     string `json:"language"`
53
	ThumbnailURL string `json:"thumbnail_url"`
54
}
55
56
// TwitchUserResult contains response of Twitch API for users
57
type TwitchUserResult struct {
58
	Data []TwitchUserData `json:"data"`
59
}
60
61
// TwitchUserData Twitch API response struct
62
type TwitchUserData struct {
63
	ID              string `json:"id"`
64
	Login           string `json:"login"`
65
	Name            string `json:"display_name"`
66
	Type            string `json:"type"`
67
	BroadcasterType string `json:"broadcaster_type"`
68
	Description     string `json:"description"`
69
	ProfileImgURL   string `json:"profile_image_url"`
70
	OfflineImgURL   string `json:"offline_image_url"`
71
	Views           int    `json:"view_count"`
72
}
73
74
// TwitchGameResult contains response of Twitch API for games
75
type TwitchGameResult struct {
76
	Data []TwitchGameData `json:"data"`
77
}
78
79
// TwitchUserData Twitch API response struct
0 ignored issues
show
introduced by
comment on exported type TwitchGameData should be of the form "TwitchGameData ..." (with optional leading article)
Loading history...
80
type TwitchGameData struct {
81
	ID     string `json:"id"`
82
	Name   string `json:"name"`
83
	ArtURL string `json:"box_art_url"`
84
}
85
86
// TwitchInit makes new instance of twitch api worker
87
func TwitchInit(session *discordgo.Session, conf *Config, db *DBWorker) *Twitch {
88
	guilds := make(map[string]*TwitchGuild)
89
	var counter int
90
	for _, g := range session.State.Guilds {
91
		guildStreams := db.GetTwitchStreams(g.ID)
92
		counter += len(guildStreams)
93
		guilds[g.ID] = &TwitchGuild{g.ID, guildStreams}
94
	}
95
	fmt.Printf("Loaded [%v] streamers\n", counter)
96
	return &Twitch{guilds, db, conf, session}
97
}
98
99
// Update updates status of streamers and notify
100
func (t *Twitch) Update() {
101
	for _,g := range t.Guilds {
102
		for _, s := range g.Streams {
103
			timeout := time.Duration(time.Duration(1) * time.Second)
104
			client := &http.Client{
105
				Timeout: time.Duration(timeout),
106
			}
107
			req, _ := http.NewRequest("GET", fmt.Sprintf("https://api.twitch.tv/helix/streams?user_login=%v", s.Login), nil)
108
			req.Header.Add("Client-ID", t.Conf.Twitch.ClientID)
109
			resp, err := client.Do(req)
110
			var result TwitchStreamResult
111
			var gameResult TwitchGameResult
112
			if err == nil {
113
				err = json.NewDecoder(resp.Body).Decode(&result)
114
				if err != nil {
115
					t.DB.Log("Twitch", "", "Parsing Twitch API stream error")
116
					continue
117
				}
118
				if len(result.Data) > 0 {
119
					greq, _ := http.NewRequest("GET", fmt.Sprintf("https://api.twitch.tv/helix/games?id=%v", result.Data[0].GameID), nil)
120
					greq.Header.Add("Client-ID", t.Conf.Twitch.ClientID)
121
					gresp, gerr := client.Do(greq)
122
					err = json.NewDecoder(gresp.Body).Decode(&gameResult)
123
					if gerr != nil {
124
						t.DB.Log("Twitch", "", "Parsing Twitch API game error")
125
					}
126
					if s.IsOnline == false {
127
						t.Guilds[s.Guild].Streams[s.Login].IsOnline = true
128
						t.DB.UpdateStream(s)
129
						imgUrl := strings.Replace(result.Data[0].ThumbnailURL, "{width}", "320", -1)
0 ignored issues
show
introduced by
var imgUrl should be imgURL
Loading history...
130
						imgUrl = strings.Replace(imgUrl, "{height}", "180", -1)
131
						emb := NewEmbed(result.Data[0].UserName).
132
							Field("Title", result.Data[0].Title, false).
133
							Field("Viewers", fmt.Sprintf("%v", result.Data[0].Viewers), true).
134
							Field("Game", gameResult.Data[0].Name, true).
135
							AttachImgURL(imgUrl).
136
							Color(t.Conf.General.EmbedColor)
137
						_, _ = t.Discord.ChannelMessageSend(s.Channel, fmt.Sprintf(t.Conf.GetLocaleLang("twitch_online", result.Data[0].Language), result.Data[0].UserName, s.Login))
0 ignored issues
show
introduced by
can't check non-constant format in call to Sprintf
Loading history...
138
						_, _ = t.Discord.ChannelMessageSendEmbed(s.Channel, emb.GetEmbed())
139
					}
140
				} else {
141
					if s.IsOnline == true {
142
						t.Guilds[s.Guild].Streams[s.Login].IsOnline = false
143
						t.DB.UpdateStream(s)
144
					}
145
				}
146
147
			}
148
		}
149
	}
150
}
151
152
// AddStreamer adds new streamer to list
153
func (t *Twitch) AddStreamer(guild, channel, login string) (string, error) {
154
	if g, ok := t.Guilds[guild]; ok {
155
		if g.Streams == nil {
156
			t.Guilds[guild].Streams = make(map[string]*TwitchStream)
157
		}
158
		for _, s := range g.Streams {
159
			if s.Guild == guild && s.Login == login {
160
				return "", errors.New("streamer already exists")
161
			}
162
		}
163
		timeout := time.Duration(time.Duration(1) * time.Second)
164
		client := &http.Client{
165
			Timeout: time.Duration(timeout),
166
		}
167
		req, _ := http.NewRequest("GET", fmt.Sprintf("https://api.twitch.tv/helix/users?login=%v", login), nil)
168
		req.Header.Add("Client-ID", t.Conf.Twitch.ClientID)
169
		resp, err := client.Do(req)
170
		var result TwitchUserResult
171
		if err == nil {
172
			err = json.NewDecoder(resp.Body).Decode(&result)
173
			if err != nil {
174
				return "", errors.New("parsing streamer error")
175
			}
176
			if len(result.Data) > 0 {
177
				stream := TwitchStream{}
178
				stream.Login = login
179
				stream.Channel = channel
180
				stream.Guild = guild
181
				t.Guilds[guild].Streams[login] = &stream
182
				t.DB.AddStream(&stream)
183
			}
184
		} else {
185
			return "", errors.New("getting streamer error")
186
		}
187
		return result.Data[0].Name, nil
188
	}
189
	return "", errors.New("guild not found")
190
}
191
192
// RemoveStreamer removes streamer from list
193
func (t *Twitch) RemoveStreamer(login, guild string) error {
194
	complete := false
195
	if g, ok := t.Guilds[guild]; ok {
196
		if g.Streams != nil {
197
			if t.Guilds[guild].Streams[login] != nil {
198
				if g.Streams[login].Login == login && g.Streams[login].Guild == guild {
199
					t.DB.RemoveStream(g.Streams[login])
200
					delete(t.Guilds[guild].Streams, login)
201
					complete = true
202
				}
203
			}
204
		}
205
	} else {
206
		return errors.New("guild not found")
207
	}
208
	if !complete {
209
		return errors.New("streamer not found")
210
	}
211
	return nil
212
}
213