R/get_cards.R

Defines functions get_card_by_id get_card_by_code get_cards_random get_cards_autocomplete get_cards_by_name get_cards_by_search

Documented in get_card_by_code get_card_by_id get_cards_autocomplete get_cards_by_name get_cards_by_search get_cards_random

#' Search for cards on Scryfall
#'
#' @md
#' @description Get a list of cards found using a fulltext search string.
#' This string uses the same syntax as [Scryfall's site](https://scryfall.com/docs/syntax)
#' This is pagenated, returning a maximum of 175 cards at a time
#'
#' @param q the string query using Scryfall Syntax
#' @param unique the roll-up mode for removing duplicates. See details
#' @param order the method to sort returned cards. See details
#' @param dir direction to sort cards
#' @param include_extras Should the search include extra cards (tokens, planes, vanguards, etc)
#' @param include_variations Should rare card varients be included Defaults to false
#' @param page the page to return defaults to 1
#' @param format The format to return the data in. json or CSV
#' @param pretty Should the JSON be prettified
#'
#'
#' @details
#'
#' `unique`
#'  This parameter specifies if Scryfall should remove duplicate results in your query. The options are:
#'  - "cards" (default): Removes duplicate game-play objects (cards that share a name and have the same functionality).
#'  - "art" : Returns only one copy of each unique artwork for matching cards.
#'  - "prints": Returns all prints for all cards matched (disables rollup).
#'
#'
#'  `order`
#'  This parameter determines how Scryfall should sort the returned cards.
#'  - "name" (default): by card name
#'  - "set": by their set and collector number
#'  - "released": by release date
#'  - "rarity": by card Rarity
#'  - "usd": by card lowest known US dollar price
#'  - "tix": by lowest known TIX price
#'  - "eur": by lowest known Euro price
#'  - "cmc": by converted mana cost
#'  - "power": by power
#'  - "toughness": by toughness
#'  - "edhrec": by edhrec ranking
#'  - "artist" by front side artist name
#'
#' @return A list or data.frame depending on output selected
#'
#' @export
#'
#' @examples
#'
#' # Find Jace Planeswalker Cards
#'
#' jace_pw_cards <- get_cards_by_search(
#'   'jace t:planeswalker',
#'   include_variations = TRUE,
#'   format = 'csv'
#' )
#'
#' # Number Crunch lands between Creeping Tar Pit and Dark Depths
#'
#'   lands_poss <- get_cards_by_search(
#'   'crunch>"creeping tar pit"
#'   crunch<"dark depths"
#'   f:vintage -in:ptk
#'   not:reserved',
#'   order = 'usd',
#'   format = 'csv'
#' )
#'
#' # Get unique arts of a Card
#'
#' unique_arts <- get_cards_by_search(
#'   'stone rain',
#'   format = 'csv',
#'   unique = 'art'
#' )
#'
#' unique_prints <- get_cards_by_search(
#'   'giant spider',
#'   format = 'json',
#'   unique = 'prints'
#' )
#'
get_cards_by_search <- function(
                                q = "*",
                                unique = "cards",
                                order = "name",
                                dir = "auto",
                                include_extras = FALSE,
                                include_variations = FALSE,
                                page = 1,
                                format = "csv",
                                pretty = FALSE) {

  #### Send args to lower ####
  order <- tolower(order)
  dir <- tolower(dir)
  format <- format(format)

  #### checks ####
  attempt::stop_if(
    q,
    is.null,
    "You must specify a query"
  )
  attempt::stop_if_not(
    pretty,
    is.logical,
    "Parameter pretty must be either TRUE or FALSE"
  )
  attempt::stop_if_not(
    include_extras,
    is.logical,
    "Parameter include_extras must be either TRUE or FALSE"
  )
  attempt::stop_if_not(
    include_variations,
    is.logical,
    "Parameter include_variations must be either TRUE or FALSE"
  )
  attempt::stop_if_not(
    page,
    is.numeric,
    "Parameter page must be an whole number"
  )
  stop_if_not_in(
    unique,
    c("cards", "art", "prints"),
    "`unique` must be one of c('cards', 'art', 'prints')"
  )
  stop_if_not_in(
    order,
    c("name", "set", "released", "rarity", "usd", "tix", "eur", "cmc", "power", "toughness", "edhrec", "artist"),
    "`order` must be one of c('name', 'set', 'released','rarity', 'usd', 'tix', 'eur', 'cmc', 'power', 'toughness', 'edhrec', 'artist')"
  )
  stop_if_not_in(
    dir,
    c("auto", "asc", "desc"),
    "`dir must be on of c('auto', 'asc', 'desc')"
  )


  #### convert to api formatting ####
  pretty_search <- ifelse(pretty, "true", "false")
  extras_search <- ifelse(include_extras, "true", "false")
  variations_search <- ifelse(include_variations, "true", "false")
  page <- round(page)



  #### Connect to the API ####
  res <- httr::GET(
    paste0(card_url, "/search"),
    query = list(
      q = q,
      unique = unique,
      order = order,
      dir = dir,
      include_extras = extras_search,
      include_variations = variations_search,
      page = page,
      format = format,
      pretty = pretty_search
    )
  )

  check_status(res)

  #### Format the response ####
  if (format == "json") {
    return(jsonlite::fromJSON(httr::content(res, "text", encoding = "UTF-8")))
  }
  else if (format == "csv") {
    return(readr::read_csv(httr::content(res, "text", encoding = "UTF-8")))
  }
  else {
    usethis::ui_stop("There was an issue with the format. it should be either 'json' or 'csv'")
  }
  ##### END #####
}

