R/fox_basketball.R

Defines functions fox_wbb_league_leaders fox_wnba_league_leaders fox_wbb_teams_all fox_wbb_teams fox_wnba_teams fox_wbb_standings fox_wnba_standings fox_wbb_team_gamelog fox_wnba_team_gamelog fox_wbb_team_stats fox_wnba_team_stats fox_wbb_team_roster fox_wnba_team_roster fox_wbb_odds fox_wnba_odds fox_wbb_boxscore fox_wnba_boxscore fox_wbb_pbp fox_wnba_pbp .fox_bb_resource .fox_bb_odds .fox_bb_leaders .fox_bb_teams .fox_bb_standings .fox_bb_gamelog .fox_bb_team_stats .fox_bb_roster .fox_bb_boxscore .fox_bb_pbp .fox_table_df .fox_uri_id .fox_cells .fox_bb_get .fox_or .fox_bb_key

Documented in fox_wbb_boxscore fox_wbb_league_leaders fox_wbb_odds fox_wbb_pbp fox_wbb_standings fox_wbb_team_gamelog fox_wbb_team_roster fox_wbb_teams fox_wbb_teams_all fox_wbb_team_stats fox_wnba_boxscore fox_wnba_league_leaders fox_wnba_odds fox_wnba_pbp fox_wnba_standings fox_wnba_team_gamelog fox_wnba_team_roster fox_wnba_teams fox_wnba_team_stats

# fox_basketball.R -- Fox Sports "Bifrost" basketball wrappers (WNBA + WBB).
#
# Read-only wrappers over api.foxsports.com/bifrost/v1/{wnba,wcbk}/*, flattening
# Fox's layout JSON (sections -> tables -> rows -> cells) into tidy wehoop
# tibbles. Basketball play-by-play is period-based (QUARTER/HALF -> plays).
# WNBA uses the `wnba` slug; women's college basketball (WBB) uses the `wcbk`
# slug. Reverse-engineering notes + an OpenAPI spec live in the
# sdv-internal-refs repo.

# Fox's public Bifrost apikey. Resolvable from `getOption("wehoop.fox_data_key")`
# or the `WEHOOP_FOX_DATA_KEY` env var, with the literal as the final fallback.
.fox_bb_key <- function() {
  getOption(
    "wehoop.fox_data_key",
    Sys.getenv("WEHOOP_FOX_DATA_KEY", unset = "jE7yBJVRNAwdDesMgTzTXUUSx1It41Fq")
  )
}
.fox_or <- function(a, b) if (is.null(a) || length(a) == 0) b else a

# Internal: GET a Fox Bifrost layout document and parse it. Routes through the
# shared `.retry_request()` helper so the Fox path inherits the package-wide
# timeout, jittered exponential backoff, and proxy resolution (explicit arg ->
# `getOption("wehoop.proxy")` -> `http(s)_proxy` env vars).
#' @keywords internal
#' @importFrom jsonlite fromJSON
.fox_bb_get <- function(path, query = list()) {
  query[["apikey"]] <- .fox_or(query[["apikey"]], .fox_bb_key())
  query[["api-version"]] <- .fox_or(query[["api-version"]], "1.1")
  resp <- .retry_request(
    url = paste0("https://api.foxsports.com/bifrost/v1/", path),
    params = query,
    headers = c(Origin = "https://www.foxsports.com",
                Referer = "https://www.foxsports.com/")
  )
  check_status(resp)
  .resp_text(resp) |>
    jsonlite::fromJSON(simplifyDataFrame = FALSE, simplifyVector = FALSE, simplifyMatrix = FALSE)
}

.fox_cells <- function(cols) {
  vapply(cols, function(c) {
    v <- if (is.list(c)) c[["text"]] else c
    if (is.null(v) || length(v) == 0) NA_character_ else as.character(v)[1]
  }, character(1))
}
.fox_uri_id <- function(uri) {
  if (is.null(uri)) return(NA_character_)
  m <- regmatches(uri, regexpr("[0-9]+$", uri)); if (length(m)) m else NA_character_
}
#' @importFrom janitor make_clean_names
.fox_table_df <- function(tbl, extra = list()) {
  if (is.null(tbl)) return(NULL)
  hdr <- .fox_cells(tbl[["headers"]][[1]][["columns"]])
  nm <- janitor::make_clean_names(ifelse(is.na(hdr) | hdr == "", paste0("v", seq_along(hdr)), hdr))
  rws <- .fox_or(tbl[["rows"]], list()); if (!length(rws)) return(NULL)
  recs <- lapply(rws, function(r) {
    cells <- .fox_cells(r[["columns"]]); vals <- as.list(cells); names(vals) <- nm[seq_along(vals)]
    eid <- .fox_uri_id(.fox_or(r[["entityLink"]][["contentUri"]], NULL))
    as.data.frame(c(extra, vals, list(entity_id = eid)), stringsAsFactors = FALSE)
  })
  dplyr::bind_rows(recs)
}

