├── .rspec ├── spec ├── fixtures │ ├── v3 │ │ ├── get-ids-by-tc.json │ │ ├── get-static-champions-by-id.json │ │ ├── get-summoner-by-name.json │ │ ├── get-summoner.json │ │ ├── get-static-runes-by-id.json │ │ ├── get-static-masteries-by-id.json │ │ ├── get-champion-266.json │ │ ├── get-static-items-by-id.json │ │ ├── get-static-summoner-spells-by-id.json │ │ ├── get-league-positions.json │ │ ├── get-static-realms.json │ │ ├── get-champion-mastery.json │ │ ├── get-tournament-code.json │ │ ├── get-static-reforged-runes-by-id.json │ │ ├── get-static-maps.json │ │ ├── get-static-versions.json │ │ ├── get-matches-recent.json │ │ ├── get-lol-status-shard.json │ │ ├── get-masteries.json │ │ ├── get-static-summoner-spells.json │ │ ├── get-current-game.json │ │ ├── get-runes.json │ │ ├── get-featured-games.json │ │ ├── get-static-champions.json │ │ ├── get-static-masteries.json │ │ ├── get-champion-all.json │ │ ├── get-champion-masteries.json │ │ ├── get-match.json │ │ └── get-match-with-tc.json │ └── v1 │ │ └── get-get-code.json ├── lol │ ├── runes_request_spec.rb │ ├── masteries_request_spec.rb │ ├── lol_status_request_spec.rb │ ├── spectator_request_spec.rb │ ├── champion_request_spec.rb │ ├── tournament_request_spec.rb │ ├── summoner_request_spec.rb │ ├── match_request_spec.rb │ ├── league_request_spec.rb │ ├── champion_mastery_request_spec.rb │ ├── dynamic_model_spec.rb │ ├── invalid_api_response_spec.rb │ ├── static_request_spec.rb │ ├── client_spec.rb │ └── request_spec.rb ├── api_version_spec.rb ├── spec_helper.rb ├── support │ ├── helpers.rb │ └── model_helpers.rb └── acceptance_spec.rb ├── .autotest ├── lib ├── lol │ ├── version.rb │ ├── autoloader.rb │ ├── lol_status_request.rb │ ├── featured_game_list.rb │ ├── runes_request.rb │ ├── masteries_request.rb │ ├── spectator_request.rb │ ├── champion_request.rb │ ├── summoner_request.rb │ ├── invalid_api_response.rb │ ├── dynamic_model.rb │ ├── champion_mastery_request.rb │ ├── league_request.rb │ ├── static_request.rb │ ├── match_request.rb │ ├── client.rb │ ├── tournament_request.rb │ └── request.rb └── lol.rb ├── Gemfile ├── .travis.yml ├── TODO.md ├── .gitignore ├── .editorconfig ├── demo └── demo.rb ├── Rakefile ├── LICENSE.txt ├── Guardfile ├── ruby-lol.gemspec └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /spec/fixtures/v3/get-ids-by-tc.json: -------------------------------------------------------------------------------- 1 | [2972702365] -------------------------------------------------------------------------------- /.autotest: -------------------------------------------------------------------------------- 1 | require 'autotest/fsevent' 2 | require 'autotest/growl' -------------------------------------------------------------------------------- /lib/lol/version.rb: -------------------------------------------------------------------------------- 1 | module Lol 2 | VERSION = "1.2.0" 3 | end 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in ruby-lol.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /spec/fixtures/v3/get-static-champions-by-id.json: -------------------------------------------------------------------------------- 1 | {"id":266,"key":"Aatrox","name":"Aatrox","title":"the Darkin Blade"} -------------------------------------------------------------------------------- /spec/fixtures/v3/get-summoner-by-name.json: -------------------------------------------------------------------------------- 1 | {"id":19325407,"accountId":22016665,"name":"FoO","profileIconId":11,"revisionDate":1447284435000,"summonerLevel":30} -------------------------------------------------------------------------------- /spec/fixtures/v3/get-summoner.json: -------------------------------------------------------------------------------- 1 | {"id":100023,"accountId":105754,"name":"qjQ1rLbjXvq05BRT","profileIconId":20,"revisionDate":1323135738000,"summonerLevel":3} -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | rvm: 2.4 2 | services: redis-server 3 | addons: 4 | code_climate: 5 | repo_token: c98576197ae22bdd3bede9cd3cdfdad33fac26bfdddb1e5f2bbc4fb535812185 6 | -------------------------------------------------------------------------------- /spec/fixtures/v3/get-static-runes-by-id.json: -------------------------------------------------------------------------------- 1 | {"id":5001,"name":"Lesser Mark of Attack Damage","description":"+0.53 attack damage","rune":{"isRune":true,"tier":"1","type":"red"}} -------------------------------------------------------------------------------- /spec/fixtures/v3/get-static-masteries-by-id.json: -------------------------------------------------------------------------------- 1 | {"id":6111,"name":"Fury","description":["+0.8% Attack Speed","+1.6% Attack Speed","+2.4% Attack Speed","+3.2% Attack Speed","+4% Attack Speed"]} -------------------------------------------------------------------------------- /spec/fixtures/v3/get-champion-266.json: -------------------------------------------------------------------------------- 1 | { 2 | "botMmEnabled": false, 3 | "id": 266, 4 | "rankedPlayEnabled": true, 5 | "botEnabled": false, 6 | "active": true, 7 | "freeToPlay": false 8 | } 9 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO # 2 | 3 | * finish writing acceptance specs in spec/acceptance_spec.rb 4 | * migrate everything to the new version of the api (just migrated game for now) 5 | * release 1.0 6 | * start better object mappings 7 | -------------------------------------------------------------------------------- /spec/fixtures/v1/get-get-code.json: -------------------------------------------------------------------------------- 1 | {"id":1,"providerId":2,"tournamentId":3,"code":"CODE-FOR-TEST","region":"EUW","map":"SUMMONERS_RIFT","teamSize":5,"spectators":"ALL","pickType":"BLIND_PICK","lobbyName":"LOBBY-NAME","password":"PASSWORD"} 2 | -------------------------------------------------------------------------------- /lib/lol/autoloader.rb: -------------------------------------------------------------------------------- 1 | Dir["#{File.expand_path('..', __FILE__)}/*.rb"].each do |file| 2 | filename = File.basename file 3 | classname = filename.split('.rb').first.camelize 4 | Lol.autoload classname, File.expand_path("../#{filename}", __FILE__) 5 | end 6 | -------------------------------------------------------------------------------- /spec/fixtures/v3/get-static-items-by-id.json: -------------------------------------------------------------------------------- 1 | {"id":1001,"name":"Boots of Speed","description":"Limited to 1.

UNIQUE Passive - Enhanced Movement: +25 Movement Speed","plaintext":"Slightly increases Movement Speed"} -------------------------------------------------------------------------------- /spec/fixtures/v3/get-static-summoner-spells-by-id.json: -------------------------------------------------------------------------------- 1 | {"name":"Cleanse","description":"Removes all disables (excluding suppression) and summoner spell debuffs affecting your champion and lowers the duration of incoming disables by 65% for 3 seconds.","summonerLevel":6,"id":1,"key":"SummonerBoost"} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | /output.txt 19 | /bin 20 | /.vscode 21 | /.idea 22 | -------------------------------------------------------------------------------- /spec/fixtures/v3/get-league-positions.json: -------------------------------------------------------------------------------- 1 | [{"leagueName":"Vladimir's Ravagers","tier":"BRONZE","queueType":"RANKED_SOLO_5x5","rank":"I","playerOrTeamId":"30743211","playerOrTeamName":"Riot intinig","leaguePoints":40,"wins":11,"losses":14,"veteran":false,"inactive":false,"freshBlood":false,"hotStreak":false}] -------------------------------------------------------------------------------- /spec/fixtures/v3/get-static-realms.json: -------------------------------------------------------------------------------- 1 | {"css":"7.9.1","dd":"7.9.1","l":"en_GB","n":{"item":"7.9.1","rune":"7.9.1","mastery":"7.9.1","summoner":"7.9.1","champion":"7.9.1","profileicon":"7.9.1","map":"7.9.1","language":"7.9.1","sticker":"7.9.1"},"profileiconmax":28,"v":"7.9.1","lg":"7.9.1","cdn":"http://ddragon.leagueoflegends.com/cdn"} -------------------------------------------------------------------------------- /spec/fixtures/v3/get-champion-mastery.json: -------------------------------------------------------------------------------- 1 | { 2 | "highestGrade": "S+", 3 | "championPoints": 34356, 4 | "playerId": 1, 5 | "championPointsUntilNextLevel": 0, 6 | "chestGranted": true, 7 | "championLevel": 5, 8 | "tokensEarned": 2, 9 | "championId": 40, 10 | "championPointsSinceLastLevel": 12756, 11 | "lastPlayTime": 1464499894000 12 | } -------------------------------------------------------------------------------- /spec/fixtures/v3/get-tournament-code.json: -------------------------------------------------------------------------------- 1 | {"id":3030173,"providerId":1000,"tournamentId":244507,"code":"EUW04397-222ec1fe-cb5f-430c-b5dd-51298c8cc50d","region":"EUW","map":"SUMMONERS_RIFT","teamSize":1,"spectators":"ALL","pickType":"TOURNAMENT_DRAFT","lobbyName":"c82325b7-6c4d-4424-a249-a11834f2a366","password":"46e61504b16f30a3666fa11beb58d5","participants":[30743211,79607125]} -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | insert_final_newline = true 11 | indent_style = space 12 | trim_trailing_whitespace = true 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /demo/demo.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(__dir__ + "/../lib") 2 | 3 | require "rubygems" 4 | require "ap" 5 | require "lol" 6 | 7 | include Lol 8 | 9 | client = Client.new "18b7c486-0d5a-459f-9d60-cf8776b154c6" 10 | intinig = client.summoner.by_name "intinig" 11 | leagues = client.league.get intinig.id 12 | my_league = leagues.first 13 | 14 | my_league.entries.select {|entry| entry.player_or_team_name == "intinig"}.each do |entry| 15 | ap entry 16 | end 17 | -------------------------------------------------------------------------------- /spec/lol/runes_request_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "lol" 3 | 4 | describe Lol::RunesRequest do 5 | subject { Lol::RunesRequest.new "api_key", "euw" } 6 | 7 | describe "#by_summoner_id" do 8 | it "returns an Array of DynamicModel" do 9 | stub_request subject, "runes", "runes/by-summoner/1" 10 | result = subject.by_summoner_id 1 11 | expect(result).to be_a Array 12 | expect(result.map(&:class).uniq).to eq [Lol::DynamicModel] 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/lol/masteries_request_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "lol" 3 | 4 | describe Lol::MasteriesRequest do 5 | subject { Lol::MasteriesRequest.new "api_key", "euw" } 6 | 7 | describe "#by_summoner_id" do 8 | it "returns an Array of DynamicModel" do 9 | stub_request subject, "masteries", "masteries/by-summoner/1" 10 | result = subject.by_summoner_id 1 11 | expect(result).to be_a Array 12 | expect(result.map(&:class).uniq).to eq [Lol::DynamicModel] 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/lol/lol_status_request.rb: -------------------------------------------------------------------------------- 1 | module Lol 2 | # Bindings for the Status API. 3 | # 4 | # See: https://developer.riotgames.com/api-methods/#lol-status-v3 5 | class LolStatusRequest < Request 6 | # @!visibility private 7 | def api_base_path 8 | "/lol/status/#{self.class.api_version}" 9 | end 10 | 11 | # Get League of Legends status for the given shard 12 | # @return [DynamicModel] 13 | def shard_data 14 | DynamicModel.new perform_request api_url "shard-data" 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/lol/featured_game_list.rb: -------------------------------------------------------------------------------- 1 | module Lol 2 | # List of featured games 3 | # 4 | # Each contained featured game is represented through {DynamicModel} 5 | class FeaturedGameList < Array 6 | # The suggested interval to wait before requesting FeaturedGames again 7 | # @return [Integer] 8 | attr_reader :client_refresh_interval 9 | 10 | def initialize data 11 | @client_refresh_interval = data['clientRefreshInterval'] 12 | super data['gameList'].map { |g| DynamicModel.new g } 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/lol/runes_request.rb: -------------------------------------------------------------------------------- 1 | module Lol 2 | # Bindings for the Runes API. 3 | # 4 | # See: https://developer.riotgames.com/api-methods/#runes-v3 5 | class RunesRequest < Request 6 | # Get rune pages for a given summoner ID 7 | # @param [Integer] summoner_id Summoner ID 8 | # @return [Array] Rune pages 9 | def by_summoner_id summoner_id 10 | result = perform_request api_url "runes/by-summoner/#{summoner_id}" 11 | result["pages"].map { |p| DynamicModel.new p } 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/lol/masteries_request.rb: -------------------------------------------------------------------------------- 1 | module Lol 2 | # Bindings for the Masteries API. 3 | # 4 | # See: https://developer.riotgames.com/api-methods/#masteries-v3 5 | class MasteriesRequest < Request 6 | # Get mastery pages for a given summoner ID 7 | # @param [Integer] summoner_id Summoner ID 8 | # @return [Array] Mastery pages 9 | def by_summoner_id summoner_id 10 | result = perform_request api_url "masteries/by-summoner/#{summoner_id}" 11 | result["pages"].map { |p| DynamicModel.new p } 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/fixtures/v3/get-static-reforged-runes-by-id.json: -------------------------------------------------------------------------------- 1 | {"id":8410,"key":"ApproachVelocity","name":"Approach Velocity","shortDesc":"Bonus MS towards nearby ally champions that are movement impaired or enemy champions that you impair.","longDesc":"Gain 15% Movement Speed towards nearby ally champions that are movement impaired or enemy champions that you impair.