#' Search for cards on Scryfall
#'
#' @description Returns a Card based on a name search string.
#'
#' @param q the card name to search for
#' @param search_type is the search an "exact" or "fuzzy" search
#' @param set set code to limit search to one set
#' @param format The format to return the data in. "json", "CSV", "image"
#' @param face The face that should be returned if format selected is "image"
#' @param version The size of the image to return when using "image"
#' @param pretty Should the JSON be prettified
#'
#' @export

get_cards_by_name <- function(
                              q = "Jace, the Mind Sculptor",
                              search_type = "exact",
                              set = "",
                              format = "csv",
                              face = "front",
                              version = "large",
                              pretty = FALSE) {
  #### Checks ####
  attempt::stop_if(
    q, is.null,
    "You must specify a query"
  )
  attempt::stop_if_not(
    search_type, is.character,
    "`search_type` must be of type character"
  )
  stop_if_not_in(
    search_type, c("exact", "fuzzy"),
    "`search_type` must be one of c('exact', 'fuzzy')"
  )
  attempt::stop_if_not(
    set, is.character,
    "`set` must be of type character"
  )
  stop_if_not_in(
    format, c("json", "csv", "image"),
    "`format` must be one of c('json', 'csv', 'image')"
  )
  attempt::stop_if_not(
    face, is.character,
    "`face` must be of type character"
  )
  stop_if_not_in(
    face, c("front", "back"),
    "`face` must be one of c('front', 'back')"
  )
  attempt::stop_if_not(
    version, is.character,
    "`version` must be of type character"
  )
  stop_if_not_in(
    version, c("small", "normal", "large", "png", "art_crop", "border_crop"),
    "`version` must be one of c('small', 'normal', 'large', 'png', 'art_crop', 'border_crop')"
  )
  attempt::stop_if_not(
    pretty, is.logical,
    "`pretty` must be either TRUE or FALSE"
  )
  check_internet()

  #### Convert to proper formatting ####
  pretty_search <- ifelse(pretty, "true", "false")
  q <- gsub(pattern = "[^[:alnum:][:space:]]", "", q)
  q <- gsub(pattern = " ", "+", q)

  #### Get the API response ####
  if (search_type == "exact") {
    res <- httr::GET(paste0(card_url, "/named"),
      query = list(
        exact = q,
        set = set,
        format = format,
        face = face,
        version = version,
        pretty = pretty_search
      )
    )
  }
  else if (search_type == "fuzzy") {
    res <- httr::GET(paste0(card_url, "/named"),
      query = list(
        fuzzy = I(q),
        set = set,
        format = format,
        face = face,
        version = version,
        pretty = pretty_search
      )
    )
  }
  else {
    usethis::ui_stop("There was an issue with the search type it should be either 'exact' or 'fuzzy'")
  }


  check_status(res)

  #### Send out the data ####
  if (format == "json") {
    return(jsonlite::fromJSON(httr::content(res, "text", encoding = "UTF-8")))
  }
  else if (format == "csv") {
    return(readr::read_csv(httr::content(res, "text", encoding = "UTF-8")))
  }
  else if (format == "image") {
    return((magick::image_read(res$content)))
  }
  else {
    usethis::ui_stop("There was an issue with the format. it should be either 'json', 'csv', or 'image")
  }
  #### End ####
}