# ---- parsers (return data.frame) ------------------------------------------
.fox_bb_pbp <- function(raw, game_id) {
  rows <- list()
  for (sec in .fox_or(raw[["pbp"]][["sections"]], list())) {
    for (grp in .fox_or(sec[["groups"]], list())) {
      period <- .fox_or(grp[["title"]], NA_character_)
      left <- .fox_or(grp[["leftTeamAbbr"]], NA_character_)
      right <- .fox_or(grp[["rightTeamAbbr"]], NA_character_)
      for (p in .fox_or(grp[["plays"]], list())) {
        rows[[length(rows) + 1]] <- data.frame(
          game_id = as.character(game_id), period = period, left_team = left, right_team = right,
          play_id = as.character(.fox_or(p[["id"]], NA_character_)),
          clock = .fox_or(p[["timeOfPlay"]], NA_character_),
          team = .fox_or(p[["entityLink"]][["title"]], .fox_or(p[["imageAltText"]], NA_character_)),
          left_score_change = as.character(.fox_or(p[["leftTeamScoreChange"]], NA)),
          right_score_change = as.character(.fox_or(p[["rightTeamScoreChange"]], NA)),
          play_text = .fox_or(p[["playDescription"]], NA_character_), stringsAsFactors = FALSE)
      }
    }
  }
  if (length(rows)) do.call(rbind, rows) else data.frame()
}
.fox_bb_boxscore <- function(raw, game_id) {
  rows <- list()
  for (sec in .fox_or(raw[["boxscore"]][["boxscoreSections"]], list())) {
    team <- .fox_or(sec[["title"]], NA_character_)
    for (item in .fox_or(sec[["boxscoreItems"]], list())) {
      tbl <- item[["boxscoreTable"]]; if (is.null(tbl)) next
      hdr <- .fox_cells(tbl[["headers"]][[1]][["columns"]])
      stat_group <- .fox_or(hdr[1], NA_character_)
      stat_names <- janitor::make_clean_names(.fox_or(hdr[-1], character(0)))
      for (r in .fox_or(tbl[["rows"]], list())) {
        cells <- .fox_cells(r[["columns"]]); player <- .fox_or(cells[1], NA_character_)
        aid <- .fox_uri_id(.fox_or(r[["entityLink"]][["contentUri"]], NULL)); vals <- cells[-1]
        for (j in seq_along(vals)) {
          rows[[length(rows) + 1]] <- data.frame(
            game_id = as.character(game_id), team = team, stat_group = stat_group,
            player = player, athlete_id = aid,
            stat = .fox_or(stat_names[j], paste0("v", j)), value = vals[j], stringsAsFactors = FALSE)
        }
      }
    }
  }
  if (length(rows)) do.call(rbind, rows) else data.frame()
}
.fox_bb_roster <- function(raw, team_id) {
  rows <- list()
  for (g in .fox_or(raw[["groups"]], list())) {
    hdr <- .fox_cells(g[["headers"]][[1]][["columns"]])
    group_label <- .fox_or(g[["title"]], .fox_or(hdr[1], NA_character_))
    col_names <- c("player", tolower(.fox_or(hdr[-1], character(0))))
    for (r in .fox_or(g[["rows"]], list())) {
      uri <- .fox_or(r[["entityLink"]][["contentUri"]], NULL)
      if (is.null(uri) || !grepl("athletes/", uri)) next
      cells <- .fox_cells(r[["columns"]]); vals <- as.list(cells); names(vals) <- col_names[seq_along(vals)]
      rows[[length(rows) + 1]] <- data.frame(
        team_id = as.character(team_id), position_group = group_label,
        as.data.frame(vals, stringsAsFactors = FALSE), athlete_id = .fox_uri_id(uri), stringsAsFactors = FALSE)
    }
  }
  if (length(rows)) dplyr::bind_rows(rows) else data.frame()
}
.fox_bb_team_stats <- function(raw, team_id) {
  rows <- list()
  for (sec in .fox_or(raw[["leadersSections"]], list())) {
    for (ld in .fox_or(sec[["leaders"]], list())) {
      rows[[length(rows) + 1]] <- data.frame(
        team_id = as.character(team_id), category = .fox_or(sec[["title"]], NA_character_),
        stat = .fox_or(ld[["title"]], NA_character_),
        stat_abbreviation = .fox_or(ld[["statAbbreviation"]], NA_character_),
        player = .fox_or(ld[["name"]], NA_character_), value = .fox_or(ld[["statValue"]], NA_character_),
        stringsAsFactors = FALSE)
    }
  }
  if (length(rows)) do.call(rbind, rows) else data.frame()
}
.fox_bb_gamelog <- function(raw, team_id) {
  rows <- list()
  for (sec in .fox_or(raw[["sectionList"]], list())) {
    category <- .fox_or(sec[["id"]], NA_character_)
    for (tbl in .fox_or(sec[["tables"]], list())) {
      hdr <- .fox_cells(tbl[["headers"]][[1]][["columns"]])
      season_type <- .fox_or(hdr[1], NA_character_)
      stat_names <- janitor::make_clean_names(.fox_or(hdr[-(1:2)], character(0)))
      for (r in .fox_or(tbl[["rows"]], list())) {
        cells <- .fox_cells(r[["columns"]]); gid <- .fox_uri_id(.fox_or(r[["entityLink"]][["contentUri"]], NULL))
        vals <- cells[-(1:2)]
        for (j in seq_along(vals)) {
          rows[[length(rows) + 1]] <- data.frame(
            team_id = as.character(team_id), season_type = season_type, category = category, game_id = gid,
            game_date = .fox_or(cells[1], NA_character_), opponent = .fox_or(cells[2], NA_character_),
            stat = .fox_or(stat_names[j], paste0("v", j)), value = vals[j], stringsAsFactors = FALSE)
        }
      }
    }
  }
  if (length(rows)) do.call(rbind, rows) else data.frame()
}
.fox_bb_standings <- function(raw, team_id) {
  parts <- list()
  for (s in .fox_or(raw[["standingsSections"]], list())) {
    for (tbl in .fox_or(s[["standings"]], list())) {
      parts[[length(parts) + 1]] <- .fox_table_df(
        tbl, extra = list(team_id = as.character(team_id), section = .fox_or(s[["title"]], NA_character_)))
    }
  }
  dplyr::bind_rows(parts)
}
.fox_bb_teams <- function(raw) {
  rows <- list()
  for (s in .fox_or(raw[["standingsSections"]], list())) {
    # Prefer the conference name from metadata$parameters[[3]] (e.g. "America
    # East"), fall back to pageTitle, then the generic title ("CONFERENCE").
    meta_params <- .fox_or(s[["metadata"]][["parameters"]], list())
    conf_name <- if (length(meta_params) >= 3) {
      v <- meta_params[[3]]
      if (is.character(v) && length(v) == 1 && nzchar(trimws(v))) trimws(v) else NULL
    } else NULL
    section <- if (!is.null(conf_name)) conf_name else {
      pt <- .fox_or(s[["pageTitle"]], NULL)
      if (!is.null(pt) && nchar(trimws(pt)) > 0) trimws(pt) else .fox_or(s[["title"]], NA_character_)
    }
    for (tbl in .fox_or(s[["standings"]], list())) {
      for (r in .fox_or(tbl[["rows"]], list())) {
        cells <- .fox_cells(r[["columns"]])
        eid <- .fox_uri_id(.fox_or(r[["entityLink"]][["contentUri"]], NULL))
        if (is.na(eid)) next
        # entityLink$title carries the full team name (e.g. "NEW YORK LIBERTY");
        # the table cells lead with the rank, so fall back to the second cell.
        title <- .fox_or(r[["entityLink"]][["title"]], NA_character_)
        has_title <- !is.na(title) && nzchar(trimws(title))
        nm <- if (has_title) stringr::str_to_title(tolower(title)) else .fox_or(cells[2], NA_character_)
        rows[[length(rows) + 1]] <- data.frame(
          fox_team_id = eid,
          fox_team_name = nm,
          fox_section = section,
          stringsAsFactors = FALSE)
      }
    }
  }
  if (!length(rows)) return(data.frame())
  out <- dplyr::bind_rows(rows)
  out[!duplicated(out$fox_team_id), , drop = FALSE]
}
.fox_bb_leaders <- function(raw) {
  parts <- lapply(.fox_or(raw[["sectionList"]], list()), function(s) .fox_table_df(s[["table"]]))
  dplyr::bind_rows(parts)
}
.fox_bb_odds <- function(raw, game_id) {
  sp <- raw[["sixPack"]]; o <- if (is.null(sp)) NULL else sp[["odds"]]; rows <- list()
  if (!is.null(o)) {
    hdr <- janitor::make_clean_names(.fox_cells(o[["columnHeaders"]]))
    for (r in .fox_or(o[["rows"]], list())) {
      vals <- vapply(.fox_or(r[["values"]], list()),
                     function(v) as.character(.fox_or(v[["odds"]], NA_character_)), character(1))
      rec <- as.list(vals); names(rec) <- hdr[seq_along(rec)]
      rows[[length(rows) + 1]] <- data.frame(
        game_id = as.character(game_id), team = .fox_or(r[["fullText"]], .fox_or(r[["text"]], NA_character_)),
        as.data.frame(rec, stringsAsFactors = FALSE), stringsAsFactors = FALSE)
    }
  }
  if (length(rows)) dplyr::bind_rows(rows) else data.frame()
}

