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,277 @@
|
|
1
|
+
require "httparty"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
module SleeperApi
|
5
|
+
# HTTP client for Sleeper API requests.
|
6
|
+
#
|
7
|
+
# Handles all low-level HTTP calls, caching, retries, and error handling.
|
8
|
+
# Use via {SleeperApi.client} or create directly.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# client = SleeperApi::Client.new(config)
|
12
|
+
# league = client.league("123456")
|
13
|
+
# user = client.user("username")
|
14
|
+
class Client
|
15
|
+
include Helpers
|
16
|
+
include HTTParty
|
17
|
+
|
18
|
+
base_uri "https://api.sleeper.app/v1"
|
19
|
+
|
20
|
+
# @param config [SleeperApi::Configuration] Client configuration
|
21
|
+
def initialize(config)
|
22
|
+
@config = config
|
23
|
+
@players_cache = nil
|
24
|
+
@cache_timestamp = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
# Create a new {SleeperApi::League} instance.
|
28
|
+
#
|
29
|
+
# @param league_id [String] League identifier
|
30
|
+
# @return [SleeperApi::League]
|
31
|
+
# @see https://docs.sleeper.com/#leagues
|
32
|
+
def league(league_id)
|
33
|
+
League.new(league_id, self)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Create a new {SleeperApi::User} instance.
|
37
|
+
#
|
38
|
+
# @param identifier [String] Username or user ID
|
39
|
+
# @return [SleeperApi::User]
|
40
|
+
# @see https://docs.sleeper.com/#user
|
41
|
+
def user(identifier)
|
42
|
+
User.new(identifier, self)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Create a new {SleeperApi::Draft} instance.
|
46
|
+
#
|
47
|
+
# @param draft_id [String] Draft identifier
|
48
|
+
# @return [SleeperApi::Draft]
|
49
|
+
# @see https://docs.sleeper.com/#drafts
|
50
|
+
def draft(draft_id)
|
51
|
+
Draft.new(draft_id, self)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Fetch user data by identifier.
|
55
|
+
#
|
56
|
+
# @param identifier [String] Username or user ID
|
57
|
+
# @return [Hash] Raw user data
|
58
|
+
# @see https://docs.sleeper.com/#user
|
59
|
+
def get_user(identifier)
|
60
|
+
make_request("/user/#{identifier}")
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get leagues for a user in a specific season.
|
64
|
+
#
|
65
|
+
# @param user_id [String] User ID
|
66
|
+
# @param sport [String] Sport code (default: "nfl")
|
67
|
+
# @param season [Integer] Season year (default: current year)
|
68
|
+
# @return [Array<Hash>] League data
|
69
|
+
# @see https://docs.sleeper.com/#get-all-leagues-for-user
|
70
|
+
def get_user_leagues(user_id, sport: "nfl", season: Time.now.year)
|
71
|
+
make_request("/user/#{user_id}/leagues/#{sport}/#{season}")
|
72
|
+
end
|
73
|
+
|
74
|
+
# Get drafts for a user in a specific season.
|
75
|
+
#
|
76
|
+
# @param user_id [String] User ID
|
77
|
+
# @param sport [String] Sport code (default: "nfl")
|
78
|
+
# @param season [Integer] Season year (default: current year)
|
79
|
+
# @return [Array<Hash>] Draft data
|
80
|
+
# @see https://docs.sleeper.com/#get-all-drafts-for-user
|
81
|
+
def get_user_drafts(user_id, sport: "nfl", season: Time.now.year)
|
82
|
+
make_request("/user/#{user_id}/drafts/#{sport}/#{season}")
|
83
|
+
end
|
84
|
+
|
85
|
+
# Fetch league details.
|
86
|
+
#
|
87
|
+
# @param league_id [String] League ID
|
88
|
+
# @return [Hash] League metadata
|
89
|
+
# @see https://docs.sleeper.com/#get-a-specific-league
|
90
|
+
def get_league(league_id)
|
91
|
+
make_request("/league/#{league_id}")
|
92
|
+
end
|
93
|
+
|
94
|
+
# Get all rosters in a league.
|
95
|
+
#
|
96
|
+
# @param league_id [String] League ID
|
97
|
+
# @return [Array<Hash>] Roster data
|
98
|
+
# @see https://docs.sleeper.com/#getting-rosters-in-a-league
|
99
|
+
def get_league_rosters(league_id)
|
100
|
+
make_request("/league/#{league_id}/rosters")
|
101
|
+
end
|
102
|
+
|
103
|
+
# Get all users in a league.
|
104
|
+
#
|
105
|
+
# @param league_id [String, Integer] League ID
|
106
|
+
# @return [Array<Hash>] User data
|
107
|
+
# @see https://docs.sleeper.com/#getting-users-in-a-league
|
108
|
+
def get_league_users(league_id)
|
109
|
+
make_request("/league/#{league_id}/users")
|
110
|
+
end
|
111
|
+
|
112
|
+
# Get matchups for a specific week.
|
113
|
+
#
|
114
|
+
# @param league_id [String, Integer] League ID
|
115
|
+
# @param week [Integer] Week number (1-17)
|
116
|
+
# @return [Array<Hash>] Matchup data
|
117
|
+
# @see https://docs.sleeper.com/#getting-matchups-in-a-league
|
118
|
+
def get_league_matchups(league_id, week)
|
119
|
+
make_request("/league/#{league_id}/matchups/#{week}")
|
120
|
+
end
|
121
|
+
|
122
|
+
# Get playoff winners bracket.
|
123
|
+
#
|
124
|
+
# @param league_id [String, Integer] League ID
|
125
|
+
# @return [Array<Hash>] Bracket matchups
|
126
|
+
# @see https://docs.sleeper.com/#getting-the-playoff-bracket
|
127
|
+
def get_playoff_bracket(league_id)
|
128
|
+
make_request("/league/#{league_id}/winners_bracket")
|
129
|
+
end
|
130
|
+
|
131
|
+
# Get toilet bowl (losers bracket).
|
132
|
+
#
|
133
|
+
# @param league_id [String, Integer] League ID
|
134
|
+
# @return [Array<Hash>] Bracket matchups
|
135
|
+
# @see https://docs.sleeper.com/#getting-the-playoff-bracket
|
136
|
+
def get_toilet_bowl(league_id)
|
137
|
+
make_request("/league/#{league_id}/losers_bracket")
|
138
|
+
end
|
139
|
+
|
140
|
+
# Get transactions for a specific week.
|
141
|
+
#
|
142
|
+
# @param league_id [String, Integer] League ID
|
143
|
+
# @param week [Integer] Week number
|
144
|
+
# @return [Array<Hash>] Transaction data
|
145
|
+
# @see https://docs.sleeper.com/#get-transactions
|
146
|
+
def get_transactions(league_id, week)
|
147
|
+
make_request("/league/#{league_id}/transactions/#{week}")
|
148
|
+
end
|
149
|
+
|
150
|
+
# Get league drafts.
|
151
|
+
#
|
152
|
+
# @param league_id [String, Integer] League ID
|
153
|
+
# @return [Array<Hash>] Draft data
|
154
|
+
# @see https://docs.sleeper.com/#get-all-drafts-for-a-league
|
155
|
+
def get_league_drafts(league_id)
|
156
|
+
make_request("/league/#{league_id}/drafts")
|
157
|
+
end
|
158
|
+
|
159
|
+
# Get traded draft picks for a league.
|
160
|
+
#
|
161
|
+
# @param league_id [String, Integer] League ID
|
162
|
+
# @return [Array<Hash>] Traded picks
|
163
|
+
# @see https://docs.sleeper.com/#get-traded-picks-in-a-draft
|
164
|
+
def get_league_traded_picks(league_id)
|
165
|
+
make_request("/league/#{league_id}/traded_picks")
|
166
|
+
end
|
167
|
+
|
168
|
+
# Fetch draft details.
|
169
|
+
#
|
170
|
+
# @param draft_id [String, Integer] Draft ID
|
171
|
+
# @return [Hash] Draft metadata
|
172
|
+
# @see https://docs.sleeper.com/#get-a-specific-draft
|
173
|
+
def get_draft(draft_id)
|
174
|
+
make_request("/draft/#{draft_id}")
|
175
|
+
end
|
176
|
+
|
177
|
+
# Get draft picks.
|
178
|
+
#
|
179
|
+
# @param draft_id [String, Integer] Draft ID
|
180
|
+
# @return [Array<Hash>] Pick data
|
181
|
+
# @see https://docs.sleeper.com/#get-all-picks-in-a-draft
|
182
|
+
def get_draft_picks(draft_id)
|
183
|
+
make_request("/draft/#{draft_id}/picks")
|
184
|
+
end
|
185
|
+
|
186
|
+
# Get traded draft picks for a draft.
|
187
|
+
#
|
188
|
+
# @param draft_id [String, Integer] Draft ID
|
189
|
+
# @return [Array<Hash>] Traded picks
|
190
|
+
# @see https://docs.sleeper.com/#get-traded-picks-in-a-draft
|
191
|
+
def get_draft_traded_picks(draft_id)
|
192
|
+
make_request("/draft/#{draft_id}/traded_picks")
|
193
|
+
end
|
194
|
+
|
195
|
+
# Get NFL state (week, season status).
|
196
|
+
#
|
197
|
+
# @param sport [String] Sport code (default: "nfl")
|
198
|
+
# @return [Hash] State data
|
199
|
+
# @see https://docs.sleeper.com/#get-nfl-state
|
200
|
+
def get_nfl_state(sport = "nfl")
|
201
|
+
nfl_state = make_request("/state/#{sport}")
|
202
|
+
nfl_state.each_with_object({}) do |(k, v), result|
|
203
|
+
key = k.is_a?(String) ? k.to_sym : k
|
204
|
+
result[key] = v
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Get trending players.
|
209
|
+
#
|
210
|
+
# @param sport [String] Sport code (default: "nfl")
|
211
|
+
# @param type [String] Trend type ("add" or "drop", default: "add")
|
212
|
+
# @param lookback_hours [Integer] Hours to look back (default: 24)
|
213
|
+
# @param limit [Integer] Max results (default: 25)
|
214
|
+
# @return [Array<Hash>] Trending players
|
215
|
+
# @see https://docs.sleeper.com/#trending-players
|
216
|
+
def trending_players(sport = "nfl", type: "add", lookback_hours: 24, limit: 25)
|
217
|
+
make_request("/players/#{sport}/trending/#{type}?lookback_hours=#{lookback_hours}&limit=#{limit}")
|
218
|
+
end
|
219
|
+
|
220
|
+
# Get all player data (cached for 24 hours).
|
221
|
+
#
|
222
|
+
# @param sport [String] Sport code (default: "nfl")
|
223
|
+
# @return [Hash{String => Hash}] Player ID to player data mapping
|
224
|
+
# @see https://docs.sleeper.com/#fetch-all-players
|
225
|
+
def get_players(sport = "nfl")
|
226
|
+
return @players_cache if @players_cache && @cache_timestamp && (Time.now - @cache_timestamp) < (3600 * 24)
|
227
|
+
|
228
|
+
response = make_request("/players/#{sport}")
|
229
|
+
@players_cache = response.parsed_response
|
230
|
+
@cache_timestamp = Time.now
|
231
|
+
|
232
|
+
@players_cache
|
233
|
+
end
|
234
|
+
|
235
|
+
# Get a specific player by ID.
|
236
|
+
#
|
237
|
+
# @param player_id [String] Player ID
|
238
|
+
# @param sport [String] Sport code (default: "nfl")
|
239
|
+
# @return [Hash, nil] Player data or nil if not found
|
240
|
+
# @see #get_players
|
241
|
+
def get_player_by_id(player_id, sport = "nfl")
|
242
|
+
get_players(sport)[player_id]
|
243
|
+
end
|
244
|
+
|
245
|
+
private
|
246
|
+
|
247
|
+
# Make an HTTP request with retry logic and logging.
|
248
|
+
#
|
249
|
+
# @param path [String] API endpoint path
|
250
|
+
# @return [HTTParty::Response]
|
251
|
+
# @raise [SleeperApi::Error] On HTTP errors or timeouts
|
252
|
+
def make_request(path)
|
253
|
+
@config.logger&.info("Making request to #{self.class.base_uri}#{path}")
|
254
|
+
retries = 0
|
255
|
+
begin
|
256
|
+
response = self.class.get(path, timeout: @config.timeout)
|
257
|
+
if response.success?
|
258
|
+
@config.logger&.info("Successful response for #{path}")
|
259
|
+
response
|
260
|
+
else
|
261
|
+
@config.logger&.error("Failed to fetch #{path}: #{response.code}")
|
262
|
+
raise SleeperApi::Error, "Failed to fetch #{path}: #{response.code}"
|
263
|
+
end
|
264
|
+
rescue Net::OpenTimeout, Net::ReadTimeout => e
|
265
|
+
retries += 1
|
266
|
+
if retries <= @config.retries
|
267
|
+
@config.logger&.warn("Retrying #{path} (attempt #{retries}/#{config.retries}) due to #{e}")
|
268
|
+
sleep(1)
|
269
|
+
retry
|
270
|
+
else
|
271
|
+
@config.logger&.error("Request timed out for #{path} after #{retries} retries")
|
272
|
+
raise SleeperApi::Error, "Request timed out after #{retries} retries"
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SleeperApi
|
4
|
+
class Draft
|
5
|
+
include Helpers
|
6
|
+
|
7
|
+
# Draft attributes from the API.
|
8
|
+
ATTRIBUTES = %w[draft_id created creators draft_order last_message_id last_message_time last_picked league_id
|
9
|
+
settings season season_type metadata slot_to_roster_id sport start_time status type].freeze
|
10
|
+
|
11
|
+
attr_reader :draft_id
|
12
|
+
|
13
|
+
# @param draft_id [String] Draft identifier
|
14
|
+
# @param client [SleeperApi::Client] HTTP client instance
|
15
|
+
# @raise [ArgumentError] If draft_id is empty
|
16
|
+
def initialize(draft_id, client)
|
17
|
+
raise ArgumentError, "draft_id must be a non-empty string" if draft_id.to_s.empty?
|
18
|
+
|
19
|
+
@draft_id = draft_id
|
20
|
+
@client = client
|
21
|
+
@draft_data = nil
|
22
|
+
@picks = nil
|
23
|
+
@traded_picks = nil
|
24
|
+
|
25
|
+
fetch_draft_data
|
26
|
+
end
|
27
|
+
|
28
|
+
# Dynamically define attribute readers for draft data.
|
29
|
+
ATTRIBUTES.each do |attr|
|
30
|
+
define_method(attr) do
|
31
|
+
@draft_data[attr]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Get all draft picks with optional filtering.
|
36
|
+
#
|
37
|
+
# @param round [Integer, nil] Filter by round number
|
38
|
+
# @param roster_id [Integer, nil] Filter by team/roster
|
39
|
+
# @return [Array<Hash>] Formatted pick objects
|
40
|
+
def picks(round: nil, roster_id: nil)
|
41
|
+
fetch_picks unless @picks
|
42
|
+
picks = @picks
|
43
|
+
picks = picks.select { |pick| pick[:round] == round } if round
|
44
|
+
picks = picks.select { |pick| pick[:roster_id] == roster_id } if roster_id
|
45
|
+
picks
|
46
|
+
end
|
47
|
+
|
48
|
+
# Get traded draft picks with optional filtering.
|
49
|
+
#
|
50
|
+
# @param original_owner_id [Integer, nil] Filter by original team
|
51
|
+
# @param previous_owner_id [Integer, nil] Filter by previous owner
|
52
|
+
# @param current_owner_id [Integer, nil] Filter by current owner
|
53
|
+
# @return [Array<Hash>] Formatted traded pick objects
|
54
|
+
def traded_picks(original_owner_id: nil, previous_owner_id: nil, current_owner_id: nil)
|
55
|
+
fetch_traded_picks unless @traded_picks
|
56
|
+
traded_picks = @traded_picks
|
57
|
+
traded_picks = traded_picks.select { |pick| pick[:roster_id] == original_owner_id } if original_owner_id
|
58
|
+
traded_picks = traded_picks.select { |pick| pick[:previous_owner_id] == previous_owner_id } if previous_owner_id
|
59
|
+
traded_picks = traded_picks.select { |pick| pick[:owner_id] == current_owner_id } if current_owner_id
|
60
|
+
traded_picks
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get the associated league for this draft.
|
64
|
+
#
|
65
|
+
# @return [SleeperApi::League] League object
|
66
|
+
def league
|
67
|
+
@client.league(league_id)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Find who picked a specific player.
|
71
|
+
#
|
72
|
+
# @param player_id [String, Integer] Player ID
|
73
|
+
# @return [Hash] User data for the picker, or nil if player not drafted
|
74
|
+
def picked_by(player_id)
|
75
|
+
fetch_picks unless @picks
|
76
|
+
pick = @picks.find { |pick| pick[:player_id] == player_id }
|
77
|
+
return nil unless pick
|
78
|
+
|
79
|
+
league_users = league.users
|
80
|
+
league_users.find { |user| user[:user_id] == pick[:picked_by] }
|
81
|
+
end
|
82
|
+
|
83
|
+
# Calculate the next pick for a live draft.
|
84
|
+
#
|
85
|
+
# @return [Hash, nil] Next pick details or nil if draft is complete
|
86
|
+
def next_pick
|
87
|
+
return nil if status == "complete"
|
88
|
+
|
89
|
+
teams_count = settings["teams"]
|
90
|
+
last_pick_no = picks.map { |pick| pick[:pick_no] }.max || 0
|
91
|
+
{
|
92
|
+
pick_no: last_pick_no + 1,
|
93
|
+
round: ((last_pick_no / (teams_count || 12)) + 1).to_i,
|
94
|
+
draft_slot: ((last_pick_no % (teams_count || 12)) + 1)
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
# Get a high-level draft summary.
|
99
|
+
#
|
100
|
+
# @return [Hash] Summary with top picks and stats
|
101
|
+
def summary
|
102
|
+
fetch_picks unless @picks
|
103
|
+
{
|
104
|
+
draft_id: draft_id,
|
105
|
+
type: type,
|
106
|
+
season: season,
|
107
|
+
total_picks: picks.length,
|
108
|
+
total_rounds: settings["rounds"] || 0,
|
109
|
+
top_picks: picks.select { |pick| pick[:round] == 1 }.map do |pick|
|
110
|
+
{
|
111
|
+
player_id: pick[:player_id],
|
112
|
+
player_name: "#{pick[:metadata][:first_name]} #{pick[:metadata][:last_name]}",
|
113
|
+
position: pick[:metadata][:position],
|
114
|
+
picked_by: pick[:picked_by]
|
115
|
+
}
|
116
|
+
end
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
# Get picks grouped by round.
|
121
|
+
#
|
122
|
+
# @return [Hash{Integer => Array<Hash>}] Round number to picks mapping
|
123
|
+
def rounds
|
124
|
+
fetch_picks unless @picks
|
125
|
+
@picks.group_by { |pick| pick[:round] }
|
126
|
+
end
|
127
|
+
|
128
|
+
# Get picks grouped by team/roster.
|
129
|
+
#
|
130
|
+
# @return [Hash{Integer => Array<Hash>}] Roster ID to picks mapping
|
131
|
+
def team_picks
|
132
|
+
fetch_picks unless @picks
|
133
|
+
@picks.group_by { |pick| pick[:roster_id] }
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
def fetch_draft_data
|
139
|
+
@draft_data ||= @client.get_draft(@draft_id)
|
140
|
+
end
|
141
|
+
|
142
|
+
def fetch_picks
|
143
|
+
@picks ||= (@client.get_draft_picks(@draft_id) || []).map { |pick| deep_symbolize_keys(pick) }
|
144
|
+
end
|
145
|
+
|
146
|
+
def fetch_traded_picks
|
147
|
+
@traded_picks ||= (@client.get_draft_traded_picks(@draft_id) || []).map { |pick| deep_symbolize_keys(pick) }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module SleeperApi
|
2
|
+
module Helpers
|
3
|
+
def deep_symbolize_keys(obj)
|
4
|
+
case obj
|
5
|
+
when Hash
|
6
|
+
obj.each_with_object({}) do |(k, v), result|
|
7
|
+
key = k.is_a?(String) ? k.to_sym : k
|
8
|
+
result[key] = deep_symbolize_keys(v)
|
9
|
+
end
|
10
|
+
when Array
|
11
|
+
obj.map { |el| deep_symbolize_keys(el) }
|
12
|
+
else
|
13
|
+
obj
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def player_details(player_id)
|
18
|
+
player = @client.get_player_by_id(player_id)
|
19
|
+
return nil unless player
|
20
|
+
|
21
|
+
deep_symbolize_keys(player)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|