#' Get Potential Card names
#'
#' @description Returns up to 20 full English card names that could
#' autocomplete the supplied query string.
#'
#' @param q The query string
#' @param pretty Should the JSON be prettified
#' @param include_extras Should the search include extra cards (tokens, planes, vanguards, etc)
#'
#' @return A list
#'
#' @export
get_cards_autocomplete <- function(
                                   q = "Jace, ",
                                   pretty = FALSE,
                                   include_extras = FALSE) {
  # Checks
  attempt::stop_if(q, is.null, "You must specify a query")
  attempt::stop_if_not(pretty, is.logical, "Parameter pretty must be either TRUE or FALSE")
  attempt::stop_if_not(include_extras, is.logical, "Parameter include_extras must be either TRUE or FALSE")
  check_internet()



  pretty_search <- ifelse(pretty, "true", "false")
  extras_search <- ifelse(include_extras, "true", "false")

  # Connect to the API
  res <- httr::GET(
    paste0(card_url, "/autocomplete"),
    query = list(
      q = q,
      pretty = pretty_search,
      include_extras = extras_search
    )
  )


  check_status(res)


  return(jsonlite::fromJSON(rawToChar(res$content)))
}

#' Get  single random Card object
#'
#' @description Returns a single random Card object.
#'
#' @param q The query string
#' @param format The format to return the data in. "json", "CSV", "image"
#' @param face The face that should be returned if format selected is "image"
#' @param version The size of the image to return when using "image"
#' @param pretty Should the JSON be prettified
#'
#'
#' @export
get_cards_random <- function(
                             q = "Jace",
                             format = "csv",
                             face = "front",
                             version = "large",
                             pretty = FALSE) {
  # checks
  attempt::stop_if(q, is.null, "You must specify a query")
  stop_if_not_in(format, c("json", "csv", "image"), "`format` must be one of c('json', 'csv', 'image')")
  attempt::stop_if_not(face, is.character, "`face` must be of type character")
  stop_if_not_in(face, c("front", "back"), "`version` must be one of c('front', 'back')")
  attempt::stop_if_not(pretty, is.logical, "Parameter pretty must be either TRUE or FALSE")
  attempt::stop_if_not(face, is.character, "`face` must be of type character")
  stop_if_not_in(face, c("front", "back"), "`face` must be one of c('front', 'back')")
  attempt::stop_if_not(version, is.character, "`version` must be of type character")
  stop_if_not_in(
    version, c("small", "normal", "large", "png", "art_crop", "border_crop"),
    "`version` must be one of c('small', 'normal', 'large', 'png', 'art_crop', 'border_crop')"
  )
  check_internet()


  # convert
  pretty_search <- ifelse(pretty, "true", "false")


  # Connect to the API
  res <- httr::GET(
    paste0(card_url, "/random"),
    query = list(
      q = q,
      format = format,
      face = face,
      version = version,
      pretty = pretty_search
    )
  )


  check_status(res)


  if (format == "json") {
    return(jsonlite::fromJSON(httr::content(res, "text", encoding = "UTF-8")))
  }
  else if (format == "csv") {
    return(readr::read_csv(httr::content(res, "text", encoding = "UTF-8")))
  }
  else if (format == "image") {
    return((magick::image_read(res$content)))
  }
  else {
    usethis::ui_stop("There was an issue with the format. it should be either 'json', 'csv', or 'image")
  }
}


