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.
@@ -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