# ---- dispatcher ------------------------------------------------------------
#' @keywords internal
#' @importFrom janitor clean_names
#' @importFrom dplyr as_tibble bind_rows
.fox_bb_resource <- function(sport, resource, game_id = NULL, team_id = NULL,
                             category = "scoring", who = "player", page = 0) {
  .args <- .capture_args()

  out <- data.frame()
  tryCatch(
    expr = {
      raw <- switch(
        resource,
        pbp = , boxscore = .fox_bb_get(paste0(sport, "/event/", game_id, "/data")),
        odds = .fox_bb_get(paste0(sport, "/event/", game_id, "/odds")),
        league_leaders = .fox_bb_get(paste0(sport, "/league/stats-con/", who, "/", category, "/", page)),
        roster = .fox_bb_get(paste0(sport, "/team/", team_id, "/roster")),
        team_stats = .fox_bb_get(paste0(sport, "/team/", team_id, "/stats")),
        gamelog = .fox_bb_get(paste0(sport, "/team/", team_id, "/gamelog")),
        standings = .fox_bb_get(paste0(sport, "/team/", team_id, "/standings")),
        teams = .fox_bb_get(paste0(sport, "/team/", team_id, "/standings")))
      df <- switch(
        resource,
        pbp = .fox_bb_pbp(raw, game_id), boxscore = .fox_bb_boxscore(raw, game_id),
        odds = .fox_bb_odds(raw, game_id), roster = .fox_bb_roster(raw, team_id),
        team_stats = .fox_bb_team_stats(raw, team_id), gamelog = .fox_bb_gamelog(raw, team_id),
        standings = .fox_bb_standings(raw, team_id), league_leaders = .fox_bb_leaders(raw),
        teams = .fox_bb_teams(raw))
      out <- df |>
        dplyr::as_tibble() |>
        janitor::clean_names() |>
        make_wehoop_data(paste0("Fox Sports ", toupper(sport), " ", resource), Sys.time())
    },
    error = function(e) {
      .report_api_error(
        e, hint = "Invalid arguments or no Fox {sport} {resource} data available!", args = .args)
    },
    warning = function(w) {
      .report_api_warning(
        w, hint = "Warning fetching Fox {sport} {resource}", args = .args)
    },
    finally = {
    }
  )
  out
}