Range: 1000","icon":"perk-images/Styles/Resolve/ApproachVelocity/ApproachVelocity.png","runePathId":8300,"runePathName":"Inspiration"} 2 | -------------------------------------------------------------------------------- /lib/lol.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/core_ext/string/inflections' 2 | require 'httparty' 3 | require 'uri' 4 | require 'redis' 5 | require 'glutton_ratelimit' 6 | 7 | module Lol 8 | # Takes a hash and returns a copy of it with the keys that have been underscored 9 | # This method is here but should be somewhere else, probably an helper module 10 | # @param [Hash] a hash with keys in camelCase format 11 | # @return [Hash] a copy of the original hash, with hash keys that have been underscored 12 | def self.underscore_hash_keys hash 13 | hash.inject({}) { |memo, (key, value)| memo.update key.to_s.underscore => value } 14 | end 15 | end 16 | 17 | require 'lol/autoloader' 18 | require "lol/invalid_api_response" 19 | -------------------------------------------------------------------------------- /spec/lol/lol_status_request_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "lol" 3 | 4 | include Lol 5 | 6 | describe LolStatusRequest do 7 | subject { LolStatusRequest.new "api_key", "euw" } 8 | 9 | it 'inherits from Request' do 10 | expect(LolStatusRequest).to be < Request 11 | end 12 | 13 | describe '#shard_data' do 14 | let(:response) { subject.shard_data } 15 | 16 | before(:each) { stub_request(subject, 'lol-status-shard', 'shard-data') } 17 | 18 | it 'returns a Shard' do 19 | expect(response).to be_a(DynamicModel) 20 | end 21 | 22 | it 'services returns an array of Services' do 23 | expect(response.services.map(&:class).uniq).to eq([DynamicModel]) 24 | end 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/setup" 2 | require "bundler/gem_tasks" 3 | require "rspec/core/rake_task" 4 | 5 | RSpec::Core::RakeTask.new(:spec, :tag) do |t, task_args| 6 | t.rspec_opts = "--tag #{task_args[:tag]}" if task_args[:tag] 7 | end 8 | 9 | task :default => :spec 10 | 11 | desc "Open an irb session preloaded with this library" 12 | task :console do 13 | sh "irb -rubygems -I . -r lib/lol.rb" 14 | end 15 | 16 | desc "Clean up leftover fixtures" 17 | task :clean_fixtures do 18 | puts Dir.glob("spec/fixtures/*/*").map {|f| f.gsub("spec/fixtures/", "")}.map {|f| f.split("/")}.each_with_object({}) {|e, ack| ack[e[1]] ||= []; ack[e[1]] << e[0]}.select {|k,v| v.size > 1}.map {|k,v| "spec/fixtures/#{v.first}/#{k}"}.each {|f| File.delete f} 19 | end 20 | -------------------------------------------------------------------------------- /spec/api_version_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "lol" 3 | 4 | include Lol 5 | 6 | 7 | def check_api_version(klass, version) 8 | describe klass do 9 | it "should use API version #{version}" do 10 | expect(klass.api_version).to eq(version) 11 | end 12 | end 13 | end 14 | 15 | describe "API Versions" do 16 | check_api_version(ChampionRequest, "v3") 17 | check_api_version(ChampionMasteryRequest, "v3") 18 | check_api_version(LeagueRequest, "v3") 19 | check_api_version(StaticRequest, "v3") 20 | check_api_version(LolStatusRequest, "v3") 21 | check_api_version(MatchRequest, "v3") 22 | check_api_version(SummonerRequest, "v3") 23 | check_api_version(RunesRequest, "v3") 24 | check_api_version(MasteriesRequest, "v3") 25 | check_api_version(SpectatorRequest, "v3") 26 | end 27 | -------------------------------------------------------------------------------- /spec/lol/spectator_request_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "lol" 3 | 4 | describe Lol::SpectatorRequest do 5 | subject { described_class.new "api_key", "euw" } 6 | 7 | describe "#current_game" do 8 | it "returns a DynamicModel" do 9 | stub_request subject, "current-game", "active-games/by-summoner/23" 10 | expect(subject.current_game summoner_id: 23).to be_a Lol::DynamicModel 11 | end 12 | end 13 | 14 | describe "#featured_games" do 15 | it "returns a FeaturedGameList" do 16 | stub_request subject, "featured-games", "featured-games" 17 | result = subject.featured_games 18 | expect(result).to be_a Lol::FeaturedGameList 19 | expect(result.client_refresh_interval).not_to be_nil 20 | expect(result.size).not_to be_zero 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "rspec" 2 | require "simplecov" 3 | require "coveralls" 4 | require "codeclimate-test-reporter" 5 | require "vcr" 6 | 7 | SPEC_ROOT = __dir__ 8 | 9 | Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each {|f| require f} 10 | 11 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new [ 12 | Coveralls::SimpleCov::Formatter, 13 | SimpleCov::Formatter::HTMLFormatter 14 | ] 15 | 16 | SimpleCov.start 17 | 18 | VCR.configure do |c| 19 | c.cassette_library_dir = File.join(SPEC_ROOT, '..', 'fixtures', 'vcr_cassettes') 20 | c.hook_into :webmock 21 | c.ignore_hosts 'codeclimate.com' 22 | c.configure_rspec_metadata! 23 | end 24 | 25 | RSpec.configure do |c| 26 | # c.fail_fast = true 27 | c.filter_run_excluding :remote => true 28 | c.include Helpers 29 | end 30 | -------------------------------------------------------------------------------- /lib/lol/spectator_request.rb: -------------------------------------------------------------------------------- 1 | module Lol 2 | # Bindings for the Match API. 3 | # 4 | # See: https://developer.riotgames.com/api-methods/#match-v3 5 | class SpectatorRequest < Request 6 | # @!visibility private 7 | def api_base_path 8 | "/lol/spectator/#{self.class.api_version}" 9 | end 10 | 11 | # Get current game information for the given summoner ID. 12 | # @param [Integer] summoner_id Summoner ID 13 | # @return [DynamicModel] Current game representation 14 | def current_game summoner_id: 15 | DynamicModel.new perform_request api_url "active-games/by-summoner/#{summoner_id}" 16 | end 17 | 18 | # Get list of featured games. 19 | # @return [FeaturedGameList] list of featured games 20 | def featured_games 21 | FeaturedGameList.new perform_request api_url "featured-games" 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/fixtures/v3/get-static-maps.json: -------------------------------------------------------------------------------- 1 | {"type":"map","version":"7.9.1","data":{"11":{"mapName":"Summoner's Rift","mapId":11,"image":{"full":"map11.png","sprite":"map0.png","group":"map","x":96,"y":0,"w":48,"h":48}},"12":{"mapName":"Howling Abyss","mapId":12,"image":{"full":"map12.png","sprite":"map0.png","group":"map","x":144,"y":0,"w":48,"h":48}},"14":{"mapName":"Butcher's Bridge","mapId":14,"image":{"full":"map14.png","sprite":"map0.png","group":"map","x":192,"y":0,"w":48,"h":48}},"16":{"mapName":"Cosmic Ruins","mapId":16,"image":{"full":"map16.png","sprite":"map0.png","group":"map","x":240,"y":0,"w":48,"h":48}},"8":{"mapName":"The Crystal Scar","mapId":8,"image":{"full":"map8.png","sprite":"map0.png","group":"map","x":0,"y":0,"w":48,"h":48}},"10":{"mapName":"The Twisted Treeline","mapId":10,"image":{"full":"map10.png","sprite":"map0.png","group":"map","x":48,"y":0,"w":48,"h":48}}}} -------------------------------------------------------------------------------- /spec/lol/champion_request_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "lol" 3 | 4 | include Lol 5 | 6 | describe ChampionRequest do 7 | subject { described_class.new("api_key", "euw") } 8 | 9 | it "inherits from Request" do 10 | expect(described_class.ancestors[1]).to eq(Request) 11 | end 12 | 13 | describe "#find" do 14 | it "returns a champion" do 15 | stub_request subject, 'champion-266', 'champions/266' 16 | expect(subject.find 266).to be_a DynamicModel 17 | end 18 | end 19 | 20 | describe "#all" do 21 | before { stub_request subject, 'champion-all', 'champions', 'freeToPlay' => false } 22 | let(:result) { subject.all } 23 | 24 | it "returns an array of champions" do 25 | expect(result).to be_a Array 26 | expect(result.map(&:class).uniq).to eq [DynamicModel] 27 | end 28 | 29 | it "fetches champions from the API" do 30 | expect(result.size).to eq load_fixture('champion-all', described_class.api_version)['champions'].size 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/lol/champion_request.rb: -------------------------------------------------------------------------------- 1 | module Lol 2 | # Bindings for the Champion API. 3 | # 4 | # See: https://developer.riotgames.com/api-methods/#champion-v3 5 | class ChampionRequest < Request 6 | # Retrieve all champions 7 | # 8 | # See: https://developer.riotgames.com/api-methods/#champion-v3/GET_getChampions 9 | # @param free_to_play [Boolean] filter param to retrieve only free to play champions 10 | # @return [Array] an array of champions 11 | def all free_to_play: false 12 | result = perform_request api_url("champions", "freeToPlay" => free_to_play) 13 | result["champions"].map { |c| DynamicModel.new c } 14 | end 15 | 16 | # Retrieve champion by ID 17 | # 18 | # See: https://developer.riotgames.com/api-methods/#champion-v3/GET_getChampionsById 19 | # @param id [Integer] id of the champion to get 20 | # @return [Lol::DynamicModel] the found champion 21 | def find id 22 | result = perform_request api_url "champions/#{id}" 23 | DynamicModel.new result 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Giovanni Intini 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /lib/lol/summoner_request.rb: -------------------------------------------------------------------------------- 1 | module Lol 2 | # Bindings for the Summoner API. 3 | # 4 | # See: https://developer.riotgames.com/api-methods/#summoner-v3 5 | class SummonerRequest < Request 6 | # @!visibility private 7 | def api_base_path 8 | "/lol/summoner/#{self.class.api_version}" 9 | end 10 | 11 | # Get a summoner by summoner ID. 12 | # @param [Integer] id Summoner ID 13 | # @return [DynamicModel] Summoner representation 14 | def find id 15 | DynamicModel.new perform_request api_url "summoners/#{id}" 16 | end 17 | 18 | # Get a summoner by summoner name. 19 | # @param [String] name Summoner name 20 | # @return [DynamicModel] Summoner representation 21 | def find_by_name name 22 | name = CGI.escape name.downcase.gsub(/\s/, '') 23 | DynamicModel.new perform_request api_url "summoners/by-name/#{name}" 24 | end 25 | 26 | # Get a summoner by account ID. 27 | # @param [Integer] account_id Account ID 28 | # @return [DynamicModel] Summoner representation 29 | def find_by_account_id account_id 30 | DynamicModel.new perform_request api_url "summoners/by-account/#{account_id}" 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/lol/tournament_request_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "lol" 3 | 4 | include Lol 5 | 6 | describe TournamentRequest do 7 | subject { described_class.new "api_key", "euw" } 8 | 9 | it "inherits from Request" do 10 | expect(TournamentRequest).to be < Request 11 | end 12 | 13 | describe "#create_provider" do 14 | it "returns the provider id" do 15 | expect(subject).to receive(:perform_request).with(instance_of(String), :post, { "url" => "https://foo.com", "region" => "EUW" }).and_return 10 16 | expect(subject.create_provider url: "https://foo.com").to eq 10 17 | end 18 | end 19 | 20 | describe "#create_tournament" do 21 | it "returns the tournament id" do 22 | expect(subject).to receive(:perform_request).with(instance_of(String), :post, { "providerId" => 10, "name" => "ASD" }).and_return 10 23 | expect(subject.create_tournament provider_id: 10, name: "ASD").to eq 10 24 | end 25 | end 26 | 27 | describe "#find_code" do 28 | it "returns a DynamicModel" do 29 | stub_request subject, "tournament-code", "codes/foo" 30 | expect(subject.find_code 'foo').to be_a DynamicModel 31 | end 32 | end 33 | 34 | pending '#create_codes' 35 | pending '#update_code' 36 | end 37 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | guard :rspec, cmd: "bundle exec rspec" do 5 | watch(%r{^spec/.+_spec\.rb$}) 6 | watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } 7 | watch('spec/spec_helper.rb') { "spec" } 8 | 9 | # Rails example 10 | watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } 11 | watch(%r{^app/(.*)(\.erb|\.haml|\.slim)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } 12 | watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] } 13 | watch(%r{^spec/support/(.+)\.rb$}) { "spec" } 14 | watch('config/routes.rb') { "spec/routing" } 15 | watch('app/controllers/application_controller.rb') { "spec/controllers" } 16 | 17 | # Capybara features specs 18 | watch(%r{^app/views/(.+)/.*\.(erb|haml|slim)$}) { |m| "spec/features/#{m[1]}_spec.rb" } 19 | 20 | # Turnip features and steps 21 | watch(%r{^spec/acceptance/(.+)\.feature$}) 22 | watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' } 23 | end 24 | -------------------------------------------------------------------------------- /spec/lol/summoner_request_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "lol" 3 | 4 | include Lol 5 | 6 | describe SummonerRequest do 7 | subject { SummonerRequest.new "api_key", "euw" } 8 | 9 | it "inherits from Request" do 10 | expect(SummonerRequest).to be < Request 11 | end 12 | 13 | describe "#find" do 14 | it "returns a DynamicModel" do 15 | stub_request subject, "summoner", "summoners/23" 16 | expect(subject.find 23).to be_a DynamicModel 17 | end 18 | end 19 | 20 | describe "#find_by_name" do 21 | it "returns a DynamicModel" do 22 | stub_request subject, 'summoner-by-name', 'summoners/by-name/foo' 23 | expect(subject.find_by_name 'foo').to be_a DynamicModel 24 | end 25 | 26 | it "escapes the given name" do 27 | stub_request subject, 'summoner-by-name', 'summoners/by-name/f%C3%B2%C3%A5' 28 | subject.find_by_name 'fòå' 29 | end 30 | 31 | it "downcases the given name" do 32 | stub_request subject, 'summoner-by-name', 'summoners/by-name/arg' 33 | subject.find_by_name 'ARG' 34 | end 35 | 36 | it 'strips spaces from names' do 37 | stub_request(subject, 'summoner-by-name', 'summoners/by-name/foo') 38 | subject.find_by_name('fo o') 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/lol/invalid_api_response.rb: -------------------------------------------------------------------------------- 1 | module Lol 2 | class InvalidAPIResponse < StandardError 3 | attr_reader :raw 4 | 5 | def initialize url, response 6 | @raw = extract_raw_response_info response 7 | super "#{raw[:status]} calling #{url}" 8 | end 9 | 10 | private 11 | 12 | def extract_raw_response_info response 13 | { 14 | headers: extract_raw_headers(response), 15 | body: extract_raw_body(response), 16 | status: extract_raw_status(response) 17 | } 18 | end 19 | 20 | def extract_raw_headers response 21 | response.respond_to?(:headers) && response.headers || {} 22 | end 23 | 24 | def extract_raw_body response 25 | if response.respond_to?(:parsed_response) 26 | response.parsed_response 27 | elsif response.respond_to?(:body) 28 | response.body 29 | else 30 | response 31 | end 32 | end 33 | 34 | def extract_raw_status response 35 | if response.is_a?(Hash) && response['status'] 36 | response['status']['message'] 37 | elsif response.respond_to?(:response) 38 | "#{response.response.code} #{response.response.message}" 39 | else 40 | "Unknown Error" 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/fixtures/v3/get-static-versions.json: -------------------------------------------------------------------------------- 1 | ["7.10.1","7.9.2","7.9.1","7.8.1","7.7.1","7.6.1","7.5.2","7.5.1","7.4.3","7.4.2","7.4.1","7.3.3","7.3.2","7.3.1","7.2.1","7.1.1","6.24.1","6.23.1","6.22.1","6.21.1","6.20.1","6.19.1","6.18.1","6.17.1","6.16.2","6.16.1","6.15.1","6.14.2","6.14.1","6.13.1","6.12.1","6.11.1","6.10.1","6.9.1","6.8.1","6.7.1","6.6.1","6.5.1","6.4.2","6.4.1","6.3.1","6.2.1","6.1.1","5.24.2","5.24.1","5.23.1","5.22.3","5.22.2","5.22.1","5.21.1","5.20.1","5.19.1","5.18.1","5.17.1","5.16.1","5.15.1","5.14.1","5.13.1","5.12.1","5.11.1","5.10.1","5.9.1","5.8.1","5.7.2","5.7.1","5.6.2","5.6.1","5.5.3","5.5.2","5.5.1","5.4.1","5.3.1","5.2.2","5.2.1","5.1.2","5.1.1","4.21.5","4.21.4","4.21.3","4.21.1","4.20.2","4.20.1","4.19.3","4.19.2","4.18.1","4.17.1","4.16.1","4.15.1","4.14.2","4.13.1","4.12.2","4.12.1","4.11.3","4.10.7","4.10.2","4.9.1","4.8.3","4.8.2","4.8.1","4.7.16","4.7.9","4.7.8","4.6.3","4.5.4","4.4.3","4.4.2","4.3.18","4.3.12","4.3.10","4.3.4","4.3.2","4.2.6","4.2.5","4.2.1","4.1.43","4.1.41","4.1.13","4.1.9","4.1.2","3.15.5","3.15.4","3.15.2","3.14.41","3.14.23","3.14.22","3.14.20","3.14.19","3.14.16","3.14.13","3.14.12","3.13.24","3.13.8","3.13.6","3.13.1","3.12.37","3.12.36","3.12.34","3.12.33","3.12.26","3.12.24","3.12.2","3.11.4","3.11.2","3.10.6","3.10.3","3.10.2","3.9.7","3.9.5","3.9.4","3.8.5","3.8.3","3.8.1","3.7.9","3.7.2","3.7.1","3.6.15","3.6.14","0.154.3","0.154.2","0.153.2","0.152.115","0.152.108","0.152.107","0.152.55","0.151.101","0.151.2"] -------------------------------------------------------------------------------- /spec/lol/match_request_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "lol" 3 | 4 | include Lol 5 | 6 | describe MatchRequest do 7 | subject { MatchRequest.new "api_key", "euw" } 8 | 9 | it "inherits from Request" do 10 | expect(MatchRequest).to be < Request 11 | end 12 | 13 | describe "#find" do 14 | it "returns a DynamicModel" do 15 | stub_request subject, 'match', "matches/1" 16 | expect(subject.find match_id: 1).to be_a DynamicModel 17 | end 18 | end 19 | 20 | describe "#find_timeline" do 21 | it "returns a DynamicModel" do 22 | stub_request subject, 'timeline', "timelines/by-match/1" 23 | expect(subject.find_timeline 1).to be_a DynamicModel 24 | end 25 | end 26 | 27 | describe "#ids_by_tournament_code" do 28 | it "returns a list of ids" do 29 | stub_request subject, 'ids-by-tc', "matches/by-tournament-code/1/ids" 30 | result = subject.ids_by_tournament_code '1' 31 | expect(result).to be_a Array 32 | expect(result.map(&:class).uniq).to eq [Fixnum] 33 | end 34 | end 35 | 36 | describe "#find_by_tournament" do 37 | it "returns a DynamicModel" do 38 | stub_request subject, 'match-with-tc', "matches/1/by-tournament-code/2" 39 | expect(subject.find_by_tournament 1, 2).to be_a DynamicModel 40 | end 41 | end 42 | 43 | describe "#all" do 44 | it "returns a DynamicModel" do 45 | stub_request subject, 'matches', "matchlists/by-account/1" 46 | expect(subject.all account_id: 1).to be_a DynamicModel 47 | end 48 | end 49 | 50 | describe "#recent" do 51 | it "returns a DynamicModel" do 52 | stub_request subject, 'matches-recent', "matchlists/by-account/1/recent" 53 | expect(subject.recent account_id: 1).to be_a DynamicModel 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/lol/dynamic_model.rb: -------------------------------------------------------------------------------- 1 | require 'ostruct' 2 | require 'active_support/core_ext/string/inflections' 3 | 4 | module Lol 5 | # DynamicModel extends OpenStruct adding the following features: 6 | # - nested generation ({a: {}}) results in DynamicModel(a: DynamicModel) 7 | # - parsing of date/time when property name ends with _at or _date and the value is a number 8 | class DynamicModel < OpenStruct 9 | attr_reader :raw 10 | 11 | def initialize(hash={}) 12 | raise ArgumentError, 'An hash is required as parameter' unless hash.is_a? Hash 13 | @raw = hash 14 | @table = {} 15 | @hash_table = {} 16 | 17 | hash.each do |k,v| 18 | key = k.to_s.underscore 19 | set_property key, v 20 | new_ostruct_member(key) 21 | end 22 | end 23 | 24 | def to_h 25 | @hash_table 26 | end 27 | 28 | def as_json opts={} 29 | @table.as_json 30 | end 31 | 32 | protected 33 | 34 | def class_for_property property 35 | self.class 36 | end 37 | 38 | private 39 | 40 | def date_key? key 41 | key.match(/^(.+_)?(at|date)$/) 42 | end 43 | 44 | def set_property key, v 45 | if date_key?(key) && v.is_a?(Integer) 46 | @table[key.to_sym] = @hash_table[key.to_sym] = value_to_date v 47 | else 48 | @table[key.to_sym] = convert_object v, property: key.to_sym 49 | @hash_table[key.to_sym] = v 50 | end 51 | end 52 | 53 | def value_to_date v 54 | Time.at(v / 1000) 55 | end 56 | 57 | def convert_object obj, property: 58 | if obj.is_a? Hash 59 | class_for_property(property).new obj 60 | elsif obj.respond_to?(:map) 61 | obj.map { |o| convert_object o, property: property } 62 | else 63 | obj 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /ruby-lol.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'lol/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "ruby-lol" 8 | spec.version = Lol::VERSION 9 | spec.authors = ["Giovanni Intini"] 10 | spec.email = ["giovanni@mikamai.com"] 11 | spec.description = %q{Ruby wrapper to Riot Games API. Maps results to full blown ruby objects.} 12 | spec.summary = %q{Ruby wrapper to Riot Games API} 13 | spec.homepage = "https://github.com/mikamai/ruby-lol" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files`.split($/) 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_development_dependency "bundler", "~> 1.3" 22 | spec.add_development_dependency "rake" 23 | spec.add_development_dependency "yard" 24 | spec.add_development_dependency "redcarpet" 25 | spec.add_development_dependency "rspec" 26 | spec.add_development_dependency "guard-rspec" 27 | spec.add_development_dependency "ZenTest" 28 | spec.add_development_dependency "autotest-growl" 29 | spec.add_development_dependency "codeclimate-test-reporter", '~> 0.5.0' 30 | spec.add_development_dependency "coveralls" 31 | spec.add_development_dependency "vcr" 32 | spec.add_development_dependency "webmock", ">= 1.8.0", "< 1.16" 33 | spec.add_development_dependency "awesome_print" 34 | 35 | spec.add_runtime_dependency "httparty", "0.14.0" # due to https://github.com/jnunemaker/httparty/issues/533 36 | spec.add_runtime_dependency "activesupport" 37 | spec.add_runtime_dependency "redis" 38 | spec.add_runtime_dependency "glutton_ratelimit" 39 | end 40 | -------------------------------------------------------------------------------- /lib/lol/champion_mastery_request.rb: -------------------------------------------------------------------------------- 1 | module Lol 2 | # Bindings for the Champion Mastery API. 3 | # 4 | # See: https://developer.riotgames.com/api-methods/#champion-mastery-v3 5 | class ChampionMasteryRequest < Request 6 | # @!visibility private 7 | def api_base_path 8 | "/lol/champion-mastery/#{api_version}" 9 | end 10 | 11 | # Get a player's total champion mastery score, which is the sum of individual champion mastery levels 12 | # 13 | # See: https://developer.riotgames.com/api-methods/#champion-mastery-v3/GET_getDynamicModelScore 14 | # @param [Integer] summoner_id Summoner ID associated with the player 15 | # @return [Integer] Player's total champion master score 16 | def total_score summoner_id: 17 | perform_request api_url "scores/by-summoner/#{summoner_id}" 18 | end 19 | 20 | # Get all champion mastery entries sorted by number of champion points descending 21 | # 22 | # See: https://developer.riotgames.com/api-methods/#champion-mastery-v3/GET_getAllChampionMasteries 23 | # @param [Integer] summoner_id Summoner ID associated with the player 24 | # @return [Array] Champion Masteries 25 | def all summoner_id: 26 | result = perform_request api_url "champion-masteries/by-summoner/#{summoner_id}" 27 | result.map { |c| DynamicModel.new c } 28 | end 29 | 30 | # Get a champion mastery by player ID and champion ID 31 | # 32 | # See: https://developer.riotgames.com/api-methods/#champion-mastery-v3/GET_getDynamicModel 33 | # @param [Integer] summoner_id Summoner ID associated with the player 34 | # @return [Lol::DynamicModel] Champion Mastery 35 | def find champion_id, summoner_id: 36 | result = perform_request api_url "champion-masteries/by-summoner/#{summoner_id}/by-champion/#{champion_id}" 37 | DynamicModel.new result 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/lol/league_request_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "lol" 3 | 4 | include Lol 5 | 6 | describe LeagueRequest do 7 | subject { LeagueRequest.new 'api_key', 'euw' } 8 | 9 | it 'inherits from V3 Request' do 10 | expect(LeagueRequest).to be < Request 11 | end 12 | 13 | describe '#find_challenger' do 14 | it 'returns a DynamicModel' do 15 | stub_request subject, 'league-challenger', 'challengerleagues/by-queue/RANKED_SOLO_5x5' 16 | expect(subject.find_challenger).to be_a DynamicModel 17 | end 18 | 19 | it 'finds the challenger league for the given queue' do 20 | stub_request subject, 'league-challenger', 'challengerleagues/by-queue/foo' 21 | subject.find_challenger queue: 'foo' 22 | end 23 | end 24 | 25 | describe '#find_master' do 26 | it 'returns a LeagueList' do 27 | stub_request subject, 'league-master', 'masterleagues/by-queue/RANKED_SOLO_5x5' 28 | expect(subject.find_master).to be_a DynamicModel 29 | end 30 | 31 | it 'finds the master league for the given queue' do 32 | stub_request subject, 'league-master', 'masterleagues/by-queue/foo' 33 | subject.find_master queue: 'foo' 34 | end 35 | end 36 | 37 | describe '#summoner_leagues' do 38 | it 'returns an array of LeagueList objects' do 39 | stub_request subject, 'league-summoner', 'leagues/by-summoner/1' 40 | result = subject.summoner_leagues summoner_id: 1 41 | expect(result).to be_a Array 42 | expect(result.map(&:class).uniq).to eq [DynamicModel] 43 | end 44 | end 45 | 46 | describe '#summoner_positions' do 47 | it 'returns an array of DynamicModel objects' do 48 | stub_request subject, 'league-positions', 'positions/by-summoner/1' 49 | result = subject.summoner_positions summoner_id: 1 50 | expect(result).to be_a Array 51 | expect(result.map(&:class).uniq).to eq [DynamicModel] 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/lol/league_request.rb: -------------------------------------------------------------------------------- 1 | module Lol 2 | # Bindings for the League API. 3 | # 4 | # See: https://developer.riotgames.com/api-methods/#league-v3 5 | class LeagueRequest < Request 6 | # @!visibility private 7 | def api_base_path 8 | "/lol/league/#{api_version}" 9 | end 10 | 11 | # Get the challenger league for a given queue 12 | # @param [String] queue Queue identifier. See the list of game constants (developer.riotgames.com/game-constants.html) for the available queue identifiers 13 | # @return [DynamicModel] Challenger league 14 | def find_challenger queue: 'RANKED_SOLO_5x5' 15 | DynamicModel.new perform_request api_url "challengerleagues/by-queue/#{queue}" 16 | end 17 | 18 | # Get the master league for a given queue 19 | # @param [String] queue Queue identifier. See the list of game constants (developer.riotgames.com/game-constants.html) for the available queue identifiers 20 | # @return [DynamicModel] lMaster league 21 | def find_master queue: 'RANKED_SOLO_5x5' 22 | DynamicModel.new perform_request api_url "masterleagues/by-queue/#{queue}" 23 | end 24 | 25 | # Get leagues in all queues for a given summoner ID 26 | # @param [Integer] summoner_id Summoner ID associated with the player 27 | # @return [Array] List of leagues summoner is participating in 28 | def summoner_leagues summoner_id: 29 | result = perform_request api_url "leagues/by-summoner/#{summoner_id}" 30 | result.map { |c| DynamicModel.new c } 31 | end 32 | 33 | # Get league positions in all queues for a given summoner ID 34 | # @param [Integer] summoner_id Summoner ID associated with the player 35 | # @return [Array] list of league positions 36 | def summoner_positions summoner_id: 37 | result = perform_request api_url "positions/by-summoner/#{summoner_id}" 38 | result.map { |c| DynamicModel.new c } 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/lol/champion_mastery_request_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "lol" 3 | include Lol 4 | 5 | describe ChampionMasteryRequest do 6 | subject { ChampionMasteryRequest.new("api_key", "euw") } 7 | 8 | it "inherits from Request" do 9 | expect(ChampionMasteryRequest.ancestors[1]).to eq Request 10 | end 11 | 12 | describe "#total_score" do 13 | it "returns the total score" do 14 | stub_request_raw subject, 60, 'scores/by-summoner/1' 15 | expect(subject.total_score summoner_id: 1).to eq 60 16 | end 17 | end 18 | 19 | describe "#find" do 20 | it "returns a ChampionMastery" do 21 | stub_request(subject, 'champion-mastery', 'champion-masteries/by-summoner/1/by-champion/40') 22 | expect(subject.find 40, summoner_id: 1).to be_a DynamicModel 23 | end 24 | 25 | it "fetches ChampionMastery from the API" do 26 | stub_request(subject, 'champion-mastery', 'champion-masteries/by-summoner/1/by-champion/40') 27 | result = subject.find 40, summoner_id: 1 28 | expect(result.highest_grade).to eq('S+') 29 | expect(result.champion_points).to eq(34356) 30 | expect(result.player_id).to eq(1) 31 | expect(result.champion_points_until_next_level).to eq(0) 32 | expect(result.chest_granted).to be(true) 33 | expect(result.champion_level).to eq(5) 34 | expect(result.tokens_earned).to eq(2) 35 | expect(result.champion_id).to eq(40) 36 | expect(result.champion_points_since_last_level).to eq(12756) 37 | end 38 | end 39 | 40 | describe "#all" do 41 | before { stub_request(subject, 'champion-masteries', 'champion-masteries/by-summoner/1') } 42 | let(:result) { subject.all summoner_id: 1 } 43 | 44 | it "returns an Array of ChampionMastery" do 45 | expect(result).to be_a Array 46 | expect(result.map(&:class).uniq).to eq [DynamicModel] 47 | end 48 | 49 | it "fetches ChampionMastery properties from the API" do 50 | fixture = load_fixture('champion-masteries', described_class.api_version) 51 | expect(result.count).to eq fixture.count 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /spec/support/helpers.rb: -------------------------------------------------------------------------------- 1 | module Helpers 2 | def underscore(s) 3 | s.to_s.scan(/[A-Z][a-z]*/).join("_").downcase 4 | end 5 | 6 | def camelize(s) 7 | s[0] + s.to_s.split("_").each {|s| s.capitalize! }.join("")[1..-1] 8 | end 9 | 10 | def load_fixture(subject, version, method='get') 11 | fixture_file = File.join(SPEC_ROOT, 'fixtures', "#{version}", "#{method}-#{subject}.json") 12 | JSON.parse(File.read(fixture_file, :encoding => "utf-8")) 13 | end 14 | 15 | def expect_init_attribute(subject, attribute) 16 | expect(subject.new(camelize(attribute) => "foo").send(attribute)).to eq("foo") 17 | end 18 | 19 | def expect_read_only_attribute(subject, attribute) 20 | expect { subject.new.send("#{attribute}=".to_sym, "bar") }.to raise_error(NoMethodError) 21 | end 22 | 23 | def error_401 24 | response = {"status" => {"message" => "Foo", "status_code" => 401}} 25 | response.send :instance_eval do 26 | def code; 401; end 27 | def not_found?; false; end 28 | end 29 | response 30 | end 31 | 32 | def error_429 33 | response = {"status" => {"message" => "Foo", "status_code" => 429}} 34 | response.send :instance_eval do 35 | def code; 429; end 36 | def not_found?; false; end 37 | end 38 | response 39 | end 40 | 41 | def summoners 42 | { 43 | "euw" => "30743211", 44 | "na" => "5908", 45 | "eune" => "35778105" 46 | } 47 | end 48 | 49 | def stub_request(request_object, fixture_name, url, params={}) 50 | request_class = request_object.class 51 | full_url = request_object.api_url(url, params) 52 | fixture_json = load_fixture(fixture_name, request_class.api_version, :get) 53 | 54 | expect(request_class).to receive(:get).with(full_url, instance_of(Hash)).and_return(fixture_json) 55 | end 56 | 57 | def stub_request_raw(request_object, raw_response, url, params={}) 58 | request_class = request_object.class 59 | full_url = request_object.api_url(url, params) 60 | 61 | expect(request_class).to receive(:get).with(full_url, instance_of(Hash)).and_return(raw_response) 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /spec/acceptance_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "lol" 3 | require "awesome_print" 4 | 5 | # Requires connection 6 | describe "Live API testing", :remote => true do 7 | before(:all) do 8 | VCR.configure do |c| 9 | c.allow_http_connections_when_no_cassette = true 10 | end 11 | end 12 | 13 | let(:api_key) { ENV['RIOT_GAMES_API_KEY'] } 14 | subject { Lol::Client.new api_key } 15 | let(:fallback) { Lol::Client.new ENV['RIOT_GAMES_NEW_KEY'] } 16 | 17 | # @TODO: Maybe have aliases with singular / plural names so I can do subject.champions? 18 | let (:champions) { subject.champion.get } 19 | let (:intinig) { subject.summoner.by_name("intinig").first } 20 | let (:team) { fallback.team.by_summoner(intinig.id).first } 21 | 22 | describe "champion" do 23 | it "works on the collection" do 24 | expect {champions}.not_to raise_error 25 | end 26 | 27 | it "works on the single champion" do 28 | expect {subject.champion.get(:id => champions.first.id)}.not_to raise_error 29 | end 30 | end 31 | 32 | describe "game" do 33 | it "works on recent games for a summoner" do 34 | expect {fallback.game.recent intinig.id}.not_to raise_error 35 | end 36 | end 37 | 38 | describe "league" do 39 | it "works with get" do 40 | expect {fallback.league.get intinig.id}.not_to raise_error 41 | end 42 | 43 | it "works with entries" do 44 | expect {fallback.league.get_entries intinig.id}.not_to raise_error 45 | end 46 | 47 | it "works with teams" do 48 | expect {fallback.league.by_team team.id}.not_to raise_error 49 | end 50 | end 51 | 52 | describe "lol-static-data" do 53 | pending 54 | end 55 | 56 | describe "match" do 57 | pending 58 | end 59 | 60 | describe "matchhistory" do 61 | pending 62 | end 63 | 64 | describe "stats" do 65 | pending 66 | end 67 | 68 | describe "summoner" do 69 | pending 70 | end 71 | 72 | describe "team" do 73 | pending 74 | end 75 | 76 | after(:all) do 77 | VCR.configure do |c| 78 | c.allow_http_connections_when_no_cassette = true 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/lol/dynamic_model_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'lol/dynamic_model' 3 | 4 | include Lol 5 | 6 | describe DynamicModel do 7 | shared_examples 'enables date parsing' do 8 | let(:timestamp_value) { 1423243755000 } 9 | subject { DynamicModel.new attribute => timestamp_value} 10 | let(:attribute_value) { subject.send attribute } 11 | 12 | it 'and casts the value to Time' do 13 | expect(attribute_value).to be_a Time 14 | end 15 | 16 | it 'and divides the number by 1000' do 17 | expect(attribute_value.to_i).to eq timestamp_value / 1000 18 | end 19 | 20 | it 'and ignores casting when the value is not an integer' do 21 | subject = DynamicModel.new attribute => 'foo bar' 22 | expect(subject.send attribute).not_to be_a Time 23 | end 24 | end 25 | 26 | context 'when is not initialized with an hash' do 27 | subject { DynamicModel.new 'not enumerable data' } 28 | 29 | it 'raises an argument error' do 30 | expect{ subject }.to raise_error ArgumentError, 'An hash is required as parameter' 31 | end 32 | end 33 | 34 | context 'when initialized with hash' do 35 | subject { DynamicModel.new key: 'value' } 36 | 37 | it 'does not raise error' do 38 | expect { subject }.not_to raise_error 39 | end 40 | 41 | it 'defines a method per each argument key' do 42 | expect(subject).to respond_to(:key) 43 | end 44 | end 45 | 46 | %w(date at).each do |attr_name| 47 | context "when an hash key is called '#{attr_name}'" do 48 | it_behaves_like 'enables date parsing' do 49 | let(:attribute) { attr_name } 50 | end 51 | end 52 | 53 | context "when an hash key ends with '_#{attr_name}'" do 54 | it_behaves_like 'enables date parsing' do 55 | let(:attribute) { "a2342_#{attr_name}"} 56 | end 57 | end 58 | end 59 | 60 | context "when an hash value is an array" do 61 | it 'preserves the array' do 62 | subject = DynamicModel.new foo: ['foo'] 63 | expect(subject.foo).to eq ['foo'] 64 | end 65 | 66 | context 'and one or more array items are hashes' do 67 | subject { DynamicModel.new foo: [{}] } 68 | 69 | it 'casts each hash to another DynamicModel instance' do 70 | expect(subject.foo.map(&:class).uniq).to eq [DynamicModel] 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/lol/static_request.rb: -------------------------------------------------------------------------------- 1 | module Lol 2 | class StaticRequest < Request 3 | # @!visibility private 4 | def api_base_path 5 | "/lol/static-data/#{self.class.api_version}" 6 | end 7 | 8 | { 9 | "champion" => "champions", 10 | "item" => "items", 11 | "mastery" => "masteries", 12 | "rune" => "runes", 13 | "summoner_spell" => "summoner_spells" 14 | }.each do |old_endpoint, new_endpoint| 15 | define_method new_endpoint do 16 | Proxy.new self, new_endpoint 17 | end 18 | end 19 | 20 | def language_strings params={} 21 | perform_request(api_url "language-strings", params).to_hash["data"] 22 | end 23 | 24 | def languages 25 | perform_request api_url "languages" 26 | end 27 | 28 | def maps 29 | Proxy.new self, "maps" 30 | end 31 | 32 | def profile_icons params={} 33 | all "profile_icons", params 34 | end 35 | 36 | def realms 37 | Proxy.new self, "realms" 38 | end 39 | 40 | def reforged_runes 41 | Proxy.new self, "reforged_runes" 42 | end 43 | 44 | def versions 45 | Proxy.new self, "versions" 46 | end 47 | 48 | def get(endpoint, id=nil, params={}) 49 | return perform_request(api_url("versions")) if endpoint == "versions" 50 | id ? find(endpoint, id, params) : all(endpoint, params) 51 | end 52 | 53 | private 54 | 55 | def find(endpoint, id, params={}) 56 | OpenStruct.new \ 57 | perform_request(api_url("#{endpoint.dasherize}/#{id}", params)).to_hash 58 | end 59 | 60 | def all(endpoint, params={}) 61 | if %w(realms).include? endpoint 62 | OpenStruct.new perform_request(api_url(endpoint.dasherize, params)).to_hash 63 | elsif %w(reforged_runes).include? endpoint 64 | perform_request(api_url(endpoint.dasherize, params)).map do |hash| 65 | OpenStruct.new(hash) 66 | end 67 | else 68 | perform_request(api_url(endpoint.dasherize, params))["data"].map do |id, values| 69 | OpenStruct.new(values.merge(id: values["id"] || id)) 70 | end 71 | end 72 | end 73 | 74 | class Proxy 75 | def initialize(request, endpoint) 76 | @request = request 77 | @endpoint = endpoint 78 | end 79 | 80 | def get(id=nil, params={}) 81 | if id.is_a?(Hash) 82 | params = id 83 | id = nil 84 | end 85 | 86 | @request.get @endpoint, id, params 87 | end 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /spec/fixtures/v3/get-matches-recent.json: -------------------------------------------------------------------------------- 1 | {"matches":[{"platformId":"EUW1","gameId":3185083402,"champion":41,"queue":420,"season":8,"timestamp":1494888385722,"role":"SOLO","lane":"TOP"},{"platformId":"EUW1","gameId":3184692827,"champion":60,"queue":0,"season":8,"timestamp":1494881999945,"role":"NONE","lane":"JUNGLE"},{"platformId":"EUW1","gameId":3184652546,"champion":60,"queue":0,"season":8,"timestamp":1494878962107,"role":"NONE","lane":"JUNGLE"},{"platformId":"EUW1","gameId":3184652015,"champion":60,"queue":0,"season":8,"timestamp":1494875667971,"role":"NONE","lane":"JUNGLE"},{"platformId":"EUW1","gameId":3184772187,"champion":64,"queue":420,"season":8,"timestamp":1494871012420,"role":"NONE","lane":"JUNGLE"},{"platformId":"EUW1","gameId":3184673600,"champion":114,"queue":420,"season":8,"timestamp":1494868332986,"role":"SOLO","lane":"TOP"},{"platformId":"EUW1","gameId":3184101351,"champion":114,"queue":420,"season":8,"timestamp":1494851759999,"role":"SOLO","lane":"TOP"},{"platformId":"EUW1","gameId":3184039602,"champion":114,"queue":420,"season":8,"timestamp":1494849918307,"role":"SOLO","lane":"TOP"},{"platformId":"EUW1","gameId":3183878862,"champion":4,"queue":420,"season":8,"timestamp":1494807966896,"role":"SOLO","lane":"MID"},{"platformId":"EUW1","gameId":3183868620,"champion":5,"queue":420,"season":8,"timestamp":1494805667573,"role":"NONE","lane":"JUNGLE"},{"platformId":"EUW1","gameId":3183876056,"champion":412,"queue":610,"season":8,"timestamp":1494804584364,"role":"DUO","lane":"MID"},{"platformId":"EUW1","gameId":3183875342,"champion":412,"queue":610,"season":8,"timestamp":1494803790404,"role":"DUO","lane":"MID"},{"platformId":"EUW1","gameId":3183874708,"champion":412,"queue":610,"season":8,"timestamp":1494802949703,"role":"NONE","lane":"MID"},{"platformId":"EUW1","gameId":3183853468,"champion":107,"queue":420,"season":8,"timestamp":1494800755359,"role":"NONE","lane":"JUNGLE"},{"platformId":"EUW1","gameId":3183814158,"champion":64,"queue":420,"season":8,"timestamp":1494797442606,"role":"NONE","lane":"JUNGLE"},{"platformId":"EUW1","gameId":3183511601,"champion":238,"queue":420,"season":8,"timestamp":1494781445331,"role":"SOLO","lane":"MID"},{"platformId":"EUW1","gameId":3183407264,"champion":238,"queue":420,"season":8,"timestamp":1494778042415,"role":"SOLO","lane":"MID"},{"platformId":"EUW1","gameId":3183339181,"champion":238,"queue":420,"season":8,"timestamp":1494775693663,"role":"SOLO","lane":"MID"},{"platformId":"EUW1","gameId":3183306511,"champion":412,"queue":420,"season":8,"timestamp":1494773237191,"role":"DUO_SUPPORT","lane":"BOTTOM"},{"platformId":"EUW1","gameId":3183235120,"champion":41,"queue":420,"season":8,"timestamp":1494770284775,"role":"SOLO","lane":"TOP"}],"startIndex":0,"endIndex":20,"totalGames":20} -------------------------------------------------------------------------------- /spec/fixtures/v3/get-lol-status-shard.json: -------------------------------------------------------------------------------- 1 | {"name":"EU West","slug":"euw","locales":["en_GB","fr_FR","it_IT","es_ES","de_DE"],"hostname":"prod.euw1.lol.riotgames.com","region_tag":"eu","services":[{"name":"Game","slug":"game","status":"online","incidents":[{"id":4365,"active":true,"created_at":"2017-05-15T19:39:34.590Z","updates":[{"id":"591a03f6f76f3d01008927e0","author":"","content":"On 17/05/17, starting at 03:30 UK Time (02:30 UTC), ranked queues will be disabled in preparation for patch 7.10. At 05:00 UK Time (04:00 UTC), the servers will be shut down and all games currently in progress will end in a draw and stats will not be recorded. We estimate the battlegrounds will be unavailable for 3 hours.","severity":"info","created_at":"2017-05-15T19:39:34.590Z","updated_at":"2017-05-15T19:39:34.590Z","translations":[{"locale":"fr_FR","heading":"7.10","content":"Le 17/05/17 à partir de 04:30 heure de l'hexagone (02:30 UTC), les parties classées seront désactivées pour préparer le patch 7.10. À 06:00 (04:00 UTC), les serveurs seront fermés, toutes les parties en cours prendront fin sur un match nul et les statistiques ne seront pas prises en compte. Nous estimons l'indisponibilité des champs de justice à 3 heures."},{"locale":"it_IT","heading":"7.10","content":"Dalle 04:30 italiane (02:30 UTC) del 17/05/17, le code classificate verranno disabilitate in preparazione per la patch 7.10. Alle 06:00 italiane (04:00 UTC) del 17/05/17, i server andranno offline e tutte le partite in corso finiranno in un pareggio e le statistiche non verranno registrate. Le nostre arene dovrebbero essere offline per circa 3 ore."},{"locale":"es_ES","heading":"7.10","content":"El 17/05/17, desde las 04:30 horario peninsular (02:30 UTC), las colas de clasificatorias estarán desactivadas en preparación para la versión 7.10. A las 06:00 horario peninsular (04:00 UTC), los servidores se deshabilitarán, las partidas que estén en juego acabarán con empate y las estadísticas no serán registradas. Los campos de batalla estarán desactivados durante unas 3 horas."},{"locale":"de_DE","heading":"7.10","content":"Am 17/05/17, um 04:30 Uhr deutscher Zeit (02:30 UTC), werden die Ranglistenwarteschlangen in Vorbereitung auf den Patch 7.10 deaktiviert. Um 06:00 Uhr deutscher Zeit (04:00 UTC), werden die Server heruntergefahren und alle laufenden Spiele werden in einem Unentschieden enden, wobei keine Statistiken aufgezeichnet werden. Wir gehen davon aus, dass die Richtfelder für etwa 3 Stunden nicht verfügbar sein werden."}]}]}]},{"name":"Store","slug":"store","status":"online","incidents":[]},{"name":"Website","slug":"website","status":"online","incidents":[]},{"name":"Client","slug":"client","status":"online","incidents":[]},{"name":"League client update","slug":"league client update","status":"online","incidents":[]}]} -------------------------------------------------------------------------------- /spec/fixtures/v3/get-masteries.json: -------------------------------------------------------------------------------- 1 | {"summonerId":100012,"pages":[{"id":34059355,"name":"ad critique","current":false,"masteries":[{"id":6343,"rank":1},{"id":6131,"rank":5},{"id":6122,"rank":1},{"id":6331,"rank":5},{"id":6111,"rank":5},{"id":6141,"rank":1},{"id":6312,"rank":5},{"id":6322,"rank":1},{"id":6161,"rank":1},{"id":6151,"rank":5}]},{"id":34059356,"name":"ad vitesse","current":false,"masteries":[{"id":6343,"rank":1},{"id":6131,"rank":5},{"id":6122,"rank":1},{"id":6331,"rank":5},{"id":6111,"rank":5},{"id":6141,"rank":1},{"id":6312,"rank":5},{"id":6322,"rank":1},{"id":6162,"rank":1},{"id":6151,"rank":5}]},{"id":34059357,"name":"cast AP","current":false,"masteries":[{"id":6343,"rank":1},{"id":6131,"rank":5},{"id":6114,"rank":5},{"id":6122,"rank":1},{"id":6331,"rank":5},{"id":6312,"rank":5},{"id":6142,"rank":1},{"id":6322,"rank":1},{"id":6154,"rank":5},{"id":6164,"rank":1}]},{"id":34059358,"name":"Burst AP","current":false,"masteries":[{"id":6343,"rank":1},{"id":6114,"rank":5},{"id":6122,"rank":1},{"id":6331,"rank":5},{"id":6312,"rank":5},{"id":6142,"rank":1},{"id":6322,"rank":1},{"id":6351,"rank":5},{"id":6134,"rank":5},{"id":6362,"rank":1}]},{"id":34059359,"name":"tank","current":false,"masteries":[{"id":6121,"rank":1},{"id":6223,"rank":1},{"id":6114,"rank":5},{"id":6242,"rank":1},{"id":6232,"rank":3},{"id":6312,"rank":5},{"id":6263,"rank":1},{"id":6322,"rank":1},{"id":6212,"rank":5},{"id":6231,"rank":2},{"id":6252,"rank":5}]},{"id":34059360,"name":"support agressif","current":false,"masteries":[{"id":6342,"rank":1},{"id":6121,"rank":1},{"id":6311,"rank":5},{"id":6131,"rank":5},{"id":6114,"rank":5},{"id":6331,"rank":5},{"id":6142,"rank":1},{"id":6322,"rank":1},{"id":6154,"rank":5},{"id":6164,"rank":1}]},{"id":34059361,"name":"support deffensif","current":false,"masteries":[{"id":6342,"rank":1},{"id":6241,"rank":1},{"id":6311,"rank":5},{"id":6221,"rank":1},{"id":6211,"rank":5},{"id":6322,"rank":1},{"id":6332,"rank":5},{"id":6231,"rank":5},{"id":6363,"rank":1},{"id":6352,"rank":5}]},{"id":34059362,"name":"jungle bruiser","current":false,"masteries":[{"id":6121,"rank":1},{"id":6343,"rank":1},{"id":6311,"rank":5},{"id":6114,"rank":5},{"id":6331,"rank":5},{"id":6321,"rank":1},{"id":6142,"rank":1},{"id":6351,"rank":5},{"id":6134,"rank":5},{"id":6362,"rank":1}]},{"id":34059363,"name":"jungle mage","current":false,"masteries":[{"id":6121,"rank":1},{"id":6343,"rank":1},{"id":6311,"rank":5},{"id":6131,"rank":5},{"id":6114,"rank":5},{"id":6331,"rank":5},{"id":6321,"rank":1},{"id":6142,"rank":1},{"id":6154,"rank":5},{"id":6164,"rank":1}]},{"id":34059364,"name":"AP DE CHEZ AP","current":true,"masteries":[{"id":6121,"rank":1},{"id":6114,"rank":5},{"id":6311,"rank":5},{"id":6343,"rank":1},{"id":6331,"rank":5},{"id":6142,"rank":1},{"id":6322,"rank":1},{"id":6351,"rank":5},{"id":6134,"rank":5},{"id":6362,"rank":1}]}]} -------------------------------------------------------------------------------- /spec/lol/invalid_api_response_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "lol" 3 | 4 | include Lol 5 | 6 | describe InvalidAPIResponse do 7 | subject { InvalidAPIResponse.new "foo", "status" => { "message" => "bar"} } 8 | 9 | it "does not crash on initialization" do 10 | expect { subject }.not_to raise_error 11 | end 12 | 13 | it "assigns raw" do 14 | expect(subject.raw).not_to be_nil 15 | end 16 | 17 | context "when the received response is an hash with a 'status' key" do 18 | it "sets a certain message including the status message" do 19 | expect(subject.message).to match /^bar/ 20 | end 21 | 22 | it "sets a certain message including the url" do 23 | expect(subject.message).to match /foo$/ 24 | end 25 | end 26 | 27 | context "when the received response has no 'status' key" do 28 | subject { InvalidAPIResponse.new "foo", headers: {}, body: 'asd' } 29 | 30 | it "sets a certain message including 'Unknown Error'" do 31 | expect(subject.message).to match /^Unknown Error/ 32 | end 33 | 34 | it "sets a certain message including the url" do 35 | expect(subject.message).to match /foo$/ 36 | end 37 | end 38 | 39 | describe '#raw' do 40 | describe ':headers' do 41 | it 'equals to headers in the response hash' do 42 | subject = InvalidAPIResponse.new 'foo', OpenStruct.new(headers: { a: 1 }) 43 | expect(subject.raw[:headers]).to eq a: 1 44 | end 45 | 46 | it 'equals an empty hash if response contain no headers' do 47 | subject = InvalidAPIResponse.new 'foo', OpenStruct.new 48 | expect(subject.raw[:headers]).to eq Hash.new 49 | end 50 | end 51 | 52 | describe ':body' do 53 | it 'equals to #parsed_response' do 54 | subject = InvalidAPIResponse.new 'foo', OpenStruct.new(parsed_response: 'a') 55 | expect(subject.raw[:body]).to eq 'a' 56 | end 57 | 58 | it 'equals to #body if response has no #parsed_response' do 59 | subject = InvalidAPIResponse.new 'foo', OpenStruct.new(body: 'a') 60 | expect(subject.raw[:body]).to eq 'a' 61 | end 62 | 63 | it 'equals to the response itself if response has no #body or #parsed_response' do 64 | r = OpenStruct.new 65 | expect(InvalidAPIResponse.new('foo', r).raw[:body]).to eq r 66 | end 67 | end 68 | 69 | describe ':status' do 70 | it 'equals to response status if response is an hash' do 71 | subject = InvalidAPIResponse.new 'foo', 'status' => { 'message' => 'a' } 72 | expect(subject.raw[:status]).to eq 'a' 73 | end 74 | 75 | it 'equals to Unknown Error otherwise' do 76 | subject = InvalidAPIResponse.new 'foo', OpenStruct.new 77 | expect(subject.raw[:status]).to eq 'Unknown Error' 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/lol/match_request.rb: -------------------------------------------------------------------------------- 1 | module Lol 2 | # Bindings for the Match API. 3 | # 4 | # See: https://developer.riotgames.com/api-methods/#match-v3 5 | class MatchRequest < Request 6 | # @!visibility private 7 | def api_base_path 8 | "/lol/match/#{self.class.api_version}" 9 | end 10 | 11 | # Get match by match ID. 12 | # @param [Integer] match_id Match ID 13 | # @option options [Integer] forAccountId Optional used to identify the participant to be unobfuscated 14 | # @option options [Integer] forPlatformId Optional used to identify the participant to be unobfuscated (for when user have changed regions) 15 | # @return [DynamicModel] Match representation 16 | def find options={}, match_id: 17 | DynamicModel.new perform_request api_url "matches/#{match_id}", options 18 | end 19 | 20 | # Get match timeline by match ID. 21 | # @param [Integer] match_id Match ID 22 | # @return [DynamicModel] Timeline represantion 23 | def find_timeline match_id 24 | DynamicModel.new perform_request api_url "timelines/by-match/#{match_id}" 25 | end 26 | 27 | # Get match IDs by tournament code. 28 | # @param [String] tournament_code Tournament code 29 | # @return [Array] List of match IDs 30 | def ids_by_tournament_code tournament_code 31 | perform_request api_url "matches/by-tournament-code/#{tournament_code}/ids" 32 | end 33 | 34 | # Get match by match ID and tournament code. 35 | # @param [Integer] match_id Match ID 36 | # @param [String] tournament_code Tournament code the match belongs to 37 | # @return [DynamicModel] Match representation 38 | def find_by_tournament match_id, tournament_code 39 | DynamicModel.new perform_request api_url "matches/#{match_id}/by-tournament-code/#{tournament_code}" 40 | end 41 | 42 | # Get matchlist for ranked games played on given account ID and platform ID and filtered using given filter parameters, if any. 43 | # @param [Integer] account_id Account ID 44 | # @param [Hash] options the options to pass to the call 45 | # @option options [Array] queue Set of queue IDs for which to filtering matchlist. 46 | # @option options [Integer] beginTime The begin time to use for filtering matchlist specified as epoch milliseconds. 47 | # @option options [Integer] endTime The end time to use for filtering matchlist specified as epoch milliseconds. 48 | # @option options [Integer] beginIndex The begin index to use for filtering matchlist. 49 | # @option options [Integer] endIndex The end index to use for filtering matchlist. 50 | # @option options [Array] season Set of season IDs for which to filtering matchlist. 51 | # @option options [Array] champion Set of champion IDs for which to filtering matchlist. 52 | # @return [DynamicModel] MatchList represantion 53 | def all options={}, account_id: 54 | DynamicModel.new perform_request api_url "matchlists/by-account/#{account_id}", options 55 | end 56 | 57 | # Get matchlist for last 20 matches played on given account ID and platform ID. 58 | # @param [Integer] account_id Account ID 59 | # @return [DynamicModel] MatchList represantion 60 | def recent account_id: 61 | DynamicModel.new perform_request api_url "matchlists/by-account/#{account_id}/recent" 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/support/model_helpers.rb: -------------------------------------------------------------------------------- 1 | shared_examples 'attribute' do 2 | let(:setter) { "#{attribute}=" } 3 | 4 | it 'is read only' do 5 | expect(subject).to_not respond_to setter 6 | end 7 | end 8 | 9 | shared_examples 'Lol model' do 10 | let(:subject_class) { subject.class } 11 | 12 | describe '#new' do 13 | it "takes an option hash as argument" do 14 | expect { subject_class.new valid_attributes }.not_to raise_error 15 | end 16 | 17 | it 'raises an error if an attribute is not allowed' do 18 | expect { subject_class.new({ :foo => :bar }) }.to raise_error NoMethodError 19 | end 20 | 21 | it 'sets the given option hash as #raw' do 22 | expect(subject_class.new(valid_attributes).raw).to eq valid_attributes 23 | end 24 | end 25 | 26 | describe '#raw' do 27 | it_behaves_like 'attribute' do 28 | let(:attribute) { 'raw' } 29 | end 30 | end 31 | end 32 | 33 | shared_examples 'plain attribute' do 34 | let(:subject_class) { subject.class } 35 | let(:setter) { "#{attribute}=" } 36 | 37 | it_behaves_like 'attribute' 38 | 39 | context 'during #new' do 40 | it 'is set if the hash contains the attribute name "underscored"' do 41 | model = subject_class.new attribute => attribute_value 42 | expect(model.send attribute).to eq attribute_value 43 | end 44 | 45 | it 'is set if the hash contains the attribute name "camelized"' do 46 | model = subject_class.new camelize(attribute) => attribute_value 47 | expect(model.send attribute).to eq attribute_value 48 | end 49 | end 50 | end 51 | 52 | shared_examples 'time attribute' do 53 | let(:subject_class) { subject.class } 54 | let(:setter) { "#{attribute}=" } 55 | 56 | it_behaves_like 'plain attribute' do 57 | let(:attribute_value) { Time.now } 58 | end 59 | 60 | it "does not parse the value is it isn't a Numeric value" do 61 | model = subject_class.new(attribute => Date.today) 62 | expect(model.send attribute).to be_a Date 63 | end 64 | 65 | it "works with LoL format" do 66 | model = subject_class.new(attribute => 1386804971247) 67 | expect(model.send(attribute).year).to eq 2013 68 | end 69 | end 70 | 71 | shared_examples 'collection attribute' do 72 | let(:subject_class) { subject.class } 73 | let(:setter) { "#{attribute}=" } 74 | 75 | it_behaves_like 'attribute' 76 | 77 | it 'is sets if the hash contains the attribute name "underscored"' do 78 | value = respond_to?(:attribute_value) && attribute_value || [{}, {}] 79 | model = subject_class.new({ attribute => value }) 80 | expect(model.send(attribute).size).to eq 2 81 | end 82 | 83 | it 'is set if the hash contains the attribute name "camelized"' do 84 | value = respond_to?(:attribute_value) && attribute_value || [{}, {}] 85 | model = subject_class.new({ camelize(attribute) => value }) 86 | expect(model.send(attribute).size).to eq 2 87 | end 88 | 89 | context 'if the value is enumerable' do 90 | context 'and contains items as Hash' do 91 | it 'parses the item' do 92 | value = respond_to?(:attribute_value) && attribute_value || [{}, {}] 93 | model = subject_class.new attribute => value 94 | expect(model.send(attribute).map(&:class).uniq).to eq [attribute_class] 95 | end 96 | end 97 | 98 | context 'and contains items as non-Hash' do 99 | it 'does not parse the item' do 100 | model = subject_class.new attribute => [attribute_class.new, Object.new] 101 | expect(model.send(attribute).map(&:class).uniq).to eq [attribute_class, Object] 102 | end 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /spec/fixtures/v3/get-static-summoner-spells.json: -------------------------------------------------------------------------------- 1 | {"type":"summoner","version":"7.9.1","data":{"SummonerBarrier":{"name":"Barrier","description":"Shields your champion from 115-455 damage (depending on champion level) for 2 seconds.","summonerLevel":4,"id":21,"key":"SummonerBarrier"},"SummonerPoroRecall":{"name":"To the King!","description":"Quickly travel to the Poro King's side.","summonerLevel":1,"id":30,"key":"SummonerPoroRecall"},"SummonerBoost":{"name":"Cleanse","description":"Removes all disables (excluding suppression) and summoner spell debuffs affecting your champion and lowers the duration of incoming disables by 65% for 3 seconds.","summonerLevel":6,"id":1,"key":"SummonerBoost"},"SummonerDarkStarChampSelect2":{"name":"Disabled Summoner Spells","description":"Summoner spells are disabled in this mode.","summonerLevel":1,"id":36,"key":"SummonerDarkStarChampSelect2"},"SummonerTeleport":{"name":"Teleport","description":"After channeling for 4.5 seconds, teleports your champion to target allied structure, minion, or ward.","summonerLevel":6,"id":12,"key":"SummonerTeleport"},"SummonerDarkStarChampSelect1":{"name":"Disabled Summoner Spells","description":"Summoner spells are disabled in this mode.","summonerLevel":1,"id":35,"key":"SummonerDarkStarChampSelect1"},"SummonerFlash":{"name":"Flash","description":"Teleports your champion a short distance toward your cursor's location.","summonerLevel":8,"id":4,"key":"SummonerFlash"},"SummonerSnowball":{"name":"Mark","description":"Throw a snowball in a straight line at your enemies. If it hits an enemy, they become marked, granting True Sight, and your champion can quickly travel to the marked target as a follow up.","summonerLevel":1,"id":32,"key":"SummonerSnowball"},"SummonerHeal":{"name":"Heal","description":"Restores 90-345 Health (depending on champion level) and grants 30% Movement Speed for 1 second to you and target allied champion. This healing is halved for units recently affected by Summoner Heal.","summonerLevel":1,"id":7,"key":"SummonerHeal"},"SummonerMana":{"name":"Clarity","description":"Restores 50% of your champion's maximum Mana. Also restores allies for 25% of their maximum Mana.","summonerLevel":1,"id":13,"key":"SummonerMana"},"SummonerPoroThrow":{"name":"Poro Toss","description":"Toss a Poro at your enemies. If it hits, you can quickly travel to your target as a follow up.","summonerLevel":1,"id":31,"key":"SummonerPoroThrow"},"SummonerSmite":{"name":"Smite","description":"Deals 390-1000 true damage (depending on champion level) to target epic or large monster or enemy minion. Restores Health based on your maximum life when used against monsters.","summonerLevel":10,"id":11,"key":"SummonerSmite"},"SummonerSiegeChampSelect1":{"name":"Nexus Siege: Siege Weapon Slot","description":"In Nexus Siege, Summoner Spells are replaced with Siege Weapon Slots. Spend Crystal Shards to buy single-use Siege Weapons from the item shop, then use your Summoner Spell keys to activate them!","summonerLevel":1,"id":33,"key":"SummonerSiegeChampSelect1"},"SummonerSiegeChampSelect2":{"name":"Nexus Siege: Siege Weapon Slot","description":"In Nexus Siege, Summoner Spells are replaced with Siege Weapon Slots. Spend Crystal Shards to buy single-use Siege Weapons from the item shop, then use your Summoner Spell keys to activate them!","summonerLevel":1,"id":34,"key":"SummonerSiegeChampSelect2"},"SummonerExhaust":{"name":"Exhaust","description":"Exhausts target enemy champion, reducing their Movement Speed by 30%, and their damage dealt by 40% for 2.5 seconds.","summonerLevel":4,"id":3,"key":"SummonerExhaust"},"SummonerDot":{"name":"Ignite","description":"Ignites target enemy champion, dealing 70-410 true damage (depending on champion level) over 5 seconds, grants you vision of the target, and reduces healing effects on them for the duration.","summonerLevel":10,"id":14,"key":"SummonerDot"},"SummonerHaste":{"name":"Ghost","description":"Your champion gains increased Movement Speed and can move through units for 10 seconds. Grants a maximum of 28-45% (depending on champion level) Movement Speed after accelerating for 2 seconds.","summonerLevel":1,"id":6,"key":"SummonerHaste"}}} -------------------------------------------------------------------------------- /spec/lol/static_request_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "lol" 3 | 4 | include Lol 5 | 6 | describe StaticRequest do 7 | let(:subject) { StaticRequest.new("api_key", "euw") } 8 | 9 | { 10 | "champion" => "champions", 11 | "item" => "items", 12 | "mastery" => "masteries", 13 | "rune" => "runes", 14 | "summoner_spell" => "summoner_spells" 15 | }.each do |old_edpoint, new_endpoint| 16 | describe "##{new_endpoint}" do 17 | it "returns a Proxy" do 18 | expect(subject.send(new_endpoint).class).to eq(StaticRequest::Proxy) 19 | end 20 | 21 | describe "#get" do 22 | let(:fixture_name) { "static-#{new_endpoint.dasherize}" } 23 | 24 | it "proxies get to StaticRequest with the correct endpoint" do 25 | expect(subject).to receive(:get).with(new_endpoint, anything, anything) 26 | subject.send(new_endpoint).get 27 | end 28 | 29 | context "without an id" do 30 | let(:fixture) { load_fixture(fixture_name, StaticRequest.api_version) } 31 | 32 | let(:result) { subject.public_send(new_endpoint).get } 33 | 34 | before { stub_request(subject, fixture_name, "#{new_endpoint.dasherize}") } 35 | 36 | it "returns an Array of OpenStruct" do 37 | expect(result).to be_a Array 38 | expect(result.map(&:class).uniq).to eq([OpenStruct]) 39 | end 40 | 41 | it "fetches #{new_endpoint} from the API" do 42 | expect(result.size).to eq(fixture["data"].size) 43 | end 44 | end 45 | 46 | context "with an id" do 47 | it "returns an OpenStruct" do 48 | stub_request(subject, "#{fixture_name}-by-id", "#{new_endpoint.dasherize}/1") 49 | expect(subject.public_send(new_endpoint).get 1).to be_a OpenStruct 50 | end 51 | end 52 | end 53 | end 54 | end 55 | 56 | describe "#reforged_runes" do 57 | it "returns a Proxy" do 58 | expect(subject.reforged_runes.class).to eq(StaticRequest::Proxy) 59 | end 60 | 61 | describe "#get" do 62 | let(:fixture_name) { "static-reforged-runes" } 63 | 64 | it "proxies get to StaticRequest with the correct endpoint" do 65 | expect(subject).to receive(:get).with('reforged_runes', anything, anything) 66 | subject.reforged_runes.get 67 | end 68 | 69 | context "without an id" do 70 | let(:fixture) { load_fixture(fixture_name, StaticRequest.api_version) } 71 | 72 | let(:result) { subject.reforged_runes.get } 73 | 74 | before { stub_request(subject, fixture_name, "reforged-runes") } 75 | 76 | it "returns an Array of OpenStruct" do 77 | expect(result).to be_a Array 78 | expect(result.map(&:class).uniq).to eq([OpenStruct]) 79 | end 80 | 81 | it "fetches reforged_runes from the API" do 82 | expect(result.size).to eq(fixture.size) 83 | end 84 | end 85 | 86 | context "with an id" do 87 | it "returns an OpenStruct" do 88 | stub_request(subject, "#{fixture_name}-by-id", "reforged-runes/1") 89 | expect(subject.reforged_runes.get 1).to be_a OpenStruct 90 | end 91 | end 92 | end 93 | end 94 | 95 | describe "#maps" do 96 | let(:result) { subject.maps.get } 97 | 98 | before(:each) { stub_request(subject, 'static-maps', 'maps') } 99 | 100 | it "returns an Array of OpenStructs" do 101 | expect(result).to be_a Array 102 | expect(result.map(&:class).uniq).to eq [OpenStruct] 103 | end 104 | end 105 | 106 | describe "#realms" do 107 | let(:result) { subject.realms.get } 108 | 109 | before(:each) { stub_request(subject, 'static-realms', 'realms') } 110 | 111 | it "returns an OpenStruct" do 112 | expect(result).to be_a OpenStruct 113 | end 114 | end 115 | 116 | describe "#versions" do 117 | let(:result) { subject.versions.get } 118 | 119 | before(:each) { stub_request(subject, 'static-versions', 'versions') } 120 | 121 | it "returns an Array" do 122 | expect(result).to be_an(Array) 123 | end 124 | end 125 | 126 | end 127 | -------------------------------------------------------------------------------- /lib/lol/client.rb: -------------------------------------------------------------------------------- 1 | module Lol 2 | class Client 3 | 4 | # @!attribute [rw] region 5 | # @return [String] name of region 6 | attr_accessor :region 7 | 8 | # @!attribute [r] api_key 9 | # @return [String] the API key that has been used 10 | attr_reader :api_key 11 | 12 | # @!attribute [r] ttl 13 | # @return [Integer] the ttl on cached requests 14 | attr_reader :ttl 15 | 16 | # @!attribute[r] rate_limiter 17 | # @return [Object] the rate limiter, if one exists (else nil) 18 | attr_reader :rate_limiter 19 | 20 | # @return [ChampionRequest] 21 | def champion 22 | @champion_request ||= ChampionRequest.new(api_key, region, cache_store, rate_limiter) 23 | end 24 | 25 | # @return [ChampionMasteryRequest] 26 | def champion_mastery 27 | @champion_mastery_request ||= ChampionMasteryRequest.new(api_key, region, cache_store, rate_limiter) 28 | end 29 | 30 | # @return [MatchRequest] 31 | def match 32 | @match_request ||= MatchRequest.new(api_key, region, cache_store, rate_limiter) 33 | end 34 | 35 | # @return [LeagueRequest] 36 | def league 37 | @league_request ||= LeagueRequest.new(api_key, region, cache_store, rate_limiter) 38 | end 39 | 40 | # @return [RunesRequest] 41 | def runes 42 | @runes_request ||= RunesRequest.new(api_key, region, cache_store, rate_limiter) 43 | end 44 | 45 | # @return [MasteriesRequest] 46 | def masteries 47 | @masteries_request ||= MasteriesRequest.new(api_key, region, cache_store, rate_limiter) 48 | end 49 | 50 | # @return [SpectatorRequest] 51 | def spectator 52 | @spectator_request ||= SpectatorRequest.new(api_key, region, cache_store, rate_limiter) 53 | end 54 | 55 | # @return [SummonerRequest] 56 | def summoner 57 | @summoner_request ||= SummonerRequest.new(api_key, region, cache_store, rate_limiter) 58 | end 59 | 60 | # @return [StaticRequest] 61 | def static 62 | @static_request ||= StaticRequest.new(api_key, region, cache_store, rate_limiter) 63 | end 64 | 65 | # @return [LolStatusRequest] 66 | def lol_status 67 | @lol_status ||= LolStatusRequest.new(api_key, region, cache_store, rate_limiter) 68 | end 69 | 70 | # @return [CurrentGameRequest] 71 | def current_game 72 | @current_game ||= CurrentGameRequest.new(api_key, region, cache_store, rate_limiter) 73 | end 74 | 75 | # @return [FeaturedGamesRequest] 76 | def featured_games 77 | @featured_games ||= FeaturedGamesRequest.new(api_key, region, cache_store, rate_limiter) 78 | end 79 | 80 | # @return [TournamentProviderRequest] 81 | def tournament 82 | @tournament ||= TournamentRequest.new(api_key, region, cache_store, rate_limiter) 83 | end 84 | 85 | # Initializes a Lol::Client 86 | # @param api_key [String] 87 | # @param options [Hash] 88 | # @option options [String] :region ("EUW") The region on which the requests will be made 89 | # @option options [String] :redis the redis url to use for caching 90 | # @option options [Integer] :ttl (900) the cache ttl 91 | # @option options [Fixnum] :rate_limit_requests number of requests 92 | # @option options [Fixnum] :rate_limit_seconds number of seconds to limit the rate in 93 | # @return [Lol::Client] 94 | def initialize api_key, options = {} 95 | @api_key = api_key 96 | @region = options.delete(:region) || "euw" 97 | set_up_cache(options.delete(:redis), options.delete(:ttl)) 98 | set_up_rate_limiter(options.delete(:rate_limit_requests), options.delete(:rate_limit_seconds)) 99 | end 100 | 101 | def set_up_cache(redis_url, ttl) 102 | return @cached = false unless redis_url 103 | 104 | @ttl = ttl || 900 105 | @cached = true 106 | @redis = Redis.new :url => redis_url 107 | end 108 | 109 | def set_up_rate_limiter(number_of_requests, number_of_seconds) 110 | return @rate_limited = false unless number_of_requests 111 | 112 | @rate_limited = true 113 | @rate_limiter = GluttonRatelimit::AveragedThrottle.new number_of_requests, number_of_seconds 114 | end 115 | 116 | # Returns an options hash with cache keys 117 | # @return [Hash] 118 | def cache_store 119 | { 120 | redis: @redis, 121 | ttl: @ttl, 122 | cached: @cached, 123 | } 124 | end 125 | 126 | # @return [Boolean] true if requests are automatically rate limited 127 | def rate_limited? 128 | @rate_limiter 129 | end 130 | 131 | # @return [Boolean] true if requests are cached 132 | def cached? 133 | @cached 134 | end 135 | 136 | # @return [Redis] the cache store (if available) 137 | def redis 138 | @redis 139 | end 140 | end 141 | end 142 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ruby-lol 2 | [![Gem Version](https://badge.fury.io/rb/ruby-lol.png)](http://badge.fury.io/rb/ruby-lol) [![Coverage Status](https://coveralls.io/repos/mikamai/ruby-lol/badge.png)](https://coveralls.io/r/mikamai/ruby-lol) [![Build Status](https://travis-ci.org/mikamai/ruby-lol.png?branch=master)](https://travis-ci.org/mikamai/ruby-lol) [![Dependency Status](https://gemnasium.com/mikamai/ruby-lol.png)](https://gemnasium.com/mikamai/ruby-lol) [![Inline docs](http://inch-ci.org/github/mikamai/ruby-lol.png?branch=master)](http://inch-ci.org/github/mikamai/ruby-lol) 3 | 4 | 5 | ruby-lol is a wrapper to the [Riot Games API](https://developer.riotgames.com). 6 | 7 | ## Installation 8 | 9 | Add this line to your application's Gemfile: 10 | 11 | ```ruby 12 | gem 'ruby-lol' 13 | ``` 14 | 15 | And then execute: 16 | 17 | $ bundle 18 | 19 | Or install it yourself as: 20 | 21 | $ gem install ruby-lol 22 | 23 | ## Usage 24 | 25 | [ Outdated, anyone who wants to contribute to this please do it :) ] 26 | 27 | ```ruby 28 | require 'lol' 29 | 30 | client = Lol::Client.new "my_api_key", region: "euw" 31 | # => 32 | 33 | # You can cache requests using Redis now 34 | # ttl defaults to 900 35 | client = Lol::Client.new "my_api_key", region: "euw", redis: "redis://localhost:6379", ttl: 900 36 | 37 | # You can specify your rate limits so that the library will throttle requests to avoid errors 38 | client = Lol::Client.new "new_api_key", region: "euw", rate_limit_requests: 1, rate_limit_seconds: 10 39 | 40 | # Available Requests 41 | client.champion 42 | # => Lol::ChampionRequest 43 | client.game 44 | # => Lol::GameRequest 45 | client.league 46 | # => Lol::LeagueRequest 47 | client.stats 48 | # => Lol::StatsRequest 49 | client.summoner 50 | # => Lol::SummonerRequest 51 | client.team 52 | # => Lol::TeamRequest 53 | 54 | # Available methods for each request type 55 | client.champion.get 56 | # => Lol::Champion 57 | 58 | client.game.recent(summoner_id) 59 | # => Lol::Game 60 | 61 | client.league.get(summoner_id) 62 | # => Lol::League 63 | 64 | client.stats.summary(summoner_id) 65 | # => Lol::SummaryStats 66 | client.stats.ranked(summoner_id) 67 | # => Lol::RankedStats 68 | 69 | client.summoner.masteries(summoner_id) 70 | # => [Lol::Masterypage] 71 | client.summoner.runes(summoner_id) 72 | # => [Lol::Runepage] 73 | client.summoner.by_name(name) 74 | # => Lol::Summoner 75 | client.summoner.get(summoner_id) 76 | # => Lol::Summoner 77 | client.summoner.name(summoner_ids) 78 | # => [Hash] 79 | 80 | client.team.get(summoner_id) 81 | # => Array 82 | ``` 83 | 84 | # Making Static Requests 85 | The Riot API has a [section](http://developer.riotgames.com/api/methods#!/378) carved out for static-data. These requests don't count against your rate limit. The mechanism for using them is similar to the standard requests above. 86 | 87 | Each static endpoint has two possible requests: `get` and `get(id)`. `get` returns an array of OpenStructs representing the data from Riot's API, and `get(id)` returns an OpenStruct with a single record. Here are some examples: 88 | 89 | ```ruby 90 | client.static 91 | # => Lol::StaticRequest 92 | **NOTE**: StaticRequest is not meant to be used directly, but can be! 93 | 94 | client.static.champion 95 | # => Lol::StaticRequest (for endpoint /static-data/champion) 96 | 97 | client.static.champion.get 98 | # => [OpenStruct] (with keys from http://developer.riotgames.com/api/methods#!/378/1349) 99 | 100 | client.static.champion.get(id) 101 | # => OpenStruct (with keys from http://developer.riotgames.com/api/methods#!/378/1349) 102 | ``` 103 | 104 | You can also pass query parameters as listed in the Riot API documentation. For example: 105 | 106 | ```ruby 107 | 108 | # returns all attributes 109 | client.static.champion.get(champData: 'all') 110 | 111 | # returns only lore information 112 | client.static.champion.get(champData: 'lore') 113 | ``` 114 | 115 | **NOTE**: The realm endpoint is not implemented. Let us know if you need it, but it seemed somewhat unnecessary. 116 | 117 | ## Contributing 118 | 119 | 1. Fork it 120 | 2. Create your feature branch (`git checkout -b my-new-feature`) 121 | 3. Commit your changes (`git commit -am 'Add some feature'`) 122 | 4. Push to the branch (`git push origin my-new-feature`) 123 | 5. Create new Pull Request 124 | 125 | ## Changelog 126 | - 0.9.14 Fixed a caching bug 127 | - 0.9.13 Updated to latest API versions 128 | - 0.9.12 Fixed a caching bug 129 | - 0.9.11 Added caching support via REDIS 130 | - 0.9.7 Updated LeagueRequest to API v2.3 131 | - 0.9.6 Updated SummonerRequest and GameRequest to API v1.3 132 | - 0.9.5 Fixed documentation 133 | - 0.9.4 Completed support for updated API 134 | 135 | 136 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/mikamai/ruby-lol/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 137 | -------------------------------------------------------------------------------- /lib/lol/tournament_request.rb: -------------------------------------------------------------------------------- 1 | module Lol 2 | class TournamentRequest < Request 3 | # @!visibility private 4 | def api_base_path 5 | "/lol/tournament/#{self.class.api_version}" 6 | end 7 | 8 | # @!visibility private 9 | def api_base_url 10 | "https://americas.api.riotgames.com" 11 | end 12 | 13 | # Creates a tournament provider and returns its ID. 14 | # @param [String] url The provider's callback URL to which tournament game results in this region should be posted. The URL must be well-formed, use the http or https protocol, and use the default port for the protocol 15 | # @return [Integer] Provider ID 16 | def create_provider url: 17 | body = { 18 | "url" => url, 19 | "region" => region.upcase 20 | } 21 | perform_request api_url("providers"), :post, body 22 | end 23 | 24 | # Creates a tournament and returns its ID. 25 | # @param [Integer] provider_id The provider ID to specify the regional registered provider data to associate this tournament. 26 | # @param [String] name Name of the tournament 27 | # @return [Integer] Tournament ID 28 | def create_tournament provider_id:, name: nil 29 | body = { "providerId" => provider_id, "name" => name }.delete_if do |k, v| 30 | v.nil? 31 | end 32 | perform_request api_url("tournaments"), :post, body 33 | end 34 | 35 | # Create a tournament code for the given tournament. 36 | # @param [Integer] count The number of codes to create (max 1000) 37 | # @param [Integer] tournament_id The tournament ID 38 | # @param [String] spectator_type The spectator type of the game. Valid values are NONE, LOBBYONLY, ALL. 39 | # @param [Integer] team_size The team size of the game. Valid values are 1-5. 40 | # @param [String] pick_type The pick type of the game. Valid values are BLIND_PICK, DRAFT_MODE, ALL_RANDOM, TOURNAMENT_DRAFT. 41 | # @param [String] map_type The map type of the game. Valid values are SUMMONERS_RIFT, TWISTED_TREELINE, CRYSTAL_SCAR, and HOWLING_ABYSS. 42 | # @param [Array] allowed_participants List of participants in order to validate the players eligible to join the lobby. 43 | # @param [String] metadata Optional string that may contain any data in any format, if specified at all. Used to denote any custom information about the game. 44 | # @return [Array] generated tournament codes 45 | def create_codes tournament_id:, count: nil, allowed_participants: nil, 46 | map_type: "SUMMONERS_RIFT", metadata: nil, team_size: 5, 47 | pick_type: "TOURNAMENT_DRAFT", spectator_type: "ALL" 48 | body = { 49 | "allowedSummonerIds" => allowed_participants, 50 | "mapType" => map_type, 51 | "metadata" => metadata, 52 | "pickType" => pick_type, 53 | "spectatorType" => spectator_type, 54 | "teamSize" => team_size 55 | }.compact 56 | uri_params = { 57 | "tournamentId" => tournament_id, 58 | "count" => count 59 | }.compact 60 | perform_request api_url("codes", uri_params), :post, body 61 | end 62 | 63 | # Update the pick type, map, spectator type, or allowed summoners for a code. 64 | # @param [String] tournament_code The tournament code to update 65 | # @param [Array] allowed_participants List of participants in order to validate the players eligible to join the lobby. 66 | # @param [String] map_type The map type of the game. Valid values are SUMMONERS_RIFT, TWISTED_TREELINE, CRYSTAL_SCAR, and HOWLING_ABYSS. 67 | # @param [String] pick_type The pick type of the game. Valid values are BLIND_PICK, DRAFT_MODE, ALL_RANDOM, TOURNAMENT_DRAFT. 68 | # @param [String] spectator_type The spectator type of the game. Valid values are NONE, LOBBYONLY, ALL. 69 | def update_code tournament_code, allowed_participants: nil, map_type: nil, pick_type: nil, spectator_type: nil 70 | body = { 71 | "allowedSummonerIds" => allowed_participants, 72 | "mapType" => map_type, 73 | "pickType" => pick_type, 74 | "spectatorType" => spectator_type 75 | }.compact 76 | perform_request api_url("codes/#{tournament_code}"), :put, body 77 | end 78 | 79 | # Returns the tournament code details 80 | # @param [String] tournament_code the tournament code string 81 | # @return [DynamicModel] A tournament code representation 82 | def find_code tournament_code 83 | DynamicModel.new perform_request api_url "codes/#{tournament_code}" 84 | end 85 | 86 | # Gets a list of lobby events by tournament code 87 | # @param [String] tournament_code the tournament code string 88 | # @return [Array] List of lobby events 89 | def all_lobby_events tournament_code: 90 | result = perform_request api_url "lobby-events/by-code/#{tournament_code}" 91 | result["eventList"].map { |e| DynamicModel.new e } 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /spec/fixtures/v3/get-current-game.json: -------------------------------------------------------------------------------- 1 | {"gameId":3186584028,"mapId":12,"gameMode":"ARAM","gameType":"MATCHED_GAME","gameQueueConfigId":65,"participants":[{"teamId":100,"spell1Id":1,"spell2Id":4,"championId":119,"profileIconId":1212,"summonerName":"FaisaI","bot":false,"summonerId":52576163,"runes":[{"count":9,"runeId":5245},{"count":5,"runeId":5277},{"count":4,"runeId":5289},{"count":9,"runeId":5317},{"count":3,"runeId":5337}],"masteries":[{"rank":5,"masteryId":6111},{"rank":1,"masteryId":6121},{"rank":5,"masteryId":6131},{"rank":1,"masteryId":6141},{"rank":5,"masteryId":6151},{"rank":1,"masteryId":6161},{"rank":5,"masteryId":6312},{"rank":1,"masteryId":6322},{"rank":5,"masteryId":6331},{"rank":1,"masteryId":6343}]},{"teamId":100,"spell1Id":32,"spell2Id":4,"championId":12,"profileIconId":907,"summonerName":"AndersStefan","bot":false,"summonerId":29311441,"runes":[{"count":9,"runeId":5245},{"count":9,"runeId":5289},{"count":9,"runeId":5317},{"count":3,"runeId":5347}],"masteries":[{"rank":5,"masteryId":6114},{"rank":1,"masteryId":6122},{"rank":5,"masteryId":6131},{"rank":1,"masteryId":6142},{"rank":5,"masteryId":6212},{"rank":1,"masteryId":6223},{"rank":5,"masteryId":6232},{"rank":1,"masteryId":6242},{"rank":5,"masteryId":6251},{"rank":1,"masteryId":6261}]},{"teamId":100,"spell1Id":6,"spell2Id":4,"championId":112,"profileIconId":903,"summonerName":"ChaoticHero","bot":false,"summonerId":19892437,"runes":[{"count":9,"runeId":5273},{"count":3,"runeId":5289},{"count":6,"runeId":5296},{"count":5,"runeId":5316},{"count":4,"runeId":5317},{"count":3,"runeId":5357}],"masteries":[{"rank":5,"masteryId":6114},{"rank":1,"masteryId":6122},{"rank":5,"masteryId":6134},{"rank":1,"masteryId":6143},{"rank":5,"masteryId":6312},{"rank":1,"masteryId":6322},{"rank":5,"masteryId":6331},{"rank":1,"masteryId":6342},{"rank":5,"masteryId":6351},{"rank":1,"masteryId":6362}]},{"teamId":100,"spell1Id":4,"spell2Id":14,"championId":432,"profileIconId":2073,"summonerName":"Holakamionom","bot":false,"summonerId":78977495,"runes":[{"count":6,"runeId":5245},{"count":2,"runeId":5247},{"count":1,"runeId":5251},{"count":9,"runeId":5289},{"count":9,"runeId":5317},{"count":2,"runeId":5335},{"count":1,"runeId":5337}],"masteries":[{"rank":5,"masteryId":6211},{"rank":1,"masteryId":6222},{"rank":5,"masteryId":6231},{"rank":1,"masteryId":6241},{"rank":5,"masteryId":6311},{"rank":1,"masteryId":6322},{"rank":5,"masteryId":6332},{"rank":1,"masteryId":6343},{"rank":5,"masteryId":6351},{"rank":1,"masteryId":6362}]},{"teamId":100,"spell1Id":32,"spell2Id":4,"championId":57,"profileIconId":1391,"summonerName":"xWr4th","bot":false,"summonerId":88007182,"runes":[{"count":9,"runeId":5273},{"count":9,"runeId":5290},{"count":9,"runeId":5317},{"count":3,"runeId":5357}],"masteries":[{"rank":5,"masteryId":6114},{"rank":1,"masteryId":6122},{"rank":5,"masteryId":6134},{"rank":1,"masteryId":6142},{"rank":5,"masteryId":6154},{"rank":1,"masteryId":6164},{"rank":5,"masteryId":6312},{"rank":1,"masteryId":6322},{"rank":5,"masteryId":6331},{"rank":1,"masteryId":6343}]},{"teamId":200,"spell1Id":4,"spell2Id":7,"championId":203,"profileIconId":1637,"summonerName":"Heika","bot":false,"summonerId":29385999,"runes":[{"count":9,"runeId":5245},{"count":5,"runeId":5289},{"count":4,"runeId":5290},{"count":9,"runeId":5317},{"count":3,"runeId":5337}],"masteries":[{"rank":5,"masteryId":6114},{"rank":1,"masteryId":6121},{"rank":1,"masteryId":6131},{"rank":4,"masteryId":6134},{"rank":1,"masteryId":6143},{"rank":5,"masteryId":6151},{"rank":1,"masteryId":6161},{"rank":5,"masteryId":6212},{"rank":1,"masteryId":6223},{"rank":5,"masteryId":6231},{"rank":1,"masteryId":6241}]},{"teamId":200,"spell1Id":32,"spell2Id":4,"championId":84,"profileIconId":984,"summonerName":"Swamp00","bot":false,"summonerId":56610525,"runes":[{"count":3,"runeId":5257},{"count":6,"runeId":5273},{"count":9,"runeId":5289},{"count":9,"runeId":5317},{"count":3,"runeId":5357}],"masteries":[{"rank":5,"masteryId":6114},{"rank":1,"masteryId":6121},{"rank":5,"masteryId":6134},{"rank":1,"masteryId":6141},{"rank":5,"masteryId":6312},{"rank":1,"masteryId":6323},{"rank":3,"masteryId":6331},{"rank":2,"masteryId":6332},{"rank":1,"masteryId":6343},{"rank":5,"masteryId":6351},{"rank":1,"masteryId":6362}]},{"teamId":200,"spell1Id":4,"spell2Id":3,"championId":267,"profileIconId":1427,"summonerName":"Pîzza","bot":false,"summonerId":60314423,"runes":[{"count":6,"runeId":5245},{"count":3,"runeId":5273},{"count":9,"runeId":5297},{"count":7,"runeId":5315},{"count":2,"runeId":5317},{"count":2,"runeId":5347},{"count":1,"runeId":5357}],"masteries":[{"rank":5,"masteryId":6212},{"rank":1,"masteryId":6221},{"rank":5,"masteryId":6231},{"rank":1,"masteryId":6241},{"rank":5,"masteryId":6311},{"rank":1,"masteryId":6322},{"rank":5,"masteryId":6332},{"rank":1,"masteryId":6342},{"rank":5,"masteryId":6352},{"rank":1,"masteryId":6363}]},{"teamId":200,"spell1Id":4,"spell2Id":32,"championId":420,"profileIconId":711,"summonerName":"ECraft","bot":false,"summonerId":19542828,"runes":[{"count":9,"runeId":5245},{"count":9,"runeId":5289},{"count":9,"runeId":5317},{"count":3,"runeId":5335}],"masteries":[{"rank":5,"masteryId":6111},{"rank":1,"masteryId":6121},{"rank":5,"masteryId":6131},{"rank":1,"masteryId":6142},{"rank":5,"masteryId":6311},{"rank":1,"masteryId":6322},{"rank":5,"masteryId":6331},{"rank":1,"masteryId":6341},{"rank":5,"masteryId":6352},{"rank":1,"masteryId":6362}]},{"teamId":200,"spell1Id":4,"spell2Id":32,"championId":11,"profileIconId":1132,"summonerName":"LÖBÖLÖNK","bot":false,"summonerId":88689058,"runes":[{"count":9,"runeId":5245},{"count":3,"runeId":5289},{"count":6,"runeId":5296},{"count":9,"runeId":5317},{"count":3,"runeId":5335}],"masteries":[{"rank":5,"masteryId":6111},{"rank":1,"masteryId":6121},{"rank":5,"masteryId":6131},{"rank":1,"masteryId":6141},{"rank":5,"masteryId":6151},{"rank":1,"masteryId":6162},{"rank":5,"masteryId":6311},{"rank":1,"masteryId":6322},{"rank":5,"masteryId":6331},{"rank":1,"masteryId":6343}]}],"observers":{"encryptionKey":"U+rZl9qb1YBLuZS63/cDazyOQIhp4pSb"},"platformId":"EUW1","bannedChampions":[],"gameStartTime":1495009864765,"gameLength":531} -------------------------------------------------------------------------------- /spec/fixtures/v3/get-runes.json: -------------------------------------------------------------------------------- 1 | {"summonerId":100012,"pages":[{"id":599388,"name":"ad","current":false,"slots":[{"runeSlotId":1,"runeId":5253},{"runeSlotId":2,"runeId":5253},{"runeSlotId":3,"runeId":5253},{"runeSlotId":4,"runeId":5253},{"runeSlotId":5,"runeId":5253},{"runeSlotId":6,"runeId":5253},{"runeSlotId":7,"runeId":5253},{"runeSlotId":8,"runeId":5253},{"runeSlotId":9,"runeId":5253},{"runeSlotId":10,"runeId":5195},{"runeSlotId":11,"runeId":5331},{"runeSlotId":12,"runeId":5331},{"runeSlotId":13,"runeId":5331},{"runeSlotId":14,"runeId":5331},{"runeSlotId":15,"runeId":5331},{"runeSlotId":16,"runeId":5331},{"runeSlotId":17,"runeId":5331},{"runeSlotId":18,"runeId":5331},{"runeSlotId":19,"runeId":5289},{"runeSlotId":20,"runeId":5289},{"runeSlotId":21,"runeId":5289},{"runeSlotId":22,"runeId":5289},{"runeSlotId":23,"runeId":5289},{"runeSlotId":24,"runeId":5289},{"runeSlotId":25,"runeId":5289},{"runeSlotId":26,"runeId":5289},{"runeSlotId":27,"runeId":5289},{"runeSlotId":28,"runeId":5335},{"runeSlotId":29,"runeId":5335},{"runeSlotId":30,"runeId":5335}]},{"id":599389,"name":"ad steel life","current":false,"slots":[{"runeSlotId":1,"runeId":5253},{"runeSlotId":2,"runeId":5253},{"runeSlotId":3,"runeId":5253},{"runeSlotId":4,"runeId":5253},{"runeSlotId":5,"runeId":5253},{"runeSlotId":6,"runeId":5253},{"runeSlotId":7,"runeId":5253},{"runeSlotId":8,"runeId":5253},{"runeSlotId":9,"runeId":5253},{"runeSlotId":10,"runeId":5317},{"runeSlotId":11,"runeId":5317},{"runeSlotId":12,"runeId":5317},{"runeSlotId":13,"runeId":5317},{"runeSlotId":14,"runeId":5317},{"runeSlotId":15,"runeId":5317},{"runeSlotId":16,"runeId":5317},{"runeSlotId":17,"runeId":5317},{"runeSlotId":18,"runeId":5317},{"runeSlotId":19,"runeId":5289},{"runeSlotId":20,"runeId":5289},{"runeSlotId":21,"runeId":5289},{"runeSlotId":22,"runeId":5289},{"runeSlotId":23,"runeId":5289},{"runeSlotId":24,"runeId":5289},{"runeSlotId":25,"runeId":5289},{"runeSlotId":26,"runeId":5289},{"runeSlotId":27,"runeId":5289},{"runeSlotId":28,"runeId":5343},{"runeSlotId":29,"runeId":5343},{"runeSlotId":30,"runeId":5343}]},{"id":3976397,"name":"AP regen mana","current":true,"slots":[{"runeSlotId":1,"runeId":5273},{"runeSlotId":2,"runeId":5273},{"runeSlotId":3,"runeId":5273},{"runeSlotId":4,"runeId":5273},{"runeSlotId":5,"runeId":5273},{"runeSlotId":6,"runeId":5273},{"runeSlotId":7,"runeId":5273},{"runeSlotId":8,"runeId":5273},{"runeSlotId":9,"runeId":5273},{"runeSlotId":10,"runeId":5327},{"runeSlotId":11,"runeId":5327},{"runeSlotId":12,"runeId":5327},{"runeSlotId":13,"runeId":5327},{"runeSlotId":14,"runeId":5327},{"runeSlotId":15,"runeId":5327},{"runeSlotId":16,"runeId":5327},{"runeSlotId":17,"runeId":5327},{"runeSlotId":18,"runeId":5327},{"runeSlotId":19,"runeId":5289},{"runeSlotId":20,"runeId":5289},{"runeSlotId":21,"runeId":5289},{"runeSlotId":22,"runeId":5289},{"runeSlotId":23,"runeId":5289},{"runeSlotId":24,"runeId":5289},{"runeSlotId":25,"runeId":5289},{"runeSlotId":26,"runeId":5289},{"runeSlotId":27,"runeId":5289},{"runeSlotId":28,"runeId":5357},{"runeSlotId":29,"runeId":5357},{"runeSlotId":30,"runeId":5357}]},{"id":23126766,"name":"AP armor","current":false,"slots":[{"runeSlotId":1,"runeId":5273},{"runeSlotId":2,"runeId":5273},{"runeSlotId":3,"runeId":5273},{"runeSlotId":4,"runeId":5273},{"runeSlotId":5,"runeId":5273},{"runeSlotId":6,"runeId":5273},{"runeSlotId":7,"runeId":5273},{"runeSlotId":8,"runeId":5273},{"runeSlotId":9,"runeId":5273},{"runeSlotId":10,"runeId":5317},{"runeSlotId":11,"runeId":5317},{"runeSlotId":12,"runeId":5317},{"runeSlotId":13,"runeId":5317},{"runeSlotId":14,"runeId":5317},{"runeSlotId":15,"runeId":5317},{"runeSlotId":16,"runeId":5317},{"runeSlotId":17,"runeId":5317},{"runeSlotId":18,"runeId":5317},{"runeSlotId":19,"runeId":5289},{"runeSlotId":20,"runeId":5289},{"runeSlotId":21,"runeId":5289},{"runeSlotId":22,"runeId":5289},{"runeSlotId":23,"runeId":5289},{"runeSlotId":24,"runeId":5289},{"runeSlotId":25,"runeId":5289},{"runeSlotId":26,"runeId":5289},{"runeSlotId":27,"runeId":5289},{"runeSlotId":28,"runeId":5357},{"runeSlotId":29,"runeId":5357},{"runeSlotId":30,"runeId":5357}]},{"id":84133736,"name":"rune je suis un costeau","current":false,"slots":[{"runeSlotId":1,"runeId":5257},{"runeSlotId":2,"runeId":5257},{"runeSlotId":3,"runeId":5257},{"runeSlotId":4,"runeId":5257},{"runeSlotId":5,"runeId":5257},{"runeSlotId":6,"runeId":5257},{"runeSlotId":7,"runeId":5257},{"runeSlotId":8,"runeId":5257},{"runeSlotId":9,"runeId":5257},{"runeSlotId":10,"runeId":5317},{"runeSlotId":11,"runeId":5317},{"runeSlotId":12,"runeId":5317},{"runeSlotId":13,"runeId":5317},{"runeSlotId":14,"runeId":5317},{"runeSlotId":15,"runeId":5317},{"runeSlotId":16,"runeId":5317},{"runeSlotId":17,"runeId":5317},{"runeSlotId":18,"runeId":5317},{"runeSlotId":19,"runeId":5289},{"runeSlotId":20,"runeId":5289},{"runeSlotId":21,"runeId":5289},{"runeSlotId":22,"runeId":5289},{"runeSlotId":23,"runeId":5289},{"runeSlotId":24,"runeId":5289},{"runeSlotId":25,"runeId":5289},{"runeSlotId":26,"runeId":5289},{"runeSlotId":27,"runeId":5289},{"runeSlotId":28,"runeId":5347},{"runeSlotId":29,"runeId":5347},{"runeSlotId":30,"runeId":5347}]},{"id":101534484,"name":"ad critik de chez mendes","current":false,"slots":[{"runeSlotId":1,"runeId":5249},{"runeSlotId":2,"runeId":5249},{"runeSlotId":3,"runeId":5249},{"runeSlotId":4,"runeId":5249},{"runeSlotId":5,"runeId":5249},{"runeSlotId":6,"runeId":5249},{"runeSlotId":7,"runeId":5245},{"runeSlotId":8,"runeId":5245},{"runeSlotId":9,"runeId":5245},{"runeSlotId":10,"runeId":5311},{"runeSlotId":11,"runeId":5311},{"runeSlotId":12,"runeId":5311},{"runeSlotId":13,"runeId":5311},{"runeSlotId":14,"runeId":5311},{"runeSlotId":15,"runeId":5311},{"runeSlotId":16,"runeId":5311},{"runeSlotId":17,"runeId":5311},{"runeSlotId":18,"runeId":5311},{"runeSlotId":19,"runeId":5289},{"runeSlotId":20,"runeId":5289},{"runeSlotId":21,"runeId":5289},{"runeSlotId":22,"runeId":5289},{"runeSlotId":23,"runeId":5289},{"runeSlotId":24,"runeId":5289},{"runeSlotId":25,"runeId":5289},{"runeSlotId":26,"runeId":5289},{"runeSlotId":27,"runeId":5289},{"runeSlotId":28,"runeId":5339},{"runeSlotId":29,"runeId":5335},{"runeSlotId":30,"runeId":5335}]}]} -------------------------------------------------------------------------------- /lib/lol/request.rb: -------------------------------------------------------------------------------- 1 | require "uri" 2 | require "active_support/core_ext/object/to_query" 3 | 4 | module Lol 5 | class NotFound < StandardError; end 6 | class TooManyRequests < StandardError; end 7 | class InvalidCacheStore < StandardError; end 8 | 9 | # Encapsulates common methods for all requests 10 | # Request classes inherit from this 11 | class Request 12 | include HTTParty 13 | 14 | # @!attribute [r] api_key 15 | # @return [String] api_key 16 | attr_reader :api_key 17 | 18 | 19 | # @!attribute [rw] region 20 | # @return [String] region 21 | attr_accessor :region 22 | 23 | # @!attribute[r] cache_store 24 | # @return [Object] the cache_store 25 | attr_reader :cache_store 26 | 27 | # @!attribute[r] rate_limiter 28 | # @return [Object] the rate limiter, if one exists (else nil) 29 | attr_reader :rate_limiter 30 | 31 | # Returns the supported API Version. 32 | # @return [String] v3 33 | def self.api_version 34 | "v3" 35 | end 36 | 37 | def self.platforms 38 | { 39 | :br => 'br1', 40 | :eune => 'eun1', 41 | :euw => 'euw1', 42 | :jp => 'jp1', 43 | :kr => 'kr', 44 | :lan => 'la1', 45 | :las => 'la2', 46 | :na => 'na1', 47 | :oce => 'oc1', 48 | :ru => 'ru', 49 | :tr => 'tr1', 50 | } 51 | end 52 | 53 | # Initializes a new Request 54 | # @param api_key [String] the Riot Games API key 55 | # @param region [String] the region you want to use in API calls 56 | # @param cache_store [Hash] 57 | # @option cache_store [Redis] :redis Redis instance to use 58 | # @option cache_store [Boolean] :cached should the request be cached 59 | # @option cacche_store [Integer] :ttl ttl for cache keys 60 | # @return [Request] 61 | def initialize api_key, region, cache_store = {}, rate_limiter = nil 62 | @cache_store = cache_store 63 | @rate_limiter = rate_limiter 64 | raise InvalidCacheStore if cached? && !store.is_a?(Redis) 65 | @api_key = api_key 66 | @region = region 67 | end 68 | 69 | def platform 70 | self.class.platforms[region.downcase.to_sym] 71 | end 72 | 73 | # Returns the supported API Version. 74 | # @return [String] v3 75 | def api_version 76 | self.class.api_version 77 | end 78 | 79 | # Returns a full url for an API call 80 | # @param path [String] API path to call 81 | # @return [String] full fledged url 82 | def api_url path, params = {} 83 | url = File.join File.join(api_base_url, api_base_path), path 84 | "#{url}?#{api_query_string params}" 85 | end 86 | 87 | # Returns the API base domain 88 | # @return [String] path domain 89 | def api_base_url 90 | "https://#{platform}.api.riotgames.com" 91 | end 92 | 93 | # Returns the API base path, which is everything between the domain and the request-specific path 94 | # @return [String] API path 95 | def api_base_path 96 | "/lol/platform/#{api_version}" 97 | end 98 | 99 | def api_query_string params = {} 100 | URI.encode_www_form params.merge api_key: api_key 101 | end 102 | 103 | # Returns just a path from a full api url 104 | # @return [String] 105 | def clean_url(url) 106 | uri = URI.parse(url) 107 | uri.query = CGI.parse(uri.query || '').reject { |k| k == 'api_key' }.to_query 108 | uri.to_s 109 | end 110 | 111 | # Calls the API via HTTParty and handles errors caching it if a cache is 112 | # enabled and rate limiting it if a rate limiter is configured 113 | # @param url [String] the url to call 114 | # @param verb [Symbol] HTTP verb to use. Defaults to :get 115 | # @param body [Hash] Body for POST request 116 | # @param options [Hash] Options passed to HTTParty 117 | # @return [String] raw response of the call 118 | def perform_request url, verb = :get, body = nil, options = {} 119 | options_id = options.inspect 120 | can_cache = [:post, :put].include?(verb) ? false : cached? 121 | if can_cache && result = store.get("#{clean_url(url)}#{options_id}") 122 | return JSON.parse(result) 123 | end 124 | response = perform_rate_limited_request(url, verb, body, options) 125 | store.setex "#{clean_url(url)}#{options_id}", ttl, response.to_json if can_cache 126 | response 127 | end 128 | 129 | def perform_rate_limited_request url, verb = :get, body = nil, options = {} 130 | return perform_uncached_request(url, verb, body, options) unless rate_limiter 131 | @rate_limiter.times 1 do 132 | return perform_uncached_request(url, verb, body, options) 133 | end 134 | end 135 | 136 | def perform_uncached_request url, verb = :get, body = nil, options = {} 137 | options[:headers] ||= {} 138 | options[:headers].merge!({ 139 | "Content-Type" => "application/json", 140 | "Accept" => "application/json" 141 | }) 142 | if [:post, :put].include?(verb) 143 | options[:body] = body.to_json if body 144 | options[:headers]['X-Riot-Token'] = api_key 145 | end 146 | response = self.class.send(verb, url, options) 147 | if response.respond_to?(:code) && !(200...300).include?(response.code) 148 | raise NotFound.new("404 Not Found") if response.not_found? 149 | raise TooManyRequests.new('429 Rate limit exceeded') if response.code == 429 150 | raise InvalidAPIResponse.new(url, response) 151 | end 152 | 153 | if response.respond_to?(:parsed_response) 154 | response.parsed_response 155 | else 156 | response 157 | end 158 | end 159 | 160 | # @return [Redis] returns the cache store 161 | def store 162 | cache_store[:redis] 163 | end 164 | 165 | # @return [Boolean] true if the request should be cached 166 | def cached? 167 | cache_store[:cached] 168 | end 169 | 170 | # @return [Integer] the ttl to apply to cached keys 171 | def ttl 172 | cache_store[:ttl] 173 | end 174 | end 175 | end 176 | -------------------------------------------------------------------------------- /spec/lol/client_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "lol" 3 | 4 | include Lol 5 | 6 | describe Client do 7 | subject { Client.new "foo" } 8 | 9 | describe "#new" do 10 | it "requires an API key argument" do 11 | expect { Client.new }.to raise_error(ArgumentError) 12 | end 13 | 14 | it "accepts a region argument" do 15 | expect { Client.new("foo", :region => "na") }.not_to raise_error 16 | end 17 | 18 | it "defaults on EUW as a region" do 19 | expect(subject.region).to eq("euw") 20 | end 21 | 22 | context "caching" do 23 | let(:client) { Client.new "foo", redis: "redis://dummy-url" } 24 | let(:real_redis) { Client.new "foo", redis: "redis://localhost:6379" } 25 | 26 | it "sets caching if redis is specified in the options" do 27 | expect(client.cached?).to be_truthy 28 | end 29 | 30 | it "sets a default ttl of 15 * 60s" do 31 | expect(client.ttl).to eq(15*60) 32 | end 33 | 34 | it "accepts a custom ttl" do 35 | client = Client.new "foo", redis: "redis://dummy-url", ttl: 10 36 | expect(client.ttl).to eq(10) 37 | end 38 | 39 | it "instantiates a redis client if redis is in the options" do 40 | expect(real_redis.instance_variable_get(:@redis)).to be_a(Redis) 41 | end 42 | 43 | it "passes the redis_store to the request" do 44 | champion_request = real_redis.champion 45 | expect(champion_request.cache_store).to eq(real_redis.cache_store) 46 | end 47 | end 48 | 49 | context "rate_limiting" do 50 | let(:client) { Client.new "foo", rate_limit_requests: 10 } 51 | 52 | it "sets rate limiting if specified in the options" do 53 | expect(client.rate_limited?).to be_truthy 54 | end 55 | 56 | it "instantiates a rate limiter if it is in the options" do 57 | expect(client.rate_limiter).not_to be_nil 58 | end 59 | 60 | it "passes the rate limiter to the request" do 61 | champion_request = client.champion 62 | expect(champion_request.rate_limiter).to eq(client.rate_limiter) 63 | end 64 | end 65 | end 66 | 67 | describe "#cached?" do 68 | it "is true if @cached is true" do 69 | subject.instance_variable_set(:@cached, true) 70 | expect(subject.cached?).to be_truthy 71 | end 72 | 73 | it "is false if @cached is false" do 74 | subject.instance_variable_set(:@cached, false) 75 | expect(subject.cached?).to be_falsy 76 | end 77 | end 78 | 79 | describe "#champion" do 80 | it "returns an instance of ChampionRequest" do 81 | expect(subject.champion).to be_a(ChampionRequest) 82 | end 83 | 84 | it "initializes the ChampionRequest with the current API key and region" do 85 | expect(ChampionRequest).to receive(:new).with(subject.api_key, subject.region, subject.cache_store, subject.rate_limiter) 86 | 87 | subject.champion 88 | end 89 | end 90 | 91 | describe '#match' do 92 | it "returns an instance of MatchRequest" do 93 | expect(subject.match).to be_a(MatchRequest) 94 | end 95 | 96 | it "initializes the MatchRequest with the current API key and region" do 97 | expect(MatchRequest).to receive(:new).with(subject.api_key, subject.region, subject.cache_store, subject.rate_limiter) 98 | 99 | subject.match 100 | end 101 | end 102 | 103 | describe '#runes' do 104 | it "returns an instance of RunesRequest" do 105 | expect(subject.runes).to be_a(RunesRequest) 106 | end 107 | 108 | it "initializes the RunesRequest with the current API key and region" do 109 | expect(RunesRequest).to receive(:new).with(subject.api_key, subject.region, subject.cache_store, subject.rate_limiter) 110 | 111 | subject.runes 112 | end 113 | end 114 | 115 | describe '#masteries' do 116 | it "returns an instance of MasteriesRequest" do 117 | expect(subject.masteries).to be_a(MasteriesRequest) 118 | end 119 | 120 | it "initializes the MasteriesRequest with the current API key and region" do 121 | expect(MasteriesRequest).to receive(:new).with(subject.api_key, subject.region, subject.cache_store, subject.rate_limiter) 122 | 123 | subject.masteries 124 | end 125 | end 126 | 127 | describe "#league" do 128 | it "returns an instance of LeagueRequest" do 129 | expect(subject.league).to be_a(LeagueRequest) 130 | end 131 | 132 | it "initializes the LeagueRequest with the current API key and region" do 133 | expect(LeagueRequest).to receive(:new).with(subject.api_key, subject.region, subject.cache_store, subject.rate_limiter) 134 | 135 | subject.league 136 | end 137 | end 138 | 139 | describe "#summoner" do 140 | it "returns an instance of SummonerRequest" do 141 | expect(subject.summoner).to be_a(SummonerRequest) 142 | end 143 | 144 | it "initializes the SummonerRequest with the current API key and region" do 145 | expect(SummonerRequest).to receive(:new).with(subject.api_key, subject.region, subject.cache_store, subject.rate_limiter) 146 | 147 | subject.summoner 148 | end 149 | end 150 | 151 | describe "#static" do 152 | it "returns an instance of StaticRequest" do 153 | expect(subject.static).to be_a(StaticRequest) 154 | end 155 | 156 | it "initializes the StaticRequest with the current API key and region" do 157 | expect(StaticRequest).to receive(:new).with(subject.api_key, subject.region, subject.cache_store, subject.rate_limiter) 158 | 159 | subject.static 160 | end 161 | end 162 | 163 | describe '#lol_status' do 164 | it 'return an instance of LolStatusRequest' do 165 | expect(subject.lol_status).to be_a(LolStatusRequest) 166 | end 167 | end 168 | 169 | describe "#api_key" do 170 | it "returns an api key" do 171 | expect(subject.api_key).to eq("foo") 172 | end 173 | 174 | it "is read_only" do 175 | expect { subject.api_key = "bar" }.to raise_error(NoMethodError) 176 | end 177 | end 178 | 179 | describe "#region" do 180 | it "returns current region" do 181 | expect(subject.region).to eq("euw") 182 | end 183 | 184 | it "can be set to a new region" do 185 | subject.region = "NA" 186 | expect(subject.region).to eq("NA") 187 | end 188 | end 189 | 190 | end 191 | -------------------------------------------------------------------------------- /spec/fixtures/v3/get-featured-games.json: -------------------------------------------------------------------------------- 1 | {"gameList":[{"gameId":3186584028,"mapId":12,"gameMode":"ARAM","gameType":"MATCHED_GAME","gameQueueConfigId":65,"participants":[{"teamId":100,"spell1Id":1,"spell2Id":4,"championId":119,"profileIconId":1212,"summonerName":"FaisaI","bot":false},{"teamId":100,"spell1Id":32,"spell2Id":4,"championId":12,"profileIconId":907,"summonerName":"AndersStefan","bot":false},{"teamId":100,"spell1Id":6,"spell2Id":4,"championId":112,"profileIconId":903,"summonerName":"ChaoticHero","bot":false},{"teamId":100,"spell1Id":4,"spell2Id":14,"championId":432,"profileIconId":2073,"summonerName":"Holakamionom","bot":false},{"teamId":100,"spell1Id":32,"spell2Id":4,"championId":57,"profileIconId":1391,"summonerName":"xWr4th","bot":false},{"teamId":200,"spell1Id":4,"spell2Id":7,"championId":203,"profileIconId":1637,"summonerName":"Heika","bot":false},{"teamId":200,"spell1Id":32,"spell2Id":4,"championId":84,"profileIconId":984,"summonerName":"Swamp00","bot":false},{"teamId":200,"spell1Id":4,"spell2Id":3,"championId":267,"profileIconId":1427,"summonerName":"Pîzza","bot":false},{"teamId":200,"spell1Id":4,"spell2Id":32,"championId":420,"profileIconId":711,"summonerName":"ECraft","bot":false},{"teamId":200,"spell1Id":4,"spell2Id":32,"championId":11,"profileIconId":1132,"summonerName":"LÖBÖLÖNK","bot":false}],"observers":{"encryptionKey":"U+rZl9qb1YBLuZS63/cDazyOQIhp4pSb"},"platformId":"EUW1","bannedChampions":[],"gameStartTime":1495009864765,"gameLength":269},{"gameId":3186554674,"mapId":11,"gameMode":"CLASSIC","gameType":"MATCHED_GAME","gameQueueConfigId":420,"participants":[{"teamId":100,"spell1Id":4,"spell2Id":11,"championId":64,"profileIconId":1629,"summonerName":"Gripex","bot":false},{"teamId":100,"spell1Id":14,"spell2Id":4,"championId":1,"profileIconId":588,"summonerName":"Back2Me","bot":false},{"teamId":100,"spell1Id":4,"spell2Id":12,"championId":59,"profileIconId":595,"summonerName":"bog","bot":false},{"teamId":100,"spell1Id":7,"spell2Id":4,"championId":236,"profileIconId":1597,"summonerName":"OSP","bot":false},{"teamId":100,"spell1Id":4,"spell2Id":3,"championId":25,"profileIconId":578,"summonerName":"nigdy szczeście","bot":false},{"teamId":200,"spell1Id":4,"spell2Id":12,"championId":76,"profileIconId":585,"summonerName":"br0keñ","bot":false},{"teamId":200,"spell1Id":4,"spell2Id":14,"championId":55,"profileIconId":1594,"summonerName":"Revilous","bot":false},{"teamId":200,"spell1Id":11,"spell2Id":4,"championId":121,"profileIconId":1601,"summonerName":"Áncient","bot":false},{"teamId":200,"spell1Id":4,"spell2Id":14,"championId":89,"profileIconId":508,"summonerName":"TEA Darkvenger","bot":false},{"teamId":200,"spell1Id":7,"spell2Id":4,"championId":51,"profileIconId":22,"summonerName":"Upiornyy","bot":false}],"observers":{"encryptionKey":"iDz9y9ULmEOIyDhyB6hK2VN4vWkgG4mG"},"platformId":"EUW1","bannedChampions":[{"championId":427,"teamId":100,"pickTurn":1},{"championId":117,"teamId":200,"pickTurn":2},{"championId":105,"teamId":100,"pickTurn":3},{"championId":104,"teamId":200,"pickTurn":4},{"championId":7,"teamId":100,"pickTurn":5},{"championId":38,"teamId":200,"pickTurn":6}],"gameStartTime":1495009791957,"gameLength":342},{"gameId":3186583988,"mapId":12,"gameMode":"ARAM","gameType":"MATCHED_GAME","gameQueueConfigId":65,"participants":[{"teamId":100,"spell1Id":4,"spell2Id":7,"championId":96,"profileIconId":1162,"summonerName":"Kawaboungara","bot":false},{"teamId":100,"spell1Id":4,"spell2Id":32,"championId":53,"profileIconId":1301,"summonerName":"Lnerbo","bot":false},{"teamId":100,"spell1Id":32,"spell2Id":4,"championId":86,"profileIconId":1436,"summonerName":"BuryMySoul","bot":false},{"teamId":100,"spell1Id":4,"spell2Id":21,"championId":268,"profileIconId":642,"summonerName":"Cank Wannister","bot":false},{"teamId":100,"spell1Id":4,"spell2Id":7,"championId":202,"profileIconId":898,"summonerName":"Halanah","bot":false},{"teamId":200,"spell1Id":4,"spell2Id":7,"championId":90,"profileIconId":522,"summonerName":"Milfbubi","bot":false},{"teamId":200,"spell1Id":32,"spell2Id":4,"championId":50,"profileIconId":508,"summonerName":"blyxXx","bot":false},{"teamId":200,"spell1Id":4,"spell2Id":3,"championId":84,"profileIconId":10,"summonerName":"Raíñbow Dash","bot":false},{"teamId":200,"spell1Id":4,"spell2Id":32,"championId":113,"profileIconId":1642,"summonerName":"Aninis","bot":false},{"teamId":200,"spell1Id":32,"spell2Id":4,"championId":35,"profileIconId":717,"summonerName":"3X1st3NC3","bot":false}],"observers":{"encryptionKey":"jx39BM9Lwsp5a6MStzCkVP2OBSjGg9zc"},"platformId":"EUW1","bannedChampions":[],"gameStartTime":1495009789297,"gameLength":345},{"gameId":3186343650,"mapId":11,"gameMode":"CLASSIC","gameType":"MATCHED_GAME","gameQueueConfigId":2,"participants":[{"teamId":100,"spell1Id":4,"spell2Id":12,"championId":92,"profileIconId":563,"summonerName":"basouK","bot":false},{"teamId":100,"spell1Id":14,"spell2Id":4,"championId":63,"profileIconId":1640,"summonerName":"Witoad v1","bot":false},{"teamId":100,"spell1Id":4,"spell2Id":7,"championId":81,"profileIconId":1157,"summonerName":"Leafor","bot":false},{"teamId":100,"spell1Id":3,"spell2Id":4,"championId":30,"profileIconId":1601,"summonerName":"IS1ffdb2a4c978a0","bot":false},{"teamId":100,"spell1Id":11,"spell2Id":4,"championId":28,"profileIconId":1601,"summonerName":"EVELYNN POGCHAMP","bot":false},{"teamId":200,"spell1Id":4,"spell2Id":7,"championId":51,"profileIconId":1601,"summonerName":"Shepherd Shock","bot":false},{"teamId":200,"spell1Id":4,"spell2Id":12,"championId":245,"profileIconId":1594,"summonerName":"Tusci","bot":false},{"teamId":200,"spell1Id":11,"spell2Id":4,"championId":20,"profileIconId":1154,"summonerName":"Ghandi handi","bot":false},{"teamId":200,"spell1Id":14,"spell2Id":4,"championId":57,"profileIconId":1634,"summonerName":"Decapitator Jake","bot":false},{"teamId":200,"spell1Id":4,"spell2Id":12,"championId":75,"profileIconId":713,"summonerName":"Adeanin96","bot":false}],"observers":{"encryptionKey":"sPzPqqa6m2KNvtuzhipz/NIVNVQjAbci"},"platformId":"EUW1","bannedChampions":[],"gameStartTime":1495010031086,"gameLength":103},{"gameId":3186603689,"mapId":12,"gameMode":"ARAM","gameType":"MATCHED_GAME","gameQueueConfigId":65,"participants":[{"teamId":100,"spell1Id":4,"spell2Id":21,"championId":115,"profileIconId":781,"summonerName":"Verievil","bot":false},{"teamId":100,"spell1Id":4,"spell2Id":3,"championId":43,"profileIconId":685,"summonerName":"tacotak21","bot":false},{"teamId":100,"spell1Id":21,"spell2Id":32,"championId":238,"profileIconId":983,"summonerName":"Kurau","bot":false},{"teamId":100,"spell1Id":7,"spell2Id":4,"championId":81,"profileIconId":1375,"summonerName":"Chitter","bot":false},{"teamId":100,"spell1Id":13,"spell2Id":4,"championId":117,"profileIconId":544,"summonerName":"Jòhn Wick","bot":false},{"teamId":200,"spell1Id":4,"spell2Id":32,"championId":92,"profileIconId":1155,"summonerName":"Turken22","bot":false},{"teamId":200,"spell1Id":4,"spell2Id":32,"championId":62,"profileIconId":907,"summonerName":"ReIief","bot":false},{"teamId":200,"spell1Id":3,"spell2Id":4,"championId":37,"profileIconId":22,"summonerName":"C me C you","bot":false},{"teamId":200,"spell1Id":7,"spell2Id":4,"championId":161,"profileIconId":747,"summonerName":"SR GunsRosesXIII","bot":false},{"teamId":200,"spell1Id":3,"spell2Id":4,"championId":67,"profileIconId":512,"summonerName":"FeestHoed","bot":false}],"observers":{"encryptionKey":"9GCwWjk59SOoS5igQ8F6baztrpfXtgRO"},"platformId":"EUW1","bannedChampions":[],"gameStartTime":1495009938752,"gameLength":195}],"clientRefreshInterval":300} -------------------------------------------------------------------------------- /spec/lol/request_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "lol" 3 | 4 | include Lol 5 | 6 | describe Request do 7 | context "initialization" do 8 | it "requires an api_key parameter" do 9 | expect { ChampionRequest.new }.to raise_error(ArgumentError) 10 | end 11 | 12 | it "accepts a region parameter" do 13 | expect { ChampionRequest.new "foo" }.to raise_error(ArgumentError) 14 | end 15 | 16 | it "correctly sets api key" do 17 | expect(ChampionRequest.new("api_key", "euw").api_key).to eq("api_key") 18 | end 19 | 20 | it "sets the cache store" do 21 | redis_store = Redis.new 22 | c = ChampionRequest.new("api_key", "euw", {redis: redis_store}) 23 | expect(c.store).to eq(redis_store) 24 | end 25 | 26 | it "returns an error if the cache store is not supported" do 27 | expect { ChampionRequest.new "api_key", "euw", {cached: true, redis: "FOO"}}.to raise_error(InvalidCacheStore) 28 | end 29 | end 30 | 31 | subject { Request.new "api_key", "euw", {}, rate_limiter } 32 | let(:rate_limiter) { nil } 33 | 34 | describe "#perform_request" do 35 | 36 | let(:error401) { error_401 } 37 | let(:error429) { error_429 } 38 | 39 | it "calls HTTParty get" do 40 | expect(subject.class).to receive(:get).and_return(error401) 41 | expect { subject.perform_request subject.api_url("foo")}.to raise_error(InvalidAPIResponse) 42 | end 43 | 44 | it "sets content type in get requests" do 45 | expect(subject.class).to receive(:get) do |url, options| 46 | expect(options[:headers]["Content-Type"]).to eq "application/json" 47 | end 48 | subject.perform_request subject.api_url("foo") 49 | end 50 | 51 | it "sets the accept header in get requests" do 52 | expect(subject.class).to receive(:get) do |url, options| 53 | expect(options[:headers]["Accept"]).to eq "application/json" 54 | end 55 | subject.perform_request subject.api_url("foo") 56 | end 57 | 58 | it "sets content type in post requests" do 59 | expect(subject.class).to receive(:post) do |url, options| 60 | expect(options[:headers]["Content-Type"]).to eq "application/json" 61 | end 62 | subject.perform_request subject.api_url("foo"), :post 63 | end 64 | 65 | it "sets the accept header in post requests" do 66 | expect(subject.class).to receive(:post) do |url, options| 67 | expect(options[:headers]["Accept"]).to eq "application/json" 68 | end 69 | subject.perform_request subject.api_url("foo"), :post 70 | end 71 | 72 | it "sets the api key header in post requests" do 73 | expect(subject.class).to receive(:post) do |url, options| 74 | expect(options[:headers]["X-Riot-Token"]).to eq(subject.api_key) 75 | end 76 | subject.perform_request subject.api_url("foo"), :post 77 | end 78 | 79 | it "handles 401" do 80 | expect(subject.class).to receive(:get).and_return(error401) 81 | expect { subject.perform_request subject.api_url("foo") }.to raise_error(InvalidAPIResponse) 82 | end 83 | 84 | it "handles 404" do 85 | expect(error401).to receive(:respond_to?).at_least(:once).and_return(true) 86 | expect(error401).to receive(:not_found?).and_return(true) 87 | expect(subject.class).to receive(:get).and_return(error401) 88 | expect { subject.perform_request subject.api_url("foo")}.to raise_error(NotFound) 89 | end 90 | 91 | it 'handles 429' do 92 | expect(error429).to receive(:respond_to?).and_return(true) 93 | expect(subject.class).to receive(:get).and_return(error429) 94 | expect { subject.perform_request subject.api_url("foo")}.to raise_error(TooManyRequests) 95 | end 96 | 97 | context "post requests" do 98 | it "supports post" do 99 | expect(subject.class).to receive(:post).and_return(true) 100 | expect { subject.perform_request "http://foo.com", :post }.not_to raise_error 101 | end 102 | end 103 | 104 | context "caching" do 105 | before :all do 106 | class FakeRedis < Redis 107 | attr_reader :store 108 | 109 | def initialize options = {} 110 | @store = {} 111 | end 112 | 113 | def get key 114 | @store[key] 115 | end 116 | 117 | def setex key, ttl, val 118 | @store[key] = val 119 | @store["#{key}:ttl"] = ttl 120 | end 121 | end 122 | end 123 | 124 | let(:fake_redis) { FakeRedis.new } 125 | let(:request) { Request.new "api_key", "euw", {redis: fake_redis, ttl: 60, cached: true }} 126 | before :each do 127 | expect(request.class).to receive(:get).with("/foo?api_key=asd", instance_of(Hash)).and_return({foo: "bar"}).at_least(:once) 128 | first_result = request.perform_request "/foo?api_key=asd" 129 | end 130 | 131 | it "is cached" do 132 | expect(request.class).not_to receive(:get) 133 | request.perform_request "/foo?api_key=asd" 134 | end 135 | 136 | it "is not cached if post" do 137 | expect(request.class).to receive(:post).twice 138 | request.perform_request "/foo?api_key=asd", :post 139 | request.perform_request "/foo?api_key=asd", :post 140 | end 141 | 142 | it "serializes cached responses" do 143 | expect(JSON).to receive(:parse) 144 | request.perform_request "/foo?api_key=asd" 145 | end 146 | 147 | it "sets ttl" do 148 | expect(fake_redis.get("/foo?{}:ttl")).to eq(60) 149 | end 150 | 151 | it "uses clean urls" do 152 | expect(request).to receive(:clean_url).at_least(:once) 153 | request.perform_request "/foo?api_key=asd" 154 | end 155 | end 156 | 157 | context 'with rate limits' do 158 | let(:rate_limiter) { Lol::Client.new('api_key').set_up_rate_limiter(1, 20) } 159 | before { stub_request subject, 'champion-all', 'champions' } 160 | 161 | it 'uses rate limiter' do 162 | expect(subject.rate_limiter).to receive(:times).and_call_original 163 | subject.perform_request subject.api_url('champions') 164 | end 165 | end 166 | end 167 | 168 | describe "clean_url" do 169 | it "works when url does not have a query string" do 170 | expect { subject.clean_url 'http://www.leagueoflegends.com' }.not_to raise_error 171 | end 172 | end 173 | 174 | describe "api_url" do 175 | it "defaults on Request#region" do 176 | expect(subject.api_url("bar")).to match(/\/euw1\./) 177 | end 178 | 179 | it "defaults on Reques.api_version" do 180 | expect(subject.api_url("bar")).to match(/\/v3\//) 181 | end 182 | 183 | it "a path" do 184 | expect { subject.api_url }.to raise_error(ArgumentError) 185 | end 186 | 187 | it "returns a full fledged api url" do 188 | expect(subject.api_url("bar")).to eq("https://euw1.api.riotgames.com/lol/platform/v3/bar?api_key=api_key") 189 | end 190 | 191 | it "optionally accept query string parameters" do 192 | expect(subject.api_url("foo", a: 'b')).to eq("https://euw1.api.riotgames.com/lol/platform/v3/foo?a=b&api_key=api_key") 193 | end 194 | 195 | it "delegates the base url to #api_base_url" do 196 | allow(subject).to receive(:api_base_url).and_return 'foo' 197 | expect(subject.api_url 'bar').to match /^foo/ 198 | end 199 | 200 | it "delegates the query string to #api_query_string" do 201 | allow(subject).to receive(:api_query_string).and_return 'foo' 202 | expect(subject.api_url 'bar').to match /\?foo$/ 203 | end 204 | end 205 | 206 | describe "#api_base_url" do 207 | it "returns the base domain" do 208 | expect(subject.api_base_url).to eq "https://euw1.api.riotgames.com" 209 | end 210 | end 211 | 212 | describe "#api_query_string" do 213 | it "returns the api key as query string" do 214 | expect(subject.api_query_string).to eq "api_key=api_key" 215 | end 216 | 217 | it "optionally accept other params" do 218 | expect(subject.api_query_string foo: 'bar').to eq "foo=bar&api_key=api_key" 219 | end 220 | end 221 | end 222 | -------------------------------------------------------------------------------- /spec/fixtures/v3/get-static-champions.json: -------------------------------------------------------------------------------- 1 | {"type":"champion","version":"7.9.1","data":{"89":{"id":89,"key":"Leona","name":"Leona","title":"the Radiant Dawn"},"110":{"id":110,"key":"Varus","name":"Varus","title":"the Arrow of Retribution"},"111":{"id":111,"key":"Nautilus","name":"Nautilus","title":"the Titan of the Depths"},"112":{"id":112,"key":"Viktor","name":"Viktor","title":"the Machine Herald"},"113":{"id":113,"key":"Sejuani","name":"Sejuani","title":"Fury of the North"},"114":{"id":114,"key":"Fiora","name":"Fiora","title":"the Grand Duelist"},"236":{"id":236,"key":"Lucian","name":"Lucian","title":"the Purifier"},"115":{"id":115,"key":"Ziggs","name":"Ziggs","title":"the Hexplosives Expert"},"117":{"id":117,"key":"Lulu","name":"Lulu","title":"the Fae Sorceress"},"90":{"id":90,"key":"Malzahar","name":"Malzahar","title":"the Prophet of the Void"},"238":{"id":238,"key":"Zed","name":"Zed","title":"the Master of Shadows"},"91":{"id":91,"key":"Talon","name":"Talon","title":"the Blade's Shadow"},"119":{"id":119,"key":"Draven","name":"Draven","title":"the Glorious Executioner"},"92":{"id":92,"key":"Riven","name":"Riven","title":"the Exile"},"96":{"id":96,"key":"KogMaw","name":"Kog'Maw","title":"the Mouth of the Abyss"},"10":{"id":10,"key":"Kayle","name":"Kayle","title":"The Judicator"},"98":{"id":98,"key":"Shen","name":"Shen","title":"the Eye of Twilight"},"99":{"id":99,"key":"Lux","name":"Lux","title":"the Lady of Luminosity"},"11":{"id":11,"key":"MasterYi","name":"Master Yi","title":"the Wuju Bladesman"},"12":{"id":12,"key":"Alistar","name":"Alistar","title":"the Minotaur"},"13":{"id":13,"key":"Ryze","name":"Ryze","title":"the Rune Mage"},"14":{"id":14,"key":"Sion","name":"Sion","title":"The Undead Juggernaut"},"15":{"id":15,"key":"Sivir","name":"Sivir","title":"the Battle Mistress"},"16":{"id":16,"key":"Soraka","name":"Soraka","title":"the Starchild"},"17":{"id":17,"key":"Teemo","name":"Teemo","title":"the Swift Scout"},"18":{"id":18,"key":"Tristana","name":"Tristana","title":"the Yordle Gunner"},"19":{"id":19,"key":"Warwick","name":"Warwick","title":"the Uncaged Wrath of Zaun"},"240":{"id":240,"key":"Kled","name":"Kled","title":"the Cantankerous Cavalier"},"120":{"id":120,"key":"Hecarim","name":"Hecarim","title":"the Shadow of War"},"121":{"id":121,"key":"Khazix","name":"Kha'Zix","title":"the Voidreaver"},"1":{"id":1,"key":"Annie","name":"Annie","title":"the Dark Child"},"122":{"id":122,"key":"Darius","name":"Darius","title":"the Hand of Noxus"},"2":{"id":2,"key":"Olaf","name":"Olaf","title":"the Berserker"},"245":{"id":245,"key":"Ekko","name":"Ekko","title":"the Boy Who Shattered Time"},"3":{"id":3,"key":"Galio","name":"Galio","title":"the Colossus"},"4":{"id":4,"key":"TwistedFate","name":"Twisted Fate","title":"the Card Master"},"126":{"id":126,"key":"Jayce","name":"Jayce","title":"the Defender of Tomorrow"},"5":{"id":5,"key":"XinZhao","name":"Xin Zhao","title":"the Seneschal of Demacia"},"127":{"id":127,"key":"Lissandra","name":"Lissandra","title":"the Ice Witch"},"6":{"id":6,"key":"Urgot","name":"Urgot","title":"the Headsman's Pride"},"7":{"id":7,"key":"Leblanc","name":"LeBlanc","title":"the Deceiver"},"8":{"id":8,"key":"Vladimir","name":"Vladimir","title":"the Crimson Reaper"},"9":{"id":9,"key":"Fiddlesticks","name":"Fiddlesticks","title":"the Harbinger of Doom"},"20":{"id":20,"key":"Nunu","name":"Nunu","title":"the Yeti Rider"},"21":{"id":21,"key":"MissFortune","name":"Miss Fortune","title":"the Bounty Hunter"},"22":{"id":22,"key":"Ashe","name":"Ashe","title":"the Frost Archer"},"23":{"id":23,"key":"Tryndamere","name":"Tryndamere","title":"the Barbarian King"},"24":{"id":24,"key":"Jax","name":"Jax","title":"Grandmaster at Arms"},"25":{"id":25,"key":"Morgana","name":"Morgana","title":"Fallen Angel"},"26":{"id":26,"key":"Zilean","name":"Zilean","title":"the Chronokeeper"},"27":{"id":27,"key":"Singed","name":"Singed","title":"the Mad Chemist"},"28":{"id":28,"key":"Evelynn","name":"Evelynn","title":"the Widowmaker"},"29":{"id":29,"key":"Twitch","name":"Twitch","title":"the Plague Rat"},"131":{"id":131,"key":"Diana","name":"Diana","title":"Scorn of the Moon"},"133":{"id":133,"key":"Quinn","name":"Quinn","title":"Demacia's Wings"},"254":{"id":254,"key":"Vi","name":"Vi","title":"the Piltover Enforcer"},"497":{"id":497,"key":"Rakan","name":"Rakan","title":"The Charmer"},"134":{"id":134,"key":"Syndra","name":"Syndra","title":"the Dark Sovereign"},"498":{"id":498,"key":"Xayah","name":"Xayah","title":"the Rebel"},"136":{"id":136,"key":"AurelionSol","name":"Aurelion Sol","title":"The Star Forger"},"412":{"id":412,"key":"Thresh","name":"Thresh","title":"the Chain Warden"},"30":{"id":30,"key":"Karthus","name":"Karthus","title":"the Deathsinger"},"31":{"id":31,"key":"Chogath","name":"Cho'Gath","title":"the Terror of the Void"},"32":{"id":32,"key":"Amumu","name":"Amumu","title":"the Sad Mummy"},"33":{"id":33,"key":"Rammus","name":"Rammus","title":"the Armordillo"},"34":{"id":34,"key":"Anivia","name":"Anivia","title":"the Cryophoenix"},"35":{"id":35,"key":"Shaco","name":"Shaco","title":"the Demon Jester"},"36":{"id":36,"key":"DrMundo","name":"Dr. Mundo","title":"the Madman of Zaun"},"37":{"id":37,"key":"Sona","name":"Sona","title":"Maven of the Strings"},"38":{"id":38,"key":"Kassadin","name":"Kassadin","title":"the Void Walker"},"39":{"id":39,"key":"Irelia","name":"Irelia","title":"the Will of the Blades"},"143":{"id":143,"key":"Zyra","name":"Zyra","title":"Rise of the Thorns"},"266":{"id":266,"key":"Aatrox","name":"Aatrox","title":"the Darkin Blade"},"420":{"id":420,"key":"Illaoi","name":"Illaoi","title":"the Kraken Priestess"},"267":{"id":267,"key":"Nami","name":"Nami","title":"the Tidecaller"},"421":{"id":421,"key":"RekSai","name":"Rek'Sai","title":"the Void Burrower"},"268":{"id":268,"key":"Azir","name":"Azir","title":"the Emperor of the Sands"},"427":{"id":427,"key":"Ivern","name":"Ivern","title":"the Green Father"},"429":{"id":429,"key":"Kalista","name":"Kalista","title":"the Spear of Vengeance"},"40":{"id":40,"key":"Janna","name":"Janna","title":"the Storm's Fury"},"41":{"id":41,"key":"Gangplank","name":"Gangplank","title":"the Saltwater Scourge"},"42":{"id":42,"key":"Corki","name":"Corki","title":"the Daring Bombardier"},"43":{"id":43,"key":"Karma","name":"Karma","title":"the Enlightened One"},"44":{"id":44,"key":"Taric","name":"Taric","title":"the Shield of Valoran"},"45":{"id":45,"key":"Veigar","name":"Veigar","title":"the Tiny Master of Evil"},"48":{"id":48,"key":"Trundle","name":"Trundle","title":"the Troll King"},"150":{"id":150,"key":"Gnar","name":"Gnar","title":"the Missing Link"},"154":{"id":154,"key":"Zac","name":"Zac","title":"the Secret Weapon"},"432":{"id":432,"key":"Bard","name":"Bard","title":"the Wandering Caretaker"},"157":{"id":157,"key":"Yasuo","name":"Yasuo","title":"the Unforgiven"},"50":{"id":50,"key":"Swain","name":"Swain","title":"the Master Tactician"},"51":{"id":51,"key":"Caitlyn","name":"Caitlyn","title":"the Sheriff of Piltover"},"53":{"id":53,"key":"Blitzcrank","name":"Blitzcrank","title":"the Great Steam Golem"},"54":{"id":54,"key":"Malphite","name":"Malphite","title":"Shard of the Monolith"},"55":{"id":55,"key":"Katarina","name":"Katarina","title":"the Sinister Blade"},"56":{"id":56,"key":"Nocturne","name":"Nocturne","title":"the Eternal Nightmare"},"57":{"id":57,"key":"Maokai","name":"Maokai","title":"the Twisted Treant"},"58":{"id":58,"key":"Renekton","name":"Renekton","title":"the Butcher of the Sands"},"59":{"id":59,"key":"JarvanIV","name":"Jarvan IV","title":"the Exemplar of Demacia"},"161":{"id":161,"key":"Velkoz","name":"Vel'Koz","title":"the Eye of the Void"},"163":{"id":163,"key":"Taliyah","name":"Taliyah","title":"the Stoneweaver"},"164":{"id":164,"key":"Camille","name":"Camille","title":"the Steel Shadow"},"201":{"id":201,"key":"Braum","name":"Braum","title":"the Heart of the Freljord"},"202":{"id":202,"key":"Jhin","name":"Jhin","title":"the Virtuoso"},"203":{"id":203,"key":"Kindred","name":"Kindred","title":"The Eternal Hunters"},"60":{"id":60,"key":"Elise","name":"Elise","title":"the Spider Queen"},"61":{"id":61,"key":"Orianna","name":"Orianna","title":"the Lady of Clockwork"},"62":{"id":62,"key":"MonkeyKing","name":"Wukong","title":"the Monkey King"},"63":{"id":63,"key":"Brand","name":"Brand","title":"the Burning Vengeance"},"64":{"id":64,"key":"LeeSin","name":"Lee Sin","title":"the Blind Monk"},"67":{"id":67,"key":"Vayne","name":"Vayne","title":"the Night Hunter"},"68":{"id":68,"key":"Rumble","name":"Rumble","title":"the Mechanized Menace"},"69":{"id":69,"key":"Cassiopeia","name":"Cassiopeia","title":"the Serpent's Embrace"},"72":{"id":72,"key":"Skarner","name":"Skarner","title":"the Crystal Vanguard"},"74":{"id":74,"key":"Heimerdinger","name":"Heimerdinger","title":"the Revered Inventor"},"75":{"id":75,"key":"Nasus","name":"Nasus","title":"the Curator of the Sands"},"76":{"id":76,"key":"Nidalee","name":"Nidalee","title":"the Bestial Huntress"},"77":{"id":77,"key":"Udyr","name":"Udyr","title":"the Spirit Walker"},"78":{"id":78,"key":"Poppy","name":"Poppy","title":"Keeper of the Hammer"},"79":{"id":79,"key":"Gragas","name":"Gragas","title":"the Rabble Rouser"},"222":{"id":222,"key":"Jinx","name":"Jinx","title":"the Loose Cannon"},"101":{"id":101,"key":"Xerath","name":"Xerath","title":"the Magus Ascendant"},"102":{"id":102,"key":"Shyvana","name":"Shyvana","title":"the Half-Dragon"},"223":{"id":223,"key":"TahmKench","name":"Tahm Kench","title":"the River King"},"103":{"id":103,"key":"Ahri","name":"Ahri","title":"the Nine-Tailed Fox"},"104":{"id":104,"key":"Graves","name":"Graves","title":"the Outlaw"},"105":{"id":105,"key":"Fizz","name":"Fizz","title":"the Tidal Trickster"},"106":{"id":106,"key":"Volibear","name":"Volibear","title":"the Thunder's Roar"},"80":{"id":80,"key":"Pantheon","name":"Pantheon","title":"the Artisan of War"},"107":{"id":107,"key":"Rengar","name":"Rengar","title":"the Pridestalker"},"81":{"id":81,"key":"Ezreal","name":"Ezreal","title":"the Prodigal Explorer"},"82":{"id":82,"key":"Mordekaiser","name":"Mordekaiser","title":"the Iron Revenant"},"83":{"id":83,"key":"Yorick","name":"Yorick","title":"Shepherd of Souls"},"84":{"id":84,"key":"Akali","name":"Akali","title":"the Fist of Shadow"},"85":{"id":85,"key":"Kennen","name":"Kennen","title":"the Heart of the Tempest"},"86":{"id":86,"key":"Garen","name":"Garen","title":"The Might of Demacia"}}} -------------------------------------------------------------------------------- /spec/fixtures/v3/get-static-masteries.json: -------------------------------------------------------------------------------- 1 | {"type":"mastery","version":"7.9.1","data":{"6131":{"id":6131,"name":"Vampirism","description":["+0.4% Lifesteal and Spell Vamp","+0.8% Lifesteal and Spell Vamp","+1.2% Lifesteal and Spell Vamp","+1.6% Lifesteal and Spell Vamp","+2.0% Lifesteal and Spell Vamp"]},"6351":{"id":6351,"name":"Precision","description":["Gain 1.2 Lethality and 0.3 + 0.05 per level Magic Penetration","Gain 2.4 Lethality and 0.6 + 0.10 per level Magic Penetration","Gain 3.6 Lethality and 0.9 + 0.15 per level Magic Penetration","Gain 4.8 Lethality and 1.2 + 0.20 per level Magic Penetration","Gain 6 Lethality and 1.5 + 0.25 per level Magic Penetration"]},"6252":{"id":6252,"name":"Legendary Guardian","description":["+0.6 Armor and Magic Resist for each nearby enemy champion","+1.2 Armor and Magic Resist for each nearby enemy champion","+1.8 Armor and Magic Resist for each nearby enemy champion","+2.4 Armor and Magic Resist for each nearby enemy champion","+3 Armor and Magic Resist for each nearby enemy champion"]},"6251":{"id":6251,"name":"Swiftness","description":["+3% Tenacity and Slow Resist","+6% Tenacity and Slow Resist","+9% Tenacity and Slow Resist","+12% Tenacity and Slow Resist","+15% Tenacity and Slow Resist"]},"6111":{"id":6111,"name":"Fury","description":["+0.8% Attack Speed","+1.6% Attack Speed","+2.4% Attack Speed","+3.2% Attack Speed","+4% Attack Speed"]},"6331":{"id":6331,"name":"Merciless","description":["Deal 0.6% increased damage to champions below 40% Health","Deal 1.2% increased damage to champions below 40% Health","Deal 1.8% increased damage to champions below 40% Health","Deal 2.4% increased damage to champions below 40% Health","Deal 3% increased damage to champions below 40% Health"]},"6232":{"id":6232,"name":"Veteran's Scars","description":["+10 Health","+20 Health","+30 Health","+40 Health","+50 Health"]},"6154":{"id":6154,"name":"Piercing Thoughts","description":["+1.4% Magic Penetration","+2.8% Magic Penetration","+4.2% Magic Penetration","+5.6% Magic Penetration","+7% Magic Penetration"]},"6352":{"id":6352,"name":"Intelligence","description":["Your Cooldown Reduction cap is increased to 41% and you gain 1% Cooldown Reduction","Your Cooldown Reduction cap is increased to 42% and you gain 2% Cooldown Reduction","Your Cooldown Reduction cap is increased to 43% and you gain 3% Cooldown Reduction","Your Cooldown Reduction cap is increased to 44% and you gain 4% Cooldown Reduction","Your Cooldown Reduction cap is increased to 45% and you gain 5% Cooldown Reduction"]},"6231":{"id":6231,"name":"Runic Armor","description":["Shields, healing, regeneration, and lifesteal on you are 1.6% stronger","Shields, healing, regeneration, and lifesteal on you are 3.2% stronger","Shields, healing, regeneration, and lifesteal on you are 4.8% stronger","Shields, healing, regeneration, and lifesteal on you are 6.4% stronger","Shields, healing, regeneration, and lifesteal on you are 8% stronger"]},"6311":{"id":6311,"name":"Wanderer","description":["+0.6% Movement Speed out of combat","+1.2% Movement Speed out of combat","+1.8% Movement Speed out of combat","+2.4% Movement Speed out of combat","+3% Movement Speed out of combat"]},"6212":{"id":6212,"name":"Unyielding","description":["+1% Bonus Armor and Magic Resist","+2% Bonus Armor and Magic Resist","+3% Bonus Armor and Magic Resist","+4% Bonus Armor and Magic Resist","+5% Bonus Armor and Magic Resist"]},"6134":{"id":6134,"name":"Natural Talent","description":["Gain 0.4 + 0.09 per level Attack Damage, and 0.6 + 0.13 per level Ability Power (+2 Attack Damage and 3 Ability Power at level 18)","Gain 0.8 + 0.18 per level Attack Damage, and 1.2 + 0.27 per level Ability Power (+4 Attack Damage and 6 Ability Power at level 18)","Gain 1.2 + 0.27 per level Attack Damage, and 1.8 + 0.4 per level Ability Power (+6 Attack Damage and 9 Ability Power at level 18)","Gain 1.6 + 0.36 per level Attack Damage, and 2.4 + 0.53 per level Ability Power (+8 Attack Damage and 12 Ability Power at level 18)","Gain 2 + 0.44 per level Attack Damage, and 3 + 0.67 per level Ability Power (+10 Attack Damage and 15 Ability Power at level 18)"]},"6332":{"id":6332,"name":"Meditation","description":["Regenerate 0.25% of your missing Mana every 5 seconds","Regenerate 0.5% of your missing Mana every 5 seconds","Regenerate 0.75% of your missing Mana every 5 seconds","Regenerate 1.0% of your missing Mana every 5 seconds","Regenerate 1.25% of your missing Mana every 5 seconds"]},"6211":{"id":6211,"name":"Recovery","description":["+0.4 Health per 5 seconds","+0.8 Health per 5 seconds","+1.2 Health per 5 seconds","+1.6 Health per 5 seconds","+2.0 Health per 5 seconds"]},"6114":{"id":6114,"name":"Sorcery","description":["+0.4% increased Ability damage","+0.8% increased Ability damage","+1.2% increased Ability damage","+1.6% increased Ability damage","+2.0% increased Ability damage"]},"6312":{"id":6312,"name":"Savagery","description":["Single target attacks and spells deal 1 bonus damage to minions and monsters","Single target attacks and spells deal 2 bonus damage to minions and monsters","Single target attacks and spells deal 3 bonus damage to minions and monsters","Single target attacks and spells deal 4 bonus damage to minions and monsters","Single target attacks and spells deal 5 bonus damage to minions and monsters"]},"6151":{"id":6151,"name":"Battering Blows","description":["+1.4% Armor Penetration","+2.8% Armor Penetration","+4.2% Armor Penetration","+5.6% Armor Penetration","+7% Armor Penetration"]},"6142":{"id":6142,"name":"Double Edged Sword","description":["Deal 3% additional damage, take 1.5% additional damage."]},"6164":{"id":6164,"name":"Deathfire Touch","description":["Your damaging abilities cause enemy champions to take magic damage over 4 seconds.