#' Get card by set code and collector number
#'
#' @param code the 3 to 5 letter set code
#' @param number the collector number of the card (must be whole number)
#' @param lang the 2 to 3 letter language code as described at https://scryfall.com/docs/api/languages
#' @param format The format to return the data in. "json", "CSV", "image"
#' @param face The face that should be returned if format selected is "image"
#' @param version The size of the image to return when using "image"
#' @param pretty Should the JSON be prettified
#' @param rulings (Boolean) should the rulings for the card be returned?
#'
#' @export
get_card_by_code <- function(
                             code = "wwk",
                             number = 31,
                             lang = "en",
                             format = "csv",
                             face = "front",
                             version = "large",
                             pretty = FALSE,
                             rulings = FALSE
                             ) {

  # checks
  assertthat::assert_that(nchar(code) <= 5, nchar(code) >= 3,
    msg = "code must be a set code between 3 and 5 characters in length"
  )
  attempt::stop_if_not(number, is.numeric, "Number must be a number")
  assertthat::assert_that(nchar(lang) <= 3, nchar(lang) >= 2,
    msg = "lang must be a language code between 2 and 3 characters in length"
  )
  stop_if_not_in(format, c("json", "csv", "image"), "`format` must be one of c('json', 'csv', 'image')")
  attempt::stop_if_not(face, is.character, "`face` must be of type character")
  stop_if_not_in(face, c("front", "back"), "`version` must be one of c('front', 'back')")
  attempt::stop_if_not(version, is.character, "`version` must be of type character")
  stop_if_not_in(
    version, c("small", "normal", "large", "png", "art_crop", "border_crop"),
    "`version` must be one of c('small', 'normal', 'large', 'png', 'art_crop', 'border_crop')"
  )
  attempt::stop_if_not(pretty, is.logical, "`pretty` must be either TRUE or FALSE")
  stop_if_not(
    rulings,
    is.logical,
    "'rulings' must be either TRUE or FALSE"
  )
  check_internet()


  pretty_search <- ifelse(pretty, "true", "false")


  if(rulings){
    if (format != "json") {
      usethis::ui_warn(
        paste0("Rulings only provided in JSON format changing '", format, "' to 'json")
      )
      format <- "json"
    }

    query <- list(
      format = format,
      pretty = pretty_search
    )
    url <- paste(card_url, code, number, "rulings", sep = "/")

  }
  else{
    url <- paste(card_url, code, number, lang, sep = "/")
    query <- list(
      format = format,
      face = face,
      version = version,
      pretty = pretty_search
    )
  }



  res <- httr::GET(
    url = url,
    query = query
  )


  check_status(res)


  if (format == "json") {
    return(jsonlite::fromJSON(httr::content(res, "text", encoding = "UTF-8")))
  }
  else if (format == "text") {
    return(httr::content(res, "text", encoding = "UTF-8"))
  }
  else if (format == "image") {
    return((magick::image_read(res$content)))
  }
  else {
    usethis::ui_stop("There was an issue with the format. it should be either 'json', 'text', or 'image")
  }
}



