First steps using the League of Legends API

TL;DR In this post I show you how to get comfortable with Riot's League of Legends API. We'll build a script to query the outcome of a summoner's last game. You can see the final Ruby script here – download it, insert your API-key and and run it:

# Usage: ruby last_game.rb <summoner_name> <na/euw/...>
$ ruby last_game.rb Imaqtipie na
"On 2016-08-16 (18:03) Imaqtipie lost his last game, which was a RANKED_SOLO_5x5 (CLASSIC)"

Yeah, I admit it, I enjoy playing League of Legends from time to time. In this realtime MMO gamers play together as team and try to overcome their opponents' base. Each game lasts for ~45 minutes and produces, in addition to a rollercoaster ride of emotions, also loads of data.

Because I'm also a developer I like to play with these data and build small scripts to get more information about me or fellow players. That's possible because Riot provides these data via a JSON-API that registered users have access to.

Many services have been created around this API, like lolking.net or champion.gg to provide players with additional stats and infos about themselves and their oponents. In this post I want to show you how you can access this API with a few lines of simple ruby code.

1. Find a use case

Before we start let's think about what we like to build.

In this post I'd like to build a simple cli Ruby script that shows basic infos about the last game of a specific player (also called "summoner" in LoL-speak).

So if I want to know, how the last game of high elo player "Imaqtpie" from North America (NA) went, I'd like to to do this: preview

2. Request an API-Key for the Riot Games API

Before we can start using the API, we have to request an access key, which Riot uses to prevent unauthorized or abusive usage.

Head over to Riot's developer page and sign in with your League of Legends account credentials. On the next page you should find a section "MY DEVELOPMENT API KEY" already showing your personal API-Key (like 550e8400-e29b-11d4-a716-446655440000).

3. Build a simple Ruby script

Enough talking!

3.1 Basics

Before we start sending requests, let's set up some basics first:

1
2
3
4
5
6
7
8
9
require "net/http"
require "json"
require "date"

# TODO: Insert your own key here!
API_KEY = "550e8400-e29b-11d4-a716-446655440000"

@summoner_name = ARGV[0]
@region = ARGV[1] || "euw"

At first we require some libraries we are going to rely on – don't worry, those are all standard libraries which every Ruby environment has installed by default.

Then the API-key we got from Riot earlier gets defined – please insert your own key you got earlier here.

Last but not least we set summoner name and region, the latter defaulting to West Europe (since I'm from Germany).

3.2 What we want

Let's pretend for a second we already wrote the main logic and we just needed to invoke it – how would that look like? I went with this:

1
2
summoner = Summoner.new(@summoner_name, @region)
summoner.print_recent_game_info

We instantiate an object called Summoner and tell it to print some infos.

Of course there is no Summoner class – yet!

3.3 The main logic

Now we have to create both, the Summoner class and the invoked method Summoner#print_recent_game_info. Let's do this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Summoner < Struct.new(:name, :region)
  def print_recent_game_info
    info = {
      date: Time.at(recent_game["createDate"] / 1000)
                .strftime("%Y-%m-%d (%H:%M)"),
      summoner: name,
      outcome: recent_game["stats"]["win"] ? "won" : "lost",
      game_type: recent_game["subType"],
      game_mode: recent_game["gameMode"]
    }

    p "On %{date} %{summoner} %{outcome} his last game, " \
          "which was a %{game_type} (%{game_mode})" % info
  end
end

The class descends from Struct.new which is a helper Ruby provides that enables us to initialize a Summoner with :name and :region without having to deal with cluttering #initialize code.

In #print_recent_game_info's first line we collect all data needed for the output. It calls Summoner#recent_game – another method which we have yet to define. We extract information from the API's response and format it, so we can output a nice date, or get an expressive answer for the game's outcome.

Then info gets mixed with a string template, which results in the final output we want.

3.3.1 #recent_game

Let's see what Summoner#recent_game does:

1
2
3
4
5
6
7
8
9
10
class Summoner < Struct.new(:name, :region)

  def print_recent_game_info
    # ...
  end

  def recent_game
    @recent_game ||= get_from_api("/api/lol/#{region}/v1.3/game/by-summoner/#{summoner_id}/recent")["games"].first
  end
end

There are multiple things happening in one line here – let's start with this long string in line 8. It's the API's endpoint we need to call if we want information about a summoner's recent game. It has two dynamic components: The Summoner#region, which got set by the constructor, and Summoner#summoner_id, which needs to determine the ID Riot is using internally to identify Summoners and isn't implemented yet.

The endpoint-url-string gets passed into Summoner#get_from_api, which returns the games data hash from Riot's API. We immediately grab "games" that contains an array of game-hashes (check the API-documentation for more info on the data structure), and return the first entry (which should be the youngest one).

3.3.2 #summoner_id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Summoner < Struct.new(:name, :region)

  def print_recent_game_info
    # ...
  end

  def recent_game
    # ...
  end

  def summoner_id
    standardized_summoner_name = name.downcase.gsub(" ", "")
    @summoner_id ||= get_from_api("/api/lol/#{region}/v1.4/summoner/by-name/#{standardized_summoner_name}")
      .fetch(standardized_summoner_name)
      .fetch("id")
  end
end

Before we can query for the summoner_id, we need to standardize the summoner name – we do that and store the result in standardized_summoner_name. Then we build the endpoint path, execute the request and fetch the id from the resulting hash. I use fetch here because it immediately complains if a key is missing.

3.4 The API call

Last but not least we need to make the actual API call:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Summoner < Struct.new(:name, :region)

  def print_recent_game_info
    # ...
  end

  def recent_game
    # ...
  end

  def summoner_id
    # ...
  end

  private

  def get_from_api(path)
    uri = URI(URI.join("https://#{region}.api.pvp.net", path) + "?api_key=#{API_KEY}")

    http = Net::HTTP.new(uri.hostname, uri.port)
    http.use_ssl = true

    response = http.request(Net::HTTP::Get.new(uri, _header = {'Content-Type' =>'application/json'}))

    case response
    when Net::HTTPSuccess
      return JSON.parse(response.body)
    when Net::HTTPNotFound
      raise "Couldn't find the record, maybe you misspelled something?"
    else
      raise "An error occurred while processing #{path}
           Response #{response.code} #{response.message}:
          #{response.body}"
    end
  end
end

At first we build an URI object containing host, path and the API key. We then build a new HTTP-object for the API, enable SSL, and do a JSON get request. http.request's response gets saved in, well, response. Last but not least we check for the response-type with a case statement to handle success or failure. If you, for example, mistype the summoner-name Riot will return a 404 – since that can happen more often than not we are going to handle that by checking for the response type Net::HTTPNotFound. Anything but success and not found will be put into a more generic error message and will help us debugging problems.

And that's it! Check the final script here and try it for yourself – with relatively low effort we can query League of Legends' API for all kinds of data. Check Riot's formidable documentation to learn what else can be done – you could for example build a Summoner#current_game that shows a summoner's live stats and items while he is in a game.