Damage: 8 + 45% Bonus Attack Damage and 25% Ability Power

Deathfire Touch's duration is reduced for:
- Area of Effect: 2 second duration.
- Damage over Time: 1 second duration."]},"6362":{"id":6362,"name":"Thunderlord's Decree","description":["Your 3rd attack or damaging spell against the same enemy champion calls down a lightning strike, dealing magic damage in the area.

Damage: 10 per level, plus 30% of your Bonus Attack Damage, and 10% of your Ability Power (25-15 second cooldown, based on level)."]},"6241":{"id":6241,"name":"Insight","description":["Reduces the cooldown of Summoner Spells by 15%"]},"6263":{"id":6263,"name":"Stoneborn Pact","description":["Gain 5% total health.
Your movement impairing effects brand enemy champions with an earthen rune for 4 seconds. Other allied champions who attack branded enemies heal for 5 + 2.5% of your maximum health over 2 seconds (halved if you are ranged)."]},"6141":{"id":6141,"name":"Bounty Hunter","description":["Deal 1% increased damage for each unique enemy champion you have killed"]},"6361":{"id":6361,"name":"Stormraider's Surge","description":["Dealing 30% of a champion's max Health within 2.5 seconds grants you 40% Movement Speed and 75% Slow Resistance for 3 seconds (10 second cooldown)."]},"6262":{"id":6262,"name":"Courage of the Colossus","description":["Gain a shield for 3-54 (+5% of your maximum health) for each nearby enemy champion for 3 seconds after hitting an enemy champion with a stun, taunt, snare, or knock up (45-30 second cooldown, based on level)."]},"6122":{"id":6122,"name":"Feast","description":["Killing a unit restores 20 Health (30 second cooldown)"]},"6342":{"id":6342,"name":"Bandit","description":["Gain 1 gold for each nearby minion killed by an ally.

