sleeper_api 1.7.0 → 1.9.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 +30 -0
- data/CLAUDE.md +1 -1
- data/README.md +17 -0
- data/lib/sleeper_api/client.rb +14 -0
- data/lib/sleeper_api/league.rb +41 -3
- data/lib/sleeper_api/user.rb +28 -16
- data/lib/sleeper_api/version.rb +1 -1
- 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: ea2a84bc8be057968c5822116b20f53d1dac11550c8baf70d437fab4d72b427a
|
|
4
|
+
data.tar.gz: 3c518c6191fc0e117229a10d9ae5f7d1a04d4f8dc62db53c52e8d6af99e6a635
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5e248beba16cf5d2ebba74570ce4856ddcefa4fa95a8c118e685d2e89ca57eea31268c848bcfc59501b25f40d0bb08bfd8c4f65d4f9f2129f799b5f8d829c053
|
|
7
|
+
data.tar.gz: 060bef1860b827761fdcbfc38e65e0a0a5ec4b1abb6da4e2e52d43416a400e3c574135ed4001363049728cf47f34d64e13465f9cfc00067f1c1ab03cb770e076
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
## [1.9.0] - 2026-10-09
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- **`avatar_url(thumb: true)`** on `User` and `League` returns
|
|
6
|
+
`sleepercdn.com/avatars/thumbs/{id}`: 19 KB of PNG where the full size was
|
|
7
|
+
2.2 MB of `application/octet-stream` (measured 2026-10-09). The default is
|
|
8
|
+
unchanged.
|
|
9
|
+
- **`sport:` on `User#leagues`, `#rosters`, `#drafts` and `#summary`**, default
|
|
10
|
+
`"nfl"`, passed through to the client, which already took it. Each sport is
|
|
11
|
+
cached apart. Sleeper answers an unknown sport with `200` and `[]` (measured
|
|
12
|
+
2026-10-09), so a typo reads as "no leagues"; it is not checked against a list.
|
|
13
|
+
- **`League#traded_picks`**, with `Draft#traded_picks`' three filters
|
|
14
|
+
(`original_owner_id:`, `previous_owner_id:`, `current_owner_id:`). `season`
|
|
15
|
+
stays the string Sleeper sends.
|
|
16
|
+
- **`League#drafts`**, as symbol-keyed hashes rather than `Draft` objects, since
|
|
17
|
+
a `Draft` fetches on construction.
|
|
18
|
+
|
|
19
|
+
## [1.8.0] - 2026-10-02
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **`Client#player_news(player_id, sport: "nfl", limit: 10)`** fetches one
|
|
24
|
+
player's recent news from the undocumented
|
|
25
|
+
`api.sleeper.com/players/{sport}/{id}/news`, newest first: RotoWire,
|
|
26
|
+
FantasyPros and RotoBaller items with a `metadata` title, description and
|
|
27
|
+
usually an analysis. Measured 2026-10-02: Sleeper returns at most 10 whatever
|
|
28
|
+
the limit, there is no paging, and an unknown id answers `200` with `[]`. The
|
|
29
|
+
path exists on the web host only.
|
|
30
|
+
|
|
1
31
|
## [1.7.0] - 2026-10-02
|
|
2
32
|
|
|
3
33
|
### Added
|
data/CLAUDE.md
CHANGED
|
@@ -70,7 +70,7 @@ Four layers, with a deliberate split between HTTP and modeling:
|
|
|
70
70
|
|
|
71
71
|
**Guard nil defensively.** Sleeper omits fields freely — users without `metadata`, rosters without `players`, matchups without `points`. Real leagues hit these; the spec fixtures often don't. Use `&.dig` rather than `[]` on anything nested off an API response.
|
|
72
72
|
|
|
73
|
-
**Never fan out `get_players`.** It's a multi-megabyte payload of every NFL player. It's cached for 24h in an ivar on the client instance — which means the cache dies with the client. Consumers holding a short-lived client re-download it every time. For one player, `#player(id)` fetches `/v1/players/nfl/{id}` live (~1.2 KB, undocumented, uncached); an unknown id is a **404** there, not the `200`/`null` an unknown user gets, and becomes `PlayerNotFound`.
|
|
73
|
+
**Never fan out `get_players`.** It's a multi-megabyte payload of every NFL player. It's cached for 24h in an ivar on the client instance — which means the cache dies with the client. Consumers holding a short-lived client re-download it every time. For one player, `#player(id)` fetches `/v1/players/nfl/{id}` live (~1.2 KB, undocumented, uncached); an unknown id is a **404** there, not the `200`/`null` an unknown user gets, and becomes `PlayerNotFound`. `#player_news(id)` is that player's news on `WEB_HOST` only (`.app` 404s the path): at most 10 items whatever `limit` says, and an unknown id is `200` with `[]`, not a 404.
|
|
74
74
|
|
|
75
75
|
### Undocumented endpoints carry undocumented vocabularies
|
|
76
76
|
|
data/README.md
CHANGED
|
@@ -76,6 +76,7 @@ user = SleeperApi.client.user('johndoe')
|
|
|
76
76
|
user.username # => "johndoe"
|
|
77
77
|
user.display_name # => "John Doe"
|
|
78
78
|
user.avatar_url # => "https://sleepercdn.com/avatars/abc123"
|
|
79
|
+
user.avatar_url(thumb: true) # => "https://sleepercdn.com/avatars/thumbs/abc123"
|
|
79
80
|
user.is_bot # => false
|
|
80
81
|
|
|
81
82
|
# All leagues for current season
|
|
@@ -89,6 +90,9 @@ end
|
|
|
89
90
|
old_leagues = user.leagues(2023)
|
|
90
91
|
championships = old_leagues.select { |l| l[:status] == "finished" }
|
|
91
92
|
|
|
93
|
+
# Another sport (default "nfl"). An unknown sport answers [] rather than an error.
|
|
94
|
+
basketball = user.leagues(2026, sport: "nba")
|
|
95
|
+
|
|
92
96
|
# Get all my rosters across all leagues
|
|
93
97
|
rosters = user.rosters
|
|
94
98
|
rosters.each do |roster|
|
|
@@ -129,6 +133,7 @@ league.season # => "2024"
|
|
|
129
133
|
league.total_rosters # => 12
|
|
130
134
|
league.status # => "regular"
|
|
131
135
|
league.avatar_url # => "https://sleepercdn.com/avatars/abc123"
|
|
136
|
+
league.avatar_url(thumb: true) # => "https://sleepercdn.com/avatars/thumbs/abc123"
|
|
132
137
|
|
|
133
138
|
# Formatted rosters (with team names and records)
|
|
134
139
|
rosters = league.rosters
|
|
@@ -176,6 +181,13 @@ toilet_bowl_matchups = league.toilet_bowl
|
|
|
176
181
|
matchup = toilet_bowl_matchups.first
|
|
177
182
|
matchup[:team1_owner] # => "Team Beta"
|
|
178
183
|
matchup[:winner_owner] # => "Team Beta"
|
|
184
|
+
|
|
185
|
+
# Traded picks (only picks that moved), with the same filters as Draft#traded_picks
|
|
186
|
+
picks = league.traded_picks(current_owner_id: 8)
|
|
187
|
+
picks.first # => { season: "2027", round: 1, roster_id: 1, previous_owner_id: 1, owner_id: 8 }
|
|
188
|
+
|
|
189
|
+
# Drafts, as hashes; open one with SleeperApi.client.draft(id)
|
|
190
|
+
league.drafts.map { |d| d[:status] } # => ["complete"]
|
|
179
191
|
```
|
|
180
192
|
|
|
181
193
|
### Drafts
|
|
@@ -252,6 +264,11 @@ puts "#{player['first_name']} #{player['last_name']} - #{player['position']}"
|
|
|
252
264
|
fresh = client.player("1234")
|
|
253
265
|
puts "#{fresh['injury_status']} (#{fresh['injury_body_part']})"
|
|
254
266
|
|
|
267
|
+
# One player's recent news, newest first (at most 10, from api.sleeper.com)
|
|
268
|
+
client.player_news("1234").each do |item|
|
|
269
|
+
puts "#{item['source']}: #{item.dig('metadata', 'title')}"
|
|
270
|
+
end
|
|
271
|
+
|
|
255
272
|
# Get trending players
|
|
256
273
|
trending_adds = client.trending_players(type: "add", limit: 10)
|
|
257
274
|
trending_drops = client.trending_players(type: "drop", limit: 10)
|
data/lib/sleeper_api/client.rb
CHANGED
|
@@ -487,6 +487,20 @@ module SleeperApi
|
|
|
487
487
|
not_found: -> { PlayerNotFound.new("No Sleeper player #{player_id.to_s.inspect}") })
|
|
488
488
|
end
|
|
489
489
|
|
|
490
|
+
# One player's recent news, newest first: RotoWire, FantasyPros and
|
|
491
|
+
# RotoBaller items, each with a `metadata` title, description and usually
|
|
492
|
+
# an analysis. Undocumented and on WEB_HOST only; Sleeper returns at most
|
|
493
|
+
# 10 whatever the limit, and an unknown id answers 200 with [].
|
|
494
|
+
#
|
|
495
|
+
# @param player_id [String] Player ID
|
|
496
|
+
# @param sport [String] Sport code (default: "nfl")
|
|
497
|
+
# @param limit [Integer] How many items, up to 10
|
|
498
|
+
# @return [HTTParty::Response] The items, newest first
|
|
499
|
+
def player_news(player_id, sport: "nfl", limit: 10)
|
|
500
|
+
query = URI.encode_www_form(limit: limit)
|
|
501
|
+
make_request("#{path("players", sport, player_id, "news")}?#{query}", host: WEB_HOST)
|
|
502
|
+
end
|
|
503
|
+
|
|
490
504
|
private
|
|
491
505
|
|
|
492
506
|
# Every request path is built here, from escaped segments. An unescaped
|
data/lib/sleeper_api/league.rb
CHANGED
|
@@ -30,6 +30,8 @@ module SleeperApi
|
|
|
30
30
|
@transactions = nil
|
|
31
31
|
@playoff_bracket = nil
|
|
32
32
|
@toilet_bowl = nil
|
|
33
|
+
@traded_picks = nil
|
|
34
|
+
@drafts = nil
|
|
33
35
|
|
|
34
36
|
fetch_league_data unless no_data
|
|
35
37
|
end
|
|
@@ -43,9 +45,15 @@ module SleeperApi
|
|
|
43
45
|
|
|
44
46
|
# Generate avatar URL from ID.
|
|
45
47
|
#
|
|
46
|
-
#
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
# The full size was 2.2 MB of `application/octet-stream` and the thumbnail
|
|
49
|
+
# 19 KB of PNG, measured 2026-10-09; a list of avatars wants the thumbnail.
|
|
50
|
+
#
|
|
51
|
+
# @param thumb [Boolean] The thumbnail instead of the full-size image
|
|
52
|
+
# @return [String, nil] Avatar URL or nil if no avatar
|
|
53
|
+
def avatar_url(thumb: false)
|
|
54
|
+
return nil unless avatar
|
|
55
|
+
|
|
56
|
+
thumb ? "https://sleepercdn.com/avatars/thumbs/#{avatar}" : "https://sleepercdn.com/avatars/#{avatar}"
|
|
49
57
|
end
|
|
50
58
|
|
|
51
59
|
# Get the reigning champion's roster.
|
|
@@ -172,8 +180,38 @@ module SleeperApi
|
|
|
172
180
|
@toilet_bowl
|
|
173
181
|
end
|
|
174
182
|
|
|
183
|
+
# Get the league's traded draft picks, as Sleeper sends them.
|
|
184
|
+
#
|
|
185
|
+
# `season` is a string ("2027"); every owner is a roster id. A pick that
|
|
186
|
+
# never moved is not listed.
|
|
187
|
+
#
|
|
188
|
+
# @param original_owner_id [Integer, nil] Filter by the roster the pick began with
|
|
189
|
+
# @param previous_owner_id [Integer, nil] Filter by previous owner
|
|
190
|
+
# @param current_owner_id [Integer, nil] Filter by current owner
|
|
191
|
+
# @return [Array<Hash>] Traded picks, symbol-keyed
|
|
192
|
+
def traded_picks(original_owner_id: nil, previous_owner_id: nil, current_owner_id: nil)
|
|
193
|
+
fetch_traded_picks unless @traded_picks
|
|
194
|
+
picks = @traded_picks
|
|
195
|
+
picks = picks.select { |pick| pick[:roster_id] == original_owner_id } if original_owner_id
|
|
196
|
+
picks = picks.select { |pick| pick[:previous_owner_id] == previous_owner_id } if previous_owner_id
|
|
197
|
+
picks = picks.select { |pick| pick[:owner_id] == current_owner_id } if current_owner_id
|
|
198
|
+
picks
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Get the league's drafts. Hashes rather than `Draft` objects, because a
|
|
202
|
+
# `Draft` fetches on construction: `client.draft(id)` opens one.
|
|
203
|
+
#
|
|
204
|
+
# @return [Array<Hash>] Drafts, symbol-keyed
|
|
205
|
+
def drafts
|
|
206
|
+
@drafts ||= (@client.get_league_drafts(@league_id) || []).map { |draft| deep_symbolize_keys(draft) }
|
|
207
|
+
end
|
|
208
|
+
|
|
175
209
|
private
|
|
176
210
|
|
|
211
|
+
def fetch_traded_picks
|
|
212
|
+
@traded_picks ||= (@client.get_league_traded_picks(@league_id) || []).map { |pick| deep_symbolize_keys(pick) }
|
|
213
|
+
end
|
|
214
|
+
|
|
177
215
|
def fetch_league_data
|
|
178
216
|
@league_data ||= @client.get_league(@league_id)
|
|
179
217
|
end
|
data/lib/sleeper_api/user.rb
CHANGED
|
@@ -39,31 +39,40 @@ module SleeperApi
|
|
|
39
39
|
|
|
40
40
|
# Generate avatar URL from ID.
|
|
41
41
|
#
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
# The full size was 2.2 MB of `application/octet-stream` and the thumbnail
|
|
43
|
+
# 19 KB of PNG, measured 2026-10-09; a list of avatars wants the thumbnail.
|
|
44
|
+
#
|
|
45
|
+
# @param thumb [Boolean] The thumbnail instead of the full-size image
|
|
46
|
+
# @return [String, nil] Avatar URL or nil if no avatar
|
|
47
|
+
def avatar_url(thumb: false)
|
|
48
|
+
return nil unless avatar
|
|
49
|
+
|
|
50
|
+
thumb ? "https://sleepercdn.com/avatars/thumbs/#{avatar}" : "https://sleepercdn.com/avatars/#{avatar}"
|
|
45
51
|
end
|
|
46
52
|
|
|
47
53
|
# Get all leagues for a specific season.
|
|
48
54
|
#
|
|
49
55
|
# @param season [Integer] Season year (default: current year)
|
|
56
|
+
# @param sport [String] Sport code (default: "nfl"). An unknown one answers
|
|
57
|
+
# 200 with [] (measured 2026-10-09), so a typo reads as "no leagues".
|
|
50
58
|
# @return [Array<Hash>] Formatted league objects
|
|
51
59
|
# @raise [ArgumentError] If season is not an integer
|
|
52
|
-
def leagues(season = Time.now.year)
|
|
60
|
+
def leagues(season = Time.now.year, sport: "nfl")
|
|
53
61
|
raise ArgumentError, "season must be a valid year" unless season.is_a?(Integer)
|
|
54
62
|
|
|
55
|
-
fetch_leagues(season)
|
|
63
|
+
fetch_leagues(season, sport)
|
|
56
64
|
end
|
|
57
65
|
|
|
58
66
|
# Get all rosters for this user across all leagues in a season.
|
|
59
67
|
#
|
|
60
68
|
# @param season [Integer] Season year (default: current year)
|
|
69
|
+
# @param sport [String] Sport code (default: "nfl")
|
|
61
70
|
# @return [Array<Hash>] Formatted roster objects from all leagues
|
|
62
71
|
# @raise [ArgumentError] If season is not an integer
|
|
63
|
-
def rosters(season = Time.now.year)
|
|
72
|
+
def rosters(season = Time.now.year, sport: "nfl")
|
|
64
73
|
raise ArgumentError, "season must be a valid year" unless season.is_a?(Integer)
|
|
65
74
|
|
|
66
|
-
fetch_leagues(season).flat_map do |league|
|
|
75
|
+
fetch_leagues(season, sport).flat_map do |league|
|
|
67
76
|
league_instance = League.new(league[:league_id], @client, no_data: true)
|
|
68
77
|
league_instance.rosters(user_id: @user_id)
|
|
69
78
|
end
|
|
@@ -72,21 +81,23 @@ module SleeperApi
|
|
|
72
81
|
# Get all drafts for a specific season with enhanced metadata.
|
|
73
82
|
#
|
|
74
83
|
# @param season [Integer] Season year (default: current year)
|
|
84
|
+
# @param sport [String] Sport code (default: "nfl")
|
|
75
85
|
# @return [Array<Hash>] Formatted draft objects with league info
|
|
76
86
|
# @raise [ArgumentError] If season is not an integer
|
|
77
|
-
def drafts(season = Time.now.year)
|
|
87
|
+
def drafts(season = Time.now.year, sport: "nfl")
|
|
78
88
|
raise ArgumentError, "season must be a valid year" unless season.is_a?(Integer)
|
|
79
89
|
|
|
80
|
-
format_drafts(fetch_drafts(season))
|
|
90
|
+
format_drafts(fetch_drafts(season, sport))
|
|
81
91
|
end
|
|
82
92
|
|
|
83
93
|
# Get a summary of user's league participation.
|
|
84
94
|
#
|
|
85
95
|
# @param season [Integer] Season year (default: current year)
|
|
96
|
+
# @param sport [String] Sport code (default: "nfl")
|
|
86
97
|
# @return [Hash] Summary statistics
|
|
87
|
-
def summary(season = Time.now.year)
|
|
88
|
-
leagues_data = leagues(season)
|
|
89
|
-
rosters_data = rosters(season)
|
|
98
|
+
def summary(season = Time.now.year, sport: "nfl")
|
|
99
|
+
leagues_data = leagues(season, sport: sport)
|
|
100
|
+
rosters_data = rosters(season, sport: sport)
|
|
90
101
|
|
|
91
102
|
{
|
|
92
103
|
season: season,
|
|
@@ -113,14 +124,15 @@ module SleeperApi
|
|
|
113
124
|
@user_data ||= @client.get_user(@identifier)
|
|
114
125
|
end
|
|
115
126
|
|
|
116
|
-
|
|
117
|
-
|
|
127
|
+
# Keyed on the sport too, or one sport's answer would serve the other's.
|
|
128
|
+
def fetch_leagues(season, sport)
|
|
129
|
+
@leagues[[sport, season]] ||= (@client.get_user_leagues(@user_id, sport: sport, season: season) || []).map do |league|
|
|
118
130
|
deep_symbolize_keys(league)
|
|
119
131
|
end
|
|
120
132
|
end
|
|
121
133
|
|
|
122
|
-
def fetch_drafts(season)
|
|
123
|
-
@drafts[season] ||= (@client.get_user_drafts(@user_id, season: season) || []).map do |draft|
|
|
134
|
+
def fetch_drafts(season, sport)
|
|
135
|
+
@drafts[[sport, season]] ||= (@client.get_user_drafts(@user_id, sport: sport, season: season) || []).map do |draft|
|
|
124
136
|
deep_symbolize_keys(draft)
|
|
125
137
|
end
|
|
126
138
|
end
|
data/lib/sleeper_api/version.rb
CHANGED