sleeper_api 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.editorconfig +17 -0
- data/.github/workflows/ci.yml +30 -0
- data/.gitignore +13 -0
- data/.rubocop.yml +114 -0
- data/.rubocop_todo.yml +26 -0
- data/CHANGELOG.md +28 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +21 -0
- data/README.md +447 -0
- data/Rakefile +11 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/lib/sleeper_api/client.rb +277 -0
- data/lib/sleeper_api/draft.rb +150 -0
- data/lib/sleeper_api/helpers.rb +24 -0
- data/lib/sleeper_api/league.rb +399 -0
- data/lib/sleeper_api/user.rb +137 -0
- data/lib/sleeper_api/version.rb +5 -0
- data/lib/sleeper_api.rb +87 -0
- data/sig/sleeper_api.rbs +4 -0
- data/sleeper_api.gemspec +53 -0
- metadata +184 -0
@@ -0,0 +1,399 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SleeperApi
|
4
|
+
class League
|
5
|
+
include Helpers
|
6
|
+
|
7
|
+
# League attributes from the API.
|
8
|
+
ATTRIBUTES = %w[name league_id total_rosters status sport settings season_type season scoring_settings
|
9
|
+
roster_positions previous_league_id draft_id bracket_id bracket_overrides_id loser_bracket_id
|
10
|
+
loser_bracket_overrides_id group_id avatar company_id shard last_message_id last_author_avatar
|
11
|
+
last_author_display_name last_author_id last_author_is_bot last_message_attachment
|
12
|
+
last_message_text_map last_message_time last_pinned_message_id last_read_id metadata].freeze
|
13
|
+
|
14
|
+
attr_reader :league_id, :weeks
|
15
|
+
|
16
|
+
# @param league_id [String] League identifier
|
17
|
+
# @param client [SleeperApi::Client] HTTP client instance
|
18
|
+
# @param no_data [Boolean] Skip initial data fetch (default: false)
|
19
|
+
# @raise [ArgumentError] If league_id is empty
|
20
|
+
def initialize(league_id, client, no_data: false)
|
21
|
+
raise ArgumentError, "league_id must be a non-empty string" if league_id.to_s.empty?
|
22
|
+
|
23
|
+
@league_id = league_id
|
24
|
+
@client = client
|
25
|
+
@weeks = 1..17
|
26
|
+
@league_data = nil
|
27
|
+
@league_rosters = nil
|
28
|
+
@league_users = nil
|
29
|
+
@matchups = nil
|
30
|
+
@transactions = nil
|
31
|
+
@playoff_bracket = nil
|
32
|
+
@toilet_bowl = nil
|
33
|
+
|
34
|
+
fetch_league_data unless no_data
|
35
|
+
end
|
36
|
+
|
37
|
+
# Dynamically define attribute readers for league data.
|
38
|
+
ATTRIBUTES.each do |attr|
|
39
|
+
define_method(attr) do
|
40
|
+
@league_data[attr]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Generate avatar URL from ID.
|
45
|
+
#
|
46
|
+
# @return [String, nil] Full avatar URL or nil if no avatar
|
47
|
+
def avatar_url
|
48
|
+
avatar ? "https://sleepercdn.com/avatars/#{avatar}" : nil
|
49
|
+
end
|
50
|
+
|
51
|
+
# Get the reigning champion's roster.
|
52
|
+
#
|
53
|
+
# @return [Hash, nil] Formatted roster data or nil if no champion data
|
54
|
+
def reigning_champ
|
55
|
+
roster_id = @league_data&.dig("metadata", "latest_league_winner_roster_id")
|
56
|
+
return nil unless roster_id
|
57
|
+
|
58
|
+
roster_id = roster_id.to_i
|
59
|
+
rosters(roster_id: roster_id)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Get all league users (raw data).
|
63
|
+
#
|
64
|
+
# @return [Array<Hash>] Raw user data from API
|
65
|
+
def league_rosters
|
66
|
+
fetch_rosters unless @league_rosters
|
67
|
+
@league_rosters
|
68
|
+
end
|
69
|
+
|
70
|
+
# Get all matchups across all weeks.
|
71
|
+
#
|
72
|
+
# @return [Hash{Integer => Array<Hash>}] Week number to matchup data
|
73
|
+
def league_users
|
74
|
+
fetch_users unless @league_users
|
75
|
+
@league_users
|
76
|
+
end
|
77
|
+
|
78
|
+
# Get all matchups across all weeks.
|
79
|
+
#
|
80
|
+
# @return [Hash{Integer => Array<Hash>}] Week number to matchup data
|
81
|
+
def matchups
|
82
|
+
fetch_matchups unless @matchups&.keys&.sort == @weeks.to_a.sort
|
83
|
+
@matchups
|
84
|
+
end
|
85
|
+
|
86
|
+
# Get formatted rosters with team names, records, and player lists.
|
87
|
+
#
|
88
|
+
# @param roster_id [Integer, nil] Filter by specific roster
|
89
|
+
# @param user_id [String, nil] Filter by user owner
|
90
|
+
# @return [Array<Hash>] Formatted roster objects
|
91
|
+
#
|
92
|
+
# @example Get all rosters
|
93
|
+
# rosters = league.rosters
|
94
|
+
# roster = rosters.first
|
95
|
+
# puts "#{roster[:owner_display_name]}: #{roster[:wins]}-#{roster[:losses]}"
|
96
|
+
#
|
97
|
+
# @example Get my roster
|
98
|
+
# my_roster = league.rosters(user_id: my_user_id)
|
99
|
+
def rosters(roster_id: nil, user_id: nil)
|
100
|
+
fetch_rosters unless @league_rosters
|
101
|
+
fetch_users unless @league_users
|
102
|
+
|
103
|
+
format_rosters(roster_id: roster_id, user_id: user_id)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Get formatted matchups for a specific week.
|
107
|
+
#
|
108
|
+
# @param week [Integer] Week number (1-17)
|
109
|
+
# @return [Array<Hash>] Matchup data with scoring breakdown
|
110
|
+
# @raise [ArgumentError] If week is invalid
|
111
|
+
#
|
112
|
+
# @example
|
113
|
+
# matchups = league.matchups_by_week(week: 5)
|
114
|
+
# matchups.each do |matchup|
|
115
|
+
# matchup[:rosters].each do |team|
|
116
|
+
# puts "#{team[:points]} points from #{team[:starters].length} starters"
|
117
|
+
# end
|
118
|
+
# end
|
119
|
+
def matchups_by_week(week: nil)
|
120
|
+
raise ArgumentError, "Week must be between 1 and 17" unless @weeks.include?(week)
|
121
|
+
|
122
|
+
fetch_matchups([week]) unless @matchups&.key?(week)
|
123
|
+
format_matchups(week)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Get formatted league users with team names and commissioner status.
|
127
|
+
#
|
128
|
+
# @return [Array<Hash>] Formatted user objects
|
129
|
+
#
|
130
|
+
# @example
|
131
|
+
# users = league.users
|
132
|
+
# users.each do |user|
|
133
|
+
# role = user[:commissioner] ? "(Commissioner)" : ""
|
134
|
+
# puts "#{user[:display_name]} #{role}"
|
135
|
+
# end
|
136
|
+
def users
|
137
|
+
fetch_users unless @fetch_users
|
138
|
+
format_users
|
139
|
+
end
|
140
|
+
|
141
|
+
# Get formatted transactions for a specific week.
|
142
|
+
#
|
143
|
+
# @param week [Integer] Week number (1-17)
|
144
|
+
# @return [Array<Hash>] Transaction data with adds/drops and draft picks
|
145
|
+
# @raise [ArgumentError] If week is invalid
|
146
|
+
#
|
147
|
+
# @example
|
148
|
+
# transactions = league.transactions(week: 5)
|
149
|
+
# transactions.each do |tx|
|
150
|
+
# puts "#{tx[:type]}: #{tx[:adds]&.length || 0} adds, #{tx[:drops]&.length || 0} drops"
|
151
|
+
# end
|
152
|
+
def transactions(week: nil)
|
153
|
+
raise ArgumentError, "Week must be between 1 and 17" unless @weeks.include?(week)
|
154
|
+
|
155
|
+
fetch_transactions([week]) unless @transactions&.key?(week)
|
156
|
+
format_transactions(week)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Get formatted playoff bracket with team names.
|
160
|
+
#
|
161
|
+
# @return [Array<Hash>] Bracket matchups with winner/loser team names
|
162
|
+
def playoff_bracket
|
163
|
+
fetch_playoff_bracket unless @playoff_bracket
|
164
|
+
@playoff_bracket
|
165
|
+
end
|
166
|
+
|
167
|
+
# Get formatted toilet bowl (losers bracket) with team names.
|
168
|
+
#
|
169
|
+
# @return [Array<Hash>] Bracket matchups with winner/loser team names
|
170
|
+
def toilet_bowl
|
171
|
+
fetch_toilet_bowl unless @toilet_bowl
|
172
|
+
@toilet_bowl
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
def fetch_league_data
|
178
|
+
@league_data ||= @client.get_league(@league_id)
|
179
|
+
end
|
180
|
+
|
181
|
+
def fetch_rosters
|
182
|
+
@league_rosters ||= @client.get_league_rosters(@league_id)
|
183
|
+
end
|
184
|
+
|
185
|
+
def fetch_users
|
186
|
+
@league_users ||= @client.get_league_users(@league_id)
|
187
|
+
end
|
188
|
+
|
189
|
+
def fetch_matchups(weeks = @weeks)
|
190
|
+
@matchups ||= {}
|
191
|
+
weeks.each do |week|
|
192
|
+
next if @matchups[week]
|
193
|
+
|
194
|
+
@matchups[week] = @client.get_league_matchups(@league_id, week)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def fetch_transactions(weeks = @weeks)
|
199
|
+
@transactions ||= {}
|
200
|
+
weeks.each do |week|
|
201
|
+
next if @transactions[week]
|
202
|
+
|
203
|
+
@transactions[week] = @client.get_transactions(@league_id, week)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def fetch_playoff_bracket
|
208
|
+
return @playoff_bracket if @playoff_bracket
|
209
|
+
|
210
|
+
response = @client.get_playoff_bracket(@league_id)
|
211
|
+
@playoff_bracket = response.parsed_response.map do |matchup|
|
212
|
+
deep_symbolize_keys(matchup).merge(
|
213
|
+
team1_owner: users.find do |user|
|
214
|
+
user[:user_id] == rosters(roster_id: matchup["t1"])&.first&.dig(:owner_id)
|
215
|
+
end&.dig(:team_name),
|
216
|
+
team2_owner: users.find do |user|
|
217
|
+
user[:user_id] == rosters(roster_id: matchup["t2"])&.first&.dig(:owner_id)
|
218
|
+
end&.dig(:team_name),
|
219
|
+
winner_owner: if matchup["w"]
|
220
|
+
users.find do |user|
|
221
|
+
user[:user_id] == rosters(roster_id: matchup["w"])&.first&.dig(:owner_id)
|
222
|
+
end&.dig(:team_name)
|
223
|
+
end,
|
224
|
+
loser_owner: if matchup["l"]
|
225
|
+
users.find do |user|
|
226
|
+
user[:user_id] == rosters(roster_id: matchup["l"])&.first&.dig(:owner_id)
|
227
|
+
end&.dig(:team_name)
|
228
|
+
end
|
229
|
+
)
|
230
|
+
end
|
231
|
+
@playoff_bracket
|
232
|
+
end
|
233
|
+
|
234
|
+
def fetch_toilet_bowl
|
235
|
+
return @toilet_bowl if @toilet_bowl
|
236
|
+
|
237
|
+
response = @client.get_toilet_bowl(@league_id)
|
238
|
+
@toilet_bowl = response.parsed_response.map do |matchup|
|
239
|
+
deep_symbolize_keys(matchup).merge(
|
240
|
+
team1_owner: users.find do |u|
|
241
|
+
u[:user_id] == rosters(roster_id: matchup["t1"])&.first&.dig(:owner_id)
|
242
|
+
end&.dig(:team_name),
|
243
|
+
team2_owner: users.find do |u|
|
244
|
+
u[:user_id] == rosters(roster_id: matchup["t2"])&.first&.dig(:owner_id)
|
245
|
+
end&.dig(:team_name),
|
246
|
+
winner_owner: if matchup["w"]
|
247
|
+
users.find do |u|
|
248
|
+
u[:user_id] == rosters(roster_id: matchup["w"])&.first&.dig(:owner_id)
|
249
|
+
end&.dig(:team_name)
|
250
|
+
end,
|
251
|
+
loser_owner: if matchup["l"]
|
252
|
+
users.find do |u|
|
253
|
+
u[:user_id] == rosters(roster_id: matchup["l"])&.first&.dig(:owner_id)
|
254
|
+
end&.dig(:team_name)
|
255
|
+
end
|
256
|
+
)
|
257
|
+
end
|
258
|
+
@toilet_bowl
|
259
|
+
end
|
260
|
+
|
261
|
+
def format_rosters(roster_id: nil, user_id: nil)
|
262
|
+
rosters = @league_rosters
|
263
|
+
|
264
|
+
rosters = rosters.select { |roster| roster["roster_id"] == roster_id } if roster_id
|
265
|
+
|
266
|
+
rosters = rosters.select { |roster| roster["owner_id"] == user_id } if user_id
|
267
|
+
|
268
|
+
rosters.map do |roster|
|
269
|
+
user = @league_users.find { |user| user["user_id"] == roster["owner_id"] }
|
270
|
+
roster_metadata = roster["metadata"]
|
271
|
+
roster_settings = roster["settings"]
|
272
|
+
|
273
|
+
{
|
274
|
+
roster_id: roster["roster_id"],
|
275
|
+
league_id: roster["league_id"],
|
276
|
+
owner_id: roster["owner_id"],
|
277
|
+
owner_display_name: user&.dig("display_name") || "Unknown",
|
278
|
+
team_name: user&.dig("metadata", "team_name") || "Unknown",
|
279
|
+
team_icon: user&.dig("metadata", "avatar"),
|
280
|
+
starters: roster["starters"] || [],
|
281
|
+
injured_reserve: roster["reserve"] || [],
|
282
|
+
taxi: roster["taxi"] || [],
|
283
|
+
bench: (roster["players"] || []) - (roster["starters"] || []) - (roster["reserve"] || []) - (roster["taxi"] || []),
|
284
|
+
total_points: roster_settings&.dig("fpts"),
|
285
|
+
wins: roster_settings&.dig("wins"),
|
286
|
+
ties: roster_settings&.dig("ties"),
|
287
|
+
losses: roster_settings&.dig("losses"),
|
288
|
+
remaining_faab: (@league_data&.dig("settings",
|
289
|
+
"waiver_budget") || 0) - (roster_settings&.dig("waiver_budget_used") || 0),
|
290
|
+
faab_used: roster_settings&.dig("waiver_budget_used"),
|
291
|
+
waiver_position: roster_settings&.dig("waiver_position"),
|
292
|
+
streak: roster_metadata&.dig("streak"),
|
293
|
+
co_owners: roster["co_owner"],
|
294
|
+
keepers: roster["keepers"],
|
295
|
+
players_map: roster["player_map"],
|
296
|
+
players: roster["players"],
|
297
|
+
metadata: roster_metadata.is_a?(Hash) ? roster_metadata.transform_keys(&:to_sym) : roster_metadata,
|
298
|
+
settings: roster_settings.is_a?(Hash) ? roster_settings.transform_keys(&:to_sym) : roster_settings
|
299
|
+
}
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
def format_matchups(week)
|
304
|
+
week_matchups = @matchups[week] || []
|
305
|
+
return [] if week_matchups.empty?
|
306
|
+
|
307
|
+
week_matchups.group_by { |match| match["matchup_id"] }.map do |matchup_id, matchup_entries|
|
308
|
+
next unless matchup_id
|
309
|
+
|
310
|
+
{
|
311
|
+
matchup_id: matchup_id,
|
312
|
+
rosters: matchup_entries.map do |roster|
|
313
|
+
starters = roster["starters"]
|
314
|
+
|
315
|
+
bench = roster["players"] - starters
|
316
|
+
{
|
317
|
+
roster_id: roster["roster_id"],
|
318
|
+
points: roster["points"],
|
319
|
+
custom_points: roster["custom_points"],
|
320
|
+
total_points: roster["points"] + (roster["custom_points"] || 0),
|
321
|
+
starters: starters,
|
322
|
+
bench: bench,
|
323
|
+
starter_points: (starters || []).map do |starter_id|
|
324
|
+
{ starter_id => roster["players_points"]&.dig(starter_id) || 0 }
|
325
|
+
end,
|
326
|
+
bench_points: bench.map do |bench_player_id|
|
327
|
+
{ bench_player_id => roster["players_points"]&.dig(bench_player_id) || 0 }
|
328
|
+
end
|
329
|
+
}
|
330
|
+
end
|
331
|
+
}
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
def format_users
|
336
|
+
(@league_users || []).map do |user|
|
337
|
+
user_settings = user["settings"]
|
338
|
+
user_metadata = user["metadata"]
|
339
|
+
|
340
|
+
{
|
341
|
+
user_id: user["user_id"],
|
342
|
+
username: user["username"],
|
343
|
+
display_name: user["display_name"],
|
344
|
+
avatar_id: user["avatar"],
|
345
|
+
team_name: user_metadata["team_name"],
|
346
|
+
commissioner: user["is_owner"],
|
347
|
+
is_bot: user["is_bot"],
|
348
|
+
metadata: user_metadata.is_a?(Hash) ? user_metadata.transform_keys(&:to_sym) : user_metadata,
|
349
|
+
settings: user_settings.is_a?(Hash) ? user_settings.transform_keys(&:to_sym) : user_settings
|
350
|
+
}
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
def format_transactions(week)
|
355
|
+
(@transactions[week] || []).map do |transaction|
|
356
|
+
draft_picks = transaction["draft_picks"]&.map do |pick|
|
357
|
+
{
|
358
|
+
season: pick["season"],
|
359
|
+
round: pick["round"],
|
360
|
+
original_roster_id: pick["owner_id"],
|
361
|
+
previous_roster_id: pick["previous_owner_id"],
|
362
|
+
new_roster_id: pick["owner_id"]
|
363
|
+
}
|
364
|
+
end || []
|
365
|
+
waiver_budget = transaction["waiver_budget"]&.map do |budget|
|
366
|
+
{
|
367
|
+
amount: budget["amount"],
|
368
|
+
receiving_roster: budget["receiver"],
|
369
|
+
sending_roster: budget["sender"]
|
370
|
+
}
|
371
|
+
end || []
|
372
|
+
|
373
|
+
transaction_metadata = transaction["metadata"]
|
374
|
+
|
375
|
+
{
|
376
|
+
week: week,
|
377
|
+
transaction_id: transaction["transaction_id"],
|
378
|
+
status: transaction["status"],
|
379
|
+
type: transaction["type"],
|
380
|
+
created_at: transaction["created"],
|
381
|
+
status_updated_at: transaction["status_updated"],
|
382
|
+
roster_ids: transaction["roster_ids"] || [],
|
383
|
+
adds: transaction["adds"]&.map do |player_id, roster_id|
|
384
|
+
{ player_id: player_id, roster_id: roster_id }
|
385
|
+
end || [],
|
386
|
+
drops: transaction["drops"]&.map do |player_id, roster_id|
|
387
|
+
{ player_id: player_id, roster_id: roster_id }
|
388
|
+
end || [],
|
389
|
+
waiver_bid: transaction["settings"]&.dig("waiver_bid"),
|
390
|
+
waiver_order: transaction["settings"]&.dig("seq"),
|
391
|
+
draft_picks: draft_picks,
|
392
|
+
waiver_budget: waiver_budget,
|
393
|
+
metadata: transaction_metadata.is_a?(Hash) ? transaction_metadata.transform_keys(&:to_sym) : transaction_metadata,
|
394
|
+
created_by_user_id: transaction["creator"]
|
395
|
+
}
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SleeperApi
|
4
|
+
class User
|
5
|
+
include Helpers
|
6
|
+
|
7
|
+
# User attributes from the API.
|
8
|
+
ATTRIBUTES = %w[username user_id display_name avatar email cookies created currencies data_updated deleted is_bot
|
9
|
+
metadata notifications pending phone real_name solicitable summoner_name summoner_region token
|
10
|
+
verification].freeze
|
11
|
+
|
12
|
+
attr_reader :identifier
|
13
|
+
|
14
|
+
# @param identifier [String, Integer] Username or user ID
|
15
|
+
# @param client [SleeperApi::Client] HTTP client instance
|
16
|
+
# @raise [ArgumentError] If identifier is empty
|
17
|
+
# @raise [SleeperApi::Error] If user data is invalid
|
18
|
+
def initialize(identifier, client)
|
19
|
+
raise ArgumentError, "identifier must be a non-empty string" if identifier.to_s.empty?
|
20
|
+
|
21
|
+
@identifier = identifier
|
22
|
+
@client = client
|
23
|
+
@user_data = nil
|
24
|
+
@leagues = nil
|
25
|
+
@drafts = nil
|
26
|
+
|
27
|
+
fetch_user_data
|
28
|
+
@user_id = @user_data["user_id"] || raise(SleeperApi::Error, "Invalid user data: user_id not found")
|
29
|
+
end
|
30
|
+
|
31
|
+
# Dynamically define attribute readers for user data.
|
32
|
+
ATTRIBUTES.each do |attr|
|
33
|
+
define_method(attr) do
|
34
|
+
@user_data[attr]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Generate avatar URL from ID.
|
39
|
+
#
|
40
|
+
# @return [String, nil] Full avatar URL or nil if no avatar
|
41
|
+
def avatar_url
|
42
|
+
avatar ? "https://sleepercdn.com/avatars/#{avatar}" : nil
|
43
|
+
end
|
44
|
+
|
45
|
+
# Get all leagues for a specific season.
|
46
|
+
#
|
47
|
+
# @param season [Integer] Season year (default: current year)
|
48
|
+
# @return [Array<Hash>] Formatted league objects
|
49
|
+
# @raise [ArgumentError] If season is not an integer
|
50
|
+
def leagues(season = Time.now.year)
|
51
|
+
raise ArgumentError, "season must be a valid year" unless season.is_a?(Integer)
|
52
|
+
|
53
|
+
fetch_leagues(season) unless @leagues
|
54
|
+
end
|
55
|
+
|
56
|
+
# Get all rosters for this user across all leagues in a season.
|
57
|
+
#
|
58
|
+
# @param season [Integer] Season year (default: current year)
|
59
|
+
# @return [Array<Hash>] Formatted roster objects from all leagues
|
60
|
+
# @raise [ArgumentError] If season is not an integer
|
61
|
+
def rosters(season = Time.now.year)
|
62
|
+
raise ArgumentError, "season must be a valid year" unless season.is_a?(Integer)
|
63
|
+
|
64
|
+
fetch_leagues(season) unless @leagues
|
65
|
+
(@leagues || []).flat_map do |league|
|
66
|
+
league_instance = League.new(league[:league_id], @client, no_data: true)
|
67
|
+
league_instance.rosters(user_id: @user_id)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Get all drafts for a specific season with enhanced metadata.
|
72
|
+
#
|
73
|
+
# @param season [Integer] Season year (default: current year)
|
74
|
+
# @return [Array<Hash>] Formatted draft objects with league info
|
75
|
+
# @raise [ArgumentError] If season is not an integer
|
76
|
+
def drafts(season = Time.now.year)
|
77
|
+
raise ArgumentError, "season must be a valid year" unless season.is_a?(Integer)
|
78
|
+
|
79
|
+
fetch_drafts(season) unless @drafts
|
80
|
+
format_drafts
|
81
|
+
end
|
82
|
+
|
83
|
+
# Get a summary of user's league participation.
|
84
|
+
#
|
85
|
+
# @param season [Integer] Season year (default: current year)
|
86
|
+
# @return [Hash] Summary statistics
|
87
|
+
def summary(season = Time.now.year)
|
88
|
+
leagues_data = leagues(season)
|
89
|
+
rosters_data = rosters(season)
|
90
|
+
|
91
|
+
{
|
92
|
+
season: season,
|
93
|
+
total_leagues: leagues_data.length,
|
94
|
+
winning_record: rosters_data.select { |r| r[:wins] > r[:losses] },
|
95
|
+
total_wins: rosters_data.sum { |r| r[:wins] },
|
96
|
+
total_losses: rosters_data.sum { |r| r[:losses] },
|
97
|
+
total_ties: rosters_data.sum { |r| r[:ties] },
|
98
|
+
avg_record: if rosters_data.any?
|
99
|
+
{
|
100
|
+
wins: rosters_data.sum { |r| r[:wins].to_f } / rosters_data.length,
|
101
|
+
losses: rosters_data.sum { |r| r[:losses].to_f } / rosters_data.length,
|
102
|
+
ties: rosters_data.sum { |r| r[:ties].to_f } / rosters_data.length
|
103
|
+
}
|
104
|
+
end,
|
105
|
+
best_team: rosters_data.max_by { |r| r[:wins].to_i - r[:losses].to_i },
|
106
|
+
worst_team: rosters_data.min_by { |r| r[:wins].to_i - r[:losses].to_i }
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def fetch_user_data
|
113
|
+
@user_data ||= @client.get_user(@identifier)
|
114
|
+
end
|
115
|
+
|
116
|
+
def fetch_leagues(season)
|
117
|
+
@leagues ||= (@client.get_user_leagues(@user_id, season: season) || []).map do |league|
|
118
|
+
deep_symbolize_keys(league)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def fetch_drafts(season)
|
123
|
+
@drafts ||= (@client.get_user_drafts(@user_id, season: season) || []).map { |draft| deep_symbolize_keys(draft) }
|
124
|
+
end
|
125
|
+
|
126
|
+
def format_drafts
|
127
|
+
(@drafts || []).map do |draft|
|
128
|
+
draft.merge(
|
129
|
+
league_name: draft.dig(:metadata, :name),
|
130
|
+
scoring_type: draft.dig(:metadata, :scoring_type),
|
131
|
+
total_teams: draft.dig(:settings, :teams),
|
132
|
+
rounds: draft.dig(:settings, :rounds)
|
133
|
+
)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
data/lib/sleeper_api.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require_relative "sleeper_api/version"
|
2
|
+
require_relative "sleeper_api/helpers"
|
3
|
+
require_relative "sleeper_api/client"
|
4
|
+
require_relative "sleeper_api/league"
|
5
|
+
require_relative "sleeper_api/user"
|
6
|
+
require_relative "sleeper_api/draft"
|
7
|
+
|
8
|
+
module SleeperApi
|
9
|
+
class Error < StandardError; end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_writer :configuration
|
13
|
+
end
|
14
|
+
|
15
|
+
# Configure the gem globally.
|
16
|
+
#
|
17
|
+
# @yieldparam [SleeperApi::Configuration] config
|
18
|
+
# @example
|
19
|
+
# SleeperApi.configure do |config|
|
20
|
+
# config.timeout = 20
|
21
|
+
# config.retries = 5
|
22
|
+
# config.logger = Logger.new('sleeper.log')
|
23
|
+
# end
|
24
|
+
def self.configure
|
25
|
+
self.configuration ||= Configuration.new
|
26
|
+
yield(configuration)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Get the configured client instance.
|
30
|
+
#
|
31
|
+
# @return [SleeperApi::Client]
|
32
|
+
def self.client
|
33
|
+
@client ||= Client.new(configuration)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Get the global configuration.
|
37
|
+
#
|
38
|
+
# @return [SleeperApi::Configuration]
|
39
|
+
def self.configuration
|
40
|
+
@configuration ||= Configuration.new
|
41
|
+
end
|
42
|
+
|
43
|
+
# Configuration for SleeperApi client behavior.
|
44
|
+
#
|
45
|
+
# @attr timeout [Integer] HTTP request timeout (10-60 seconds)
|
46
|
+
# @attr retries [Integer] Maximum retry attempts (0-5)
|
47
|
+
# @attr logger [Logger, nil] Logger for debugging requests
|
48
|
+
class Configuration
|
49
|
+
attr_accessor :logger
|
50
|
+
attr_reader :timeout, :retries
|
51
|
+
|
52
|
+
MIN_TIMEOUT = 10
|
53
|
+
MAX_TIMEOUT = 60
|
54
|
+
MIN_RETRIES = 0
|
55
|
+
MAX_RETRIES = 5
|
56
|
+
|
57
|
+
def initialize
|
58
|
+
@timeout = 30
|
59
|
+
@retries = 3
|
60
|
+
@logger = nil
|
61
|
+
end
|
62
|
+
|
63
|
+
# Set HTTP timeout with validation.
|
64
|
+
#
|
65
|
+
# @param value [Integer] Timeout in seconds
|
66
|
+
# @raise [SleeperApi::Error] If outside valid range
|
67
|
+
def timeout=(value)
|
68
|
+
unless value.between?(MIN_TIMEOUT, MAX_TIMEOUT)
|
69
|
+
raise SleeperApi::Error, "Timeout must be between #{MIN_TIMEOUT} and #{MAX_TIMEOUT} seconds"
|
70
|
+
end
|
71
|
+
|
72
|
+
@timeout = value
|
73
|
+
end
|
74
|
+
|
75
|
+
# Set retry count with validation.
|
76
|
+
#
|
77
|
+
# @param value [Integer] Number of retries
|
78
|
+
# @raise [SleeperApi::Error] If outside valid range
|
79
|
+
def retries=(value)
|
80
|
+
unless value.between?(MIN_RETRIES, MAX_RETRIES)
|
81
|
+
raise SleeperApi::Error, "Retries must be between #{MIN_RETRIES} and #{MAX_RETRIES}"
|
82
|
+
end
|
83
|
+
|
84
|
+
@retries = value
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/sig/sleeper_api.rbs
ADDED
data/sleeper_api.gemspec
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/sleeper_api/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "sleeper_api"
|
7
|
+
spec.version = SleeperApi::VERSION
|
8
|
+
spec.authors = ["Eruity1"]
|
9
|
+
spec.email = ["ethanruity@icloud.com"]
|
10
|
+
|
11
|
+
spec.summary = "Comprehensive Ruby wrapper for the Sleeper's fantasy sports API"
|
12
|
+
spec.description = "A comprehensive Ruby gem for interacting with Sleeper's fantasy football API. Features include league management, user profiles, draft data, matchups, transactions, player data with caching, and comprehensive error handling. Built with performance and reliability in mind for production applications."
|
13
|
+
spec.homepage = "https://github.com/eruity1/sleeper_api"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = ">= 2.6.0"
|
16
|
+
|
17
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
18
|
+
|
19
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
20
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
21
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
22
|
+
spec.metadata["documentation_uri"] = "#{spec.homepage}/blob/main/README.md"
|
23
|
+
|
24
|
+
|
25
|
+
# Specify which files should be added to the gem when it is released.
|
26
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
27
|
+
all_files = `git ls-files`.split("\n")
|
28
|
+
test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
29
|
+
ci_files = `git ls-files -- .github/*`.split("\n")
|
30
|
+
|
31
|
+
spec.files = all_files - test_files
|
32
|
+
spec.files += ["sig/sleeper_api.rbs"]
|
33
|
+
|
34
|
+
spec.files -= [
|
35
|
+
"players_cache.json",
|
36
|
+
"coverage/",
|
37
|
+
".rspec_status",
|
38
|
+
].select { |file| File.exist?(file) }
|
39
|
+
|
40
|
+
spec.bindir = "exe"
|
41
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
42
|
+
spec.require_paths = ["lib"]
|
43
|
+
|
44
|
+
spec.add_dependency "httparty", "~> 0.21"
|
45
|
+
|
46
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
47
|
+
spec.add_development_dependency "rspec", "~> 3.13"
|
48
|
+
spec.add_development_dependency "rubocop", "~> 1.80"
|
49
|
+
spec.add_development_dependency "rubocop-performance", "~> 1.26"
|
50
|
+
spec.add_development_dependency "rubocop-rspec", "~> 3.7"
|
51
|
+
spec.add_development_dependency "simplecov", "~> 0.21"
|
52
|
+
spec.add_development_dependency "webmock", "~> 3.14"
|
53
|
+
end
|