-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathraid.go
341 lines (304 loc) · 9.17 KB
/
raid.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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
package raiderio
import (
"encoding/json"
"time"
)
// RaidQuery is a struct that represents the query parameters
// sent for a raid request
// Supports optional request fields: difficulty, region, realm, name
type RaidQuery struct {
Slug string
Difficulty RaidDifficulty
Region *Region
Realm string
Limit int
Page int
}
// RaidRankings is a struct that represents the response from a
// raid rankings request
type RaidRankings struct {
RaidRanking []RaidRanking `json:"raidRankings"`
}
// RaidRanking is a struct that represents a raid ranking in a
// raid rankings response from the api
// Unfortunately the "Guild" object differs in structure from the
// guild profile response. This requires a separate struct
type RaidRanking struct {
Rank int `json:"rank"`
RegionalRank int `json:"region_rank"`
Guild struct {
Id int `json:"id"`
Name string `json:"name"`
Faction string `json:"faction"`
Realm Realm `json:"realm"`
Region Region `json:"region"`
Path string `json:"path"`
Logo string `json:"logo"`
Color string `json:"color"`
} `json:"guild"`
EncountersDefeated []struct {
Slug string `json:"slug"`
LastDefeatedAt string `json:"lastDefeated"`
FirstDefeated string `json:"firstDefeated"`
} `json:"encountersDefeated"`
EncountersPulled []struct {
Id int `json:"id"`
Slug string `json:"slug"`
Pulls int `json:"numPulls"`
PullsStartedAt string `json:"pullStartedAt"`
BestPercent float32 `json:"bestPercent"`
IsDefeated bool `json:"isDefeated"`
} `json:"encountersPulled"`
}
// RaidProgression is a struct that contains the raid progression of a guild
// in a guild profile response
type RaidProgression struct {
Summary string `json:"summary"`
Bosses int `json:"total_bosses"`
NormalKills int `json:"normal_bosses_killed"`
HeroicKills int `json:"heroic_bosses_killed"`
MythicKills int `json:"mythic_bosses_killed"`
}
// GuildRaidRanking is a struct that contains the raid rankings of a guild
// in a guild profile response
// Includes Normal Heroic and Mythic rankings
type GuildRaidRanking struct {
RaidSlug string
Normal struct {
World int `json:"world"`
Region int `json:"region"`
Realm int `json:"realm"`
} `json:"normal"`
Heroic struct {
World int `json:"world"`
Region int `json:"region"`
Realm int `json:"realm"`
} `json:"heroic"`
Mythic struct {
World int `json:"world"`
Region int `json:"region"`
Realm int `json:"realm"`
} `json:"mythic"`
}
// Raids is a struct that represents the response from a
// raid static data request
type Raids struct {
Raids []Raid `json:"raids"`
}
// Raid is a struct that represents a raid in a raid static
// data response. Includes raid encounters and other static data
type Raid struct {
Id int `json:"id"`
Slug string `json:"slug"`
Name string `json:"name"`
ShortName string `json:"short_name"`
Icon string `json:"icon"`
Starts struct {
Us string `json:"us"`
Eu string `json:"eu"`
Tw string `json:"tw"`
Kr string `json:"kr"`
Cn string `json:"cn"`
} `json:"starts"`
Ends struct {
Us string `json:"us"`
Eu string `json:"eu"`
Tw string `json:"tw"`
Kr string `json:"kr"`
Cn string `json:"cn"`
} `json:"ends"`
Encounters []Encounter `json:"encounters"`
}
// Encounter is a struct that represents an encounter in a raid
// in a raid static data response
type Encounter struct {
Id int `json:"id"`
Slug string `json:"slug"`
Name string `json:"name"`
}
// RaidDifficulty is a string type that represents the difficulty of a raid
// in a raid request
// Options are "normal", "heroic", and "mythic"
type RaidDifficulty string
// Options for different difficulties for raid and dugneon queries
var Difficulty = struct {
NormalRaid RaidDifficulty
HeroicRaid RaidDifficulty
MythicRaid RaidDifficulty
}{
NormalRaid: "normal",
HeroicRaid: "heroic",
MythicRaid: "mythic",
}
// Includes BossKillData along with the roster of characters
// which were present for the first kill
type BossKill struct {
Kill BossKillData
Roster []Character
}
// BossKillData provides metadata for the guilds first boss kill
// Includes timestamps and Item Levels etc...
type BossKillData struct {
PulledAt time.Time `json:"pulledAt"`
DefeatedAt time.Time `json:"defeatedAt"`
Duration time.Duration `json:"duration"`
IsSuccess bool `json:"isSuccess"`
ItemLevelEquippedAvg float32 `json:"itemLevelEquippedAvg"`
ItemLevelEquippedMax float32 `json:"itemLevelEquippedMax"`
ItemLevelEquippedMin float32 `json:"itemLevelEquippedMin"`
}
// The following two structs are unexported, for use within the package
// to convert the ugly incoming boss-kill roster into standard "Character"
// types. I couldnt think of a better way to covert the incoming json to
// a standardized object, without exporting the ugly structs to the client
// Hopefully this is fixed in json/2
type bossKillResp struct {
Kill struct {
PulledAt time.Time `json:"pulledAt"`
DefeatedAt time.Time `json:"defeatedAt"`
DurationMs int `json:"durationMs"`
IsSuccess bool `json:"isSuccess"`
ItemLevelEquippedAvg float32 `json:"itemLevelEquippedAvg"`
ItemLevelEquippedMax float32 `json:"itemLevelEquippedMax"`
ItemLevelEquippedMin float32 `json:"itemLevelEquippedMin"`
}
Roster []bossKillCharacter `json:"roster"`
}
type bossKillCharacter struct {
Character struct {
Name string `json:"name"`
Class struct {
Slug string `json:"slug"`
} `json:"class"`
Spec struct {
Slug string `json:"slug"`
} `json:"spec"`
TalentLoadout struct {
LoadoutSpecID int `json:"loadoutSpecId"`
LoadoutText string `json:"loadoutText"`
} `json:"talentLoadout"`
Realm struct {
Slug string `json:"slug"`
} `json:"realm"`
Region struct {
Slug string `json:"slug"`
} `json:"region"`
ItemLevelEquipped float32 `json:"itemLevelEquipped"`
} `json:"character"`
}
// GuildBossKillQuery requires all fields to be valid when sending
// a request to the api. Use GetRaids() to see a list of raids and bosses
type GuildBossKillQuery struct {
Region *Region
Realm string
GuildName string
RaidSlug string
BossSlug string
Difficulty RaidDifficulty
}
// Current /guild/boss-kill api returns an enormous json
// structure for each character in the raid roster
// this library offers a simplified version of the data set
func unmarshalGuildBossKill(b []byte) (*BossKill, error) {
resp := bossKillResp{}
err := json.Unmarshal(b, &resp)
if err != nil {
return nil, err
}
kd := BossKillData{
PulledAt: resp.Kill.PulledAt,
DefeatedAt: resp.Kill.DefeatedAt,
Duration: time.Duration(resp.Kill.DurationMs) * time.Millisecond,
IsSuccess: resp.Kill.IsSuccess,
ItemLevelEquippedAvg: resp.Kill.ItemLevelEquippedAvg,
ItemLevelEquippedMax: resp.Kill.ItemLevelEquippedMax,
ItemLevelEquippedMin: resp.Kill.ItemLevelEquippedMin,
}
k := BossKill{
Kill: kd,
Roster: unmarshalBossKillRoster(&resp),
}
return &k, nil
}
func unmarshalBossKillRoster(k *bossKillResp) []Character {
var chars []Character
for _, c := range k.Roster {
g := Gear{
ItemLevelEquipped: int(c.Character.ItemLevelEquipped),
}
tl := TalentLoadout{
LoadoutText: c.Character.TalentLoadout.LoadoutText,
}
char := Character{
Name: c.Character.Name,
Class: c.Character.Class.Slug,
Spec: c.Character.Spec.Slug,
Realm: c.Character.Realm.Slug,
Region: c.Character.Region.Slug,
TalentLoadout: tl,
Gear: g,
}
chars = append(chars, char)
}
return chars
}
func validateGuildBossKillQuery(q *GuildBossKillQuery) error {
if q.Region == nil {
return ErrInvalidRegion
}
if q.Realm == "" {
return ErrInvalidRealm
}
if q.GuildName == "" {
return ErrInvalidGuildName
}
if q.RaidSlug == "" {
return ErrInvalidRaidName
}
if q.BossSlug == "" {
return ErrInvalidBoss
}
if q.Difficulty == "" || !raidDifficltyValid(q.Difficulty) {
return ErrInvalidRaidDiff
}
return nil
}
// Validates raid difficulty before sending to the api
// making an http request to the api with an invalid difficulty
// results in an empty result instead of an error message. So
// we add the error by checking for valid difficulty before sending
// the request to the api
func raidDifficltyValid(d RaidDifficulty) bool {
if d == Difficulty.NormalRaid || d == Difficulty.HeroicRaid || d == Difficulty.MythicRaid {
return true
}
return false
}
// validateRaidQuery validates a RaidQuery struct
// ensures that the required parameters are not empty
func validateRaidRankingsQuery(rq *RaidQuery) error {
if rq.Slug == "" {
return ErrInvalidRaidName
}
if rq.Difficulty == "" || !raidDifficltyValid(rq.Difficulty) {
return ErrInvalidRaidDiff
}
if rq.Region == nil {
return ErrInvalidRegion
}
if rq.Limit < 0 {
return ErrLimitOutOfBounds
}
if rq.Page < 0 {
return ErrPageOutOfBounds
}
return nil
}
func (r *Raids) GetRaidBySlug(slug string) (*Raid, error) {
for _, raid := range r.Raids {
if raid.Slug == slug {
return &raid, nil
}
}
return nil, ErrInvalidRaid
}