Gain 3 gold (10 if melee) when hitting an enemy champion with a basic attack (5 second cooldown)"]},"6221":{"id":6221,"name":"Explorer","description":["+15 Movement Speed in Brush and River"]},"6243":{"id":6243,"name":"Fearless","description":["Gain 10% +1.5 per level bonus Armor and Magic Resist when damaged by an enemy champion for 2 seconds (9s Cooldown)"]},"6121":{"id":6121,"name":"Fresh Blood","description":["Your first basic attack against a champion deals an additional 10 +1 per level damage (6 second cooldown)"]},"6143":{"id":6143,"name":"Battle Trance","description":["Gain up to 3% increased damage over 3 seconds when in combat with enemy Champions"]},"6341":{"id":6341,"name":"Greenfather's Gift","description":["Stepping into brush causes your next damaging attack or ability to deal 3% of your target's current health as bonus magic damage (9s Cooldown)"]},"6363":{"id":6363,"name":"Windspeaker's Blessing","description":["Your heals and shields are 10% stronger. Additionally, your shields and heals on other allies increase their armor by 5-22 (based on level) and their magic resistance by half that amount for 3 seconds."]},"6242":{"id":6242,"name":"Perseverance","description":["+50% Base Health Regen, increased to +200% when below 25% Health"]},"6322":{"id":6322,"name":"Secret Stash","description":["Your Potions and Elixirs last 10% longer.