# ---- public wrappers (WNBA + WBB share each resource via @rdname) -----------
#' @title
#' **Get Fox Sports basketball play-by-play**
#' @description
#' **Get Fox Sports (Bifrost) WNBA / women's college basketball (WBB)
#' play-by-play.** `fox_wnba_pbp()` hits the `wnba` slug; `fox_wbb_pbp()` hits
#' the `wcbk` slug.
#' @rdname fox_basketball_boxscore
#' @param game_id Fox Bifrost event id (e.g. `"2215"`).
#' @return A `wehoop_data` tibble, one row per play: `game_id`, `period`,
#'   `left_team`, `right_team`, `play_id`, `clock`, `team`, `left_score_change`,
#'   `right_score_change`, `play_text`.
#' @importFrom janitor clean_names
#' @importFrom dplyr as_tibble bind_rows
#' @export
#' @family Fox Sports Functions
#' @examples
#' \donttest{
#'   try(fox_wnba_pbp("2215"))
#' }
fox_wnba_pbp <- function(game_id) .fox_bb_resource("wnba", "pbp", game_id = game_id)
#' @rdname fox_basketball_boxscore
#' @export
#' @examples
#' \donttest{
#'   try(fox_wbb_pbp("388986"))
#' }
fox_wbb_pbp <- function(game_id) .fox_bb_resource("wcbk", "pbp", game_id = game_id)

