mhuggins/lol_client

View on GitHub
lib/lol_client.rb

Summary

Maintainability
D
1 day
Test Coverage
require 'lol_client/models'
require 'lol_client/representers'
require 'lol_client/version'

require 'cgi'
require 'json'
require 'ostruct'
require 'rest-client'
require 'set'

class LolClient
  REGIONS = [:br, :eune, :euw, :kr, :lan, :las, :na, :oce, :ru, :tr].to_set.freeze

  CHALLENGER_LEAGUE_TYPES = [:RANKED_SOLO_5x5, :RANKED_TEAM_3x3, :RANKED_TEAM_5x5].to_set.freeze

  STATIC_CHAMPION_DATA_OPTIONS = %w[all image skins lore blurb allytips enemytips
                                    tags partype info stats spells passive
                                    recommended].to_set.freeze

  STATIC_ITEM_DATA_OPTIONS = %w[all gold calloq consumed stacks depth consumeOnFull
                                from into specialRecipe inStore hideFromAll
                                requiredChampion tags maps image stats].to_set.freeze

  STATIC_MASTERY_DATA_OPTIONS = %w[all image prereq ranks tree].to_set.freeze

  STATIC_RUNE_DATA_OPTIONS = %w[all image prereq ranks tree"].to_set.freeze

  STATIC_SPELL_DATA_OPTIONS = %w[all tooltip leveltip image resource maxrank cost
                                 costType costBurn cooldown cooldownBurn effect
                                 effectBurn vars range rangeBurn key modes].to_set.freeze

  ##
  # Stores the region associated with the client.
  #
  # @return [Symbol] The region associated with the client.
  #
  attr_reader :region

  ##
  # Instantiates a new League of Legends REST API client.
  #
  # @param api_key [String] Your private API key generated from
  #   {https://developer.riotgames.com/ Riot Games API}.
  # @param region [Symbol] The region this client is associated with.  Must be
  #   one of the values defined in {REGIONS}.
  #
  def initialize(api_key, region: :na)
    @api_key = api_key
    @region = region

    raise ArgumentError, 'invalid API key' unless api_key.is_a?(String)
    raise ArgumentError, "invalid region, must be one of: #{REGIONS.map(&:inspect).join(', ')}" unless REGIONS.include?(region)
  end

  ##
  # Returns a string containing a human-readable representation of this object.
  #
  # @return [String] The human-readable representation of this object.
  #
  def inspect
    super
    hex_id = '%x' % (object_id << 1)
    hex_id = "0x#{hex_id.rjust(14, '0')}"

    "#<#{self.class.name}:#{hex_id} @region=#{region.inspect}>"
  end

  ##
  # Retrieves all champions.
  #
  # @return [Array] The list of {Champion} objects.
  #
  def champions
    url = url_for("#{region}/v1.1/champion")
    get url, ChampionsRepresenter.new([])
  end

  ##
  # Retrieves recent games for a summoner.
  #
  # @param summoner_id [Fixnum] The player's summoner ID.
  #
  # @return [Array] The list of {Game} objects.
  #
  def recent_games(summoner_id)
    url = url_for("#{region}/v1.3/game/by-summoner/#{summoner_id}/recent")
    get url, GamesRepresenter.new([])
  end

  ##
  # Retrieves challenger tier leagues.
  #
  # @param type [Symbol] The type of league to retrieve.  Must be one of the
  #   values defined in {CHALLENGER_LEAGUE_TYPES}.
  #
  # @return [League] The challenger league.
  #
  def challenger_league(type)
    raise ArgumentError, "invalid league type, must be one of: #{CHALLENGER_LEAGUE_TYPES.map(&:inspect).join(', ')}" unless CHALLENGER_LEAGUE_TYPES.include?(type)

    url = url_for("#{region}/v2.3/league/challenger", type: type)
    get url, LeagueRepresenter.new(League.new)
  end

  ##
  # Retrieves league entries data for a summoner, including league entries for
  # all of the summoner's teams.
  #
  # @param summoner_id [Fixnum] The player's summoner ID.
  #
  # @return [Array] The list of {LeagueEntry} objects.
  #
  def league_entries(summoner_id)
    url = url_for("#{region}/v2.3/league/by-summoner/#{summoner_id}/entry")
    get url, LeagueEntriesRepresenter.new([])
  end

  ##
  # Retrieves leagues data for a summoner, including leagues for all of the
  # summoner's teams.
  #
  # @param summoner_id [Fixnum] The player's summoner ID.
  #
  # @return [Array] The list of {LeagueEntry} objects.
  #
  def leagues(summoner_id)
    url = url_for("#{region}/v2.3/league/by-summoner/#{summoner_id}")
    get url, LeaguesRepresenter.new([])
  end

  ##
  # Retrieves summoner data for one or more summoners.
  #
  # @param summoner_ids [Array, String, Fixnum] One or more summoner IDs.
  #
  # @return [Hash] The {Summoner} objects, where each key is a string
  #   representing a requested summoner ID.
  #
  def summoners(summoner_ids)
    summoner_ids = array_options(summoner_ids)
    url = url_for("#{region}/v1.3/summoner/#{summoner_ids.join(',')}")
    get url, SummonersRepresenter.new({})
  end

  ##
  # Retrieves summoner data for a single summoner.
  #
  # @param summoner_id [Fixnum] The player's summoner ID.
  #
  # @return [Summoner] The summoner's data.
  #
  def summoner(summoner_id)
    summoner_ids = array_options(summoner_id)
    summoner_id = summoner_ids.first
    raise ArgumentError, 'invalid summoner ID' if summoner_ids.size != 1

    summoners(summoner_id)[summoner_id]
  end

  ##
  # Retrieves summoner data for one or more summoners.
  #
  # @param summoner_names [Array, String] One or more summoner names.
  #
  # @return [Array] The {Summoner} objects, where each key is a string
  #   representing a requested lower-case summoner name.
  #
  def summoners_by_name(summoner_names)
    summoner_names = array_options(summoner_names)
    url = url_for("#{region}/v1.3/summoner/by-name/#{summoner_names.join(',')}")
    get url, SummonersRepresenter.new({})
  end

  ##
  # Retrieves summoner data for a single summoner.
  #
  # @param summoner_name [String] The player's summoner name.
  #
  # @return [Summoner] The summoner's data.
  #
  def summoner_by_name(summoner_name)
    summoner_names = array_options(summoner_name)
    summoner_name = summoner_names.first
    raise ArgumentError, 'invalid summoner name' if summoner_names.size != 1

    summoners_by_name(summoner_name)[summoner_name.downcase]
  end

  ##
  # Retrieves summoner names for one or more summoners.
  #
  # @param summoner_ids [Array, String, Fixnum] One or more summoner IDs.
  #
  # @return [Hash] The summoner names, where each key is a string representing
  #   a requested summoner ID.
  #
  def summoner_names(summoner_ids)
    summoner_ids = array_options(summoner_ids)
    url = url_for("#{region}/v1.3/summoner/#{summoner_ids.join(',')}/name")
    get url, SummonerNamesRepresenter.new({})
  end

  ##
  # Retrieves summoner name for a single summoner.
  #
  # @param summoner_id [Fixnum] The player's summoner ID.
  #
  # @return [Summoner] The summoner's data.
  #
  def summoner_name(summoner_id)
    summoner_ids = array_options(summoner_id)
    summoner_id = summoner_ids.first
    raise ArgumentError, 'invalid summoner ID' if summoner_ids.size != 1

    summoner_names(summoner_id)[summoner_id]
  end

  ##
  # Retrieves info for all champions.
  #
  # @param locale [String] The locale to use for champion data (e.g.: "en_US"
  #   or "es_ES").
  # @param version [String] The version to use for champion data.
  # @param data [String, Array] The data to include in the response.  May only
  #   include values defined in {STATIC_CHAMPION_DATA_OPTIONS}.
  #
  # @return [Hash] The {Static::Champion} objects, where each key is a string
  #   representing the champion's id.
  #
  def static_champions(locale: nil, version: nil, data: nil)
    data = array_options(data)
    data.each { |opt| raise ArgumentError, "invalid data option \"#{opt}\"" unless STATIC_CHAMPION_DATA_OPTIONS.include?(opt) }

    params = params_for(locale: locale, version: version, champData: data.join(','))
    url = url_for("static-data/#{region}/v1/champion", params)

    get url, Static::ChampionsRepresenter.new({})
  end

  ##
  # Retrieves info for a specific champion.
  #
  # @param champion_id [Fixnum] The champion's ID.
  # @param locale [String] The locale to use for champion data (e.g.: "en_US"
  #   or "es_ES").
  # @param version [String] The version to use for champion data.
  # @param data [String, Array] The data to include in the response.  May only
  #   include values defined in {STATIC_CHAMPION_DATA_OPTIONS}.
  #
  # @return [Static::Champion] The champion's data.
  #
  def static_champion(champion_id, locale: nil, version: nil, data: nil)
    data = array_options(data)
    data.each { |opt| raise ArgumentError, "invalid data option \"#{opt}\"" unless STATIC_CHAMPION_DATA_OPTIONS.include?(opt) }

    params = params_for(locale: locale, version: version, champData: data.join(','))
    url = url_for("static-data/#{region}/v1/champion/#{champion_id}", params)

    get url, Static::ChampionRepresenter.new(Static::Champion.new)
  end

  ##
  # Retrieves info for all items.
  #
  # @param locale [String] The locale to use for item data (e.g.: "en_US"
  #   or "es_ES").
  # @param version [String] The version to use for item data.
  # @param data [String, Array] The data to include in the response.  May only
  #   include values defined in {STATIC_ITEM_DATA_OPTIONS}.
  #
  # @return [Hash] The {Static::Item} objects, where each key is a string
  #   representing the item's id.
  #
  def static_items(locale: nil, version: nil, data: nil)
    data = array_options(data)
    data.each { |opt| raise ArgumentError, "invalid data option \"#{opt}\"" unless STATIC_ITEM_DATA_OPTIONS.include?(opt) }

    params = params_for(locale: locale, version: version, itemListData: data.join(','))
    url = url_for("static-data/#{region}/v1/item", params)

    get url, Static::ItemsRepresenter.new(OpenStruct.new)
  end

  ##
  # Retrieves info for a specific item.
  #
  # @param item_id [Fixnum] The item's ID.
  # @param locale [String] The locale to use for item data (e.g.: "en_US"
  #   or "es_ES").
  # @param version [String] The version to use for item data.
  # @param data [String, Array] The data to include in the response.  May only
  #   include values defined in {STATIC_ITEM_DATA_OPTIONS}.
  #
  # @return [Static::Item] The item's data.
  #
  def static_item(item_id, locale: nil, version: nil, data: nil)
    data = array_options(data)
    data.each { |opt| raise ArgumentError, "invalid data option \"#{opt}\"" unless STATIC_ITEM_DATA_OPTIONS.include?(opt) }

    params = params_for(locale: locale, version: version, itemData: data.join(','))
    url = url_for("static-data/#{region}/v1/item/#{item_id}", params)

    get url, Static::ItemRepresenter.new(Static::Item.new)
  end

  ##
  # Retrieves info for all masteries.
  #
  # @param locale [String] The locale to use for item data (e.g.: "en_US"
  #   or "es_ES").
  # @param version [String] The version to use for item data.
  # @param data [String, Array] The data to include in the response.  May only
  #   contain values defined in {STATIC_MASTERY_DATA_OPTIONS}.
  #
  # @return [Hash] The {Static::Mastery} objects, where each key is a string
  #   representing the mastery's id.
  #
  def static_masteries(locale: nil, version: nil, data: nil)
    data = array_options(data)
    data.each { |opt| raise ArgumentError, "invalid data option \"#{opt}\"" unless STATIC_MASTERY_DATA_OPTIONS.include?(opt) }

    params = params_for(locale: locale, version: version, masteryListData: data.join(','))
    url = url_for("static-data/#{region}/v1/mastery", params)

    get url, Static::MasteriesRepresenter.new({})
  end

  ##
  # Retrieves info for a specific mastery.
  #
  # @param mastery_id [Fixnum] The mastery's ID.
  # @param locale [String] The locale to use for item data (e.g.: "en_US"
  #   or "es_ES").
  # @param version [String] The version to use for item data.
  # @param data [String, Array] The data to include in the response.  May only
  #   contain values defined in {STATIC_MASTERY_DATA_OPTIONS}.
  #
  # @return [Static::Mastery] The mastery's data.
  #
  def static_mastery(mastery_id, locale: nil, version: nil, data: nil)
    data = array_options(data)
    data.each { |opt| raise ArgumentError, "invalid data option \"#{opt}\"" unless STATIC_MASTERY_DATA_OPTIONS.include?(opt) }

    params = params_for(locale: locale, version: version, masteryData: data.join(','))
    url = url_for("static-data/#{region}/v1/mastery/#{mastery_id}", params)

    get url, Static::MasteryRepresenter.new(Static::Mastery.new)
  end

  ##
  # Retrieves info for all runes.
  #
  # @param locale [String] The locale to use for rune data (e.g.: "en_US"
  #   or "es_ES").
  # @param version [String] The version to use for rune data.
  # @param data [String, Array] The data to include in the response.  May only
  #   contain values defined in {STATIC_RUNE_DATA_OPTIONS}.
  #
  # @return [Hash] The {Static::Rune} objects, where each key is a string
  #   representing the rune's id.
  #
  def static_runes(locale: nil, version: nil, data: nil)
    data = array_options(data)
    data.each { |opt| raise ArgumentError, "invalid data option \"#{opt}\"" unless STATIC_RUNE_DATA_OPTIONS.include?(opt) }

    params = params_for(locale: locale, version: version, runeListData: data.join(','))
    url = url_for("static-data/#{region}/v1/rune", params)

    get url, Static::RunesRepresenter.new(OpenStruct.new)
  end

  ##
  # Retrieves info for a specific rune.
  #
  # @param rune_id [Fixnum] The rune's ID.
  # @param locale [String] The locale to use for rune data (e.g.: "en_US"
  #   or "es_ES").
  # @param version [String] The version to use for rune data.
  # @param data [String, Array] The data to include in the response.  May only
  #   contain values defined in {STATIC_RUNE_DATA_OPTIONS}.
  #
  # @return [Static::Rune] The rune's data.
  #
  def static_rune(rune_id, locale: nil, version: nil, data: nil)
    data = array_options(data)
    data.each { |opt| raise ArgumentError, "invalid data option \"#{opt}\"" unless STATIC_RUNE_DATA_OPTIONS.include?(opt) }

    params = params_for(locale: locale, version: version, runeData: data.join(','))
    url = url_for("static-data/#{region}/v1/rune/#{rune_id}", params)

    get url, Static::RuneRepresenter.new(Static::Rune.new)
  end

  ##
  # Retrieves info for all summoner spells.
  #
  # @param locale [String] The locale to use for spell data (e.g.: "en_US"
  #   or "es_ES").
  # @param version [String] The version to use for spell data.
  # @param data [String, Array] The data to include in the response.  May only
  #   contain values defined in {STATIC_SPELL_DATA_OPTIONS}.
  #
  # @return [Hash] The {Static::SummonerSpell} objects, where each key is a string
  #   representing the spell's id.
  #
  def static_spells(locale: nil, version: nil, data: nil)
    data = array_options(data)
    data.each { |opt| raise ArgumentError, "invalid data option \"#{opt}\"" unless STATIC_SPELL_DATA_OPTIONS.include?(opt) }

    params = params_for(locale: locale, version: version, spellData: data.join(','))
    url = url_for("static-data/#{region}/v1/summoner-spell", params)

    get url, Static::SummonerSpellsRepresenter.new({})
  end

  ##
  # Retrieves info for a specific summoner spell.
  #
  # @param spell_id [Fixnum] The spell's ID.
  # @param locale [String] The locale to use for spell data (e.g.: "en_US"
  #   or "es_ES").
  # @param version [String] The version to use for spell data.
  # @param data [String, Array] The data to include in the response.  May only
  #   contain values defined in {STATIC_SPELL_DATA_OPTIONS}.
  #
  # @return [Static::SummonerSpell] The spell's data.
  #
  def static_spell(spell_id, locale: nil, version: nil, data: nil)
    data = array_options(data)
    data.each { |opt| raise ArgumentError, "invalid data option \"#{opt}\"" unless STATIC_SPELL_DATA_OPTIONS.include?(opt) }

    params = params_for(locale: locale, version: version, spellData: data.join(','))
    url = url_for("static-data/#{region}/v1/summoner-spell/#{spell_id}", params)

    get url, Static::SummonerSpellRepresenter.new(Static::SummonerSpell.new)
  end

  private

  attr_reader :api_key

  ##
  # Generates the request URL for an endpoint.
  #
  def url_for(path, params = {})
    url = "https://prod.api.pvp.net/api/lol/#{path}"

    query = params.merge(api_key: api_key)
    query = query.map { |k,v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
    query = nil if query.empty?

    [url, query].compact.join('?')
  end

  ##
  # Builds a hash of parameters, removing parameters with empty values.
  #
  def params_for(params = {})
    params.select { |_, v| !v.nil? }
  end

  ##
  # Performs a GET request, mapping the response to the appropriate object.
  #
  def get(url, representer)
    response = begin
                 RestClient.get(url).body
               rescue RestClient::Exception => e
                 raise RequestError, e.message
               end

    representer.from_json(response)
  end

  ##
  # Converts an array into a comma-delimited string.
  #
  def array_options(values)
    [*values].join(',').split(',').map(&:strip).reject(&:empty?).compact
  end

  ##
  # General error class for all API requests.
  #
  class RequestError < StandardError
  end
end