#' Get card by ID
#'
#' @md
#' @description Get a single card by it's given id from one of the following ID schemes:
#' * multiverse
#' * mtgo
#' * arena
#' * tcgplayer
#' * cardmarket
#' * scryfall
#' See details for information about each of these ID systems.
#'
#' @param id Arena ID
#' @param format The format to return the data in. "json", "text", "image"
#' @param face The face that should be returned if format selected is "image"
#' @param version The size of the image to return when using "image"
#' @param pretty Should the JSON be prettified
#' @param id_type what kind of id is being provided See details for options.
#' @param rulings Boolean should the rulings for the card be returned?
#'
#' @details
#' id_type
#' + 'multiverse': Get a single card by given Multiverse ID. If the card has multiple Multiverse IDs this method finds both of them.
#' A Multiverse ID is the unique identifier from Wizards of the Coast's Gatherer database.
#' + `mtgo': Get a single card by given it's MTGO ID, also known as Catalog ID. These can be either the mtgo_id or it's mtgo_foil_id.
#' + 'arena': Get a single card given it's arena ID
#' + 'tcgplayer:' Get a single card with the given tcgplayer_id, also known as the productId on TCGplayer’s API
#' + 'cardmarket' Get a single card with the given cardmarket_id, also known as the idProduct" or the Product ID on Cardmarket’s APIs
#' + 'scryfall' Get a single card with the given Scryfall ID
#'
#'
#' @return either a list, data frame, or image depending on `format`
#' @export
get_card_by_id <- function(
                           id = 195297,
                           format = "csv",
                           face = "front",
                           version = "large",
                           pretty = FALSE,
                           id_type = "multiverse",
                           rulings = FALSE) {
  id_type <- tolower(id_type)

  #### Checks ####
  attempt::stop_if_not(
    id,
    is.numeric,
    msg = "id must be a number"
  )

  stop_if_not_in(
    format,
    c("json", "text", "image"),
    "`format` must be one of c('json', 'text', 'image')"
  )
  attempt::stop_if_not(
    face, is.character,
    "`face` must be of type character"
  )
  stop_if_not_in(
    face,
    c("front", "back"),
    "`version` must be one of c('front', 'back')"
  )
  attempt::stop_if_not(
    version,
    is.character,
    "`version` must be of type character"
  )
  stop_if_not_in(
    version,
    c("small", "normal", "large", "png", "art_crop", "border_crop"),
    "`version` must be one of c('small', 'normal', 'large', 'png', 'art_crop', 'border_crop')"
  )
  attempt::stop_if_not(
    pretty,
    is.logical,
    "`pretty` must be either TRUE or FALSE"
  )
  stop_if_not_in(
    id_type,
    c("multiverse", "mtgo", "mtgo_foil", "arena", "tcgplayer", "cardmarket", "scryfall"),
    '`format` must be one of c("multiverse", "mtgo", "mtgo_foil", "arena", "tcgplayer", "cardmarket", "scryfall")'
  )
  stop_if_not(
    rulings,
    is.logical,
    "'rulings' must be either TRUE or FALSE"
    )
  check_internet()



  #### convert ####
  pretty_search <- ifelse(pretty, "true", "false")

  if(rulings){
    if (format != "json") {
      usethis::ui_warn(
        paste0("Rulings only provided in JSON format. Changing '", format, "' to 'json")
        )
      format <- "json"

    }

    query <- list(
      format = format,
      pretty = pretty_search
    )

    url <- paste(card_url, id_type, id, "rulings", sep = "/")
  }
  else{
    url <- paste(card_url, id_type, id, sep = "/")
    query <- list(
      format = format,
      face = face,
      version = version,
      pretty = pretty_search
    )
  }



  #### Connect to API ####
  res <- httr::GET(
    url = url,
    query = query
  )


  check_status(res)
  print(res$url)

  #### Return Results ####
  if (format == "json") {
    return(jsonlite::fromJSON(httr::content(res, "text", encoding = "UTF-8")))
  }
  else if (format == "text") {
    return(httr::content(res, "text", encoding = "UTF-8"))
  }
  else if (format == "image") {
    return((magick::image_read(res$content)))
  }
  else {
    usethis::ui_stop("There was an issue with the format. it should be either 'json', 'text', or 'image")
  }
  #### END ####
}
delabj/scryAnotherDay documentation built on May 13, 2021, 9:22 p.m.