#' @title
#' **Get Fox Sports basketball boxscore**
#' @description
#' **Get Fox Sports (Bifrost) WNBA / women's college basketball (WBB) boxscore.**
#' `fox_wnba_boxscore()` hits the `wnba` slug; `fox_wbb_boxscore()` hits the
#' `wcbk` slug.
#' @name fox_basketball_boxscore
#' @param game_id Fox Bifrost event id.
#' @return A `wehoop_data` tibble (long), one row per (player, stat): `game_id`,
#'   `team`, `stat_group`, `player`, `athlete_id`, `stat`, `value`.
#' @importFrom janitor clean_names
#' @importFrom dplyr as_tibble bind_rows
#' @export
#' @family Fox Sports Functions
#' @examples
#' \donttest{
#'   try(fox_wnba_boxscore("2215"))
#' }
fox_wnba_boxscore <- function(game_id) .fox_bb_resource("wnba", "boxscore", game_id = game_id)
#' @rdname fox_basketball_boxscore
#' @export
#' @examples
#' \donttest{
#'   try(fox_wbb_boxscore("388986"))
#' }
fox_wbb_boxscore <- function(game_id) .fox_bb_resource("wcbk", "boxscore", game_id = game_id)

#' @title
#' **Get Fox Sports basketball game odds**
#' @description
#' **Get Fox Sports (Bifrost) WNBA / women's college basketball (WBB) game
#' odds.** `fox_wnba_odds()` hits the `wnba` slug; `fox_wbb_odds()` hits the
#' `wcbk` slug.
#' @rdname fox_basketball_boxscore
#' @param game_id Fox Bifrost event id.
#' @return A `wehoop_data` tibble, one row per team: `game_id`, `team`, plus the
#'   six-pack odds columns (spread / to-win / total). Empty when no market.
#' @importFrom janitor clean_names
#' @importFrom dplyr as_tibble bind_rows
#' @export
#' @family Fox Sports Functions
#' @examples
#' \donttest{
#'   try(fox_wnba_odds("2215"))
#' }
fox_wnba_odds <- function(game_id) .fox_bb_resource("wnba", "odds", game_id = game_id)
#' @rdname fox_basketball_boxscore
#' @export
#' @examples
#' \donttest{
#'   try(fox_wbb_odds("388986"))
#' }
fox_wbb_odds <- function(game_id) .fox_bb_resource("wcbk", "odds", game_id = game_id)

#' @title
#' **Get Fox Sports basketball team roster**
#' @description
#' **Get Fox Sports (Bifrost) WNBA / women's college basketball (WBB) team
#' roster.** `fox_wnba_team_roster()` hits the `wnba` slug;
#' `fox_wbb_team_roster()` hits the `wcbk` slug.
#' @rdname fox_basketball_standings
#' @param team_id Fox Bifrost team id (e.g. `"1"`). Discover via the league team directory.
#' @return A `wehoop_data` tibble, one row per player: `team_id`, `position_group`,
#'   `player`, position/age/etc. columns, `athlete_id`.
#' @importFrom janitor clean_names
#' @importFrom dplyr as_tibble bind_rows
#' @export
#' @family Fox Sports Functions
#' @examples
#' \donttest{
#'   try(fox_wnba_team_roster("1"))
#' }
fox_wnba_team_roster <- function(team_id) .fox_bb_resource("wnba", "roster", team_id = team_id)
#' @rdname fox_basketball_standings
#' @export
#' @examples
#' \donttest{
#'   try(fox_wbb_team_roster("11"))
#' }
fox_wbb_team_roster <- function(team_id) .fox_bb_resource("wcbk", "roster", team_id = team_id)