Your Health Potions are replaced with Biscuits that restore 15 Health and Mana instantly on use"]},"6223":{"id":6223,"name":"Tough Skin","description":["You take 2 less damage from champion and neutral monster basic attacks"]},"6123":{"id":6123,"name":"Expose Weakness","description":["Damaging enemy champions causes them to take 3% more damage from your allies"]},"6321":{"id":6321,"name":"Runic Affinity","description":["Buffs from neutral monsters last 15% longer"]},"6343":{"id":6343,"name":"Dangerous Game","description":["Champion kills and assists restore 5% of your missing Health and Mana"]},"6222":{"id":6222,"name":"Siegemaster","description":["Gain 8 Armor and Magic Resistance when near an allied tower"]},"6323":{"id":6323,"name":"Assassin","description":["Deal 2% increased damage to champions when no allied champions are nearby"]},"6162":{"id":6162,"name":"Fervor of Battle","description":["Hitting champions with basic attacks generates a Fervor stack (2 for melee attacks). Stacks of Fervor last 8 seconds (max 8 stacks)and increase your AD by 1-8 for each stack."]},"6261":{"id":6261,"name":"Grasp of the Undying","description":["Every 4 seconds in combat, your next attack against an enemy champion deals damage equal to 3% of your max Health and heals you for 1.5% of your max Health (halved for ranged champions, deals magic damage)"]},"6161":{"id":6161,"name":"Warlord's Bloodlust","description":["Moving or attacking will charge an Energized attack. Energized attacks heal for 5-40% of your total Attack Damage (amplified by Critical Strikes) and grant 30% Movement Speed for 0.75 seconds."]}}} -------------------------------------------------------------------------------- /spec/fixtures/v3/get-champion-all.json: -------------------------------------------------------------------------------- 1 | {"champions":[{"id":266,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":103,"active":true,"botEnabled":false,"freeToPlay":true,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":84,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":12,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":32,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":34,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":1,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":22,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":53,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":63,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":51,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":69,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":31,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":42,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":122,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":131,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":119,"active":true,"botEnabled":false,"freeToPlay":true,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":36,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":60,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":28,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":81,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":9,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":114,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":105,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":3,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":41,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":86,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":79,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":104,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":120,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":74,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":39,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":40,"active":true,"botEnabled":false,"freeToPlay":true,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":59,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":24,"active":true,"botEnabled":false,"freeToPlay":true,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":126,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":222,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":43,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":30,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":38,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":55,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":10,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":85,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":121,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":96,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":7,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":64,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":89,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":127,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":236,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":117,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":99,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":54,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":90,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":57,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":11,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":21,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":62,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":82,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":25,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":267,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":75,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":111,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":76,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":56,"active":true,"botEnabled":false,"freeToPlay":true,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":20,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":2,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":61,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":80,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":78,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":133,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":33,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":58,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":107,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":92,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":68,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":13,"active":true,"botEnabled":true,"freeToPlay":true,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":113,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":35,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":98,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":102,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":27,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":14,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":15,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":72,"active":true,"botEnabled":false,"freeToPlay":true,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":37,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":16,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":50,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":134,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":91,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":44,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":17,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":412,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":18,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":48,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":23,"active":true,"botEnabled":false,"freeToPlay":true,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":4,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":29,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":77,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":6,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":110,"active":true,"botEnabled":false,"freeToPlay":true,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":67,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":45,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":161,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":254,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":112,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":8,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":106,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":19,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":101,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":5,"active":true,"botEnabled":true,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":157,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":83,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":154,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":238,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":false,"rankedPlayEnabled":true},{"id":115,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":26,"active":true,"botEnabled":false,"freeToPlay":false,"botMmEnabled":true,"rankedPlayEnabled":true},{"id":143,"active":true,"botEnabled":false,"freeToPlay":true,"botMmEnabled":true,"rankedPlayEnabled":true}]} -------------------------------------------------------------------------------- /spec/fixtures/v3/get-champion-masteries.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "highestGrade": "S+", 4 | "championPoints": 34356, 5 | "playerId": 496402, 6 | "championPointsUntilNextLevel": 0, 7 | "chestGranted": true, 8 | "championLevel": 5, 9 | "tokensEarned": 2, 10 | "championId": 40, 11 | "championPointsSinceLastLevel": 12756, 12 | "lastPlayTime": 1464499894000 13 | }, 14 | { 15 | "highestGrade": "A-", 16 | "championPoints": 8972, 17 | "playerId": 496402, 18 | "championPointsUntilNextLevel": 3628, 19 | "chestGranted": false, 20 | "championLevel": 3, 21 | "tokensEarned": 0, 22 | "championId": 429, 23 | "championPointsSinceLastLevel": 2972, 24 | "lastPlayTime": 1463373502000 25 | }, 26 | { 27 | "highestGrade": "A-", 28 | "championPoints": 7355, 29 | "playerId": 496402, 30 | "championPointsUntilNextLevel": 5245, 31 | "chestGranted": false, 32 | "championLevel": 3, 33 | "tokensEarned": 0, 34 | "championId": 15, 35 | "championPointsSinceLastLevel": 1355, 36 | "lastPlayTime": 1462509127000 37 | }, 38 | { 39 | "highestGrade": "A+", 40 | "championPoints": 6581, 41 | "playerId": 496402, 42 | "championPointsUntilNextLevel": 6019, 43 | "chestGranted": true, 44 | "championLevel": 3, 45 | "tokensEarned": 0, 46 | "championId": 236, 47 | "championPointsSinceLastLevel": 581, 48 | "lastPlayTime": 1463462873000 49 | }, 50 | { 51 | "highestGrade": "S+", 52 | "championPoints": 6555, 53 | "playerId": 496402, 54 | "championPointsUntilNextLevel": 6045, 55 | "chestGranted": false, 56 | "championLevel": 3, 57 | "tokensEarned": 0, 58 | "championId": 267, 59 | "championPointsSinceLastLevel": 555, 60 | "lastPlayTime": 1463983471000 61 | }, 62 | { 63 | "highestGrade": "A+", 64 | "championPoints": 4478, 65 | "playerId": 496402, 66 | "championPointsUntilNextLevel": 1522, 67 | "chestGranted": false, 68 | "championLevel": 2, 69 | "tokensEarned": 0, 70 | "championId": 56, 71 | "championPointsSinceLastLevel": 2678, 72 | "lastPlayTime": 1463723059000 73 | }, 74 | { 75 | "highestGrade": "A+", 76 | "championPoints": 4285, 77 | "playerId": 496402, 78 | "championPointsUntilNextLevel": 1715, 79 | "chestGranted": false, 80 | "championLevel": 2, 81 | "tokensEarned": 0, 82 | "championId": 245, 83 | "championPointsSinceLastLevel": 2485, 84 | "lastPlayTime": 1462688704000 85 | }, 86 | { 87 | "championPoints": 3162, 88 | "playerId": 496402, 89 | "championPointsUntilNextLevel": 2838, 90 | "chestGranted": false, 91 | "championLevel": 2, 92 | "tokensEarned": 0, 93 | "championId": 67, 94 | "championPointsSinceLastLevel": 1362, 95 | "lastPlayTime": 1437768811000 96 | }, 97 | { 98 | "highestGrade": "S", 99 | "championPoints": 2686, 100 | "playerId": 496402, 101 | "championPointsUntilNextLevel": 3314, 102 | "chestGranted": false, 103 | "championLevel": 2, 104 | "tokensEarned": 0, 105 | "championId": 81, 106 | "championPointsSinceLastLevel": 886, 107 | "lastPlayTime": 1464415184000 108 | }, 109 | { 110 | "highestGrade": "S-", 111 | "championPoints": 2589, 112 | "playerId": 496402, 113 | "championPointsUntilNextLevel": 3411, 114 | "chestGranted": true, 115 | "championLevel": 2, 116 | "tokensEarned": 0, 117 | "championId": 53, 118 | "championPointsSinceLastLevel": 789, 119 | "lastPlayTime": 1463114828000 120 | }, 121 | { 122 | "championPoints": 2567, 123 | "playerId": 496402, 124 | "championPointsUntilNextLevel": 3433, 125 | "chestGranted": false, 126 | "championLevel": 2, 127 | "tokensEarned": 0, 128 | "championId": 59, 129 | "championPointsSinceLastLevel": 767, 130 | "lastPlayTime": 1444791429000 131 | }, 132 | { 133 | "championPoints": 2556, 134 | "playerId": 496402, 135 | "championPointsUntilNextLevel": 3444, 136 | "chestGranted": false, 137 | "championLevel": 2, 138 | "tokensEarned": 0, 139 | "championId": 37, 140 | "championPointsSinceLastLevel": 756, 141 | "lastPlayTime": 1444277799000 142 | }, 143 | { 144 | "highestGrade": "S-", 145 | "championPoints": 2332, 146 | "playerId": 496402, 147 | "championPointsUntilNextLevel": 3668, 148 | "chestGranted": false, 149 | "championLevel": 2, 150 | "tokensEarned": 0, 151 | "championId": 157, 152 | "championPointsSinceLastLevel": 532, 153 | "lastPlayTime": 1464412893000 154 | }, 155 | { 156 | "highestGrade": "A-", 157 | "championPoints": 2164, 158 | "playerId": 496402, 159 | "championPointsUntilNextLevel": 3836, 160 | "chestGranted": false, 161 | "championLevel": 2, 162 | "tokensEarned": 0, 163 | "championId": 51, 164 | "championPointsSinceLastLevel": 364, 165 | "lastPlayTime": 1463375891000 166 | }, 167 | { 168 | "championPoints": 1561, 169 | "playerId": 496402, 170 | "championPointsUntilNextLevel": 239, 171 | "chestGranted": false, 172 | "championLevel": 1, 173 | "tokensEarned": 0, 174 | "championId": 22, 175 | "championPointsSinceLastLevel": 1561, 176 | "lastPlayTime": 1433019816000 177 | }, 178 | { 179 | "highestGrade": "A-", 180 | "championPoints": 1435, 181 | "playerId": 496402, 182 | "championPointsUntilNextLevel": 365, 183 | "chestGranted": false, 184 | "championLevel": 1, 185 | "tokensEarned": 0, 186 | "championId": 82, 187 | "championPointsSinceLastLevel": 1435, 188 | "lastPlayTime": 1462607727000 189 | }, 190 | { 191 | "highestGrade": "S", 192 | "championPoints": 1226, 193 | "playerId": 496402, 194 | "championPointsUntilNextLevel": 574, 195 | "chestGranted": true, 196 | "championLevel": 1, 197 | "tokensEarned": 0, 198 | "championId": 103, 199 | "championPointsSinceLastLevel": 1226, 200 | "lastPlayTime": 1464327096000 201 | }, 202 | { 203 | "highestGrade": "A-", 204 | "championPoints": 1198, 205 | "playerId": 496402, 206 | "championPointsUntilNextLevel": 602, 207 | "chestGranted": false, 208 | "championLevel": 1, 209 | "tokensEarned": 0, 210 | "championId": 203, 211 | "championPointsSinceLastLevel": 1198, 212 | "lastPlayTime": 1462164785000 213 | }, 214 | { 215 | "highestGrade": "A+", 216 | "championPoints": 1159, 217 | "playerId": 496402, 218 | "championPointsUntilNextLevel": 641, 219 | "chestGranted": false, 220 | "championLevel": 1, 221 | "tokensEarned": 0, 222 | "championId": 127, 223 | "championPointsSinceLastLevel": 1159, 224 | "lastPlayTime": 1463981860000 225 | }, 226 | { 227 | "highestGrade": "S-", 228 | "championPoints": 1143, 229 | "playerId": 496402, 230 | "championPointsUntilNextLevel": 657, 231 | "chestGranted": true, 232 | "championLevel": 1, 233 | "tokensEarned": 0, 234 | "championId": 412, 235 | "championPointsSinceLastLevel": 1143, 236 | "lastPlayTime": 1462335465000 237 | }, 238 | { 239 | "highestGrade": "A+", 240 | "championPoints": 1136, 241 | "playerId": 496402, 242 | "championPointsUntilNextLevel": 664, 243 | "chestGranted": true, 244 | "championLevel": 1, 245 | "tokensEarned": 0, 246 | "championId": 143, 247 | "championPointsSinceLastLevel": 1136, 248 | "lastPlayTime": 1462601925000 249 | }, 250 | { 251 | "highestGrade": "A-", 252 | "championPoints": 1055, 253 | "playerId": 496402, 254 | "championPointsUntilNextLevel": 745, 255 | "chestGranted": false, 256 | "championLevel": 1, 257 | "tokensEarned": 0, 258 | "championId": 136, 259 | "championPointsSinceLastLevel": 1055, 260 | "lastPlayTime": 1462600100000 261 | }, 262 | { 263 | "highestGrade": "B", 264 | "championPoints": 1012, 265 | "playerId": 496402, 266 | "championPointsUntilNextLevel": 788, 267 | "chestGranted": false, 268 | "championLevel": 1, 269 | "tokensEarned": 0, 270 | "championId": 3, 271 | "championPointsSinceLastLevel": 1012, 272 | "lastPlayTime": 1463976232000 273 | }, 274 | { 275 | "championPoints": 942, 276 | "playerId": 496402, 277 | "championPointsUntilNextLevel": 858, 278 | "chestGranted": false, 279 | "championLevel": 1, 280 | "tokensEarned": 0, 281 | "championId": 60, 282 | "championPointsSinceLastLevel": 942, 283 | "lastPlayTime": 1444367620000 284 | }, 285 | { 286 | "highestGrade": "A+", 287 | "championPoints": 933, 288 | "playerId": 496402, 289 | "championPointsUntilNextLevel": 867, 290 | "chestGranted": false, 291 | "championLevel": 1, 292 | "tokensEarned": 0, 293 | "championId": 64, 294 | "championPointsSinceLastLevel": 933, 295 | "lastPlayTime": 1464414045000 296 | }, 297 | { 298 | "championPoints": 930, 299 | "playerId": 496402, 300 | "championPointsUntilNextLevel": 870, 301 | "chestGranted": false, 302 | "championLevel": 1, 303 | "tokensEarned": 0, 304 | "championId": 42, 305 | "championPointsSinceLastLevel": 930, 306 | "lastPlayTime": 1437273197000 307 | }, 308 | { 309 | "highestGrade": "C-", 310 | "championPoints": 353, 311 | "playerId": 496402, 312 | "championPointsUntilNextLevel": 1447, 313 | "chestGranted": false, 314 | "championLevel": 1, 315 | "tokensEarned": 0, 316 | "championId": 89, 317 | "championPointsSinceLastLevel": 353, 318 | "lastPlayTime": 1463110408000 319 | }, 320 | { 321 | "highestGrade": "S-", 322 | "championPoints": 284, 323 | "playerId": 496402, 324 | "championPointsUntilNextLevel": 1516, 325 | "chestGranted": true, 326 | "championLevel": 1, 327 | "tokensEarned": 0, 328 | "championId": 83, 329 | "championPointsSinceLastLevel": 284, 330 | "lastPlayTime": 1462686606000 331 | }, 332 | { 333 | "highestGrade": "B", 334 | "championPoints": 229, 335 | "playerId": 496402, 336 | "championPointsUntilNextLevel": 1571, 337 | "chestGranted": false, 338 | "championLevel": 1, 339 | "tokensEarned": 0, 340 | "championId": 238, 341 | "championPointsSinceLastLevel": 229, 342 | "lastPlayTime": 1462691429000 343 | }, 344 | { 345 | "highestGrade": "B", 346 | "championPoints": 219, 347 | "playerId": 496402, 348 | "championPointsUntilNextLevel": 1581, 349 | "chestGranted": false, 350 | "championLevel": 1, 351 | "tokensEarned": 0, 352 | "championId": 21, 353 | "championPointsSinceLastLevel": 219, 354 | "lastPlayTime": 1462515262000 355 | }, 356 | { 357 | "highestGrade": "A-", 358 | "championPoints": 204, 359 | "playerId": 496402, 360 | "championPointsUntilNextLevel": 1596, 361 | "chestGranted": false, 362 | "championLevel": 1, 363 | "tokensEarned": 0, 364 | "championId": 420, 365 | "championPointsSinceLastLevel": 204, 366 | "lastPlayTime": 1462166929000 367 | }, 368 | { 369 | "championPoints": 199, 370 | "playerId": 496402, 371 | "championPointsUntilNextLevel": 1601, 372 | "chestGranted": false, 373 | "championLevel": 1, 374 | "tokensEarned": 0, 375 | "championId": 133, 376 | "championPointsSinceLastLevel": 199, 377 | "lastPlayTime": 1436418634000 378 | }, 379 | { 380 | "highestGrade": "A", 381 | "championPoints": 181, 382 | "playerId": 496402, 383 | "championPointsUntilNextLevel": 1619, 384 | "chestGranted": false, 385 | "championLevel": 1, 386 | "tokensEarned": 0, 387 | "championId": 99, 388 | "championPointsSinceLastLevel": 181, 389 | "lastPlayTime": 1463978449000 390 | }, 391 | { 392 | "championPoints": 176, 393 | "playerId": 496402, 394 | "championPointsUntilNextLevel": 1624, 395 | "chestGranted": false, 396 | "championLevel": 1, 397 | "tokensEarned": 0, 398 | "championId": 76, 399 | "championPointsSinceLastLevel": 176, 400 | "lastPlayTime": 1444598939000 401 | }, 402 | { 403 | "highestGrade": "B-", 404 | "championPoints": 174, 405 | "playerId": 496402, 406 | "championPointsUntilNextLevel": 1626, 407 | "chestGranted": false, 408 | "championLevel": 1, 409 | "tokensEarned": 0, 410 | "championId": 122, 411 | "championPointsSinceLastLevel": 174, 412 | "lastPlayTime": 1462162936000 413 | }, 414 | { 415 | "championPoints": 161, 416 | "playerId": 496402, 417 | "championPointsUntilNextLevel": 1639, 418 | "chestGranted": false, 419 | "championLevel": 1, 420 | "tokensEarned": 0, 421 | "championId": 18, 422 | "championPointsSinceLastLevel": 161, 423 | "lastPlayTime": 1437161890000 424 | }, 425 | { 426 | "highestGrade": "C", 427 | "championPoints": 159, 428 | "playerId": 496402, 429 | "championPointsUntilNextLevel": 1641, 430 | "chestGranted": false, 431 | "championLevel": 1, 432 | "tokensEarned": 0, 433 | "championId": 202, 434 | "championPointsSinceLastLevel": 159, 435 | "lastPlayTime": 1462603951000 436 | }, 437 | { 438 | "championPoints": 158, 439 | "playerId": 496402, 440 | "championPointsUntilNextLevel": 1642, 441 | "chestGranted": false, 442 | "championLevel": 1, 443 | "tokensEarned": 0, 444 | "championId": 222, 445 | "championPointsSinceLastLevel": 158, 446 | "lastPlayTime": 1431475624000 447 | }, 448 | { 449 | "highestGrade": "B-", 450 | "championPoints": 124, 451 | "playerId": 496402, 452 | "championPointsUntilNextLevel": 1676, 453 | "chestGranted": false, 454 | "championLevel": 1, 455 | "tokensEarned": 0, 456 | "championId": 5, 457 | "championPointsSinceLastLevel": 124, 458 | "lastPlayTime": 1464547676000 459 | } 460 | ] -------------------------------------------------------------------------------- /spec/fixtures/v3/get-match.json: -------------------------------------------------------------------------------- 1 | {"gameId":2972702365,"platformId":"EUW1","gameCreation":1481654475333,"gameDuration":1514,"queueId":0,"mapId":11,"seasonId":8,"gameVersion":"6.24.168.1268","gameMode":"CLASSIC","gameType":"CUSTOM_GAME","teams":[{"teamId":100,"win":"Win","firstBlood":true,"firstTower":true,"firstInhibitor":true,"firstBaron":true,"firstDragon":true,"firstRiftHerald":false,"towerKills":9,"inhibitorKills":1,"baronKills":1,"dragonKills":3,"vilemawKills":0,"riftHeraldKills":0,"dominionVictoryScore":0,"bans":[{"championId":121,"pickTurn":1},{"championId":41,"pickTurn":3},{"championId":78,"pickTurn":5}]},{"teamId":200,"win":"Fail","firstBlood":false,"firstTower":false,"firstInhibitor":false,"firstBaron":false,"firstDragon":false,"firstRiftHerald":false,"towerKills":2,"inhibitorKills":0,"baronKills":0,"dragonKills":0,"vilemawKills":0,"riftHeraldKills":0,"dominionVictoryScore":0,"bans":[{"championId":7,"pickTurn":2},{"championId":134,"pickTurn":4},{"championId":55,"pickTurn":6}]}],"participants":[{"participantId":1,"teamId":100,"championId":57,"spell1Id":4,"spell2Id":12,"masteries":[{"masteryId":6114,"rank":5},{"masteryId":6122,"rank":1},{"masteryId":6134,"rank":5},{"masteryId":6143,"rank":1},{"masteryId":6211,"rank":5},{"masteryId":6223,"rank":1},{"masteryId":6231,"rank":5},{"masteryId":6241,"rank":1},{"masteryId":6251,"rank":5},{"masteryId":6262,"rank":1}],"runes":[{"runeId":5247,"rank":9},{"runeId":5290,"rank":9},{"runeId":5318,"rank":9},{"runeId":5357,"rank":3}],"highestAchievedSeasonTier":"CHALLENGER","stats":{"participantId":1,"win":true,"item0":1056,"item1":3211,"item2":1028,"item3":3068,"item4":3025,"item5":3111,"item6":3340,"kills":1,"deaths":2,"assists":11,"largestKillingSpree":0,"largestMultiKill":1,"killingSprees":0,"longestTimeSpentLiving":1079,"doubleKills":0,"tripleKills":0,"quadraKills":0,"pentaKills":0,"unrealKills":0,"totalDamageDealt":112612,"magicDamageDealt":89519,"physicalDamageDealt":23093,"trueDamageDealt":0,"largestCriticalStrike":0,"totalDamageDealtToChampions":13447,"magicDamageDealtToChampions":11117,"physicalDamageDealtToChampions":2330,"trueDamageDealtToChampions":0,"totalHeal":3602,"totalUnitsHealed":1,"damageSelfMitigated":0,"damageDealtToObjectives":0,"damageDealtToTurrets":0,"visionScore":0,"timeCCingOthers":0,"totalDamageTaken":15042,"magicalDamageTaken":6334,"physicalDamageTaken":7035,"trueDamageTaken":1672,"goldEarned":10313,"goldSpent":9360,"turretKills":2,"inhibitorKills":0,"totalMinionsKilled":192,"neutralMinionsKilled":0,"neutralMinionsKilledTeamJungle":0,"neutralMinionsKilledEnemyJungle":0,"totalTimeCrowdControlDealt":533,"champLevel":15,"visionWardsBoughtInGame":0,"sightWardsBoughtInGame":0,"wardsPlaced":9,"wardsKilled":4,"firstBloodKill":false,"firstBloodAssist":false,"firstTowerKill":true,"firstTowerAssist":false,"firstInhibitorKill":false,"firstInhibitorAssist":true,"combatPlayerScore":0,"objectivePlayerScore":0,"totalPlayerScore":0,"totalScoreRank":0},"timeline":{"participantId":1,"creepsPerMinDeltas":{"10-20":8.0,"0-10":7.300000000000001},"xpPerMinDeltas":{"10-20":511.3,"0-10":468.9},"goldPerMinDeltas":{"10-20":373.1,"0-10":340.5},"csDiffPerMinDeltas":{"10-20":0.5,"0-10":1.1000000000000005},"xpDiffPerMinDeltas":{"10-20":70.30000000000001,"0-10":75.60000000000002},"damageTakenPerMinDeltas":{"10-20":635.8,"0-10":160.89999999999998},"damageTakenDiffPerMinDeltas":{"10-20":-216.5,"0-10":-311.2},"role":"SOLO","lane":"TOP"}},{"participantId":2,"teamId":100,"championId":64,"spell1Id":4,"spell2Id":11,"masteries":[{"masteryId":6111,"rank":5},{"masteryId":6123,"rank":1},{"masteryId":6134,"rank":5},{"masteryId":6142,"rank":1},{"masteryId":6312,"rank":5},{"masteryId":6323,"rank":1},{"masteryId":6331,"rank":5},{"masteryId":6341,"rank":1},{"masteryId":6351,"rank":5},{"masteryId":6362,"rank":1}],"runes":[{"runeId":5245,"rank":9},{"runeId":5290,"rank":3},{"runeId":5295,"rank":6},{"runeId":5317,"rank":9},{"runeId":5337,"rank":3}],"highestAchievedSeasonTier":"CHALLENGER","stats":{"participantId":2,"win":true,"item0":3142,"item1":3143,"item2":3117,"item3":3211,"item4":3067,"item5":1408,"item6":3340,"kills":12,"deaths":1,"assists":10,"largestKillingSpree":11,"largestMultiKill":2,"killingSprees":1,"longestTimeSpentLiving":524,"doubleKills":1,"tripleKills":0,"quadraKills":0,"pentaKills":0,"unrealKills":0,"totalDamageDealt":155666,"magicDamageDealt":20587,"physicalDamageDealt":125239,"trueDamageDealt":9840,"largestCriticalStrike":0,"totalDamageDealtToChampions":19731,"magicDamageDealtToChampions":2673,"physicalDamageDealtToChampions":16970,"trueDamageDealtToChampions":88,"totalHeal":5771,"totalUnitsHealed":1,"damageSelfMitigated":0,"damageDealtToObjectives":0,"damageDealtToTurrets":0,"visionScore":0,"timeCCingOthers":0,"totalDamageTaken":19710,"magicalDamageTaken":5817,"physicalDamageTaken":13218,"trueDamageTaken":674,"goldEarned":12679,"goldSpent":11700,"turretKills":2,"inhibitorKills":1,"totalMinionsKilled":20,"neutralMinionsKilled":103,"neutralMinionsKilledTeamJungle":44,"neutralMinionsKilledEnemyJungle":59,"totalTimeCrowdControlDealt":744,"champLevel":14,"visionWardsBoughtInGame":3,"sightWardsBoughtInGame":0,"wardsPlaced":21,"wardsKilled":1,"firstBloodKill":true,"firstBloodAssist":false,"firstTowerKill":false,"firstTowerAssist":true,"firstInhibitorKill":true,"firstInhibitorAssist":false,"combatPlayerScore":0,"objectivePlayerScore":0,"totalPlayerScore":0,"totalScoreRank":0},"timeline":{"participantId":2,"creepsPerMinDeltas":{"10-20":1.0,"0-10":0.4},"xpPerMinDeltas":{"10-20":599.4,"0-10":344.1},"goldPerMinDeltas":{"10-20":584.0,"0-10":300.1},"csDiffPerMinDeltas":{"10-20":0.9,"0-10":0.30000000000000004},"xpDiffPerMinDeltas":{"10-20":260.29999999999995,"0-10":50.5},"damageTakenPerMinDeltas":{"10-20":807.4,"0-10":640.0},"damageTakenDiffPerMinDeltas":{"10-20":-64.5,"0-10":146.2},"role":"NONE","lane":"JUNGLE"}},{"participantId":3,"teamId":100,"championId":13,"spell1Id":4,"spell2Id":6,"masteries":[{"masteryId":6114,"rank":5},{"masteryId":6121,"rank":1},{"masteryId":6131,"rank":5},{"masteryId":6142,"rank":1},{"masteryId":6211,"rank":5},{"masteryId":6221,"rank":1},{"masteryId":6231,"rank":5},{"masteryId":6241,"rank":1},{"masteryId":6251,"rank":5},{"masteryId":6262,"rank":1}],"runes":[{"runeId":5273,"rank":9},{"runeId":5289,"rank":3},{"runeId":5296,"rank":6},{"runeId":5316,"rank":9},{"runeId":5365,"rank":3}],"highestAchievedSeasonTier":"CHALLENGER","stats":{"participantId":3,"win":true,"item0":3070,"item1":3111,"item2":0,"item3":3001,"item4":3027,"item5":0,"item6":3363,"kills":3,"deaths":1,"assists":5,"largestKillingSpree":2,"largestMultiKill":1,"killingSprees":1,"longestTimeSpentLiving":1209,"doubleKills":0,"tripleKills":0,"quadraKills":0,"pentaKills":0,"unrealKills":0,"totalDamageDealt":108566,"magicDamageDealt":96136,"physicalDamageDealt":12430,"trueDamageDealt":0,"largestCriticalStrike":699,"totalDamageDealtToChampions":8751,"magicDamageDealtToChampions":8159,"physicalDamageDealtToChampions":592,"trueDamageDealtToChampions":0,"totalHeal":1403,"totalUnitsHealed":1,"damageSelfMitigated":0,"damageDealtToObjectives":0,"damageDealtToTurrets":0,"visionScore":0,"timeCCingOthers":0,"totalDamageTaken":7212,"magicalDamageTaken":2709,"physicalDamageTaken":3306,"trueDamageTaken":1196,"goldEarned":10076,"goldSpent":8125,"turretKills":0,"inhibitorKills":0,"totalMinionsKilled":162,"neutralMinionsKilled":4,"neutralMinionsKilledTeamJungle":2,"neutralMinionsKilledEnemyJungle":2,"totalTimeCrowdControlDealt":11,"champLevel":14,"visionWardsBoughtInGame":7,"sightWardsBoughtInGame":0,"wardsPlaced":17,"wardsKilled":3,"firstBloodKill":false,"firstBloodAssist":false,"firstTowerKill":false,"firstTowerAssist":false,"firstInhibitorKill":false,"firstInhibitorAssist":true,"combatPlayerScore":0,"objectivePlayerScore":0,"totalPlayerScore":0,"totalScoreRank":0},"timeline":{"participantId":3,"creepsPerMinDeltas":{"10-20":7.5,"0-10":7.5},"xpPerMinDeltas":{"10-20":506.9,"0-10":438.29999999999995},"goldPerMinDeltas":{"10-20":426.6,"0-10":268.8},"csDiffPerMinDeltas":{"10-20":-1.8000000000000003,"0-10":-1.6999999999999997},"xpDiffPerMinDeltas":{"10-20":-2.400000000000034,"0-10":-31.0},"damageTakenPerMinDeltas":{"10-20":216.89999999999998,"0-10":171.8},"damageTakenDiffPerMinDeltas":{"10-20":-200.20000000000005,"0-10":-35.29999999999999},"role":"SOLO","lane":"MIDDLE"}},{"participantId":4,"teamId":100,"championId":202,"spell1Id":4,"spell2Id":7,"masteries":[{"masteryId":6114,"rank":5},{"masteryId":6121,"rank":1},{"masteryId":6134,"rank":5},{"masteryId":6143,"rank":1},{"masteryId":6151,"rank":5},{"masteryId":6164,"rank":1},{"masteryId":6312,"rank":5},{"masteryId":6322,"rank":1},{"masteryId":6331,"rank":5},{"masteryId":6343,"rank":1}],"runes":[{"runeId":5245,"rank":9},{"runeId":5275,"rank":4},{"runeId":5289,"rank":5},{"runeId":5317,"rank":9},{"runeId":5335,"rank":3}],"highestAchievedSeasonTier":"CHALLENGER","stats":{"participantId":4,"win":true,"item0":3508,"item1":3094,"item2":3140,"item3":3009,"item4":1037,"item5":1055,"item6":3363,"kills":8,"deaths":1,"assists":4,"largestKillingSpree":4,"largestMultiKill":2,"killingSprees":2,"longestTimeSpentLiving":873,"doubleKills":1,"tripleKills":0,"quadraKills":0,"pentaKills":0,"unrealKills":0,"totalDamageDealt":155523,"magicDamageDealt":3953,"physicalDamageDealt":151289,"trueDamageDealt":280,"largestCriticalStrike":839,"totalDamageDealtToChampions":18355,"magicDamageDealtToChampions":2753,"physicalDamageDealtToChampions":15602,"trueDamageDealtToChampions":0,"totalHeal":1067,"totalUnitsHealed":2,"damageSelfMitigated":0,"damageDealtToObjectives":0,"damageDealtToTurrets":0,"visionScore":0,"timeCCingOthers":0,"totalDamageTaken":5048,"magicalDamageTaken":1870,"physicalDamageTaken":3135,"trueDamageTaken":42,"goldEarned":12279,"goldSpent":9900,"turretKills":4,"inhibitorKills":0,"totalMinionsKilled":232,"neutralMinionsKilled":2,"neutralMinionsKilledTeamJungle":0,"neutralMinionsKilledEnemyJungle":2,"totalTimeCrowdControlDealt":56,"champLevel":13,"visionWardsBoughtInGame":1,"sightWardsBoughtInGame":0,"wardsPlaced":8,"wardsKilled":1,"firstBloodKill":false,"firstBloodAssist":false,"firstTowerKill":false,"firstTowerAssist":false,"firstInhibitorKill":false,"firstInhibitorAssist":true,"combatPlayerScore":0,"objectivePlayerScore":0,"totalPlayerScore":0,"totalScoreRank":0},"timeline":{"participantId":4,"creepsPerMinDeltas":{"10-20":11.6,"0-10":8.2},"xpPerMinDeltas":{"10-20":481.5,"0-10":327.1},"goldPerMinDeltas":{"10-20":533.2,"0-10":330.8},"csDiffPerMinDeltas":{"10-20":0.5499999999999998,"0-10":-0.5499999999999998},"xpDiffPerMinDeltas":{"10-20":12.55000000000004,"0-10":26.85000000000005},"damageTakenPerMinDeltas":{"10-20":263.3,"0-10":129.4},"damageTakenDiffPerMinDeltas":{"10-20":-251.64999999999992,"0-10":-113.49999999999999},"role":"DUO_CARRY","lane":"BOTTOM"}},{"participantId":5,"teamId":100,"championId":43,"spell1Id":3,"spell2Id":4,"masteries":[{"masteryId":6211,"rank":5},{"masteryId":6223,"rank":1},{"masteryId":6231,"rank":5},{"masteryId":6241,"rank":1},{"masteryId":6312,"rank":5},{"masteryId":6322,"rank":1},{"masteryId":6332,"rank":5},{"masteryId":6341,"rank":1},{"masteryId":6352,"rank":5},{"masteryId":6363,"rank":1}],"runes":[{"runeId":5273,"rank":9},{"runeId":5297,"rank":9},{"runeId":5315,"rank":9},{"runeId":5347,"rank":2},{"runeId":5357,"rank":1}],"highestAchievedSeasonTier":"CHALLENGER","stats":{"participantId":5,"win":true,"item0":0,"item1":3107,"item2":2301,"item3":0,"item4":3158,"item5":1033,"item6":3364,"kills":0,"deaths":1,"assists":15,"largestKillingSpree":0,"largestMultiKill":0,"killingSprees":0,"longestTimeSpentLiving":906,"doubleKills":0,"tripleKills":0,"quadraKills":0,"pentaKills":0,"unrealKills":0,"totalDamageDealt":23658,"magicDamageDealt":15121,"physicalDamageDealt":8536,"trueDamageDealt":0,"largestCriticalStrike":0,"totalDamageDealtToChampions":6211,"magicDamageDealtToChampions":5187,"physicalDamageDealtToChampions":1024,"trueDamageDealtToChampions":0,"totalHeal":1383,"totalUnitsHealed":4,"damageSelfMitigated":0,"damageDealtToObjectives":0,"damageDealtToTurrets":0,"visionScore":0,"timeCCingOthers":0,"totalDamageTaken":7019,"magicalDamageTaken":3137,"physicalDamageTaken":3031,"trueDamageTaken":849,"goldEarned":7653,"goldSpent":6600,"turretKills":0,"inhibitorKills":0,"totalMinionsKilled":11,"neutralMinionsKilled":0,"neutralMinionsKilledTeamJungle":0,"neutralMinionsKilledEnemyJungle":0,"totalTimeCrowdControlDealt":163,"champLevel":12,"visionWardsBoughtInGame":8,"sightWardsBoughtInGame":0,"wardsPlaced":29,"wardsKilled":6,"firstBloodKill":false,"firstBloodAssist":false,"firstTowerKill":false,"firstTowerAssist":false,"firstInhibitorKill":false,"firstInhibitorAssist":true,"combatPlayerScore":0,"objectivePlayerScore":0,"totalPlayerScore":0,"totalScoreRank":0},"timeline":{"participantId":5,"creepsPerMinDeltas":{"10-20":0.4,"0-10":0.30000000000000004},"xpPerMinDeltas":{"10-20":340.9,"0-10":310.6},"goldPerMinDeltas":{"10-20":292.70000000000005,"0-10":182.0},"csDiffPerMinDeltas":{"10-20":0.5499999999999998,"0-10":-0.5499999999999998},"xpDiffPerMinDeltas":{"10-20":12.55000000000004,"0-10":26.85000000000005},"damageTakenPerMinDeltas":{"10-20":262.6,"0-10":223.6},"damageTakenDiffPerMinDeltas":{"10-20":-251.64999999999992,"0-10":-113.49999999999999},"role":"DUO_SUPPORT","lane":"BOTTOM"}},{"participantId":6,"teamId":200,"championId":48,"spell1Id":4,"spell2Id":12,"masteries":[{"masteryId":6111,"rank":5},{"masteryId":6122,"rank":1},{"masteryId":6131,"rank":5},{"masteryId":6143,"rank":1},{"masteryId":6211,"rank":5},{"masteryId":6223,"rank":1},{"masteryId":6231,"rank":5},{"masteryId":6241,"rank":1},{"masteryId":6251,"rank":5},{"masteryId":6261,"rank":1}],"runes":[{"runeId":5245,"rank":6},{"runeId":5247,"rank":3},{"runeId":5296,"rank":9},{"runeId":5316,"rank":9},{"runeId":5349,"rank":3}],"highestAchievedSeasonTier":"MASTER","stats":{"participantId":6,"win":false,"item0":3748,"item1":3111,"item2":2033,"item3":3211,"item4":3123,"item5":0,"item6":3340,"kills":1,"deaths":5,"assists":1,"largestKillingSpree":0,"largestMultiKill":1,"killingSprees":0,"longestTimeSpentLiving":392,"doubleKills":0,"tripleKills":0,"quadraKills":0,"pentaKills":0,"unrealKills":0,"totalDamageDealt":83510,"magicDamageDealt":1358,"physicalDamageDealt":82151,"trueDamageDealt":0,"largestCriticalStrike":0,"totalDamageDealtToChampions":5334,"magicDamageDealtToChampions":1358,"physicalDamageDealtToChampions":3975,"trueDamageDealtToChampions":0,"totalHeal":5810,"totalUnitsHealed":1,"damageSelfMitigated":0,"damageDealtToObjectives":0,"damageDealtToTurrets":0,"visionScore":0,"timeCCingOthers":0,"totalDamageTaken":24544,"magicalDamageTaken":11479,"physicalDamageTaken":13025,"trueDamageTaken":40,"goldEarned":7407,"goldSpent":7250,"turretKills":0,"inhibitorKills":0,"totalMinionsKilled":162,"neutralMinionsKilled":0,"neutralMinionsKilledTeamJungle":0,"neutralMinionsKilledEnemyJungle":0,"totalTimeCrowdControlDealt":254,"champLevel":13,"visionWardsBoughtInGame":2,"sightWardsBoughtInGame":0,"wardsPlaced":12,"wardsKilled":2,"firstBloodKill":false,"firstBloodAssist":false,"firstTowerKill":false,"firstTowerAssist":false,"firstInhibitorKill":false,"firstInhibitorAssist":false,"combatPlayerScore":0,"objectivePlayerScore":0,"totalPlayerScore":0,"totalScoreRank":0},"timeline":{"participantId":6,"creepsPerMinDeltas":{"10-20":7.5,"0-10":6.199999999999999},"xpPerMinDeltas":{"10-20":441.0,"0-10":393.29999999999995},"goldPerMinDeltas":{"10-20":325.5,"0-10":226.8},"csDiffPerMinDeltas":{"10-20":-0.5,"0-10":-1.1000000000000005},"xpDiffPerMinDeltas":{"10-20":-70.30000000000001,"0-10":-75.60000000000002},"damageTakenPerMinDeltas":{"10-20":852.3,"0-10":472.09999999999997},"damageTakenDiffPerMinDeltas":{"10-20":216.5,"0-10":311.2},"role":"SOLO","lane":"TOP"}},{"participantId":7,"teamId":200,"championId":60,"spell1Id":4,"spell2Id":11,"masteries":[{"masteryId":6114,"rank":5},{"masteryId":6121,"rank":1},{"masteryId":6134,"rank":5},{"masteryId":6142,"rank":1},{"masteryId":6312,"rank":5},{"masteryId":6321,"rank":1},{"masteryId":6331,"rank":5},{"masteryId":6341,"rank":1},{"masteryId":6351,"rank":5},{"masteryId":6362,"rank":1}],"runes":[{"runeId":5247,"rank":9},{"runeId":5289,"rank":3},{"runeId":5296,"rank":6},{"runeId":5315,"rank":5},{"runeId":5317,"rank":4},{"runeId":5357,"rank":3}],"highestAchievedSeasonTier":"DIAMOND","stats":{"participantId":7,"win":false,"item0":1414,"item1":2031,"item2":2055,"item3":3116,"item4":3111,"item5":0,"item6":3364,"kills":3,"deaths":7,"assists":2,"largestKillingSpree":0,"largestMultiKill":1,"killingSprees":0,"longestTimeSpentLiving":579,"doubleKills":0,"tripleKills":0,"quadraKills":0,"pentaKills":0,"unrealKills":0,"totalDamageDealt":76421,"magicDamageDealt":52030,"physicalDamageDealt":19567,"trueDamageDealt":4824,"largestCriticalStrike":0,"totalDamageDealtToChampions":8046,"magicDamageDealtToChampions":6672,"physicalDamageDealtToChampions":906,"trueDamageDealtToChampions":468,"totalHeal":5598,"totalUnitsHealed":1,"damageSelfMitigated":0,"damageDealtToObjectives":0,"damageDealtToTurrets":0,"visionScore":0,"timeCCingOthers":0,"totalDamageTaken":19533,"magicalDamageTaken":4795,"physicalDamageTaken":14690,"trueDamageTaken":48,"goldEarned":7266,"goldSpent":6850,"turretKills":0,"inhibitorKills":0,"totalMinionsKilled":10,"neutralMinionsKilled":74,"neutralMinionsKilledTeamJungle":57,"neutralMinionsKilledEnemyJungle":17,"totalTimeCrowdControlDealt":182,"champLevel":12,"visionWardsBoughtInGame":5,"sightWardsBoughtInGame":0,"wardsPlaced":5,"wardsKilled":5,"firstBloodKill":false,"firstBloodAssist":false,"firstTowerKill":false,"firstTowerAssist":false,"firstInhibitorKill":false,"firstInhibitorAssist":false,"combatPlayerScore":0,"objectivePlayerScore":0,"totalPlayerScore":0,"totalScoreRank":0},"timeline":{"participantId":7,"creepsPerMinDeltas":{"10-20":0.1,"0-10":0.1},"xpPerMinDeltas":{"10-20":339.1,"0-10":293.6},"goldPerMinDeltas":{"10-20":264.4,"0-10":241.6},"csDiffPerMinDeltas":{"10-20":-0.9,"0-10":-0.30000000000000004},"xpDiffPerMinDeltas":{"10-20":-260.29999999999995,"0-10":-50.5},"damageTakenPerMinDeltas":{"10-20":871.9,"0-10":493.79999999999995},"damageTakenDiffPerMinDeltas":{"10-20":64.5,"0-10":-146.2},"role":"NONE","lane":"JUNGLE"}},{"participantId":8,"teamId":200,"championId":103,"spell1Id":4,"spell2Id":14,"masteries":[{"masteryId":6114,"rank":5},{"masteryId":6122,"rank":1},{"masteryId":6131,"rank":1},{"masteryId":6134,"rank":4},{"masteryId":6142,"rank":1},{"masteryId":6312,"rank":5},{"masteryId":6322,"rank":1},{"masteryId":6331,"rank":4},{"masteryId":6332,"rank":1},{"masteryId":6343,"rank":1},{"masteryId":6351,"rank":5},{"masteryId":6362,"rank":1}],"runes":[{"runeId":5273,"rank":9},{"runeId":5296,"rank":6},{"runeId":5298,"rank":3},{"runeId":5316,"rank":9},{"runeId":5357,"rank":3}],"highestAchievedSeasonTier":"MASTER","stats":{"participantId":8,"win":false,"item0":1056,"item1":2031,"item2":3020,"item3":3165,"item4":3001,"item5":1026,"item6":3340,"kills":2,"deaths":1,"assists":2,"largestKillingSpree":2,"largestMultiKill":1,"killingSprees":1,"longestTimeSpentLiving":782,"doubleKills":0,"tripleKills":0,"quadraKills":0,"pentaKills":0,"unrealKills":0,"totalDamageDealt":108759,"magicDamageDealt":59980,"physicalDamageDealt":15251,"trueDamageDealt":33527,"largestCriticalStrike":0,"totalDamageDealtToChampions":11463,"magicDamageDealtToChampions":7595,"physicalDamageDealtToChampions":602,"trueDamageDealtToChampions":3265,"totalHeal":2577,"totalUnitsHealed":1,"damageSelfMitigated":0,"damageDealtToObjectives":0,"damageDealtToTurrets":0,"visionScore":0,"timeCCingOthers":0,"totalDamageTaken":8700,"magicalDamageTaken":4265,"physicalDamageTaken":4435,"trueDamageTaken":0,"goldEarned":9156,"goldSpent":8975,"turretKills":0,"inhibitorKills":0,"totalMinionsKilled":226,"neutralMinionsKilled":2,"neutralMinionsKilledTeamJungle":1,"neutralMinionsKilledEnemyJungle":1,"totalTimeCrowdControlDealt":28,"champLevel":14,"visionWardsBoughtInGame":3,"sightWardsBoughtInGame":0,"wardsPlaced":10,"wardsKilled":2,"firstBloodKill":false,"firstBloodAssist":false,"firstTowerKill":false,"firstTowerAssist":false,"firstInhibitorKill":false,"firstInhibitorAssist":false,"combatPlayerScore":0,"objectivePlayerScore":0,"totalPlayerScore":0,"totalScoreRank":0},"timeline":{"participantId":8,"creepsPerMinDeltas":{"10-20":9.3,"0-10":9.2},"xpPerMinDeltas":{"10-20":509.3,"0-10":469.29999999999995},"goldPerMinDeltas":{"10-20":374.20000000000005,"0-10":284.3},"csDiffPerMinDeltas":{"10-20":1.8000000000000003,"0-10":1.6999999999999997},"xpDiffPerMinDeltas":{"10-20":2.400000000000034,"0-10":31.0},"damageTakenPerMinDeltas":{"10-20":417.1,"0-10":207.1},"damageTakenDiffPerMinDeltas":{"10-20":200.20000000000005,"0-10":35.29999999999999},"role":"SOLO","lane":"MIDDLE"}},{"participantId":9,"teamId":200,"championId":51,"spell1Id":7,"spell2Id":4,"masteries":[{"masteryId":6111,"rank":5},{"masteryId":6121,"rank":1},{"masteryId":6131,"rank":5},{"masteryId":6141,"rank":1},{"masteryId":6151,"rank":5},{"masteryId":6161,"rank":1},{"masteryId":6312,"rank":5},{"masteryId":6322,"rank":1},{"masteryId":6331,"rank":5},{"masteryId":6343,"rank":1}],"runes":[{"runeId":5245,"rank":9},{"runeId":5289,"rank":9},{"runeId":5317,"rank":9},{"runeId":5337,"rank":3}],"highestAchievedSeasonTier":"DIAMOND","stats":{"participantId":9,"win":false,"item0":1055,"item1":3006,"item2":3085,"item3":3031,"item4":2015,"item5":1042,"item6":3363,"kills":0,"deaths":6,"assists":3,"largestKillingSpree":0,"largestMultiKill":0,"killingSprees":0,"longestTimeSpentLiving":525,"doubleKills":0,"tripleKills":0,"quadraKills":0,"pentaKills":0,"unrealKills":0,"totalDamageDealt":124947,"magicDamageDealt":413,"physicalDamageDealt":123000,"trueDamageDealt":1534,"largestCriticalStrike":1015,"totalDamageDealtToChampions":7726,"magicDamageDealtToChampions":191,"physicalDamageDealtToChampions":6833,"trueDamageDealtToChampions":702,"totalHeal":971,"totalUnitsHealed":2,"damageSelfMitigated":0,"damageDealtToObjectives":0,"damageDealtToTurrets":0,"visionScore":0,"timeCCingOthers":0,"totalDamageTaken":14459,"magicalDamageTaken":3999,"physicalDamageTaken":10460,"trueDamageTaken":0,"goldEarned":9030,"goldSpent":8975,"turretKills":1,"inhibitorKills":0,"totalMinionsKilled":206,"neutralMinionsKilled":19,"neutralMinionsKilledTeamJungle":6,"neutralMinionsKilledEnemyJungle":13,"totalTimeCrowdControlDealt":152,"champLevel":12,"visionWardsBoughtInGame":1,"sightWardsBoughtInGame":0,"wardsPlaced":8,"wardsKilled":4,"firstBloodKill":false,"firstBloodAssist":false,"firstTowerKill":false,"firstTowerAssist":false,"firstInhibitorKill":false,"firstInhibitorAssist":false,"combatPlayerScore":0,"objectivePlayerScore":0,"totalPlayerScore":0,"totalScoreRank":0},"timeline":{"participantId":9,"creepsPerMinDeltas":{"10-20":9.3,"0-10":8.5},"xpPerMinDeltas":{"10-20":483.5,"0-10":311.29999999999995},"goldPerMinDeltas":{"10-20":403.4,"0-10":273.8},"csDiffPerMinDeltas":{"10-20":-0.5499999999999998,"0-10":0.5499999999999998},"xpDiffPerMinDeltas":{"10-20":-12.55000000000004,"0-10":-26.85000000000005},"damageTakenPerMinDeltas":{"10-20":610.6,"0-10":280.4},"damageTakenDiffPerMinDeltas":{"10-20":251.64999999999992,"0-10":113.49999999999999},"role":"DUO_CARRY","lane":"BOTTOM"}},{"participantId":10,"teamId":200,"championId":412,"spell1Id":4,"spell2Id":3,"masteries":[{"masteryId":6211,"rank":5},{"masteryId":6221,"rank":1},{"masteryId":6231,"rank":5},{"masteryId":6241,"rank":1},{"masteryId":6251,"rank":5},{"masteryId":6262,"rank":1},{"masteryId":6312,"rank":5},{"masteryId":6322,"rank":1},{"masteryId":6332,"rank":5},{"masteryId":6342,"rank":1}],"runes":[{"runeId":5245,"rank":9},{"runeId":5290,"rank":3},{"runeId":5295,"rank":6},{"runeId":5315,"rank":5},{"runeId":5317,"rank":4},{"runeId":5347,"rank":2},{"runeId":5351,"rank":1}],"highestAchievedSeasonTier":"MASTER","stats":{"participantId":10,"win":false,"item0":2045,"item1":2055,"item2":3801,"item3":3302,"item4":3117,"item5":3114,"item6":3364,"kills":0,"deaths":5,"assists":4,"largestKillingSpree":0,"largestMultiKill":0,"killingSprees":0,"longestTimeSpentLiving":637,"doubleKills":0,"tripleKills":0,"quadraKills":0,"pentaKills":0,"unrealKills":0,"totalDamageDealt":14307,"magicDamageDealt":9264,"physicalDamageDealt":5042,"trueDamageDealt":0,"largestCriticalStrike":0,"totalDamageDealtToChampions":3073,"magicDamageDealtToChampions":2625,"physicalDamageDealtToChampions":447,"trueDamageDealtToChampions":0,"totalHeal":809,"totalUnitsHealed":3,"damageSelfMitigated":0,"damageDealtToObjectives":0,"damageDealtToTurrets":0,"visionScore":0,"timeCCingOthers":0,"totalDamageTaken":16156,"magicalDamageTaken":6480,"physicalDamageTaken":9675,"trueDamageTaken":0,"goldEarned":5577,"goldSpent":5300,"turretKills":0,"inhibitorKills":0,"totalMinionsKilled":31,"neutralMinionsKilled":0,"neutralMinionsKilledTeamJungle":0,"neutralMinionsKilledEnemyJungle":0,"totalTimeCrowdControlDealt":89,"champLevel":10,"visionWardsBoughtInGame":8,"sightWardsBoughtInGame":0,"wardsPlaced":26,"wardsKilled":6,"firstBloodKill":false,"firstBloodAssist":false,"firstTowerKill":false,"firstTowerAssist":false,"firstInhibitorKill":false,"firstInhibitorAssist":false,"combatPlayerScore":0,"objectivePlayerScore":0,"totalPlayerScore":0,"totalScoreRank":0},"timeline":{"participantId":10,"creepsPerMinDeltas":{"10-20":1.6,"0-10":1.1},"xpPerMinDeltas":{"10-20":313.8,"0-10":272.7},"goldPerMinDeltas":{"10-20":237.9,"0-10":153.10000000000002},"csDiffPerMinDeltas":{"10-20":-0.5499999999999998,"0-10":0.5499999999999998},"xpDiffPerMinDeltas":{"10-20":-12.55000000000004,"0-10":-26.85000000000005},"damageTakenPerMinDeltas":{"10-20":418.6,"0-10":299.6},"damageTakenDiffPerMinDeltas":{"10-20":251.64999999999992,"0-10":113.49999999999999},"role":"DUO_SUPPORT","lane":"BOTTOM"}}],"participantIdentities":[{"participantId":1},{"participantId":2},{"participantId":3},{"participantId":4},{"participantId":5},{"participantId":6},{"participantId":7},{"participantId":8},{"participantId":9},{"participantId":10}]} -------------------------------------------------------------------------------- /spec/fixtures/v3/get-match-with-tc.json: -------------------------------------------------------------------------------- 1 | {"gameId":2972702365,"platformId":"EUW1","gameCreation":1481654475333,"gameDuration":1514,"queueId":0,"mapId":11,"seasonId":8,"gameVersion":"6.24.168.1268","gameMode":"CLASSIC","gameType":"CUSTOM_GAME","teams":[{"teamId":100,"win":"Win","firstBlood":true,"firstTower":true,"firstInhibitor":true,"firstBaron":true,"firstDragon":true,"firstRiftHerald":false,"towerKills":9,"inhibitorKills":1,"baronKills":1,"dragonKills":3,"vilemawKills":0,"riftHeraldKills":0,"dominionVictoryScore":0,"bans":[{"championId":121,"pickTurn":1},{"championId":41,"pickTurn":3},{"championId":78,"pickTurn":5}]},{"teamId":200,"win":"Fail","firstBlood":false,"firstTower":false,"firstInhibitor":false,"firstBaron":false,"firstDragon":false,"firstRiftHerald":false,"towerKills":2,"inhibitorKills":0,"baronKills":0,"dragonKills":0,"vilemawKills":0,"riftHeraldKills":0,"dominionVictoryScore":0,"bans":[{"championId":7,"pickTurn":2},{"championId":134,"pickTurn":4},{"championId":55,"pickTurn":6}]}],"participants":[{"participantId":1,"teamId":100,"championId":57,"spell1Id":4,"spell2Id":12,"masteries":[{"masteryId":6114,"rank":5},{"masteryId":6122,"rank":1},{"masteryId":6134,"rank":5},{"masteryId":6143,"rank":1},{"masteryId":6211,"rank":5},{"masteryId":6223,"rank":1},{"masteryId":6231,"rank":5},{"masteryId":6241,"rank":1},{"masteryId":6251,"rank":5},{"masteryId":6262,"rank":1}],"runes":[{"runeId":5247,"rank":9},{"runeId":5290,"rank":9},{"runeId":5318,"rank":9},{"runeId":5357,"rank":3}],"highestAchievedSeasonTier":"CHALLENGER","stats":{"participantId":1,"win":true,"item0":1056,"item1":3211,"item2":1028,"item3":3068,"item4":3025,"item5":3111,"item6":3340,"kills":1,"deaths":2,"assists":11,"largestKillingSpree":0,"largestMultiKill":1,"killingSprees":0,"longestTimeSpentLiving":1079,"doubleKills":0,"tripleKills":0,"quadraKills":0,"pentaKills":0,"unrealKills":0,"totalDamageDealt":112612,"magicDamageDealt":89519,"physicalDamageDealt":23093,"trueDamageDealt":0,"largestCriticalStrike":0,"totalDamageDealtToChampions":13447,"magicDamageDealtToChampions":11117,"physicalDamageDealtToChampions":2330,"trueDamageDealtToChampions":0,"totalHeal":3602,"totalUnitsHealed":1,"damageSelfMitigated":0,"damageDealtToObjectives":0,"damageDealtToTurrets":0,"visionScore":0,"timeCCingOthers":0,"totalDamageTaken":15042,"magicalDamageTaken":6334,"physicalDamageTaken":7035,"trueDamageTaken":1672,"goldEarned":10313,"goldSpent":9360,"turretKills":2,"inhibitorKills":0,"totalMinionsKilled":192,"neutralMinionsKilled":0,"neutralMinionsKilledTeamJungle":0,"neutralMinionsKilledEnemyJungle":0,"totalTimeCrowdControlDealt":533,"champLevel":15,"visionWardsBoughtInGame":0,"sightWardsBoughtInGame":0,"wardsPlaced":9,"wardsKilled":4,"firstBloodKill":false,"firstBloodAssist":false,"firstTowerKill":true,"firstTowerAssist":false,"firstInhibitorKill":false,"firstInhibitorAssist":true,"combatPlayerScore":0,"objectivePlayerScore":0,"totalPlayerScore":0,"totalScoreRank":0},"timeline":{"participantId":1,"creepsPerMinDeltas":{"10-20":8.0,"0-10":7.300000000000001},"xpPerMinDeltas":{"10-20":511.3,"0-10":468.9},"goldPerMinDeltas":{"10-20":373.1,"0-10":340.5},"csDiffPerMinDeltas":{"10-20":0.5,"0-10":1.1000000000000005},"xpDiffPerMinDeltas":{"10-20":70.30000000000001,"0-10":75.60000000000002},"damageTakenPerMinDeltas":{"10-20":635.8,"0-10":160.89999999999998},"damageTakenDiffPerMinDeltas":{"10-20":-216.5,"0-10":-311.2},"role":"SOLO","lane":"TOP"}},{"participantId":2,"teamId":100,"championId":64,"spell1Id":4,"spell2Id":11,"masteries":[{"masteryId":6111,"rank":5},{"masteryId":6123,"rank":1},{"masteryId":6134,"rank":5},{"masteryId":6142,"rank":1},{"masteryId":6312,"rank":5},{"masteryId":6323,"rank":1},{"masteryId":6331,"rank":5},{"masteryId":6341,"rank":1},{"masteryId":6351,"rank":5},{"masteryId":6362,"rank":1}],"runes":[{"runeId":5245,"rank":9},{"runeId":5290,"rank":3},{"runeId":5295,"rank":6},{"runeId":5317,"rank":9},{"runeId":5337,"rank":3}],"highestAchievedSeasonTier":"CHALLENGER","stats":{"participantId":2,"win":true,"item0":3142,"item1":3143,"item2":3117,"item3":3211,"item4":3067,"item5":1408,"item6":3340,"kills":12,"deaths":1,"assists":10,"largestKillingSpree":11,"largestMultiKill":2,"killingSprees":1,"longestTimeSpentLiving":524,"doubleKills":1,"tripleKills":0,"quadraKills":0,"pentaKills":0,"unrealKills":0,"totalDamageDealt":155666,"magicDamageDealt":20587,"physicalDamageDealt":125239,"trueDamageDealt":9840,"largestCriticalStrike":0,"totalDamageDealtToChampions":19731,"magicDamageDealtToChampions":2673,"physicalDamageDealtToChampions":16970,"trueDamageDealtToChampions":88,"totalHeal":5771,"totalUnitsHealed":1,"damageSelfMitigated":0,"damageDealtToObjectives":0,"damageDealtToTurrets":0,"visionScore":0,"timeCCingOthers":0,"totalDamageTaken":19710,"magicalDamageTaken":5817,"physicalDamageTaken":13218,"trueDamageTaken":674,"goldEarned":12679,"goldSpent":11700,"turretKills":2,"inhibitorKills":1,"totalMinionsKilled":20,"neutralMinionsKilled":103,"neutralMinionsKilledTeamJungle":44,"neutralMinionsKilledEnemyJungle":59,"totalTimeCrowdControlDealt":744,"champLevel":14,"visionWardsBoughtInGame":3,"sightWardsBoughtInGame":0,"wardsPlaced":21,"wardsKilled":1,"firstBloodKill":true,"firstBloodAssist":false,"firstTowerKill":false,"firstTowerAssist":true,"firstInhibitorKill":true,"firstInhibitorAssist":false,"combatPlayerScore":0,"objectivePlayerScore":0,"totalPlayerScore":0,"totalScoreRank":0},"timeline":{"participantId":2,"creepsPerMinDeltas":{"10-20":1.0,"0-10":0.4},"xpPerMinDeltas":{"10-20":599.4,"0-10":344.1},"goldPerMinDeltas":{"10-20":584.0,"0-10":300.1},"csDiffPerMinDeltas":{"10-20":0.9,"0-10":0.30000000000000004},"xpDiffPerMinDeltas":{"10-20":260.29999999999995,"0-10":50.5},"damageTakenPerMinDeltas":{"10-20":807.4,"0-10":640.0},"damageTakenDiffPerMinDeltas":{"10-20":-64.5,"0-10":146.2},"role":"NONE","lane":"JUNGLE"}},{"participantId":3,"teamId":100,"championId":13,"spell1Id":4,"spell2Id":6,"masteries":[{"masteryId":6114,"rank":5},{"masteryId":6121,"rank":1},{"masteryId":6131,"rank":5},{"masteryId":6142,"rank":1},{"masteryId":6211,"rank":5},{"masteryId":6221,"rank":1},{"masteryId":6231,"rank":5},{"masteryId":6241,"rank":1},{"masteryId":6251,"rank":5},{"masteryId":6262,"rank":1}],"runes":[{"runeId":5273,"rank":9},{"runeId":5289,"rank":3},{"runeId":5296,"rank":6},{"runeId":5316,"rank":9},{"runeId":5365,"rank":3}],"highestAchievedSeasonTier":"CHALLENGER","stats":{"participantId":3,"win":true,"item0":3070,"item1":3111,"item2":0,"item3":3001,"item4":3027,"item5":0,"item6":3363,"kills":3,"deaths":1,"assists":5,"largestKillingSpree":2,"largestMultiKill":1,"killingSprees":1,"longestTimeSpentLiving":1209,"doubleKills":0,"tripleKills":0,"quadraKills":0,"pentaKills":0,"unrealKills":0,"totalDamageDealt":108566,"magicDamageDealt":96136,"physicalDamageDealt":12430,"trueDamageDealt":0,"largestCriticalStrike":699,"totalDamageDealtToChampions":8751,"magicDamageDealtToChampions":8159,"physicalDamageDealtToChampions":592,"trueDamageDealtToChampions":0,"totalHeal":1403,"totalUnitsHealed":1,"damageSelfMitigated":0,"damageDealtToObjectives":0,"damageDealtToTurrets":0,"visionScore":0,"timeCCingOthers":0,"totalDamageTaken":7212,"magicalDamageTaken":2709,"physicalDamageTaken":3306,"trueDamageTaken":1196,"goldEarned":10076,"goldSpent":8125,"turretKills":0,"inhibitorKills":0,"totalMinionsKilled":162,"neutralMinionsKilled":4,"neutralMinionsKilledTeamJungle":2,"neutralMinionsKilledEnemyJungle":2,"totalTimeCrowdControlDealt":11,"champLevel":14,"visionWardsBoughtInGame":7,"sightWardsBoughtInGame":0,"wardsPlaced":17,"wardsKilled":3,"firstBloodKill":false,"firstBloodAssist":false,"firstTowerKill":false,"firstTowerAssist":false,"firstInhibitorKill":false,"firstInhibitorAssist":true,"combatPlayerScore":0,"objectivePlayerScore":0,"totalPlayerScore":0,"totalScoreRank":0},"timeline":{"participantId":3,"creepsPerMinDeltas":{"10-20":7.5,"0-10":7.5},"xpPerMinDeltas":{"10-20":506.9,"0-10":438.29999999999995},"goldPerMinDeltas":{"10-20":426.6,"0-10":268.8},"csDiffPerMinDeltas":{"10-20":-1.8000000000000003,"0-10":-1.6999999999999997},"xpDiffPerMinDeltas":{"10-20":-2.400000000000034,"0-10":-31.0},"damageTakenPerMinDeltas":{"10-20":216.89999999999998,"0-10":171.8},"damageTakenDiffPerMinDeltas":{"10-20":-200.20000000000005,"0-10":-35.29999999999999},"role":"SOLO","lane":"MIDDLE"}},{"participantId":4,"teamId":100,"championId":202,"spell1Id":4,"spell2Id":7,"masteries":[{"masteryId":6114,"rank":5},{"masteryId":6121,"rank":1},{"masteryId":6134,"rank":5},{"masteryId":6143,"rank":1},{"masteryId":6151,"rank":5},{"masteryId":6164,"rank":1},{"masteryId":6312,"rank":5},{"masteryId":6322,"rank":1},{"masteryId":6331,"rank":5},{"masteryId":6343,"rank":1}],"runes":[{"runeId":5245,"rank":9},{"runeId":5275,"rank":4},{"runeId":5289,"rank":5},{"runeId":5317,"rank":9},{"runeId":5335,"rank":3}],"highestAchievedSeasonTier":"CHALLENGER","stats":{"participantId":4,"win":true,"item0":3508,"item1":3094,"item2":3140,"item3":3009,"item4":1037,"item5":1055,"item6":3363,"kills":8,"deaths":1,"assists":4,"largestKillingSpree":4,"largestMultiKill":2,"killingSprees":2,"longestTimeSpentLiving":873,"doubleKills":1,"tripleKills":0,"quadraKills":0,"pentaKills":0,"unrealKills":0,"totalDamageDealt":155523,"magicDamageDealt":3953,"physicalDamageDealt":151289,"trueDamageDealt":280,"largestCriticalStrike":839,"totalDamageDealtToChampions":18355,"magicDamageDealtToChampions":2753,"physicalDamageDealtToChampions":15602,"trueDamageDealtToChampions":0,"totalHeal":1067,"totalUnitsHealed":2,"damageSelfMitigated":0,"damageDealtToObjectives":0,"damageDealtToTurrets":0,"visionScore":0,"timeCCingOthers":0,"totalDamageTaken":5048,"magicalDamageTaken":1870,"physicalDamageTaken":3135,"trueDamageTaken":42,"goldEarned":12279,"goldSpent":9900,"turretKills":4,"inhibitorKills":0,"totalMinionsKilled":232,"neutralMinionsKilled":2,"neutralMinionsKilledTeamJungle":0,"neutralMinionsKilledEnemyJungle":2,"totalTimeCrowdControlDealt":56,"champLevel":13,"visionWardsBoughtInGame":1,"sightWardsBoughtInGame":0,"wardsPlaced":8,"wardsKilled":1,"firstBloodKill":false,"firstBloodAssist":false,"firstTowerKill":false,"firstTowerAssist":false,"firstInhibitorKill":false,"firstInhibitorAssist":true,"combatPlayerScore":0,"objectivePlayerScore":0,"totalPlayerScore":0,"totalScoreRank":0},"timeline":{"participantId":4,"creepsPerMinDeltas":{"10-20":11.6,"0-10":8.2},"xpPerMinDeltas":{"10-20":481.5,"0-10":327.1},"goldPerMinDeltas":{"10-20":533.2,"0-10":330.8},"csDiffPerMinDeltas":{"10-20":0.5499999999999998,"0-10":-0.5499999999999998},"xpDiffPerMinDeltas":{"10-20":12.55000000000004,"0-10":26.85000000000005},"damageTakenPerMinDeltas":{"10-20":263.3,"0-10":129.4},"damageTakenDiffPerMinDeltas":{"10-20":-251.64999999999992,"0-10":-113.49999999999999},"role":"DUO_CARRY","lane":"BOTTOM"}},{"participantId":5,"teamId":100,"championId":43,"spell1Id":3,"spell2Id":4,"masteries":[{"masteryId":6211,"rank":5},{"masteryId":6223,"rank":1},{"masteryId":6231,"rank":5},{"masteryId":6241,"rank":1},{"masteryId":6312,"rank":5},{"masteryId":6322,"rank":1},{"masteryId":6332,"rank":5},{"masteryId":6341,"rank":1},{"masteryId":6352,"rank":5},{"masteryId":6363,"rank":1}],"runes":[{"runeId":5273,"rank":9},{"runeId":5297,"rank":9},{"runeId":5315,"rank":9},{"runeId":5347,"rank":2},{"runeId":5357,"rank":1}],"highestAchievedSeasonTier":"CHALLENGER","stats":{"participantId":5,"win":true,"item0":0,"item1":3107,"item2":2301,"item3":0,"item4":3158,"item5":1033,"item6":3364,"kills":0,"deaths":1,"assists":15,"largestKillingSpree":0,"largestMultiKill":0,"killingSprees":0,"longestTimeSpentLiving":906,"doubleKills":0,"tripleKills":0,"quadraKills":0,"pentaKills":0,"unrealKills":0,"totalDamageDealt":23658,"magicDamageDealt":15121,"physicalDamageDealt":8536,"trueDamageDealt":0,"largestCriticalStrike":0,"totalDamageDealtToChampions":6211,"magicDamageDealtToChampions":5187,"physicalDamageDealtToChampions":1024,"trueDamageDealtToChampions":0,"totalHeal":1383,"totalUnitsHealed":4,"damageSelfMitigated":0,"damageDealtToObjectives":0,"damageDealtToTurrets":0,"visionScore":0,"timeCCingOthers":0,"totalDamageTaken":7019,"magicalDamageTaken":3137,"physicalDamageTaken":3031,"trueDamageTaken":849,"goldEarned":7653,"goldSpent":6600,"turretKills":0,"inhibitorKills":0,"totalMinionsKilled":11,"neutralMinionsKilled":0,"neutralMinionsKilledTeamJungle":0,"neutralMinionsKilledEnemyJungle":0,"totalTimeCrowdControlDealt":163,"champLevel":12,"visionWardsBoughtInGame":8,"sightWardsBoughtInGame":0,"wardsPlaced":29,"wardsKilled":6,"firstBloodKill":false,"firstBloodAssist":false,"firstTowerKill":false,"firstTowerAssist":false,"firstInhibitorKill":false,"firstInhibitorAssist":true,"combatPlayerScore":0,"objectivePlayerScore":0,"totalPlayerScore":0,"totalScoreRank":0},"timeline":{"participantId":5,"creepsPerMinDeltas":{"10-20":0.4,"0-10":0.30000000000000004},"xpPerMinDeltas":{"10-20":340.9,"0-10":310.6},"goldPerMinDeltas":{"10-20":292.70000000000005,"0-10":182.0},"csDiffPerMinDeltas":{"10-20":0.5499999999999998,"0-10":-0.5499999999999998},"xpDiffPerMinDeltas":{"10-20":12.55000000000004,"0-10":26.85000000000005},"damageTakenPerMinDeltas":{"10-20":262.6,"0-10":223.6},"damageTakenDiffPerMinDeltas":{"10-20":-251.64999999999992,"0-10":-113.49999999999999},"role":"DUO_SUPPORT","lane":"BOTTOM"}},{"participantId":6,"teamId":200,"championId":48,"spell1Id":4,"spell2Id":12,"masteries":[{"masteryId":6111,"rank":5},{"masteryId":6122,"rank":1},{"masteryId":6131,"rank":5},{"masteryId":6143,"rank":1},{"masteryId":6211,"rank":5},{"masteryId":6223,"rank":1},{"masteryId":6231,"rank":5},{"masteryId":6241,"rank":1},{"masteryId":6251,"rank":5},{"masteryId":6261,"rank":1}],"runes":[{"runeId":5245,"rank":6},{"runeId":5247,"rank":3},{"runeId":5296,"rank":9},{"runeId":5316,"rank":9},{"runeId":5349,"rank":3}],"highestAchievedSeasonTier":"MASTER","stats":{"participantId":6,"win":false,"item0":3748,"item1":3111,"item2":2033,"item3":3211,"item4":3123,"item5":0,"item6":3340,"kills":1,"deaths":5,"assists":1,"largestKillingSpree":0,"largestMultiKill":1,"killingSprees":0,"longestTimeSpentLiving":392,"doubleKills":0,"tripleKills":0,"quadraKills":0,"pentaKills":0,"unrealKills":0,"totalDamageDealt":83510,"magicDamageDealt":1358,"physicalDamageDealt":82151,"trueDamageDealt":0,"largestCriticalStrike":0,"totalDamageDealtToChampions":5334,"magicDamageDealtToChampions":1358,"physicalDamageDealtToChampions":3975,"trueDamageDealtToChampions":0,"totalHeal":5810,"totalUnitsHealed":1,"damageSelfMitigated":0,"damageDealtToObjectives":0,"damageDealtToTurrets":0,"visionScore":0,"timeCCingOthers":0,"totalDamageTaken":24544,"magicalDamageTaken":11479,"physicalDamageTaken":13025,"trueDamageTaken":40,"goldEarned":7407,"goldSpent":7250,"turretKills":0,"inhibitorKills":0,"totalMinionsKilled":162,"neutralMinionsKilled":0,"neutralMinionsKilledTeamJungle":0,"neutralMinionsKilledEnemyJungle":0,"totalTimeCrowdControlDealt":254,"champLevel":13,"visionWardsBoughtInGame":2,"sightWardsBoughtInGame":0,"wardsPlaced":12,"wardsKilled":2,"firstBloodKill":false,"firstBloodAssist":false,"firstTowerKill":false,"firstTowerAssist":false,"firstInhibitorKill":false,"firstInhibitorAssist":false,"combatPlayerScore":0,"objectivePlayerScore":0,"totalPlayerScore":0,"totalScoreRank":0},"timeline":{"participantId":6,"creepsPerMinDeltas":{"10-20":7.5,"0-10":6.199999999999999},"xpPerMinDeltas":{"10-20":441.0,"0-10":393.29999999999995},"goldPerMinDeltas":{"10-20":325.5,"0-10":226.8},"csDiffPerMinDeltas":{"10-20":-0.5,"0-10":-1.1000000000000005},"xpDiffPerMinDeltas":{"10-20":-70.30000000000001,"0-10":-75.60000000000002},"damageTakenPerMinDeltas":{"10-20":852.3,"0-10":472.09999999999997},"damageTakenDiffPerMinDeltas":{"10-20":216.5,"0-10":311.2},"role":"SOLO","lane":"TOP"}},{"participantId":7,"teamId":200,"championId":60,"spell1Id":4,"spell2Id":11,"masteries":[{"masteryId":6114,"rank":5},{"masteryId":6121,"rank":1},{"masteryId":6134,"rank":5},{"masteryId":6142,"rank":1},{"masteryId":6312,"rank":5},{"masteryId":6321,"rank":1},{"masteryId":6331,"rank":5},{"masteryId":6341,"rank":1},{"masteryId":6351,"rank":5},{"masteryId":6362,"rank":1}],"runes":[{"runeId":5247,"rank":9},{"runeId":5289,"rank":3},{"runeId":5296,"rank":6},{"runeId":5315,"rank":5},{"runeId":5317,"rank":4},{"runeId":5357,"rank":3}],"highestAchievedSeasonTier":"DIAMOND","stats":{"participantId":7,"win":false,"item0":1414,"item1":2031,"item2":2055,"item3":3116,"item4":3111,"item5":0,"item6":3364,"kills":3,"deaths":7,"assists":2,"largestKillingSpree":0,"largestMultiKill":1,"killingSprees":0,"longestTimeSpentLiving":579,"doubleKills":0,"tripleKills":0,"quadraKills":0,"pentaKills":0,"unrealKills":0,"totalDamageDealt":76421,"magicDamageDealt":52030,"physicalDamageDealt":19567,"trueDamageDealt":4824,"largestCriticalStrike":0,"totalDamageDealtToChampions":8046,"magicDamageDealtToChampions":6672,"physicalDamageDealtToChampions":906,"trueDamageDealtToChampions":468,"totalHeal":5598,"totalUnitsHealed":1,"damageSelfMitigated":0,"damageDealtToObjectives":0,"damageDealtToTurrets":0,"visionScore":0,"timeCCingOthers":0,"totalDamageTaken":19533,"magicalDamageTaken":4795,"physicalDamageTaken":14690,"trueDamageTaken":48,"goldEarned":7266,"goldSpent":6850,"turretKills":0,"inhibitorKills":0,"totalMinionsKilled":10,"neutralMinionsKilled":74,"neutralMinionsKilledTeamJungle":57,"neutralMinionsKilledEnemyJungle":17,"totalTimeCrowdControlDealt":182,"champLevel":12,"visionWardsBoughtInGame":5,"sightWardsBoughtInGame":0,"wardsPlaced":5,"wardsKilled":5,"firstBloodKill":false,"firstBloodAssist":false,"firstTowerKill":false,"firstTowerAssist":false,"firstInhibitorKill":false,"firstInhibitorAssist":false,"combatPlayerScore":0,"objectivePlayerScore":0,"totalPlayerScore":0,"totalScoreRank":0},"timeline":{"participantId":7,"creepsPerMinDeltas":{"10-20":0.1,"0-10":0.1},"xpPerMinDeltas":{"10-20":339.1,"0-10":293.6},"goldPerMinDeltas":{"10-20":264.4,"0-10":241.6},"csDiffPerMinDeltas":{"10-20":-0.9,"0-10":-0.30000000000000004},"xpDiffPerMinDeltas":{"10-20":-260.29999999999995,"0-10":-50.5},"damageTakenPerMinDeltas":{"10-20":871.9,"0-10":493.79999999999995},"damageTakenDiffPerMinDeltas":{"10-20":64.5,"0-10":-146.2},"role":"NONE","lane":"JUNGLE"}},{"participantId":8,"teamId":200,"championId":103,"spell1Id":4,"spell2Id":14,"masteries":[{"masteryId":6114,"rank":5},{"masteryId":6122,"rank":1},{"masteryId":6131,"rank":1},{"masteryId":6134,"rank":4},{"masteryId":6142,"rank":1},{"masteryId":6312,"rank":5},{"masteryId":6322,"rank":1},{"masteryId":6331,"rank":4},{"masteryId":6332,"rank":1},{"masteryId":6343,"rank":1},{"masteryId":6351,"rank":5},{"masteryId":6362,"rank":1}],"runes":[{"runeId":5273,"rank":9},{"runeId":5296,"rank":6},{"runeId":5298,"rank":3},{"runeId":5316,"rank":9},{"runeId":5357,"rank":3}],"highestAchievedSeasonTier":"MASTER","stats":{"participantId":8,"win":false,"item0":1056,"item1":2031,"item2":3020,"item3":3165,"item4":3001,"item5":1026,"item6":3340,"kills":2,"deaths":1,"assists":2,"largestKillingSpree":2,"largestMultiKill":1,"killingSprees":1,"longestTimeSpentLiving":782,"doubleKills":0,"tripleKills":0,"quadraKills":0,"pentaKills":0,"unrealKills":0,"totalDamageDealt":108759,"magicDamageDealt":59980,"physicalDamageDealt":15251,"trueDamageDealt":33527,"largestCriticalStrike":0,"totalDamageDealtToChampions":11463,"magicDamageDealtToChampions":7595,"physicalDamageDealtToChampions":602,"trueDamageDealtToChampions":3265,"totalHeal":2577,"totalUnitsHealed":1,"damageSelfMitigated":0,"damageDealtToObjectives":0,"damageDealtToTurrets":0,"visionScore":0,"timeCCingOthers":0,"totalDamageTaken":8700,"magicalDamageTaken":4265,"physicalDamageTaken":4435,"trueDamageTaken":0,"goldEarned":9156,"goldSpent":8975,"turretKills":0,"inhibitorKills":0,"totalMinionsKilled":226,"neutralMinionsKilled":2,"neutralMinionsKilledTeamJungle":1,"neutralMinionsKilledEnemyJungle":1,"totalTimeCrowdControlDealt":28,"champLevel":14,"visionWardsBoughtInGame":3,"sightWardsBoughtInGame":0,"wardsPlaced":10,"wardsKilled":2,"firstBloodKill":false,"firstBloodAssist":false,"firstTowerKill":false,"firstTowerAssist":false,"firstInhibitorKill":false,"firstInhibitorAssist":false,"combatPlayerScore":0,"objectivePlayerScore":0,"totalPlayerScore":0,"totalScoreRank":0},"timeline":{"participantId":8,"creepsPerMinDeltas":{"10-20":9.3,"0-10":9.2},"xpPerMinDeltas":{"10-20":509.3,"0-10":469.29999999999995},"goldPerMinDeltas":{"10-20":374.20000000000005,"0-10":284.3},"csDiffPerMinDeltas":{"10-20":1.8000000000000003,"0-10":1.6999999999999997},"xpDiffPerMinDeltas":{"10-20":2.400000000000034,"0-10":31.0},"damageTakenPerMinDeltas":{"10-20":417.1,"0-10":207.1},"damageTakenDiffPerMinDeltas":{"10-20":200.20000000000005,"0-10":35.29999999999999},"role":"SOLO","lane":"MIDDLE"}},{"participantId":9,"teamId":200,"championId":51,"spell1Id":7,"spell2Id":4,"masteries":[{"masteryId":6111,"rank":5},{"masteryId":6121,"rank":1},{"masteryId":6131,"rank":5},{"masteryId":6141,"rank":1},{"masteryId":6151,"rank":5},{"masteryId":6161,"rank":1},{"masteryId":6312,"rank":5},{"masteryId":6322,"rank":1},{"masteryId":6331,"rank":5},{"masteryId":6343,"rank":1}],"runes":[{"runeId":5245,"rank":9},{"runeId":5289,"rank":9},{"runeId":5317,"rank":9},{"runeId":5337,"rank":3}],"highestAchievedSeasonTier":"DIAMOND","stats":{"participantId":9,"win":false,"item0":1055,"item1":3006,"item2":3085,"item3":3031,"item4":2015,"item5":1042,"item6":3363,"kills":0,"deaths":6,"assists":3,"largestKillingSpree":0,"largestMultiKill":0,"killingSprees":0,"longestTimeSpentLiving":525,"doubleKills":0,"tripleKills":0,"quadraKills":0,"pentaKills":0,"unrealKills":0,"totalDamageDealt":124947,"magicDamageDealt":413,"physicalDamageDealt":123000,"trueDamageDealt":1534,"largestCriticalStrike":1015,"totalDamageDealtToChampions":7726,"magicDamageDealtToChampions":191,"physicalDamageDealtToChampions":6833,"trueDamageDealtToChampions":702,"totalHeal":971,"totalUnitsHealed":2,"damageSelfMitigated":0,"damageDealtToObjectives":0,"damageDealtToTurrets":0,"visionScore":0,"timeCCingOthers":0,"totalDamageTaken":14459,"magicalDamageTaken":3999,"physicalDamageTaken":10460,"trueDamageTaken":0,"goldEarned":9030,"goldSpent":8975,"turretKills":1,"inhibitorKills":0,"totalMinionsKilled":206,"neutralMinionsKilled":19,"neutralMinionsKilledTeamJungle":6,"neutralMinionsKilledEnemyJungle":13,"totalTimeCrowdControlDealt":152,"champLevel":12,"visionWardsBoughtInGame":1,"sightWardsBoughtInGame":0,"wardsPlaced":8,"wardsKilled":4,"firstBloodKill":false,"firstBloodAssist":false,"firstTowerKill":false,"firstTowerAssist":false,"firstInhibitorKill":false,"firstInhibitorAssist":false,"combatPlayerScore":0,"objectivePlayerScore":0,"totalPlayerScore":0,"totalScoreRank":0},"timeline":{"participantId":9,"creepsPerMinDeltas":{"10-20":9.3,"0-10":8.5},"xpPerMinDeltas":{"10-20":483.5,"0-10":311.29999999999995},"goldPerMinDeltas":{"10-20":403.4,"0-10":273.8},"csDiffPerMinDeltas":{"10-20":-0.5499999999999998,"0-10":0.5499999999999998},"xpDiffPerMinDeltas":{"10-20":-12.55000000000004,"0-10":-26.85000000000005},"damageTakenPerMinDeltas":{"10-20":610.6,"0-10":280.4},"damageTakenDiffPerMinDeltas":{"10-20":251.64999999999992,"0-10":113.49999999999999},"role":"DUO_CARRY","lane":"BOTTOM"}},{"participantId":10,"teamId":200,"championId":412,"spell1Id":4,"spell2Id":3,"masteries":[{"masteryId":6211,"rank":5},{"masteryId":6221,"rank":1},{"masteryId":6231,"rank":5},{"masteryId":6241,"rank":1},{"masteryId":6251,"rank":5},{"masteryId":6262,"rank":1},{"masteryId":6312,"rank":5},{"masteryId":6322,"rank":1},{"masteryId":6332,"rank":5},{"masteryId":6342,"rank":1}],"runes":[{"runeId":5245,"rank":9},{"runeId":5290,"rank":3},{"runeId":5295,"rank":6},{"runeId":5315,"rank":5},{"runeId":5317,"rank":4},{"runeId":5347,"rank":2},{"runeId":5351,"rank":1}],"highestAchievedSeasonTier":"MASTER","stats":{"participantId":10,"win":false,"item0":2045,"item1":2055,"item2":3801,"item3":3302,"item4":3117,"item5":3114,"item6":3364,"kills":0,"deaths":5,"assists":4,"largestKillingSpree":0,"largestMultiKill":0,"killingSprees":0,"longestTimeSpentLiving":637,"doubleKills":0,"tripleKills":0,"quadraKills":0,"pentaKills":0,"unrealKills":0,"totalDamageDealt":14307,"magicDamageDealt":9264,"physicalDamageDealt":5042,"trueDamageDealt":0,"largestCriticalStrike":0,"totalDamageDealtToChampions":3073,"magicDamageDealtToChampions":2625,"physicalDamageDealtToChampions":447,"trueDamageDealtToChampions":0,"totalHeal":809,"totalUnitsHealed":3,"damageSelfMitigated":0,"damageDealtToObjectives":0,"damageDealtToTurrets":0,"visionScore":0,"timeCCingOthers":0,"totalDamageTaken":16156,"magicalDamageTaken":6480,"physicalDamageTaken":9675,"trueDamageTaken":0,"goldEarned":5577,"goldSpent":5300,"turretKills":0,"inhibitorKills":0,"totalMinionsKilled":31,"neutralMinionsKilled":0,"neutralMinionsKilledTeamJungle":0,"neutralMinionsKilledEnemyJungle":0,"totalTimeCrowdControlDealt":89,"champLevel":10,"visionWardsBoughtInGame":8,"sightWardsBoughtInGame":0,"wardsPlaced":26,"wardsKilled":6,"firstBloodKill":false,"firstBloodAssist":false,"firstTowerKill":false,"firstTowerAssist":false,"firstInhibitorKill":false,"firstInhibitorAssist":false,"combatPlayerScore":0,"objectivePlayerScore":0,"totalPlayerScore":0,"totalScoreRank":0},"timeline":{"participantId":10,"creepsPerMinDeltas":{"10-20":1.6,"0-10":1.1},"xpPerMinDeltas":{"10-20":313.8,"0-10":272.7},"goldPerMinDeltas":{"10-20":237.9,"0-10":153.10000000000002},"csDiffPerMinDeltas":{"10-20":-0.5499999999999998,"0-10":0.5499999999999998},"xpDiffPerMinDeltas":{"10-20":-12.55000000000004,"0-10":-26.85000000000005},"damageTakenPerMinDeltas":{"10-20":418.6,"0-10":299.6},"damageTakenDiffPerMinDeltas":{"10-20":251.64999999999992,"0-10":113.49999999999999},"role":"DUO_SUPPORT","lane":"BOTTOM"}}],"participantIdentities":[{"participantId":1,"player":{"summonerName":"Kikis","profileIcon":603}},{"participantId":2,"player":{"summonerName":"Broxah","profileIcon":1424}},{"participantId":3,"player":{"summonerName":"FNC NlSQY","profileIcon":563}},{"participantId":4,"player":{"summonerName":"Dana 60 gs babay","profileIcon":2074}},{"participantId":5,"player":{"summonerName":"FNC Klaj","profileIcon":1023}},{"participantId":6,"player":{"summonerName":"MLG Glebo HLTV","profileIcon":566}},{"participantId":7,"player":{"summonerName":"KROPLA DESZCZU","profileIcon":548}},{"participantId":8,"player":{"summonerName":"Roison","profileIcon":1410}},{"participantId":9,"player":{"summonerName":"SheIsMyMorphine","profileIcon":1448}},{"participantId":10,"player":{"summonerName":"Pyrka","profileIcon":21}}]} --------------------------------------------------------------------------------