Skip to content

Commit

Permalink
Start Yahoo Implementation #336 (#424)
Browse files Browse the repository at this point in the history
* filter out empty drafts from sleeper ff_draft (#425)

* Update R-CMD-check.yaml

* Add yahoo_connect and yahoo_franchises

* Add autogenerated documentation

* Add support for ff_connect

* Run styler

* Added support for co_owners

* Update styling.  Switch userleagues to be more R like

* Switch to asking for season

* Switch to API call for game_id

* Small fixes

* add some mocked tests

* remove bypass of local_mock_api()

* Update R/yahoo_connect.R

Co-authored-by: Tan Ho <[email protected]>

* use getElement

* getElement

---------

Co-authored-by: JoeSydlowski <[email protected]>
Co-authored-by: Tan Ho <[email protected]>
Co-authored-by: Tan <[email protected]>
  • Loading branch information
4 people authored Jan 28, 2024
1 parent a9430cb commit 4b9c680
Show file tree
Hide file tree
Showing 18 changed files with 1,780 additions and 8 deletions.
1 change: 1 addition & 0 deletions .github/workflows/R-CMD-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
branches:
- main
- dev
- yahoo
workflow_dispatch:

name: R-CMD-check
Expand Down
6 changes: 6 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ S3method(ff_franchises,espn_conn)
S3method(ff_franchises,flea_conn)
S3method(ff_franchises,mfl_conn)
S3method(ff_franchises,sleeper_conn)
S3method(ff_franchises,yahoo_conn)
S3method(ff_league,default)
S3method(ff_league,espn_conn)
S3method(ff_league,flea_conn)
Expand Down Expand Up @@ -73,6 +74,7 @@ S3method(ff_userleagues,espn_conn)
S3method(ff_userleagues,flea_conn)
S3method(ff_userleagues,mfl_conn)
S3method(ff_userleagues,sleeper_conn)
S3method(ff_userleagues,yahoo_conn)
S3method(print,espn_api)
S3method(print,espn_conn)
S3method(print,flea_conn)
Expand All @@ -82,6 +84,8 @@ S3method(print,mfl_conn)
S3method(print,sleeper_api)
S3method(print,sleeper_conn)
S3method(print,template_conn)
S3method(print,yahoo_api)
S3method(print,yahoo_conn)
export("%>%")
export(.cache_path)
export(.download_cache)
Expand Down Expand Up @@ -128,6 +132,8 @@ export(sleeper_draft)
export(sleeper_getendpoint)
export(sleeper_players)
export(sleeper_userleagues)
export(yahoo_connect)
export(yahoo_getendpoint)
importFrom(data.table,":=")
importFrom(data.table,.BY)
importFrom(data.table,.EACHI)
Expand Down
4 changes: 2 additions & 2 deletions R/0_generics.R
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ ff_connect <- function(platform = "mfl", league_id = NULL, ...) {
"flea" = fleaflicker_connect(league_id = league_id, ...),
"espn" = espn_connect(league_id = league_id, ...),
"sleeper" = sleeper_connect(league_id = league_id, ...),
"mfl" = mfl_connect(league_id = league_id, ...)
# 'yahoo' = stop("Y YOU YAHOO, YOU YAHOO?")
"mfl" = mfl_connect(league_id = league_id, ...),
"yahoo" = yahoo_connect(league_id = league_id, ...)
)

if (is.null(x)) stop("We can't connect to that platform yet!")
Expand Down
11 changes: 9 additions & 2 deletions R/sleeper_draft.R
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,16 @@ ff_draft.sleeper_conn <- function(conn, ...) {
}

.sleeper_currentdraft <- function(draft_id) {
picks <- glue::glue("draft/{draft_id}/picks") %>%
picks_content <- glue::glue("draft/{draft_id}/picks") %>%
sleeper_getendpoint() %>%
purrr::pluck("content") %>%
purrr::pluck("content")

# Check length of picks_content object to filter out empty drafts
if(length(picks_content) == 0) return(data.frame(franchise_id = integer(),
player_id = character()))

picks <-
picks_content %>%
tibble::tibble() %>%
tidyr::hoist(1, "round","pick_no", "draft_slot", "roster_id", "player_id", "metadata") %>%
tidyr::hoist("metadata", "auction_amount" = "amount") %>%
Expand Down
54 changes: 54 additions & 0 deletions R/yahoo_api.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#' GET any Yahoo Fantasy Football API endpoint
#'
#' @param endpoint a string defining which endpoint to return from the Yahoo API
#' @param conn Yahoo ff_connect object
#'
#' @return A list object containing the query, response, and parsed content as an xml_doc.
#'
#' @seealso <https://developer.yahoo.com/fantasysports/guide/>
#'
#' @export
yahoo_getendpoint <- function(endpoint, conn) {
# Construct API request headers with authentication token
headers <- c("Authorization" = paste("Bearer", conn$token))

# Construct the API URL with the provided endpoint and query parameters
url_query <- httr::modify_url(
url = glue::glue("https://fantasysports.yahooapis.com/fantasy/v2/{endpoint}"),
)

# Perform the API request
response <- httr::GET(url_query, httr::add_headers(headers))

# Check the API response for errors
if (httr::http_error(response)) {
cli::cli_abort(
c(
"Yahoo FF API request failed with error <{httr::http_status(response)$message}>",
"while calling {.url {url_query}}"
)
)
}
xml_doc <- xml2::read_xml(response$content)
xml2::xml_ns_strip(xml_doc)

# Return an S3 object
structure(
list(
query = url_query,
response = response,
xml_doc = xml_doc
),
class = "yahoo_api"
)
}

## PRINT METHOD YAHOO_API OBJ ##
#' @noRd
#' @export
print.yahoo_api <- function(x, ...) {
cat("<Yahoo Fantasy Football API - GET - ", httr::http_status(x$response)$message, ">\n", sep = "")
cat("QUERY: <", x$query, ">\n", sep = "")
str(x$content, max.level = 1)
invisible(x)
}
66 changes: 66 additions & 0 deletions R/yahoo_connect.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#' Connect to Yahoo Fantasy Sports League
#'
#' This function creates a connection object for Yahoo Fantasy Sports league using the provided league_id and token.
#'
#' @param league_id League ID
#' @param token Token for authentication
#'
#' @examples
#' \donttest{
#' conn <- yahoo_connect(
#' league_id = "12345",
#' token = "your_token_here"
#' )
#' }
#'
#' @export yahoo_connect
#'
#' @return A list that stores Yahoo Fantasy Sports connection objects
yahoo_connect <- function(league_id = NULL,
season = NULL,
token = NULL,
...) {
if (length(token) == 0 || nchar(token) == 0) {
stop("token is a required field. Visit https://lemon-dune-0cd4b231e.azurestaticapps.net/ to get a token.", call. = FALSE)
}
conn <- structure(
list(
platform = "Yahoo Fantasy Sports",
token = as.character(token)
),
class = "yahoo_conn"
)

if (missing(league_id) | missing(season)) {
user_leagues <- ff_userleagues(conn)
print(user_leagues, n = Inf)
stop("league_id and season are required. You can use one of your leagues shown.", call. = FALSE)
}

# Set league_id and league_key
conn$league_id <- as.character(league_id)
game_id <- .yahoo_game_id(conn, season)
conn$league_key <- as.character(glue::glue("{game_id}.l.{conn$league_id}"))

return(conn)
}

.yahoo_game_id <- function(conn, season) {
# get the game_id for the season.
# game_ids are the same for all Yahoo users but it's easier to make the api call then maintain a static dictionary in this repo.
glue::glue("games;game_codes=nfl;seasons={season}") %>%
yahoo_getendpoint(conn) %>%
getElement("xml_doc") %>%
xml2::xml_find_first("//game_id") %>%
xml2::xml_text()
}

# nocov start
#' @noRd
#' @export
print.yahoo_conn <- function(x, ...) {
cat("<Yahoo Fantasy Sports connection ", x$league_id, ">\n", sep = "")
str(x)
invisible(x)
}
# nocov end
61 changes: 61 additions & 0 deletions R/yahoo_franchises.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#' Get a dataframe of franchise data
#'
#' @param conn a conn object created by `ff_connect()`
#'
#' @examples
#' \donttest{
#' try({ # try only shown here because sometimes CRAN checks are weird
#' yahoo_conn <- ff_connect(platform = "yahoo", league_id = "77275", token = NULL)
#' ff_franchises(yahoo_conn)
#' }) # end try
#' }
#' @describeIn ff_franchises Yahoo: Returns franchise data.
#' @export
ff_franchises.yahoo_conn <- function(conn) {
glue::glue("leagues;league_keys={conn$league_key}/teams") %>%
yahoo_getendpoint(conn) %>%
getElement("xml_doc") %>%
.yahoo_process_franchises_response()
}

.yahoo_process_franchises_response <- function(xml_doc) {
# extract franchise data
franchise_id <- xml_doc %>%
xml2::xml_find_all("//team/team_id") %>%
xml2::xml_integer()
franchise_name <- xml_doc %>%
xml2::xml_find_all("//team/name") %>%
xml2::xml_text()

# extract manager data
managers_nodes <- xml_doc %>%
xml2::xml_find_all("//managers")
first_user_name <- managers_nodes %>%
xml2::xml_find_first("./manager/nickname") %>%
xml2::xml_text()
first_user_id <- managers_nodes %>%
xml2::xml_find_first("./manager/guid") %>%
xml2::xml_text()
co_owner_ids <- managers_nodes %>%
purrr::map(.yahoo_get_co_owners)

# Create a data frame
df <- tibble::tibble(
franchise_id = franchise_id,
franchise_name = franchise_name,
user_name = first_user_name,
user_id = first_user_id,
co_owners = co_owner_ids
)
return(df)
}

.yahoo_get_co_owners <- function(managers_node) {
all_user_ids <- managers_node %>%
xml2::xml_find_all("./manager/guid") %>%
xml2::xml_text()
if (length(all_user_ids) > 1) {
return(as.list(all_user_ids[-1]))
}
return(NULL)
}
71 changes: 71 additions & 0 deletions R/yahoo_userleagues.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#' Get a dataframe of user_leagues
#'
#' @param conn a conn object created by `ff_connect()`
#'
#' @examples
#' \donttest{
#' try({ # try only shown here because sometimes CRAN checks are weird
#' yahoo_conn <- ff_connect(platform = "yahoo", league_id = "423.l.77275", token = NULL)
#' ff_userleagues(yahoo_conn)
#' }) # end try
#' }
#' @describeIn ff_userleagues Yahoo: Returns all userleagues.
#' @export
ff_userleagues.yahoo_conn <- function(conn, ...) {
glue::glue("users;use_login=1/games/leagues/teams") %>%
yahoo_getendpoint(conn) %>%
getElement("xml_doc") %>%
.yahoo_process_userleagues_response()
}

.yahoo_process_userleagues_response <- function(xml_doc) {
# Process all user nodes
user_nodes <- xml_doc %>%
xml2::xml_find_all("//user")
purrr::map_df(user_nodes, .yahoo_process_user_node)
}

# Function to process each user node
.yahoo_process_user_node <- function(user_node) {
user_id <- user_node %>%
xml2::xml_find_first("./guid") %>%
xml2::xml_text()
game_nodes <- user_node %>%
xml2::xml_find_all(".//game")
purrr::map_df(game_nodes, ~ .yahoo_process_game_node(.x, user_id))
}

# Function to process each game node
.yahoo_process_game_node <- function(game_node, user_id) {
game_id <- game_node %>%
xml2::xml_find_first("./game_id") %>%
xml2::xml_text()
season <- game_node %>%
xml2::xml_find_first("./season") %>%
xml2::xml_integer()
league_nodes <- game_node %>%
xml2::xml_find_all(".//league")

purrr::map_df(league_nodes, ~ .yahoo_process_league_node(.x, user_id, game_id, season))
}

# Function to process each league node
.yahoo_process_league_node <- function(league_node, user_id, game_id, season) {
league_id <- league_node %>%
xml2::xml_find_first("./league_id") %>%
xml2::xml_text()
league_name <- league_node %>%
xml2::xml_find_first("./name") %>%
xml2::xml_text()
franchise_name <- league_node %>%
xml2::xml_find_first(".//team/name") %>%
xml2::xml_text()
tibble::tibble(
league_name = league_name,
league_id = league_id,
franchise_name = franchise_name,
franchise_id = user_id,
game_id = game_id,
season = season
)
}
3 changes: 2 additions & 1 deletion inst/httptest/redact.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ httptest::set_redactor(
httptest::gsub_response("https\\://api.myfantasyleague.com/","mfl/") %>%
httptest::gsub_response("https\\://api.sleeper.app/","sleeper/") %>%
httptest::gsub_response("https\\://www.fleaflicker.com/","flea/") %>%
httptest::gsub_response("https\\://github.com/DynastyProcess/data/raw/master/files/","gh_dynastyprocess/")
httptest::gsub_response("https\\://github.com/DynastyProcess/data/raw/master/files/","gh_dynastyprocess/") %>%
httptest::gsub_response("https\\://fantasysports.yahooapis.com/fantasy/v2/","yahoo/")
}
)
14 changes: 13 additions & 1 deletion man/ff_franchises.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 4b9c680

Please sign in to comment.