#' @title
#' **Get Fox Sports basketball team stat leaders**
#' @description
#' **Get Fox Sports (Bifrost) WNBA / women's college basketball (WBB) team stat
#' leaders.** `fox_wnba_team_stats()` hits the `wnba` slug;
#' `fox_wbb_team_stats()` hits the `wcbk` slug.
#' @rdname fox_basketball_standings
#' @param team_id Fox Bifrost team id.
#' @return A `wehoop_data` tibble: `team_id`, `category`, `stat`,
#'   `stat_abbreviation`, `player`, `value`.
#' @importFrom janitor clean_names
#' @importFrom dplyr as_tibble bind_rows
#' @export
#' @family Fox Sports Functions
#' @examples
#' \donttest{
#'   try(fox_wnba_team_stats("1"))
#' }
fox_wnba_team_stats <- function(team_id) .fox_bb_resource("wnba", "team_stats", team_id = team_id)
#' @rdname fox_basketball_standings
#' @export
#' @examples
#' \donttest{
#'   try(fox_wbb_team_stats("11"))
#' }
fox_wbb_team_stats <- function(team_id) .fox_bb_resource("wcbk", "team_stats", team_id = team_id)

#' @title
#' **Get Fox Sports basketball team game log**
#' @description
#' **Get Fox Sports (Bifrost) WNBA / women's college basketball (WBB) team game
#' log.** `fox_wnba_team_gamelog()` hits the `wnba` slug;
#' `fox_wbb_team_gamelog()` hits the `wcbk` slug.
#' @rdname fox_basketball_standings
#' @param team_id Fox Bifrost team id.
#' @return A `wehoop_data` tibble (long): `team_id`, `season_type`, `category`,
#'   `game_id`, `game_date`, `opponent`, `stat`, `value`.
#' @importFrom janitor clean_names
#' @importFrom dplyr as_tibble bind_rows
#' @export
#' @family Fox Sports Functions
#' @examples
#' \donttest{
#'   try(fox_wnba_team_gamelog("1"))
#' }
fox_wnba_team_gamelog <- function(team_id) .fox_bb_resource("wnba", "gamelog", team_id = team_id)
#' @rdname fox_basketball_standings
#' @export
#' @examples
#' \donttest{
#'   try(fox_wbb_team_gamelog("11"))
#' }
fox_wbb_team_gamelog <- function(team_id) .fox_bb_resource("wcbk", "gamelog", team_id = team_id)

#' @title
#' **Get Fox Sports basketball standings**
#' @description
#' **Get Fox Sports (Bifrost) WNBA / women's college basketball (WBB)
#' standings.** `fox_wnba_standings()` hits the `wnba` slug;
#' `fox_wbb_standings()` hits the `wcbk` slug.
#' @name fox_basketball_standings
#' @param team_id Fox Bifrost team id (standings of that team's conference/division).
#' @return A `wehoop_data` tibble of standings rows (`team_id`, `section`, the
#'   standings columns, `entity_id`).
#' @importFrom janitor clean_names
#' @importFrom dplyr as_tibble bind_rows
#' @export
#' @family Fox Sports Functions
#' @examples
#' \donttest{
#'   try(fox_wnba_standings("1"))
#' }
fox_wnba_standings <- function(team_id) .fox_bb_resource("wnba", "standings", team_id = team_id)
#' @rdname fox_basketball_standings
#' @export
#' @examples
#' \donttest{
#'   try(fox_wbb_standings("11"))
#' }
fox_wbb_standings <- function(team_id) .fox_bb_resource("wcbk", "standings", team_id = team_id)

