nba 0.1.1 → 0.2.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 +5 -5
- data/AGENTS.md +362 -0
- data/CHANGELOG.md +169 -0
- data/CLAUDE.md +1 -0
- data/LICENSE +21 -0
- data/README.md +501 -101
- data/bin/console +10 -0
- data/bin/setup +6 -0
- data/exe/nba +8 -0
- data/lib/nba/all_time_leader.rb +77 -0
- data/lib/nba/all_time_leaders.rb +185 -0
- data/lib/nba/assist_leader.rb +92 -0
- data/lib/nba/assist_leaders.rb +64 -0
- data/lib/nba/assist_tracker.rb +108 -0
- data/lib/nba/assist_tracker_entry.rb +206 -0
- data/lib/nba/award.rb +128 -0
- data/lib/nba/box_score.rb +2 -0
- data/lib/nba/box_score_advanced.rb +114 -0
- data/lib/nba/box_score_advanced_player_stat.rb +297 -0
- data/lib/nba/box_score_advanced_team_stat.rb +237 -0
- data/lib/nba/box_score_advanced_v3.rb +124 -0
- data/lib/nba/box_score_defensive_player_stat.rb +281 -0
- data/lib/nba/box_score_defensive_team_stat.rb +85 -0
- data/lib/nba/box_score_defensive_v2.rb +190 -0
- data/lib/nba/box_score_four_factors.rb +91 -0
- data/lib/nba/box_score_four_factors_player_stat.rb +185 -0
- data/lib/nba/box_score_four_factors_team_stat.rb +141 -0
- data/lib/nba/box_score_four_factors_v3.rb +133 -0
- data/lib/nba/box_score_hustle.rb +226 -0
- data/lib/nba/box_score_hustle_player_stat.rb +233 -0
- data/lib/nba/box_score_hustle_team_stat.rb +189 -0
- data/lib/nba/box_score_matchup_stat.rb +417 -0
- data/lib/nba/box_score_matchups_v3.rb +184 -0
- data/lib/nba/box_score_misc.rb +100 -0
- data/lib/nba/box_score_misc_player_stat.rb +217 -0
- data/lib/nba/box_score_misc_team_stat.rb +173 -0
- data/lib/nba/box_score_misc_v3.rb +163 -0
- data/lib/nba/box_score_player_stat.rb +273 -0
- data/lib/nba/box_score_player_track.rb +223 -0
- data/lib/nba/box_score_player_track_stat.rb +273 -0
- data/lib/nba/box_score_player_track_team_stat.rb +229 -0
- data/lib/nba/box_score_scoring.rb +103 -0
- data/lib/nba/box_score_scoring_player_stat.rb +241 -0
- data/lib/nba/box_score_scoring_team_stat.rb +197 -0
- data/lib/nba/box_score_scoring_v3.rb +170 -0
- data/lib/nba/box_score_similarity_score.rb +119 -0
- data/lib/nba/box_score_similarity_stat.rb +76 -0
- data/lib/nba/box_score_starter_bench_stat.rb +257 -0
- data/lib/nba/box_score_summary.rb +285 -0
- data/lib/nba/box_score_summary_v2.rb +202 -0
- data/lib/nba/box_score_summary_v3.rb +120 -0
- data/lib/nba/box_score_summary_v3_data.rb +419 -0
- data/lib/nba/box_score_team_stat.rb +229 -0
- data/lib/nba/box_score_traditional.rb +101 -0
- data/lib/nba/box_score_traditional_v3.rb +195 -0
- data/lib/nba/box_score_usage.rb +102 -0
- data/lib/nba/box_score_usage_player_stat.rb +265 -0
- data/lib/nba/box_score_usage_team_stat.rb +221 -0
- data/lib/nba/box_score_usage_v3.rb +169 -0
- data/lib/nba/box_score_v3_helpers.rb +144 -0
- data/lib/nba/career_stats.rb +217 -0
- data/lib/nba/cli/display/player_display.rb +98 -0
- data/lib/nba/cli/display.rb +178 -0
- data/lib/nba/cli/formatters/game_formatters.rb +86 -0
- data/lib/nba/cli/formatters/leaders_formatters.rb +26 -0
- data/lib/nba/cli/formatters/player_formatters.rb +52 -0
- data/lib/nba/cli/formatters/standings_formatters.rb +26 -0
- data/lib/nba/cli/formatters/team_formatters.rb +67 -0
- data/lib/nba/cli/formatters/time_formatters.rb +82 -0
- data/lib/nba/cli/formatters.rb +56 -0
- data/lib/nba/cli/helpers.rb +135 -0
- data/lib/nba/cli.rb +171 -20
- data/lib/nba/client.rb +35 -0
- data/lib/nba/collection.rb +89 -0
- data/lib/nba/college_player_stat.rb +200 -0
- data/lib/nba/common_player_info.rb +142 -0
- data/lib/nba/common_playoff_series.rb +90 -0
- data/lib/nba/common_team_years.rb +113 -0
- data/lib/nba/conference.rb +39 -0
- data/lib/nba/connection.rb +84 -0
- data/lib/nba/cume_stats_player.rb +358 -0
- data/lib/nba/cume_stats_player_game.rb +217 -0
- data/lib/nba/cume_stats_player_games.rb +99 -0
- data/lib/nba/cume_stats_player_games_entry.rb +25 -0
- data/lib/nba/cume_stats_player_total.rb +481 -0
- data/lib/nba/cume_stats_team.rb +349 -0
- data/lib/nba/cume_stats_team_games.rb +145 -0
- data/lib/nba/cume_stats_team_games_entry.rb +25 -0
- data/lib/nba/cume_stats_team_player.rb +485 -0
- data/lib/nba/cume_stats_team_total.rb +267 -0
- data/lib/nba/data.rb +73 -0
- data/lib/nba/defense_hub.rb +109 -0
- data/lib/nba/defense_hub_stat.rb +57 -0
- data/lib/nba/defensive_shot_stat.rb +102 -0
- data/lib/nba/division.rb +49 -0
- data/lib/nba/draft_board.rb +126 -0
- data/lib/nba/draft_board_pick.rb +173 -0
- data/lib/nba/draft_combine_anthro_measurement.rb +163 -0
- data/lib/nba/draft_combine_drill_result.rb +115 -0
- data/lib/nba/draft_combine_drill_results.rb +112 -0
- data/lib/nba/draft_combine_non_stationary_shooting.rb +268 -0
- data/lib/nba/draft_combine_non_stationary_shooting_result.rb +355 -0
- data/lib/nba/draft_combine_player_anthro.rb +133 -0
- data/lib/nba/draft_combine_spot_shooting.rb +243 -0
- data/lib/nba/draft_combine_spot_shooting_result.rb +419 -0
- data/lib/nba/draft_combine_stat.rb +211 -0
- data/lib/nba/draft_combine_stats.rb +160 -0
- data/lib/nba/draft_history.rb +142 -0
- data/lib/nba/draft_pick.rb +154 -0
- data/lib/nba/dunk_score_leader.rb +93 -0
- data/lib/nba/dunk_score_leaders.rb +77 -0
- data/lib/nba/estimated_metrics_stat.rb +152 -0
- data/lib/nba/fantasy_profile_stat.rb +142 -0
- data/lib/nba/fantasy_widget.rb +72 -0
- data/lib/nba/fantasy_widget_player.rb +98 -0
- data/lib/nba/found_game.rb +260 -0
- data/lib/nba/franchise.rb +136 -0
- data/lib/nba/franchise_history.rb +142 -0
- data/lib/nba/franchise_leader.rb +147 -0
- data/lib/nba/franchise_leaders.rb +162 -0
- data/lib/nba/franchise_player.rb +224 -0
- data/lib/nba/franchise_players.rb +147 -0
- data/lib/nba/game.rb +80 -64
- data/lib/nba/game_log.rb +349 -0
- data/lib/nba/game_rotation.rb +152 -0
- data/lib/nba/game_streak.rb +102 -0
- data/lib/nba/games.rb +46 -0
- data/lib/nba/home_page_leader.rb +99 -0
- data/lib/nba/home_page_leaders.rb +75 -0
- data/lib/nba/home_page_stat.rb +57 -0
- data/lib/nba/home_page_v2.rb +110 -0
- data/lib/nba/hustle_stats_box_score.rb +182 -0
- data/lib/nba/infographic_fan_duel_player.rb +139 -0
- data/lib/nba/infographic_fan_duel_player_stat.rb +311 -0
- data/lib/nba/ist_standing.rb +167 -0
- data/lib/nba/ist_standings.rb +81 -0
- data/lib/nba/leader.rb +103 -0
- data/lib/nba/leaders.rb +110 -0
- data/lib/nba/leaders_tile.rb +57 -0
- data/lib/nba/leaders_tiles.rb +90 -0
- data/lib/nba/league.rb +37 -0
- data/lib/nba/league_dash_lineup_stat.rb +270 -0
- data/lib/nba/league_dash_lineups.rb +177 -0
- data/lib/nba/league_dash_opp_pt_shot.rb +150 -0
- data/lib/nba/league_dash_player_bio_stat.rb +217 -0
- data/lib/nba/league_dash_player_bio_stats.rb +164 -0
- data/lib/nba/league_dash_player_clutch.rb +212 -0
- data/lib/nba/league_dash_player_clutch_stat.rb +271 -0
- data/lib/nba/league_dash_player_pt_shot.rb +152 -0
- data/lib/nba/league_dash_player_pt_shot_stat.rb +193 -0
- data/lib/nba/league_dash_player_shot_location_stat.rb +265 -0
- data/lib/nba/league_dash_player_shot_locations.rb +210 -0
- data/lib/nba/league_dash_player_stat.rb +306 -0
- data/lib/nba/league_dash_player_stats.rb +176 -0
- data/lib/nba/league_dash_pt_defend.rb +160 -0
- data/lib/nba/league_dash_pt_defend_stat.rb +145 -0
- data/lib/nba/league_dash_pt_stats.rb +152 -0
- data/lib/nba/league_dash_pt_stats_stat.rb +169 -0
- data/lib/nba/league_dash_pt_team_defend.rb +158 -0
- data/lib/nba/league_dash_pt_team_defend_stat.rb +110 -0
- data/lib/nba/league_dash_team_clutch.rb +211 -0
- data/lib/nba/league_dash_team_clutch_stat.rb +237 -0
- data/lib/nba/league_dash_team_pt_shot.rb +150 -0
- data/lib/nba/league_dash_team_pt_shot_stat.rb +166 -0
- data/lib/nba/league_dash_team_shot_location_stat.rb +230 -0
- data/lib/nba/league_dash_team_shot_locations.rb +208 -0
- data/lib/nba/league_dash_team_stat.rb +275 -0
- data/lib/nba/league_dash_team_stats.rb +172 -0
- data/lib/nba/league_game_finder.rb +170 -0
- data/lib/nba/league_game_log.rb +224 -0
- data/lib/nba/league_hustle_stats_player.rb +161 -0
- data/lib/nba/league_hustle_stats_player_stat.rb +253 -0
- data/lib/nba/league_hustle_stats_team.rb +157 -0
- data/lib/nba/league_hustle_stats_team_stat.rb +179 -0
- data/lib/nba/league_lineup_viz.rb +184 -0
- data/lib/nba/league_lineup_viz_stat.rb +214 -0
- data/lib/nba/league_player_on_details.rb +175 -0
- data/lib/nba/league_player_on_details_stat.rb +313 -0
- data/lib/nba/league_season_matchup_stat.rb +241 -0
- data/lib/nba/league_season_matchups.rb +181 -0
- data/lib/nba/league_standing.rb +284 -0
- data/lib/nba/league_standings.rb +159 -0
- data/lib/nba/league_wide_shot_stat.rb +62 -0
- data/lib/nba/live_action.rb +240 -0
- data/lib/nba/live_box_score.rb +143 -0
- data/lib/nba/live_connection.rb +84 -0
- data/lib/nba/live_game.rb +230 -0
- data/lib/nba/live_play_by_play.rb +120 -0
- data/lib/nba/live_player_stat.rb +276 -0
- data/lib/nba/live_scoreboard.rb +102 -0
- data/lib/nba/matchup_rollup.rb +98 -0
- data/lib/nba/matchups_rollup.rb +81 -0
- data/lib/nba/pass_stat.rb +209 -0
- data/lib/nba/play.rb +258 -0
- data/lib/nba/play_by_play.rb +85 -0
- data/lib/nba/play_by_play_v3.rb +91 -0
- data/lib/nba/play_type_stat.rb +206 -0
- data/lib/nba/player.rb +242 -24
- data/lib/nba/player_awards.rb +110 -0
- data/lib/nba/player_career_by_college.rb +86 -0
- data/lib/nba/player_career_by_college_rollup.rb +143 -0
- data/lib/nba/player_career_stats.rb +77 -0
- data/lib/nba/player_compare.rb +156 -0
- data/lib/nba/player_comparison_stat.rb +242 -0
- data/lib/nba/player_dash_pt_pass.rb +164 -0
- data/lib/nba/player_dash_pt_reb.rb +235 -0
- data/lib/nba/player_dash_pt_shot_defend.rb +119 -0
- data/lib/nba/player_dash_pt_shots.rb +279 -0
- data/lib/nba/player_dashboard.rb +259 -0
- data/lib/nba/player_dashboard_stat.rb +248 -0
- data/lib/nba/player_estimated_metrics.rb +84 -0
- data/lib/nba/player_fantasy_profile_bar_graph.rb +147 -0
- data/lib/nba/player_game_log.rb +72 -0
- data/lib/nba/player_game_logs.rb +117 -0
- data/lib/nba/player_game_streak_finder.rb +108 -0
- data/lib/nba/player_index.rb +135 -0
- data/lib/nba/player_index_entry.rb +266 -0
- data/lib/nba/player_info.rb +225 -0
- data/lib/nba/player_next_n_games.rb +64 -0
- data/lib/nba/player_profile_v2.rb +169 -0
- data/lib/nba/player_vs_player.rb +153 -0
- data/lib/nba/players.rb +107 -0
- data/lib/nba/playoff_matchup.rb +84 -0
- data/lib/nba/playoff_picture.rb +98 -0
- data/lib/nba/playoff_series.rb +76 -0
- data/lib/nba/position.rb +48 -0
- data/lib/nba/rebound_stat.rb +189 -0
- data/lib/nba/response_parser.rb +116 -0
- data/lib/nba/roster.rb +74 -0
- data/lib/nba/rotation_entry.rb +154 -0
- data/lib/nba/schedule.rb +183 -0
- data/lib/nba/schedule_international.rb +182 -0
- data/lib/nba/scheduled_game.rb +240 -0
- data/lib/nba/scoreboard.rb +183 -0
- data/lib/nba/scoreboard_v3.rb +104 -0
- data/lib/nba/shot.rb +208 -0
- data/lib/nba/shot_chart.rb +75 -0
- data/lib/nba/shot_chart_league_wide.rb +102 -0
- data/lib/nba/shot_chart_lineup_detail.rb +109 -0
- data/lib/nba/shot_stat.rb +174 -0
- data/lib/nba/standing.rb +129 -0
- data/lib/nba/standings.rb +75 -0
- data/lib/nba/static.rb +107 -0
- data/lib/nba/synergy_play_types.rb +211 -0
- data/lib/nba/team.rb +203 -127
- data/lib/nba/team_and_players_vs_players.rb +227 -0
- data/lib/nba/team_and_players_vs_players_stat.rb +155 -0
- data/lib/nba/team_dash_pt_pass.rb +157 -0
- data/lib/nba/team_dash_pt_reb.rb +216 -0
- data/lib/nba/team_dash_pt_shots.rb +244 -0
- data/lib/nba/team_dashboard.rb +275 -0
- data/lib/nba/team_dashboard_stat.rb +248 -0
- data/lib/nba/team_detail.rb +117 -0
- data/lib/nba/team_details.rb +173 -0
- data/lib/nba/team_estimated_metrics.rb +91 -0
- data/lib/nba/team_estimated_metrics_stat.rb +146 -0
- data/lib/nba/team_game_log.rb +143 -0
- data/lib/nba/team_game_log_entry.rb +246 -0
- data/lib/nba/team_game_log_stat.rb +275 -0
- data/lib/nba/team_game_logs.rb +163 -0
- data/lib/nba/team_game_streak.rb +111 -0
- data/lib/nba/team_game_streak_finder.rb +109 -0
- data/lib/nba/team_historical_leader.rb +207 -0
- data/lib/nba/team_historical_leaders.rb +98 -0
- data/lib/nba/team_historical_record.rb +139 -0
- data/lib/nba/team_info.rb +150 -0
- data/lib/nba/team_info_common.rb +177 -0
- data/lib/nba/team_on_off_overall_stat.rb +477 -0
- data/lib/nba/team_on_off_player_stat.rb +523 -0
- data/lib/nba/team_on_off_player_summary.rb +135 -0
- data/lib/nba/team_pass_stat.rb +183 -0
- data/lib/nba/team_player_dashboard.rb +212 -0
- data/lib/nba/team_player_on_off_details.rb +218 -0
- data/lib/nba/team_player_on_off_summary.rb +214 -0
- data/lib/nba/team_player_stat.rb +275 -0
- data/lib/nba/team_rebound_stat.rb +189 -0
- data/lib/nba/team_season_rank.rb +110 -0
- data/lib/nba/team_shot_stat.rb +173 -0
- data/lib/nba/team_vs_player.rb +151 -0
- data/lib/nba/team_vs_player_stat.rb +157 -0
- data/lib/nba/team_year.rb +55 -0
- data/lib/nba/team_year_by_year_stats.rb +152 -0
- data/lib/nba/team_year_stat.rb +282 -0
- data/lib/nba/teams.rb +33 -0
- data/lib/nba/upcoming_game.rb +115 -0
- data/lib/nba/utils.rb +94 -0
- data/lib/nba/version.rb +5 -2
- data/lib/nba/video_detail.rb +103 -0
- data/lib/nba/video_details.rb +118 -0
- data/lib/nba/video_details_asset.rb +115 -0
- data/lib/nba/video_details_asset_entry.rb +91 -0
- data/lib/nba/video_event.rb +83 -0
- data/lib/nba/video_event_asset.rb +91 -0
- data/lib/nba/video_events.rb +106 -0
- data/lib/nba/video_events_asset.rb +107 -0
- data/lib/nba/video_status.rb +129 -0
- data/lib/nba/video_status_entry.rb +161 -0
- data/lib/nba/vs_player_stat.rb +156 -0
- data/lib/nba/win_probability.rb +117 -0
- data/lib/nba/win_probability_point.rb +140 -0
- data/lib/nba.rb +249 -5
- data/sig/equalizer.rbs +3 -0
- data/sig/nba.rbs +7297 -0
- data/sig/shale.rbs +24 -0
- data/sig/thor.rbs +19 -0
- metadata +324 -95
- data/.gitignore +0 -18
- data/.travis.yml +0 -22
- data/Gemfile +0 -23
- data/LICENSE.md +0 -22
- data/Rakefile +0 -18
- data/bin/nba +0 -7
- data/cache/teams.json +0 -16529
- data/lib/faraday_middleware/scrape_game.rb +0 -41
- data/lib/nba/request.rb +0 -37
- data/nba.gemspec +0 -28
- data/spec/fixtures/games.html +0 -785
- data/spec/fixtures/teams.json +0 -16529
- data/spec/game_spec.rb +0 -40
- data/spec/spec_helper.rb +0 -25
- data/spec/team_spec.rb +0 -93
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require_relative "client"
|
|
3
|
+
require_relative "collection"
|
|
4
|
+
require_relative "utils"
|
|
5
|
+
|
|
6
|
+
require_relative "league_standing"
|
|
7
|
+
|
|
8
|
+
module NBA
|
|
9
|
+
# Provides methods to retrieve league standings with extended data
|
|
10
|
+
module LeagueStandings
|
|
11
|
+
# Result set name for standings
|
|
12
|
+
# @return [String] the result set name
|
|
13
|
+
STANDINGS = "Standings".freeze
|
|
14
|
+
|
|
15
|
+
# Season type constant for regular season
|
|
16
|
+
# @return [String] the season type
|
|
17
|
+
REGULAR_SEASON = "Regular Season".freeze
|
|
18
|
+
|
|
19
|
+
# Retrieves all league standings
|
|
20
|
+
#
|
|
21
|
+
# @api public
|
|
22
|
+
# @example
|
|
23
|
+
# standings = NBA::LeagueStandings.all(season: 2024)
|
|
24
|
+
# standings.each { |s| puts "#{s.playoff_rank}. #{s.full_name}: #{s.wins}-#{s.losses}" }
|
|
25
|
+
# @param season [Integer] the season year
|
|
26
|
+
# @param season_type [String] the season type
|
|
27
|
+
# @param league [String, League] the league ID or League object (default NBA)
|
|
28
|
+
# @param client [Client] the API client to use
|
|
29
|
+
# @return [Collection] a collection of standings
|
|
30
|
+
def self.all(season: Utils.current_season, season_type: REGULAR_SEASON, league: League::NBA, client: CLIENT)
|
|
31
|
+
league_id = Utils.extract_league_id(league)
|
|
32
|
+
path = build_path(season, season_type, league_id)
|
|
33
|
+
response = client.get(path)
|
|
34
|
+
parse_response(response)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Retrieves standings for a specific conference
|
|
38
|
+
#
|
|
39
|
+
# @api public
|
|
40
|
+
# @example
|
|
41
|
+
# west = NBA::LeagueStandings.conference("West", season: 2024)
|
|
42
|
+
# west.each { |s| puts "#{s.playoff_rank}. #{s.full_name}" }
|
|
43
|
+
# @param conference_name [String] the conference name ("East" or "West")
|
|
44
|
+
# @param season [Integer] the season year
|
|
45
|
+
# @param season_type [String] the season type
|
|
46
|
+
# @param league [String, League] the league ID or League object (default NBA)
|
|
47
|
+
# @param client [Client] the API client to use
|
|
48
|
+
# @return [Collection] a collection of standings
|
|
49
|
+
def self.conference(conference_name, season: Utils.current_season, season_type: REGULAR_SEASON, league: League::NBA,
|
|
50
|
+
client: CLIENT)
|
|
51
|
+
all_standings = all(season: season, season_type: season_type, league: league, client: client)
|
|
52
|
+
filtered = all_standings.select { |s| s.conference.eql?(conference_name) }
|
|
53
|
+
Collection.new(filtered)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Builds the API request path
|
|
57
|
+
# @api private
|
|
58
|
+
# @return [String] the request path
|
|
59
|
+
def self.build_path(season, season_type, league_id)
|
|
60
|
+
season_str = Utils.format_season(season)
|
|
61
|
+
encoded_type = season_type
|
|
62
|
+
"leaguestandingsv3?LeagueID=#{league_id}&Season=#{season_str}&SeasonType=#{encoded_type}"
|
|
63
|
+
end
|
|
64
|
+
private_class_method :build_path
|
|
65
|
+
|
|
66
|
+
# Parses the API response into standing objects
|
|
67
|
+
# @api private
|
|
68
|
+
# @return [Collection] collection of standings
|
|
69
|
+
def self.parse_response(response)
|
|
70
|
+
return Collection.new unless response
|
|
71
|
+
|
|
72
|
+
data = JSON.parse(response)
|
|
73
|
+
result_set = find_result_set(data)
|
|
74
|
+
return Collection.new unless result_set
|
|
75
|
+
|
|
76
|
+
headers = result_set["headers"]
|
|
77
|
+
rows = result_set["rowSet"]
|
|
78
|
+
return Collection.new unless headers && rows
|
|
79
|
+
|
|
80
|
+
standings = rows.map { |row| build_standing(headers, row) }
|
|
81
|
+
Collection.new(standings)
|
|
82
|
+
end
|
|
83
|
+
private_class_method :parse_response
|
|
84
|
+
|
|
85
|
+
# Finds the standings result set in the response
|
|
86
|
+
# @api private
|
|
87
|
+
# @return [Hash, nil] the result set hash
|
|
88
|
+
def self.find_result_set(data)
|
|
89
|
+
result_sets = data["resultSets"]
|
|
90
|
+
return unless result_sets
|
|
91
|
+
|
|
92
|
+
result_sets.find { |rs| rs["name"].eql?(STANDINGS) }
|
|
93
|
+
end
|
|
94
|
+
private_class_method :find_result_set
|
|
95
|
+
|
|
96
|
+
# Builds a LeagueStanding object from raw data
|
|
97
|
+
# @api private
|
|
98
|
+
# @return [LeagueStanding] the standing object
|
|
99
|
+
def self.build_standing(headers, row)
|
|
100
|
+
data = headers.zip(row).to_h
|
|
101
|
+
LeagueStanding.new(**standing_attributes(data))
|
|
102
|
+
end
|
|
103
|
+
private_class_method :build_standing
|
|
104
|
+
|
|
105
|
+
# Combines all standing attributes
|
|
106
|
+
# @api private
|
|
107
|
+
# @return [Hash] the combined attributes
|
|
108
|
+
def self.standing_attributes(data)
|
|
109
|
+
identity_attributes(data).merge(record_attributes(data), streak_attributes(data), points_attributes(data))
|
|
110
|
+
end
|
|
111
|
+
private_class_method :standing_attributes
|
|
112
|
+
|
|
113
|
+
# Extracts identity attributes from data
|
|
114
|
+
# @api private
|
|
115
|
+
# @return [Hash] identity attributes
|
|
116
|
+
def self.identity_attributes(data)
|
|
117
|
+
{league_id: data.fetch("LeagueID"), season_id: data.fetch("SeasonID"),
|
|
118
|
+
team_id: data.fetch("TeamID"), team_city: data.fetch("TeamCity"),
|
|
119
|
+
team_name: data.fetch("TeamName"), team_slug: data.fetch("TeamSlug"),
|
|
120
|
+
conference: data.fetch("Conference"), division: data.fetch("Division"),
|
|
121
|
+
clinch_indicator: data.fetch("ClinchIndicator")}
|
|
122
|
+
end
|
|
123
|
+
private_class_method :identity_attributes
|
|
124
|
+
|
|
125
|
+
# Extracts record attributes from data
|
|
126
|
+
# @api private
|
|
127
|
+
# @return [Hash] record attributes
|
|
128
|
+
def self.record_attributes(data)
|
|
129
|
+
{conference_record: data.fetch("ConferenceRecord"), playoff_rank: data.fetch("PlayoffRank"),
|
|
130
|
+
division_record: data.fetch("DivisionRecord"), division_rank: data.fetch("DivisionRank"),
|
|
131
|
+
wins: data.fetch("WINS"), losses: data.fetch("LOSSES"), win_pct: data.fetch("WinPCT"),
|
|
132
|
+
league_rank: data.fetch("LeagueRank"), record: data.fetch("Record"),
|
|
133
|
+
home_record: data.fetch("HOME"), road_record: data.fetch("ROAD"),
|
|
134
|
+
conference_games_back: data.fetch("ConferenceGamesBack")}
|
|
135
|
+
end
|
|
136
|
+
private_class_method :record_attributes
|
|
137
|
+
|
|
138
|
+
# Extracts streak attributes from data
|
|
139
|
+
# @api private
|
|
140
|
+
# @return [Hash] streak attributes
|
|
141
|
+
def self.streak_attributes(data)
|
|
142
|
+
{l10_record: data.fetch("L10"), long_win_streak: data.fetch("LongWinStreak"),
|
|
143
|
+
long_loss_streak: data.fetch("LongLossStreak"), current_streak: data.fetch("CurrentStreak"),
|
|
144
|
+
clinched_conference_title: data.fetch("ClinchedConferenceTitle"),
|
|
145
|
+
clinched_playoff_birth: data.fetch("ClinchedPlayoffBirth"),
|
|
146
|
+
eliminated_conference: data.fetch("EliminatedConference")}
|
|
147
|
+
end
|
|
148
|
+
private_class_method :streak_attributes
|
|
149
|
+
|
|
150
|
+
# Extracts points attributes from data
|
|
151
|
+
# @api private
|
|
152
|
+
# @return [Hash] points attributes
|
|
153
|
+
def self.points_attributes(data)
|
|
154
|
+
{points_pg: data.fetch("PointsPG"), opp_points_pg: data.fetch("OppPointsPG"),
|
|
155
|
+
diff_points_pg: data.fetch("DiffPointsPG")}
|
|
156
|
+
end
|
|
157
|
+
private_class_method :points_attributes
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module NBA
|
|
2
|
+
# Represents league-wide shot data
|
|
3
|
+
class LeagueWideShotStat < Shale::Mapper
|
|
4
|
+
include Equalizer.new(:shot_zone_basic, :shot_zone_area, :shot_zone_range)
|
|
5
|
+
|
|
6
|
+
# @!attribute [rw] grid_type
|
|
7
|
+
# Returns the grid type
|
|
8
|
+
# @api public
|
|
9
|
+
# @example
|
|
10
|
+
# stat.grid_type #=> "Shot Zone Basic"
|
|
11
|
+
# @return [String] the grid type
|
|
12
|
+
attribute :grid_type, Shale::Type::String
|
|
13
|
+
|
|
14
|
+
# @!attribute [rw] shot_zone_basic
|
|
15
|
+
# Returns the basic shot zone
|
|
16
|
+
# @api public
|
|
17
|
+
# @example
|
|
18
|
+
# stat.shot_zone_basic #=> "Mid-Range"
|
|
19
|
+
# @return [String] the basic zone
|
|
20
|
+
attribute :shot_zone_basic, Shale::Type::String
|
|
21
|
+
|
|
22
|
+
# @!attribute [rw] shot_zone_area
|
|
23
|
+
# Returns the shot zone area
|
|
24
|
+
# @api public
|
|
25
|
+
# @example
|
|
26
|
+
# stat.shot_zone_area #=> "Left Side"
|
|
27
|
+
# @return [String] the zone area
|
|
28
|
+
attribute :shot_zone_area, Shale::Type::String
|
|
29
|
+
|
|
30
|
+
# @!attribute [rw] shot_zone_range
|
|
31
|
+
# Returns the shot zone range
|
|
32
|
+
# @api public
|
|
33
|
+
# @example
|
|
34
|
+
# stat.shot_zone_range #=> "16-24 ft."
|
|
35
|
+
# @return [String] the zone range
|
|
36
|
+
attribute :shot_zone_range, Shale::Type::String
|
|
37
|
+
|
|
38
|
+
# @!attribute [rw] fga
|
|
39
|
+
# Returns field goals attempted
|
|
40
|
+
# @api public
|
|
41
|
+
# @example
|
|
42
|
+
# stat.fga #=> 1500
|
|
43
|
+
# @return [Integer] field goals attempted
|
|
44
|
+
attribute :fga, Shale::Type::Integer
|
|
45
|
+
|
|
46
|
+
# @!attribute [rw] fgm
|
|
47
|
+
# Returns field goals made
|
|
48
|
+
# @api public
|
|
49
|
+
# @example
|
|
50
|
+
# stat.fgm #=> 650
|
|
51
|
+
# @return [Integer] field goals made
|
|
52
|
+
attribute :fgm, Shale::Type::Integer
|
|
53
|
+
|
|
54
|
+
# @!attribute [rw] fg_pct
|
|
55
|
+
# Returns field goal percentage
|
|
56
|
+
# @api public
|
|
57
|
+
# @example
|
|
58
|
+
# stat.fg_pct #=> 0.433
|
|
59
|
+
# @return [Float] field goal percentage
|
|
60
|
+
attribute :fg_pct, Shale::Type::Float
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
module NBA
|
|
2
|
+
# Represents a live play-by-play action
|
|
3
|
+
class LiveAction < Shale::Mapper
|
|
4
|
+
include Equalizer.new(:action_number, :game_id)
|
|
5
|
+
|
|
6
|
+
# @!attribute [rw] game_id
|
|
7
|
+
# Returns the game ID
|
|
8
|
+
# @api public
|
|
9
|
+
# @example
|
|
10
|
+
# action.game_id #=> "0022400001"
|
|
11
|
+
# @return [String] the game ID
|
|
12
|
+
attribute :game_id, Shale::Type::String
|
|
13
|
+
|
|
14
|
+
# @!attribute [rw] action_number
|
|
15
|
+
# Returns the action number
|
|
16
|
+
# @api public
|
|
17
|
+
# @example
|
|
18
|
+
# action.action_number #=> 1
|
|
19
|
+
# @return [Integer] the action number
|
|
20
|
+
attribute :action_number, Shale::Type::Integer
|
|
21
|
+
|
|
22
|
+
# @!attribute [rw] clock
|
|
23
|
+
# Returns the game clock at time of action
|
|
24
|
+
# @api public
|
|
25
|
+
# @example
|
|
26
|
+
# action.clock #=> "PT11M47S"
|
|
27
|
+
# @return [String] the clock in ISO 8601 duration format
|
|
28
|
+
attribute :clock, Shale::Type::String
|
|
29
|
+
|
|
30
|
+
# @!attribute [rw] time_actual
|
|
31
|
+
# Returns the actual time of the action
|
|
32
|
+
# @api public
|
|
33
|
+
# @example
|
|
34
|
+
# action.time_actual #=> "2024-10-22T19:10:00Z"
|
|
35
|
+
# @return [String] the actual time in UTC
|
|
36
|
+
attribute :time_actual, Shale::Type::String
|
|
37
|
+
|
|
38
|
+
# @!attribute [rw] period
|
|
39
|
+
# Returns the period
|
|
40
|
+
# @api public
|
|
41
|
+
# @example
|
|
42
|
+
# action.period #=> 1
|
|
43
|
+
# @return [Integer] the period number
|
|
44
|
+
attribute :period, Shale::Type::Integer
|
|
45
|
+
|
|
46
|
+
# @!attribute [rw] period_type
|
|
47
|
+
# Returns the period type
|
|
48
|
+
# @api public
|
|
49
|
+
# @example
|
|
50
|
+
# action.period_type #=> "REGULAR"
|
|
51
|
+
# @return [String] the period type (REGULAR, OVERTIME)
|
|
52
|
+
attribute :period_type, Shale::Type::String
|
|
53
|
+
|
|
54
|
+
# @!attribute [rw] action_type
|
|
55
|
+
# Returns the action type
|
|
56
|
+
# @api public
|
|
57
|
+
# @example
|
|
58
|
+
# action.action_type #=> "2pt"
|
|
59
|
+
# @return [String] the action type
|
|
60
|
+
attribute :action_type, Shale::Type::String
|
|
61
|
+
|
|
62
|
+
# @!attribute [rw] sub_type
|
|
63
|
+
# Returns the action subtype
|
|
64
|
+
# @api public
|
|
65
|
+
# @example
|
|
66
|
+
# action.sub_type #=> "LAYUP"
|
|
67
|
+
# @return [String] the action subtype
|
|
68
|
+
attribute :sub_type, Shale::Type::String
|
|
69
|
+
|
|
70
|
+
# @!attribute [rw] qualifier
|
|
71
|
+
# Returns any qualifier for the action
|
|
72
|
+
# @api public
|
|
73
|
+
# @example
|
|
74
|
+
# action.qualifiers #=> ["DRIVING"]
|
|
75
|
+
# @return [Array<String>] the qualifiers
|
|
76
|
+
attribute :qualifiers, Shale::Type::String, collection: true
|
|
77
|
+
|
|
78
|
+
# @!attribute [rw] description
|
|
79
|
+
# Returns the play description
|
|
80
|
+
# @api public
|
|
81
|
+
# @example
|
|
82
|
+
# action.description #=> "Curry Driving Layup"
|
|
83
|
+
# @return [String] the description
|
|
84
|
+
attribute :description, Shale::Type::String
|
|
85
|
+
|
|
86
|
+
# @!attribute [rw] player_id
|
|
87
|
+
# Returns the player ID involved in the action
|
|
88
|
+
# @api public
|
|
89
|
+
# @example
|
|
90
|
+
# action.player_id #=> 201939
|
|
91
|
+
# @return [Integer] the player ID
|
|
92
|
+
attribute :player_id, Shale::Type::Integer
|
|
93
|
+
|
|
94
|
+
# @!attribute [rw] player_name
|
|
95
|
+
# Returns the player name
|
|
96
|
+
# @api public
|
|
97
|
+
# @example
|
|
98
|
+
# action.player_name #=> "Stephen Curry"
|
|
99
|
+
# @return [String] the player name
|
|
100
|
+
attribute :player_name, Shale::Type::String
|
|
101
|
+
|
|
102
|
+
# @!attribute [rw] player_name_i
|
|
103
|
+
# Returns the player name in abbreviated format
|
|
104
|
+
# @api public
|
|
105
|
+
# @example
|
|
106
|
+
# action.player_name_i #=> "S. Curry"
|
|
107
|
+
# @return [String] the abbreviated player name
|
|
108
|
+
attribute :player_name_i, Shale::Type::String
|
|
109
|
+
|
|
110
|
+
# @!attribute [rw] team_id
|
|
111
|
+
# Returns the team ID
|
|
112
|
+
# @api public
|
|
113
|
+
# @example
|
|
114
|
+
# action.team_id #=> 1610612744
|
|
115
|
+
# @return [Integer] the team ID
|
|
116
|
+
attribute :team_id, Shale::Type::Integer
|
|
117
|
+
|
|
118
|
+
# @!attribute [rw] team_tricode
|
|
119
|
+
# Returns the team tricode
|
|
120
|
+
# @api public
|
|
121
|
+
# @example
|
|
122
|
+
# action.team_tricode #=> "GSW"
|
|
123
|
+
# @return [String] the team tricode
|
|
124
|
+
attribute :team_tricode, Shale::Type::String
|
|
125
|
+
|
|
126
|
+
# @!attribute [rw] score_home
|
|
127
|
+
# Returns the home team score after this action
|
|
128
|
+
# @api public
|
|
129
|
+
# @example
|
|
130
|
+
# action.score_home #=> "2"
|
|
131
|
+
# @return [String] the home score
|
|
132
|
+
attribute :score_home, Shale::Type::String
|
|
133
|
+
|
|
134
|
+
# @!attribute [rw] score_away
|
|
135
|
+
# Returns the away team score after this action
|
|
136
|
+
# @api public
|
|
137
|
+
# @example
|
|
138
|
+
# action.score_away #=> "0"
|
|
139
|
+
# @return [String] the away score
|
|
140
|
+
attribute :score_away, Shale::Type::String
|
|
141
|
+
|
|
142
|
+
# @!attribute [rw] points_total
|
|
143
|
+
# Returns the points from this action
|
|
144
|
+
# @api public
|
|
145
|
+
# @example
|
|
146
|
+
# action.points_total #=> 2
|
|
147
|
+
# @return [Integer] the points
|
|
148
|
+
attribute :points_total, Shale::Type::Integer
|
|
149
|
+
|
|
150
|
+
# @!attribute [rw] x_legacy
|
|
151
|
+
# Returns the x coordinate on the court
|
|
152
|
+
# @api public
|
|
153
|
+
# @example
|
|
154
|
+
# action.x_legacy #=> 5.0
|
|
155
|
+
# @return [Float] the x coordinate
|
|
156
|
+
attribute :x_legacy, Shale::Type::Float
|
|
157
|
+
|
|
158
|
+
# @!attribute [rw] y_legacy
|
|
159
|
+
# Returns the y coordinate on the court
|
|
160
|
+
# @api public
|
|
161
|
+
# @example
|
|
162
|
+
# action.y_legacy #=> 25.0
|
|
163
|
+
# @return [Float] the y coordinate
|
|
164
|
+
attribute :y_legacy, Shale::Type::Float
|
|
165
|
+
|
|
166
|
+
# @!attribute [rw] shot_distance
|
|
167
|
+
# Returns the shot distance
|
|
168
|
+
# @api public
|
|
169
|
+
# @example
|
|
170
|
+
# action.shot_distance #=> 2.5
|
|
171
|
+
# @return [Float] the shot distance in feet
|
|
172
|
+
attribute :shot_distance, Shale::Type::Float
|
|
173
|
+
|
|
174
|
+
# @!attribute [rw] is_field_goal
|
|
175
|
+
# Returns whether this is a field goal attempt
|
|
176
|
+
# @api public
|
|
177
|
+
# @example
|
|
178
|
+
# action.is_field_goal #=> 1
|
|
179
|
+
# @return [Integer] 1 if field goal, 0 otherwise
|
|
180
|
+
attribute :is_field_goal, Shale::Type::Integer
|
|
181
|
+
|
|
182
|
+
# @!attribute [rw] shot_result
|
|
183
|
+
# Returns the shot result
|
|
184
|
+
# @api public
|
|
185
|
+
# @example
|
|
186
|
+
# action.shot_result #=> "Made"
|
|
187
|
+
# @return [String] "Made" or "Missed"
|
|
188
|
+
attribute :shot_result, Shale::Type::String
|
|
189
|
+
|
|
190
|
+
# Returns the player object
|
|
191
|
+
#
|
|
192
|
+
# @api public
|
|
193
|
+
# @example
|
|
194
|
+
# action.player #=> #<NBA::Player>
|
|
195
|
+
# @return [Player, nil] the player object
|
|
196
|
+
def player
|
|
197
|
+
Players.find(player_id)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Returns the team object
|
|
201
|
+
#
|
|
202
|
+
# @api public
|
|
203
|
+
# @example
|
|
204
|
+
# action.team #=> #<NBA::Team>
|
|
205
|
+
# @return [Team, nil] the team object
|
|
206
|
+
def team
|
|
207
|
+
Teams.find(team_id)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Returns whether this is a field goal attempt
|
|
211
|
+
#
|
|
212
|
+
# @api public
|
|
213
|
+
# @example
|
|
214
|
+
# action.field_goal? #=> true
|
|
215
|
+
# @return [Boolean] true if field goal attempt
|
|
216
|
+
def field_goal?
|
|
217
|
+
is_field_goal.eql?(1)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Returns whether this shot was made
|
|
221
|
+
#
|
|
222
|
+
# @api public
|
|
223
|
+
# @example
|
|
224
|
+
# action.made? #=> true
|
|
225
|
+
# @return [Boolean] true if shot was made
|
|
226
|
+
def made?
|
|
227
|
+
shot_result.eql?("Made")
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Returns whether this shot was missed
|
|
231
|
+
#
|
|
232
|
+
# @api public
|
|
233
|
+
# @example
|
|
234
|
+
# action.missed? #=> true
|
|
235
|
+
# @return [Boolean] true if shot was missed
|
|
236
|
+
def missed?
|
|
237
|
+
shot_result.eql?("Missed")
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require_relative "collection"
|
|
3
|
+
require_relative "live_connection"
|
|
4
|
+
require_relative "utils"
|
|
5
|
+
|
|
6
|
+
require_relative "live_player_stat"
|
|
7
|
+
|
|
8
|
+
module NBA
|
|
9
|
+
# Provides methods to retrieve live box score data
|
|
10
|
+
module LiveBoxScore
|
|
11
|
+
# Retrieves live box score for a game
|
|
12
|
+
#
|
|
13
|
+
# @api public
|
|
14
|
+
# @example
|
|
15
|
+
# stats = NBA::LiveBoxScore.find(game: "0022400001")
|
|
16
|
+
# stats.each { |s| puts "#{s.name}: #{s.points} pts" }
|
|
17
|
+
# @param game [String, Game] the game ID or Game object
|
|
18
|
+
# @param client [LiveConnection] the API client to use
|
|
19
|
+
# @return [Collection] a collection of player stats
|
|
20
|
+
def self.find(game:, client: LIVE_CLIENT)
|
|
21
|
+
game_id = Utils.extract_id(game)
|
|
22
|
+
path = "boxscore/boxscore_#{game_id}.json"
|
|
23
|
+
response = client.get(path)
|
|
24
|
+
parse_response(response, game_id)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Parses the live box score API response
|
|
28
|
+
#
|
|
29
|
+
# @api private
|
|
30
|
+
# @param response [String, nil] the JSON response body
|
|
31
|
+
# @param game_id [String] the game ID
|
|
32
|
+
# @return [Collection] a collection of player stats
|
|
33
|
+
def self.parse_response(response, game_id)
|
|
34
|
+
return Collection.new unless response
|
|
35
|
+
|
|
36
|
+
data = JSON.parse(response)
|
|
37
|
+
game_data = data["game"]
|
|
38
|
+
return Collection.new unless game_data
|
|
39
|
+
|
|
40
|
+
home_players = extract_players(game_data, "homeTeam", game_id)
|
|
41
|
+
away_players = extract_players(game_data, "awayTeam", game_id)
|
|
42
|
+
Collection.new(home_players + away_players)
|
|
43
|
+
end
|
|
44
|
+
private_class_method :parse_response
|
|
45
|
+
|
|
46
|
+
# Extracts players from a team's data
|
|
47
|
+
#
|
|
48
|
+
# @api private
|
|
49
|
+
# @param game_data [Hash] the game data
|
|
50
|
+
# @param team_key [String] the team key ("homeTeam" or "awayTeam")
|
|
51
|
+
# @param game_id [String] the game ID
|
|
52
|
+
# @return [Array<LivePlayerStat>] array of player stats
|
|
53
|
+
def self.extract_players(game_data, team_key, game_id)
|
|
54
|
+
team_data = game_data[team_key]
|
|
55
|
+
return [] unless team_data
|
|
56
|
+
|
|
57
|
+
players = team_data["players"]
|
|
58
|
+
return [] unless players
|
|
59
|
+
|
|
60
|
+
team_id = team_data["teamId"]
|
|
61
|
+
team_tricode = team_data["teamTricode"]
|
|
62
|
+
|
|
63
|
+
players.map { |p| build_player_stat(p, game_id, team_id, team_tricode) }
|
|
64
|
+
end
|
|
65
|
+
private_class_method :extract_players
|
|
66
|
+
|
|
67
|
+
# Builds a LivePlayerStat object from raw data
|
|
68
|
+
#
|
|
69
|
+
# @api private
|
|
70
|
+
# @param data [Hash] the player data
|
|
71
|
+
# @param game_id [String] the game ID
|
|
72
|
+
# @param team_id [Integer] the team ID
|
|
73
|
+
# @param team_tricode [String] the team tricode
|
|
74
|
+
# @return [LivePlayerStat] the player stat object
|
|
75
|
+
def self.build_player_stat(data, game_id, team_id, team_tricode)
|
|
76
|
+
LivePlayerStat.new(**player_stat_attributes(data, game_id, team_id, team_tricode))
|
|
77
|
+
end
|
|
78
|
+
private_class_method :build_player_stat
|
|
79
|
+
|
|
80
|
+
# Combines all player stat attributes
|
|
81
|
+
#
|
|
82
|
+
# @api private
|
|
83
|
+
# @param data [Hash] the player data
|
|
84
|
+
# @param game_id [String] the game ID
|
|
85
|
+
# @param team_id [Integer] the team ID
|
|
86
|
+
# @param team_tricode [String] the team tricode
|
|
87
|
+
# @return [Hash] the combined attributes
|
|
88
|
+
def self.player_stat_attributes(data, game_id, team_id, team_tricode)
|
|
89
|
+
identity_attributes(data, game_id, team_id, team_tricode)
|
|
90
|
+
.merge(counting_attributes(data))
|
|
91
|
+
.merge(shooting_attributes(data))
|
|
92
|
+
end
|
|
93
|
+
private_class_method :player_stat_attributes
|
|
94
|
+
|
|
95
|
+
# Extracts identity attributes from data
|
|
96
|
+
#
|
|
97
|
+
# @api private
|
|
98
|
+
# @param data [Hash] the player data
|
|
99
|
+
# @param game_id [String] the game ID
|
|
100
|
+
# @param team_id [Integer] the team ID
|
|
101
|
+
# @param team_tricode [String] the team tricode
|
|
102
|
+
# @return [Hash] identity attributes
|
|
103
|
+
def self.identity_attributes(data, game_id, team_id, team_tricode)
|
|
104
|
+
{game_id: game_id, player_id: data["personId"], name: data["name"],
|
|
105
|
+
first_name: data["firstName"], family_name: data["familyName"],
|
|
106
|
+
jersey_num: data["jerseyNum"], position: data["position"],
|
|
107
|
+
team_id: team_id, team_tricode: team_tricode, starter: data["starter"],
|
|
108
|
+
minutes: data.dig("statistics", "minutes")}
|
|
109
|
+
end
|
|
110
|
+
private_class_method :identity_attributes
|
|
111
|
+
|
|
112
|
+
# Extracts counting stats attributes from data
|
|
113
|
+
#
|
|
114
|
+
# @api private
|
|
115
|
+
# @param data [Hash] the player data
|
|
116
|
+
# @return [Hash] counting attributes
|
|
117
|
+
def self.counting_attributes(data)
|
|
118
|
+
stats = data["statistics"] || {}
|
|
119
|
+
{points: stats["points"], rebounds_total: stats["reboundsTotal"],
|
|
120
|
+
rebounds_offensive: stats["reboundsOffensive"], rebounds_defensive: stats["reboundsDefensive"],
|
|
121
|
+
assists: stats["assists"], steals: stats["steals"], blocks: stats["blocks"],
|
|
122
|
+
turnovers: stats["turnovers"], fouls_personal: stats["foulsPersonal"],
|
|
123
|
+
plus_minus: stats["plusMinusPoints"]}
|
|
124
|
+
end
|
|
125
|
+
private_class_method :counting_attributes
|
|
126
|
+
|
|
127
|
+
# Extracts shooting stats attributes from data
|
|
128
|
+
#
|
|
129
|
+
# @api private
|
|
130
|
+
# @param data [Hash] the player data
|
|
131
|
+
# @return [Hash] shooting attributes
|
|
132
|
+
def self.shooting_attributes(data)
|
|
133
|
+
stats = data["statistics"] || {}
|
|
134
|
+
{field_goals_made: stats["fieldGoalsMade"], field_goals_attempted: stats["fieldGoalsAttempted"],
|
|
135
|
+
field_goals_percentage: stats["fieldGoalsPercentage"],
|
|
136
|
+
three_pointers_made: stats["threePointersMade"], three_pointers_attempted: stats["threePointersAttempted"],
|
|
137
|
+
three_pointers_percentage: stats["threePointersPercentage"],
|
|
138
|
+
free_throws_made: stats["freeThrowsMade"], free_throws_attempted: stats["freeThrowsAttempted"],
|
|
139
|
+
free_throws_percentage: stats["freeThrowsPercentage"]}
|
|
140
|
+
end
|
|
141
|
+
private_class_method :shooting_attributes
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
require "net/http"
|
|
2
|
+
require "openssl"
|
|
3
|
+
require "stringio"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "zlib"
|
|
6
|
+
|
|
7
|
+
module NBA
|
|
8
|
+
# Handles HTTP connections to the NBA Live Data API
|
|
9
|
+
class LiveConnection
|
|
10
|
+
# Default base URL for the NBA Live Data API
|
|
11
|
+
# @return [String] the default base URL
|
|
12
|
+
BASE_URL = "https://cdn.nba.com/static/json/liveData/".freeze
|
|
13
|
+
|
|
14
|
+
# Returns the base URL for API requests
|
|
15
|
+
#
|
|
16
|
+
# @api private
|
|
17
|
+
# @return [String] the base URL
|
|
18
|
+
attr_reader :base_url
|
|
19
|
+
|
|
20
|
+
# Initializes a new LiveConnection object
|
|
21
|
+
#
|
|
22
|
+
# @api public
|
|
23
|
+
# @example
|
|
24
|
+
# connection = NBA::LiveConnection.new
|
|
25
|
+
# @param base_url [String] the base URL for API requests
|
|
26
|
+
# @return [NBA::LiveConnection] a new live connection instance
|
|
27
|
+
def initialize(base_url: BASE_URL)
|
|
28
|
+
@base_url = base_url
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Makes a GET request to the specified path
|
|
32
|
+
#
|
|
33
|
+
# @api public
|
|
34
|
+
# @example
|
|
35
|
+
# connection.get("scoreboard/todaysScoreboard_00.json")
|
|
36
|
+
# @param path [String] the API path to request
|
|
37
|
+
# @return [String] the response body
|
|
38
|
+
def get(path)
|
|
39
|
+
uri = URI.join(base_url, path)
|
|
40
|
+
hostname = uri.hostname or raise ArgumentError, "Invalid URI: #{uri}"
|
|
41
|
+
request = Net::HTTP::Get.new(uri)
|
|
42
|
+
apply_headers(request)
|
|
43
|
+
|
|
44
|
+
Net::HTTP.start(hostname, uri.port, use_ssl: uri.scheme.eql?("https")) do |http|
|
|
45
|
+
response = http.request(request)
|
|
46
|
+
decode_body(response) if response.is_a?(Net::HTTPSuccess)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
# Applies the required headers for NBA Live Data API requests
|
|
53
|
+
#
|
|
54
|
+
# @api private
|
|
55
|
+
# @param request [Net::HTTP::Get] the request object
|
|
56
|
+
# @return [void]
|
|
57
|
+
def apply_headers(request)
|
|
58
|
+
request["User-Agent"] = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " \
|
|
59
|
+
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
60
|
+
request["Accept"] = "application/json, text/plain, */*"
|
|
61
|
+
request["Accept-Language"] = "en-US,en;q=0.9"
|
|
62
|
+
request["Accept-Encoding"] = "gzip, deflate, identity"
|
|
63
|
+
request["Connection"] = "keep-alive"
|
|
64
|
+
request["Referer"] = "https://www.nba.com/"
|
|
65
|
+
request["Origin"] = "https://www.nba.com"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Decodes the response body based on Content-Encoding
|
|
69
|
+
#
|
|
70
|
+
# @api private
|
|
71
|
+
# @param response [Net::HTTPResponse] the HTTP response
|
|
72
|
+
# @return [String, nil] the decoded response body
|
|
73
|
+
def decode_body(response)
|
|
74
|
+
case response["Content-Encoding"]
|
|
75
|
+
when "gzip"
|
|
76
|
+
Zlib::GzipReader.new(StringIO.new(response.body)).read
|
|
77
|
+
when "deflate"
|
|
78
|
+
Zlib::Inflate.inflate(response.body)
|
|
79
|
+
else
|
|
80
|
+
response.body
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|