sleeper_api 1.5.1 → 1.6.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 +4 -4
- data/CHANGELOG.md +40 -0
- data/CLAUDE.md +2 -0
- data/README.md +7 -7
- data/lib/sleeper_api/client.rb +38 -30
- data/lib/sleeper_api/draft.rb +9 -2
- data/lib/sleeper_api/league.rb +30 -30
- data/lib/sleeper_api/user.rb +14 -13
- data/lib/sleeper_api/version.rb +1 -1
- data/lib/sleeper_api.rb +4 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0375caa5f944f2f083cdc8cec7672f44fc9219969e8e733bde441c377e16ede4
|
|
4
|
+
data.tar.gz: a1d54df90f2ff3ba533d11756993513eca6bae884f1a1fc0e57e51d9be84fa12
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ba5338528abf3a7c924dd9fdfd7dbf6440a1ac75debc3426cca4d580520a65ce7986075c2d8a6709305a5e57720c08a9b0749710e83b46381628b2991bab702b
|
|
7
|
+
data.tar.gz: 84ff6c0503398ca629009b1976dfd2ead6a9594d63eabdf97fb0056ed29d3b4965e8f2a4d17091c1e6d02560c1b28f469afeffeacaccc36acb7b394c3415dc2a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,43 @@
|
|
|
1
|
+
## [1.6.0] - 2026-09-28
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- **`SleeperApi::UserNotFound`, a subclass of `SleeperApi::Error`.** Sleeper
|
|
6
|
+
answers an unknown user with `200` and a literal `null` body — checked live
|
|
7
|
+
2026-09-28 — so there was no error at all: `get_user` returned a response
|
|
8
|
+
whose `parsed_response` was nil. It now raises `UserNotFound`, and so does
|
|
9
|
+
`User.new` for a payload with no `user_id`. A caller can tell "no such user"
|
|
10
|
+
(permanent) from an outage without matching message strings; rescuing
|
|
11
|
+
`SleeperApi::Error` alone still sees both.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- **`League#matchups_by_week` returns rosters on a bye** instead of dropping
|
|
16
|
+
them. Each comes back unpaired as `{ matchup_id: nil, rosters: [roster] }`,
|
|
17
|
+
never grouped with another bye. A bye used to look exactly like a missing
|
|
18
|
+
team.
|
|
19
|
+
- **`get_user` raises for an unknown user** rather than returning a nil-bodied
|
|
20
|
+
response (see Added).
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- **`User#leagues`, `#rosters` and `#drafts` answer for the season asked.** The
|
|
25
|
+
memo was not keyed by season, so `leagues(2025)` then `leagues(2024)` on one
|
|
26
|
+
instance returned 2025's leagues — silently wrong rows for a multi-season
|
|
27
|
+
caller.
|
|
28
|
+
- **Every value interpolated into a request path is escaped.** `get_user("a b")`
|
|
29
|
+
raised `URI::InvalidURIError`, which is not a `SleeperApi::Error`, and
|
|
30
|
+
`get_user("../../league/123")` requested `/league/123`. Every `Client` path is
|
|
31
|
+
now built from escaped segments.
|
|
32
|
+
- **Week 18 is reachable.** `matchups_by_week` and `transactions` accepted
|
|
33
|
+
weeks 1–17 and Sleeper's regular season runs to 18; `#matchups` now fetches
|
|
34
|
+
all eighteen.
|
|
35
|
+
- **`Draft#summary` no longer raises on a pick with no metadata**, and a pick
|
|
36
|
+
with one name part is not padded with a space.
|
|
37
|
+
- **The README** named `get_league_playoff_bracket` / `get_league_toilet_bowl`
|
|
38
|
+
(the methods are `get_playoff_bracket` / `get_toilet_bowl`) and described a
|
|
39
|
+
disk cache that does not exist.
|
|
40
|
+
|
|
1
41
|
## [1.5.1] - 2026-09-25
|
|
2
42
|
|
|
3
43
|
### Fixed
|
data/CLAUDE.md
CHANGED
|
@@ -30,6 +30,8 @@ Four layers, with a deliberate split between HTTP and modeling:
|
|
|
30
30
|
|
|
31
31
|
Every call funnels through the private `make_request`, which handles retry-on-timeout, logging, and converts non-2xx into `SleeperApi::Error` — and, since 1.5.1, **every connection failure too** (`CONNECTION_ERRORS`: `SocketError`, `SystemCallError`, `OpenSSL::SSL::SSLError`, `EOFError`), unretried, as `"Could not reach <path>: …"`. A consumer can rescue `SleeperApi::Error` alone and see every way a call fails. **That error quotes the path**, so the path prefix is part of a public string — v1.2.0 changed it from `"Failed to fetch /user/x: 404"` to `"Failed to fetch /v1/user/x: 404"`. `Client` also owns the 24-hour in-memory player cache.
|
|
32
32
|
|
|
33
|
+
⚠️ **Never interpolate a value into a request path; build it with the private `path(*segments)`**, which escapes each segment. An unescaped one raised `URI::InvalidURIError` — not a `SleeperApi::Error`, so it escaped every rescue — or walked out of the endpoint: `../../league/123` as a username requested `/league/123` (G.6, v1.6.0). An unknown user is `200` with a `null` body, which `get_user` turns into `SleeperApi::UserNotFound`.
|
|
34
|
+
|
|
33
35
|
⚠️ **There are two Sleeper hosts and v1.4.0 added the second.** `WEB_HOST`
|
|
34
36
|
(`https://api.sleeper.com`) is the one the web app uses, and it serves
|
|
35
37
|
**richer rows from same-looking paths**: `#stats_with_context` returns the
|
data/README.md
CHANGED
|
@@ -339,8 +339,8 @@ season_stats = client.stats(2025)
|
|
|
339
339
|
real = projected.parsed_response.select { |_id, row| row.key?("pts_ppr") }
|
|
340
340
|
|
|
341
341
|
# Get playoff brackets
|
|
342
|
-
winners_bracket = client.
|
|
343
|
-
losers_bracket = client.
|
|
342
|
+
winners_bracket = client.get_playoff_bracket("league_id")
|
|
343
|
+
losers_bracket = client.get_toilet_bowl("league_id")
|
|
344
344
|
|
|
345
345
|
# Get league drafts
|
|
346
346
|
league_drafts = client.get_league_drafts("league_id")
|
|
@@ -366,7 +366,9 @@ end
|
|
|
366
366
|
|
|
367
367
|
### Error Types
|
|
368
368
|
|
|
369
|
-
- SleeperApi::Error - API-related errors (404, 500, timeouts,
|
|
369
|
+
- SleeperApi::Error - API-related errors (404, 500, timeouts, an unreachable host)
|
|
370
|
+
|
|
371
|
+
- SleeperApi::UserNotFound - a subclass of SleeperApi::Error: no Sleeper user has that identifier. Permanent, so rescue it before SleeperApi::Error if you retry the rest
|
|
370
372
|
|
|
371
373
|
- ArgumentError - Invalid parameters passed to methods
|
|
372
374
|
|
|
@@ -374,12 +376,10 @@ end
|
|
|
374
376
|
|
|
375
377
|
### Caching
|
|
376
378
|
|
|
377
|
-
- Player data is cached for 24 hours
|
|
379
|
+
- Player data is cached in memory for 24 hours, on the client instance — a new client downloads it again
|
|
378
380
|
|
|
379
381
|
- League/User/Draft data is cached per instance
|
|
380
382
|
|
|
381
|
-
- Cache files are stored in the current working directory
|
|
382
|
-
|
|
383
383
|
### Rate Limiting
|
|
384
384
|
|
|
385
385
|
- Be mindful of Sleeper's rate limits: stay under 1000 API calls per minute
|
|
@@ -390,7 +390,7 @@ end
|
|
|
390
390
|
|
|
391
391
|
### Memory Usage
|
|
392
392
|
|
|
393
|
-
-
|
|
393
|
+
- The all-players payload is several megabytes and held in memory; never fan it out
|
|
394
394
|
|
|
395
395
|
- League rosters and matchups are fetched lazily
|
|
396
396
|
|
data/lib/sleeper_api/client.rb
CHANGED
|
@@ -76,11 +76,18 @@ module SleeperApi
|
|
|
76
76
|
|
|
77
77
|
# Fetch user data by identifier.
|
|
78
78
|
#
|
|
79
|
+
# Sleeper answers an unknown user with 200 and a literal `null` body, not
|
|
80
|
+
# a 404, so that is turned into {SleeperApi::UserNotFound} here.
|
|
81
|
+
#
|
|
79
82
|
# @param identifier [String] Username or user ID
|
|
80
83
|
# @return [Hash] Raw user data
|
|
84
|
+
# @raise [SleeperApi::UserNotFound] If no user has that identifier
|
|
81
85
|
# @see https://docs.sleeper.com/#user
|
|
82
86
|
def get_user(identifier)
|
|
83
|
-
make_request("
|
|
87
|
+
response = make_request(path("v1", "user", identifier))
|
|
88
|
+
raise UserNotFound, "No Sleeper user #{identifier.to_s.inspect}" if response.parsed_response.nil?
|
|
89
|
+
|
|
90
|
+
response
|
|
84
91
|
end
|
|
85
92
|
|
|
86
93
|
# Get leagues for a user in a specific season.
|
|
@@ -91,7 +98,7 @@ module SleeperApi
|
|
|
91
98
|
# @return [Array<Hash>] League data
|
|
92
99
|
# @see https://docs.sleeper.com/#get-all-leagues-for-user
|
|
93
100
|
def get_user_leagues(user_id, sport: "nfl", season: Time.now.year)
|
|
94
|
-
make_request("
|
|
101
|
+
make_request(path("v1", "user", user_id, "leagues", sport, season))
|
|
95
102
|
end
|
|
96
103
|
|
|
97
104
|
# Get drafts for a user in a specific season.
|
|
@@ -102,7 +109,7 @@ module SleeperApi
|
|
|
102
109
|
# @return [Array<Hash>] Draft data
|
|
103
110
|
# @see https://docs.sleeper.com/#get-all-drafts-for-user
|
|
104
111
|
def get_user_drafts(user_id, sport: "nfl", season: Time.now.year)
|
|
105
|
-
make_request("
|
|
112
|
+
make_request(path("v1", "user", user_id, "drafts", sport, season))
|
|
106
113
|
end
|
|
107
114
|
|
|
108
115
|
# Fetch league details.
|
|
@@ -111,7 +118,7 @@ module SleeperApi
|
|
|
111
118
|
# @return [Hash] League metadata
|
|
112
119
|
# @see https://docs.sleeper.com/#get-a-specific-league
|
|
113
120
|
def get_league(league_id)
|
|
114
|
-
make_request("
|
|
121
|
+
make_request(path("v1", "league", league_id))
|
|
115
122
|
end
|
|
116
123
|
|
|
117
124
|
# Get all rosters in a league.
|
|
@@ -120,7 +127,7 @@ module SleeperApi
|
|
|
120
127
|
# @return [Array<Hash>] Roster data
|
|
121
128
|
# @see https://docs.sleeper.com/#getting-rosters-in-a-league
|
|
122
129
|
def get_league_rosters(league_id)
|
|
123
|
-
make_request("
|
|
130
|
+
make_request(path("v1", "league", league_id, "rosters"))
|
|
124
131
|
end
|
|
125
132
|
|
|
126
133
|
# Get all users in a league.
|
|
@@ -129,7 +136,7 @@ module SleeperApi
|
|
|
129
136
|
# @return [Array<Hash>] User data
|
|
130
137
|
# @see https://docs.sleeper.com/#getting-users-in-a-league
|
|
131
138
|
def get_league_users(league_id)
|
|
132
|
-
make_request("
|
|
139
|
+
make_request(path("v1", "league", league_id, "users"))
|
|
133
140
|
end
|
|
134
141
|
|
|
135
142
|
# Get matchups for a specific week.
|
|
@@ -139,7 +146,7 @@ module SleeperApi
|
|
|
139
146
|
# @return [Array<Hash>] Matchup data
|
|
140
147
|
# @see https://docs.sleeper.com/#getting-matchups-in-a-league
|
|
141
148
|
def get_league_matchups(league_id, week)
|
|
142
|
-
make_request("
|
|
149
|
+
make_request(path("v1", "league", league_id, "matchups", week))
|
|
143
150
|
end
|
|
144
151
|
|
|
145
152
|
# Get playoff winners bracket.
|
|
@@ -148,7 +155,7 @@ module SleeperApi
|
|
|
148
155
|
# @return [Array<Hash>] Bracket matchups
|
|
149
156
|
# @see https://docs.sleeper.com/#getting-the-playoff-bracket
|
|
150
157
|
def get_playoff_bracket(league_id)
|
|
151
|
-
make_request("
|
|
158
|
+
make_request(path("v1", "league", league_id, "winners_bracket"))
|
|
152
159
|
end
|
|
153
160
|
|
|
154
161
|
# Get toilet bowl (losers bracket).
|
|
@@ -157,7 +164,7 @@ module SleeperApi
|
|
|
157
164
|
# @return [Array<Hash>] Bracket matchups
|
|
158
165
|
# @see https://docs.sleeper.com/#getting-the-playoff-bracket
|
|
159
166
|
def get_toilet_bowl(league_id)
|
|
160
|
-
make_request("
|
|
167
|
+
make_request(path("v1", "league", league_id, "losers_bracket"))
|
|
161
168
|
end
|
|
162
169
|
|
|
163
170
|
# Get transactions for a specific week.
|
|
@@ -167,7 +174,7 @@ module SleeperApi
|
|
|
167
174
|
# @return [Array<Hash>] Transaction data
|
|
168
175
|
# @see https://docs.sleeper.com/#get-transactions
|
|
169
176
|
def get_transactions(league_id, week)
|
|
170
|
-
make_request("
|
|
177
|
+
make_request(path("v1", "league", league_id, "transactions", week))
|
|
171
178
|
end
|
|
172
179
|
|
|
173
180
|
# Get league drafts.
|
|
@@ -176,7 +183,7 @@ module SleeperApi
|
|
|
176
183
|
# @return [Array<Hash>] Draft data
|
|
177
184
|
# @see https://docs.sleeper.com/#get-all-drafts-for-a-league
|
|
178
185
|
def get_league_drafts(league_id)
|
|
179
|
-
make_request("
|
|
186
|
+
make_request(path("v1", "league", league_id, "drafts"))
|
|
180
187
|
end
|
|
181
188
|
|
|
182
189
|
# Get traded draft picks for a league.
|
|
@@ -185,7 +192,7 @@ module SleeperApi
|
|
|
185
192
|
# @return [Array<Hash>] Traded picks
|
|
186
193
|
# @see https://docs.sleeper.com/#get-traded-picks-in-a-draft
|
|
187
194
|
def get_league_traded_picks(league_id)
|
|
188
|
-
make_request("
|
|
195
|
+
make_request(path("v1", "league", league_id, "traded_picks"))
|
|
189
196
|
end
|
|
190
197
|
|
|
191
198
|
# Fetch draft details.
|
|
@@ -194,7 +201,7 @@ module SleeperApi
|
|
|
194
201
|
# @return [Hash] Draft metadata
|
|
195
202
|
# @see https://docs.sleeper.com/#get-a-specific-draft
|
|
196
203
|
def get_draft(draft_id)
|
|
197
|
-
make_request("
|
|
204
|
+
make_request(path("v1", "draft", draft_id))
|
|
198
205
|
end
|
|
199
206
|
|
|
200
207
|
# Get draft picks.
|
|
@@ -203,7 +210,7 @@ module SleeperApi
|
|
|
203
210
|
# @return [Array<Hash>] Pick data
|
|
204
211
|
# @see https://docs.sleeper.com/#get-all-picks-in-a-draft
|
|
205
212
|
def get_draft_picks(draft_id)
|
|
206
|
-
make_request("
|
|
213
|
+
make_request(path("v1", "draft", draft_id, "picks"))
|
|
207
214
|
end
|
|
208
215
|
|
|
209
216
|
# Get traded draft picks for a draft.
|
|
@@ -212,7 +219,7 @@ module SleeperApi
|
|
|
212
219
|
# @return [Array<Hash>] Traded picks
|
|
213
220
|
# @see https://docs.sleeper.com/#get-traded-picks-in-a-draft
|
|
214
221
|
def get_draft_traded_picks(draft_id)
|
|
215
|
-
make_request("
|
|
222
|
+
make_request(path("v1", "draft", draft_id, "traded_picks"))
|
|
216
223
|
end
|
|
217
224
|
|
|
218
225
|
# Get NFL state (week, season status).
|
|
@@ -221,7 +228,7 @@ module SleeperApi
|
|
|
221
228
|
# @return [Hash] State data
|
|
222
229
|
# @see https://docs.sleeper.com/#get-nfl-state
|
|
223
230
|
def get_nfl_state(sport = "nfl")
|
|
224
|
-
nfl_state = make_request("
|
|
231
|
+
nfl_state = make_request(path("v1", "state", sport))
|
|
225
232
|
nfl_state.each_with_object({}) do |(k, v), result|
|
|
226
233
|
key = k.is_a?(String) ? k.to_sym : k
|
|
227
234
|
result[key] = v
|
|
@@ -424,7 +431,7 @@ module SleeperApi
|
|
|
424
431
|
# @param sport [String] Sport code (default: "nfl")
|
|
425
432
|
# @return [HTTParty::Response] Array of games
|
|
426
433
|
def schedule(season, season_type: "regular", sport: "nfl")
|
|
427
|
-
make_request("
|
|
434
|
+
make_request(path("schedule", sport, season_type, season))
|
|
428
435
|
end
|
|
429
436
|
|
|
430
437
|
# Get trending players.
|
|
@@ -436,7 +443,8 @@ module SleeperApi
|
|
|
436
443
|
# @return [Array<Hash>] Trending players
|
|
437
444
|
# @see https://docs.sleeper.com/#trending-players
|
|
438
445
|
def trending_players(sport = "nfl", type: "add", lookback_hours: 24, limit: 25)
|
|
439
|
-
|
|
446
|
+
query = URI.encode_www_form(lookback_hours: lookback_hours, limit: limit)
|
|
447
|
+
make_request("#{path("v1", "players", sport, "trending", type)}?#{query}")
|
|
440
448
|
end
|
|
441
449
|
|
|
442
450
|
# Get all player data (cached for 24 hours).
|
|
@@ -447,7 +455,7 @@ module SleeperApi
|
|
|
447
455
|
def get_players(sport = "nfl")
|
|
448
456
|
return @players_cache if @players_cache && @cache_timestamp && (Time.now - @cache_timestamp) < (3600 * 24)
|
|
449
457
|
|
|
450
|
-
response = make_request("
|
|
458
|
+
response = make_request(path("v1", "players", sport))
|
|
451
459
|
@players_cache = response.parsed_response
|
|
452
460
|
@cache_timestamp = Time.now
|
|
453
461
|
|
|
@@ -466,18 +474,18 @@ module SleeperApi
|
|
|
466
474
|
|
|
467
475
|
private
|
|
468
476
|
|
|
469
|
-
#
|
|
470
|
-
#
|
|
471
|
-
#
|
|
472
|
-
#
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
def weekly_path(resource, sport, season_type, season, week)
|
|
477
|
-
segments = [resource, sport, season_type, season, week].compact
|
|
478
|
-
escaped = segments.map { |segment| ERB::Util.url_encode(segment.to_s) }
|
|
477
|
+
# Every request path is built here, from escaped segments. An unescaped
|
|
478
|
+
# one raised URI::InvalidURIError, which is not a SleeperApi::Error, or
|
|
479
|
+
# walked out of the endpoint entirely: `../../league/123` as a username
|
|
480
|
+
# requested /league/123.
|
|
481
|
+
def path(*segments)
|
|
482
|
+
"/#{segments.map { |segment| ERB::Util.url_encode(segment.to_s) }.join("/")}"
|
|
483
|
+
end
|
|
479
484
|
|
|
480
|
-
|
|
485
|
+
# Path for #stats and #projections. A nil week drops the segment rather
|
|
486
|
+
# than defaulting, because the shorter path is season totals.
|
|
487
|
+
def weekly_path(resource, sport, season_type, season, week)
|
|
488
|
+
path("v1", *[resource, sport, season_type, season, week].compact)
|
|
481
489
|
end
|
|
482
490
|
|
|
483
491
|
# Make an HTTP request with retry logic and logging.
|
data/lib/sleeper_api/draft.rb
CHANGED
|
@@ -109,8 +109,8 @@ module SleeperApi
|
|
|
109
109
|
top_picks: picks.select { |pick| pick[:round] == 1 }.map do |pick|
|
|
110
110
|
{
|
|
111
111
|
player_id: pick[:player_id],
|
|
112
|
-
player_name:
|
|
113
|
-
position: pick[:metadata]
|
|
112
|
+
player_name: pick_player_name(pick),
|
|
113
|
+
position: pick[:metadata]&.dig(:position),
|
|
114
114
|
picked_by: pick[:picked_by]
|
|
115
115
|
}
|
|
116
116
|
end
|
|
@@ -135,6 +135,13 @@ module SleeperApi
|
|
|
135
135
|
|
|
136
136
|
private
|
|
137
137
|
|
|
138
|
+
# A pick can arrive with null metadata, or a team defense with only a
|
|
139
|
+
# last name, so the name is built from whatever parts exist.
|
|
140
|
+
def pick_player_name(pick)
|
|
141
|
+
parts = [pick[:metadata]&.dig(:first_name), pick[:metadata]&.dig(:last_name)].compact
|
|
142
|
+
parts.empty? ? nil : parts.join(" ")
|
|
143
|
+
end
|
|
144
|
+
|
|
138
145
|
def fetch_draft_data
|
|
139
146
|
@draft_data ||= @client.get_draft(@draft_id)
|
|
140
147
|
end
|
data/lib/sleeper_api/league.rb
CHANGED
|
@@ -22,7 +22,7 @@ module SleeperApi
|
|
|
22
22
|
|
|
23
23
|
@league_id = league_id
|
|
24
24
|
@client = client
|
|
25
|
-
@weeks = 1..
|
|
25
|
+
@weeks = 1..18
|
|
26
26
|
@league_data = nil
|
|
27
27
|
@league_rosters = nil
|
|
28
28
|
@league_users = nil
|
|
@@ -105,7 +105,7 @@ module SleeperApi
|
|
|
105
105
|
|
|
106
106
|
# Get formatted matchups for a specific week.
|
|
107
107
|
#
|
|
108
|
-
# @param week [Integer] Week number (1-
|
|
108
|
+
# @param week [Integer] Week number (1-18)
|
|
109
109
|
# @return [Array<Hash>] Matchup data with scoring breakdown
|
|
110
110
|
# @raise [ArgumentError] If week is invalid
|
|
111
111
|
#
|
|
@@ -117,7 +117,7 @@ module SleeperApi
|
|
|
117
117
|
# end
|
|
118
118
|
# end
|
|
119
119
|
def matchups_by_week(week: nil)
|
|
120
|
-
raise ArgumentError, "Week must be between
|
|
120
|
+
raise ArgumentError, "Week must be between #{@weeks.min} and #{@weeks.max}" unless @weeks.include?(week)
|
|
121
121
|
|
|
122
122
|
fetch_matchups([week]) unless @matchups&.key?(week)
|
|
123
123
|
format_matchups(week)
|
|
@@ -140,7 +140,7 @@ module SleeperApi
|
|
|
140
140
|
|
|
141
141
|
# Get formatted transactions for a specific week.
|
|
142
142
|
#
|
|
143
|
-
# @param week [Integer] Week number (1-
|
|
143
|
+
# @param week [Integer] Week number (1-18)
|
|
144
144
|
# @return [Array<Hash>] Transaction data with adds/drops and draft picks
|
|
145
145
|
# @raise [ArgumentError] If week is invalid
|
|
146
146
|
#
|
|
@@ -150,7 +150,7 @@ module SleeperApi
|
|
|
150
150
|
# puts "#{tx[:type]}: #{tx[:adds]&.length || 0} adds, #{tx[:drops]&.length || 0} drops"
|
|
151
151
|
# end
|
|
152
152
|
def transactions(week: nil)
|
|
153
|
-
raise ArgumentError, "Week must be between
|
|
153
|
+
raise ArgumentError, "Week must be between #{@weeks.min} and #{@weeks.max}" unless @weeks.include?(week)
|
|
154
154
|
|
|
155
155
|
fetch_transactions([week]) unless @transactions&.key?(week)
|
|
156
156
|
format_transactions(week)
|
|
@@ -320,32 +320,32 @@ module SleeperApi
|
|
|
320
320
|
week_matchups = @matchups[week] || []
|
|
321
321
|
return [] if week_matchups.empty?
|
|
322
322
|
|
|
323
|
-
|
|
324
|
-
|
|
323
|
+
# A roster on a bye has a null matchup_id. Each one is its own unpaired
|
|
324
|
+
# entry: grouping the nils together would pair two teams that never met.
|
|
325
|
+
paired, byes = week_matchups.partition { |match| match["matchup_id"] }
|
|
325
326
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
end.compact
|
|
327
|
+
paired.group_by { |match| match["matchup_id"] }.map do |matchup_id, entries|
|
|
328
|
+
{ matchup_id: matchup_id, rosters: entries.map { |roster| format_matchup_roster(roster) } }
|
|
329
|
+
end + byes.map { |roster| { matchup_id: nil, rosters: [format_matchup_roster(roster)] } }
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def format_matchup_roster(roster)
|
|
333
|
+
starters = roster["starters"] || []
|
|
334
|
+
bench = (roster["players"] || []) - starters
|
|
335
|
+
{
|
|
336
|
+
roster_id: roster["roster_id"],
|
|
337
|
+
points: roster["points"],
|
|
338
|
+
custom_points: roster["custom_points"],
|
|
339
|
+
total_points: (roster["points"] || 0) + (roster["custom_points"] || 0),
|
|
340
|
+
starters: starters,
|
|
341
|
+
bench: bench,
|
|
342
|
+
starter_points: player_points(roster, starters),
|
|
343
|
+
bench_points: player_points(roster, bench)
|
|
344
|
+
}
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def player_points(roster, player_ids)
|
|
348
|
+
player_ids.map { |player_id| { player_id => roster["players_points"]&.dig(player_id) || 0 } }
|
|
349
349
|
end
|
|
350
350
|
|
|
351
351
|
def format_users
|
data/lib/sleeper_api/user.rb
CHANGED
|
@@ -14,18 +14,20 @@ module SleeperApi
|
|
|
14
14
|
# @param identifier [String, Integer] Username or user ID
|
|
15
15
|
# @param client [SleeperApi::Client] HTTP client instance
|
|
16
16
|
# @raise [ArgumentError] If identifier is empty
|
|
17
|
-
# @raise [SleeperApi::
|
|
17
|
+
# @raise [SleeperApi::UserNotFound] If no user has that identifier
|
|
18
18
|
def initialize(identifier, client)
|
|
19
19
|
raise ArgumentError, "identifier must be a non-empty string" if identifier.to_s.empty?
|
|
20
20
|
|
|
21
21
|
@identifier = identifier
|
|
22
22
|
@client = client
|
|
23
23
|
@user_data = nil
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
# Keyed by season: one instance is asked about several seasons, and a
|
|
25
|
+
# single memo answered every later season with the first one's data.
|
|
26
|
+
@leagues = {}
|
|
27
|
+
@drafts = {}
|
|
26
28
|
|
|
27
29
|
fetch_user_data
|
|
28
|
-
@user_id = @user_data["user_id"] || raise(SleeperApi::
|
|
30
|
+
@user_id = @user_data["user_id"] || raise(SleeperApi::UserNotFound, "Invalid user data: user_id not found")
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
# Dynamically define attribute readers for user data.
|
|
@@ -51,7 +53,6 @@ module SleeperApi
|
|
|
51
53
|
raise ArgumentError, "season must be a valid year" unless season.is_a?(Integer)
|
|
52
54
|
|
|
53
55
|
fetch_leagues(season)
|
|
54
|
-
@leagues
|
|
55
56
|
end
|
|
56
57
|
|
|
57
58
|
# Get all rosters for this user across all leagues in a season.
|
|
@@ -62,8 +63,7 @@ module SleeperApi
|
|
|
62
63
|
def rosters(season = Time.now.year)
|
|
63
64
|
raise ArgumentError, "season must be a valid year" unless season.is_a?(Integer)
|
|
64
65
|
|
|
65
|
-
fetch_leagues(season)
|
|
66
|
-
(@leagues || []).flat_map do |league|
|
|
66
|
+
fetch_leagues(season).flat_map do |league|
|
|
67
67
|
league_instance = League.new(league[:league_id], @client, no_data: true)
|
|
68
68
|
league_instance.rosters(user_id: @user_id)
|
|
69
69
|
end
|
|
@@ -77,8 +77,7 @@ module SleeperApi
|
|
|
77
77
|
def drafts(season = Time.now.year)
|
|
78
78
|
raise ArgumentError, "season must be a valid year" unless season.is_a?(Integer)
|
|
79
79
|
|
|
80
|
-
fetch_drafts(season)
|
|
81
|
-
format_drafts
|
|
80
|
+
format_drafts(fetch_drafts(season))
|
|
82
81
|
end
|
|
83
82
|
|
|
84
83
|
# Get a summary of user's league participation.
|
|
@@ -115,17 +114,19 @@ module SleeperApi
|
|
|
115
114
|
end
|
|
116
115
|
|
|
117
116
|
def fetch_leagues(season)
|
|
118
|
-
@leagues ||= (@client.get_user_leagues(@user_id, season: season) || []).map do |league|
|
|
117
|
+
@leagues[season] ||= (@client.get_user_leagues(@user_id, season: season) || []).map do |league|
|
|
119
118
|
deep_symbolize_keys(league)
|
|
120
119
|
end
|
|
121
120
|
end
|
|
122
121
|
|
|
123
122
|
def fetch_drafts(season)
|
|
124
|
-
@drafts ||= (@client.get_user_drafts(@user_id, season: season) || []).map
|
|
123
|
+
@drafts[season] ||= (@client.get_user_drafts(@user_id, season: season) || []).map do |draft|
|
|
124
|
+
deep_symbolize_keys(draft)
|
|
125
|
+
end
|
|
125
126
|
end
|
|
126
127
|
|
|
127
|
-
def format_drafts
|
|
128
|
-
|
|
128
|
+
def format_drafts(drafts)
|
|
129
|
+
drafts.map do |draft|
|
|
129
130
|
draft.merge(
|
|
130
131
|
league_name: draft.dig(:metadata, :name),
|
|
131
132
|
scoring_type: draft.dig(:metadata, :scoring_type),
|
data/lib/sleeper_api/version.rb
CHANGED
data/lib/sleeper_api.rb
CHANGED
|
@@ -9,6 +9,10 @@ require_relative "sleeper_api/draft"
|
|
|
9
9
|
module SleeperApi
|
|
10
10
|
class Error < StandardError; end
|
|
11
11
|
|
|
12
|
+
# The identifier names no Sleeper user. Permanent, unlike the rest of
|
|
13
|
+
# Error — retrying will not help — so a caller can rescue it on its own.
|
|
14
|
+
class UserNotFound < Error; end
|
|
15
|
+
|
|
12
16
|
class << self
|
|
13
17
|
attr_writer :configuration
|
|
14
18
|
end
|