#' @title
#' **Get Fox Sports basketball team directory**
#' @description
#' **Get the Fox Sports (Bifrost) WNBA / women's college basketball (WBB) team
#' directory**, derived from the standings endpoint. `fox_wnba_teams()` hits the
#' `wnba` slug; `fox_wbb_teams()` hits the `wcbk` slug.
#' @rdname fox_basketball_standings
#' @param team_id Fox Bifrost seed team id used to fetch league standings
#'   (default `"3"`). The standings response enumerates every team in the seed's
#'   league sections.
#' @return A `wehoop_data` tibble, one row per team: `fox_team_id`,
#'   `fox_team_name`, `fox_section`.
#' @importFrom janitor clean_names
#' @importFrom dplyr as_tibble bind_rows
#' @export
#' @family Fox Sports Functions
#' @examples
#' \donttest{
#'   try(fox_wnba_teams())
#' }
fox_wnba_teams <- function(team_id = "3") .fox_bb_resource("wnba", "teams", team_id = team_id)
#' @rdname fox_basketball_standings
#' @export
#' @examples
#' \donttest{
#'   try(fox_wbb_teams("11"))
#' }
fox_wbb_teams <- function(team_id = "11") .fox_bb_resource("wcbk", "teams", team_id = team_id)

#' @title
#' **Get the full Fox Sports women's college basketball team directory**
#' @description
#' **Enumerate every WBB team in the Fox Sports (Bifrost) directory.** A single
#' `fox_wbb_teams()` call only returns the seed team's conference, so this walks
#' unseen team ids (calling one seed per conference) and unions the results.
#' @param max_id Highest Fox team id to probe as a seed (default `500`).
#' @param max_calls Safety cap on the number of standings calls (default `60`).
#' @return A `wehoop_data` tibble, one row per team: `fox_team_id`,
#'   `fox_team_name`, `fox_section`.
#' @importFrom dplyr bind_rows
#' @export
#' @family Fox Sports Functions
#' @examples
#' \donttest{
#'   try(fox_wbb_teams_all())
#' }
fox_wbb_teams_all <- function(max_id = 500, max_calls = 60) {
  seen <- character(0)
  parts <- list()
  calls <- 0L
  for (cand in seq_len(max_id)) {
    if (calls >= max_calls) break
    if (as.character(cand) %in% seen) next
    df <- tryCatch(fox_wbb_teams(team_id = as.character(cand)), error = function(e) NULL)
    calls <- calls + 1L
    if (is.null(df) || !nrow(df)) next
    parts[[length(parts) + 1L]] <- df
    seen <- union(seen, as.character(df$fox_team_id))
  }
  out <- if (length(parts)) dplyr::bind_rows(parts) else data.frame(
    fox_team_id = character(), fox_team_name = character(), fox_section = character(),
    stringsAsFactors = FALSE)
  out <- out[!duplicated(out$fox_team_id), , drop = FALSE]
  make_wehoop_data(out, "Fox Sports WBB full team directory", Sys.time())
}

#' @title
#' **Get Fox Sports basketball statistical leaders**
#' @description
#' **Get Fox Sports (Bifrost) WNBA / women's college basketball (WBB)
#' statistical leaders.** `fox_wnba_league_leaders()` hits the `wnba` slug;
#' `fox_wbb_league_leaders()` hits the `wcbk` slug.
#' @name fox_basketball_league_leaders
#' @param category Stat category (default `"scoring"`).
#' @param who `"player"` or `"team"` (default `"player"`).
#' @param page 0-based page index (default `0`).
#' @return A `wehoop_data` tibble of leaderboard rows (`entity_id` + stat columns).
#' @importFrom janitor clean_names
#' @importFrom dplyr as_tibble bind_rows
#' @export
#' @family Fox Sports Functions
#' @examples
#' \donttest{
#'   try(fox_wnba_league_leaders("scoring"))
#' }
fox_wnba_league_leaders <- function(category = "scoring", who = "player", page = 0) {
  .fox_bb_resource("wnba", "league_leaders", category = category, who = who, page = page)
}
#' @rdname fox_basketball_league_leaders
#' @export
#' @examples
#' \donttest{
#'   try(fox_wbb_league_leaders("scoring"))
#' }
fox_wbb_league_leaders <- function(category = "scoring", who = "player", page = 0) {
  .fox_bb_resource("wcbk", "league_leaders", category = category, who = who, page = page)
}

Try the wehoop package in your browser

Any scripts or data that you put into this service are public.

wehoop documentation built on Aug. 25, 2026, 1:06 a.m.