rito 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +30 -0
- data/LICENSE +21 -0
- data/README.md +182 -0
- data/lib/rito/client.rb +118 -0
- data/lib/rito/connection.rb +39 -0
- data/lib/rito/endpoints/account_v1.rb +45 -0
- data/lib/rito/endpoints/base.rb +35 -0
- data/lib/rito/endpoints/lol/challenges_v1.rb +31 -0
- data/lib/rito/endpoints/lol/champion_mastery_v4.rb +33 -0
- data/lib/rito/endpoints/lol/champion_v3.rb +17 -0
- data/lib/rito/endpoints/lol/clash_v1.rb +27 -0
- data/lib/rito/endpoints/lol/league_v4.rb +58 -0
- data/lib/rito/endpoints/lol/match_v5.rb +34 -0
- data/lib/rito/endpoints/lol/spectator_v5.rb +22 -0
- data/lib/rito/endpoints/lol/status_v4.rb +15 -0
- data/lib/rito/endpoints/lol/summoner_v4.rb +35 -0
- data/lib/rito/endpoints/lol/tournament_v5.rb +69 -0
- data/lib/rito/endpoints/lor.rb +95 -0
- data/lib/rito/endpoints/tft.rb +123 -0
- data/lib/rito/endpoints/valorant.rb +92 -0
- data/lib/rito/errors.rb +55 -0
- data/lib/rito/instrumentation.rb +17 -0
- data/lib/rito/middleware/authentication.rb +21 -0
- data/lib/rito/middleware/rate_limit_observe.rb +32 -0
- data/lib/rito/middleware/rate_limit_throttle.rb +58 -0
- data/lib/rito/middleware/riot_errors.rb +81 -0
- data/lib/rito/models/account.rb +38 -0
- data/lib/rito/models/lol.rb +80 -0
- data/lib/rito/models/match.rb +71 -0
- data/lib/rito/models/summoner.rb +23 -0
- data/lib/rito/rate_limiting/adaptive_limiter.rb +71 -0
- data/lib/rito/rate_limiting/bucket.rb +83 -0
- data/lib/rito/rate_limiting/header_parser.rb +53 -0
- data/lib/rito/rate_limiting/null_limiter.rb +11 -0
- data/lib/rito/rate_limiting/redis_limiter.rb +153 -0
- data/lib/rito/routing.rb +79 -0
- data/lib/rito/version.rb +5 -0
- data/lib/rito.rb +71 -0
- metadata +109 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 882b1dd78a0168c537a254240ff33562776b50a95ee04e17864229bde2cde2b0
|
|
4
|
+
data.tar.gz: ad4ec1b37559654409118de196adcdcb082f813a186e92688988adb8ebba3379
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d0cef7acb7d9d98b2176dae271f24ccd23037c4d091be4e5896ff71a08682bd9657a072bf3ede3bb7ef5c328f55ff7826b2624e8305d6895746d99b0400338ed
|
|
7
|
+
data.tar.gz: 1f54d50efe21dc86999746be59cc29704610288b8cd595a2e09b97fe78831edef5ed6a1e81815f0afd60277733ce1cc8d3a2be22807302ebfaac6bb21d15b522
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-09-05)
|
|
4
|
+
|
|
5
|
+
Initial release.
|
|
6
|
+
|
|
7
|
+
- **HTTP layer**: Faraday 2.x with a per-host connection cache, JSON
|
|
8
|
+
request/response middleware, and transport retries (5xx, connection
|
|
9
|
+
failures) via faraday-retry.
|
|
10
|
+
- **Routing**: platform vs regional routing values with automatic escalation
|
|
11
|
+
(`na1 → americas`), SEA→ASIA normalization for account-v1, and separate
|
|
12
|
+
VALORANT platform sets (`na1, eu, ap, kr, latam, br`; console: `na, eu, ap`).
|
|
13
|
+
- **Rate limiting**: `AdaptiveLimiter` (default) learns limits from
|
|
14
|
+
`X-App-Rate-Limit` / `X-Method-Rate-Limit` headers, syncs counters from
|
|
15
|
+
`*-Count` headers, throttles pre-emptively, handles 429s by scope
|
|
16
|
+
(application / method / service) honoring `Retry-After` with exponential
|
|
17
|
+
backoff + jitter fallback. `NullLimiter` for reactive-only mode.
|
|
18
|
+
`RedisLimiter` for multi-process deployments (soft dependency on `redis`).
|
|
19
|
+
- **Errors**: mapped hierarchy (400/401/403/404/405/415/429/5xx,
|
|
20
|
+
ConnectionError/TimeoutError/SSLError); `RateLimited` carries
|
|
21
|
+
`retry_after` and `limit_type`.
|
|
22
|
+
- **Models**: frozen `Data` classes with liberal mapping; unknown fields
|
|
23
|
+
preserved in `.raw`.
|
|
24
|
+
- **Coverage**: account-v1; LoL summoner-v4, match-v5, champion-mastery-v4,
|
|
25
|
+
champion-v3, league-v4, league-exp-v4, spectator-v5, lol-status-v4, clash-v1,
|
|
26
|
+
lol-challenges-v1, tournament-v5 (+ stub); TFT summoner/league/match/status/
|
|
27
|
+
spectator; VALORANT content/match/console-match/ranked/status; LoR
|
|
28
|
+
match/ranked/status/deck/inventory (RSO); riftbound-content-v1.
|
|
29
|
+
- **Instrumentation**: `request.rito` ActiveSupport::Notifications events
|
|
30
|
+
(no-op without ActiveSupport).
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kodbilenadam
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Rito
|
|
2
|
+
|
|
3
|
+
A modern, production-ready Ruby client for the Riot Games API: League of
|
|
4
|
+
Legends, TFT, VALORANT, Legends of Runeterra, Riftbound, and the Riot Account API.
|
|
5
|
+
|
|
6
|
+
Full routing-value handling, header-driven adaptive rate limiting, frozen
|
|
7
|
+
`Data` models, and a single runtime dependency (Faraday).
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem "rito"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
require "rito"
|
|
19
|
+
|
|
20
|
+
Rito.configure do |c|
|
|
21
|
+
c.api_key = ENV.fetch("RIOT_API_KEY")
|
|
22
|
+
c.region = :na1
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
client = Rito::Client.new
|
|
26
|
+
|
|
27
|
+
account = client.account.by_riot_id("Hide on bush", "KR1", region: :asia)
|
|
28
|
+
summoner = client.summoner.by_puuid(account.puuid, region: :kr)
|
|
29
|
+
match_ids = client.matches.ids_by_puuid(account.puuid, region: :asia, count: 5)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Configuration
|
|
33
|
+
|
|
34
|
+
Class-level config with per-client overrides (later wins):
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
Rito.api_key # default: ENV["RIOT_API_KEY"]
|
|
38
|
+
Rito.bearer_token # default: ENV["RIOT_RSO_TOKEN"] (RSO endpoints)
|
|
39
|
+
Rito.region # default routing value, e.g. :na1
|
|
40
|
+
Rito.max_retries # 429 / transport retries (default: 3)
|
|
41
|
+
Rito.rate_limiter # rate limit strategy (default: AdaptiveLimiter)
|
|
42
|
+
|
|
43
|
+
# per-client, for multi-key apps:
|
|
44
|
+
client = Rito::Client.new(api_key: "RGAPI-...", region: :kr)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Routing values
|
|
48
|
+
|
|
49
|
+
Endpoints declare whether they are platform-routed (`na1`, `kr`, ...) or
|
|
50
|
+
regionally routed (`americas`, `europe`, `asia`, `sea`). You pass whichever you
|
|
51
|
+
have; the client escalates automatically:
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
client.summoner.by_puuid(puuid, region: :kr) # platform host: kr
|
|
55
|
+
client.matches.ids_by_puuid(puuid, region: :kr) # regional host: asia (escalated)
|
|
56
|
+
client.account.by_puuid(puuid, region: :sea) # account-v1 has no SEA host → asia
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Rate limiting
|
|
60
|
+
|
|
61
|
+
The default `AdaptiveLimiter` learns your key's limits from
|
|
62
|
+
`X-App-Rate-Limit` / `X-Method-Rate-Limit` headers, syncs counters from
|
|
63
|
+
`*-Count` headers, throttles pre-emptively, and handles 429s:
|
|
64
|
+
|
|
65
|
+
- `Retry-After` present → blocks that long (app-scope blocks the whole
|
|
66
|
+
key+region; method-scope blocks only the endpoint).
|
|
67
|
+
- `Retry-After` missing → exponential backoff with jitter.
|
|
68
|
+
- No `X-Rate-Limit-Type` header → service-level 429 → endpoint-only backoff.
|
|
69
|
+
|
|
70
|
+
Exhausted retries raise `Rito::RateLimited` (with `.retry_after` and
|
|
71
|
+
`.limit_type`). Opt out with `Rito::RateLimiting::NullLimiter`.
|
|
72
|
+
|
|
73
|
+
### Multi-worker deployments
|
|
74
|
+
|
|
75
|
+
Each process using the default `AdaptiveLimiter` tracks limits independently,
|
|
76
|
+
so a multi-worker setup (Puma cluster, multiple hosts) can collectively exceed
|
|
77
|
+
one key's limits. Use the Redis limiter for shared state:
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
limiter = Rito::RateLimiting::RedisLimiter.new(redis: Redis.new)
|
|
81
|
+
client = Rito::Client.new(rate_limiter: limiter)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Note: `acquire!` blocks the calling thread while throttled — fine in
|
|
85
|
+
background jobs; in web requests prefer jobs or `max_retries: 0` with
|
|
86
|
+
explicit `Rito::RateLimited` handling.
|
|
87
|
+
|
|
88
|
+
### Multi-process (Redis)
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
gem "redis" # soft dependency
|
|
92
|
+
|
|
93
|
+
limiter = Rito::RateLimiting::RedisLimiter.new(redis: Redis.new)
|
|
94
|
+
client = Rito::Client.new(rate_limiter: limiter)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Errors
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
Rito::Error
|
|
101
|
+
├── BadRequest (400) ├── MethodNotAllowed (405)
|
|
102
|
+
├── Unauthorized (401) ├── UnsupportedMediaType (415)
|
|
103
|
+
├── Forbidden (403) ├── RateLimited (429)
|
|
104
|
+
├── NotFound (404) ├── ServerError / ServiceUnavailable (5xx)
|
|
105
|
+
└── ConnectionError (TimeoutError, SSLError)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Every error carries `.status` and `.response`. Messages include the method,
|
|
109
|
+
URL, and Riot's error detail.
|
|
110
|
+
|
|
111
|
+
## Models
|
|
112
|
+
|
|
113
|
+
Responses are frozen `Data` objects. Documented fields are typed accessors;
|
|
114
|
+
unknown fields Riot adds later are preserved in `.raw`:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
entry = client.leagues.entries_by_summoner_id(summoner_id).first
|
|
118
|
+
entry.tier # => "DIAMOND"
|
|
119
|
+
entry.winrate # => 57.14
|
|
120
|
+
entry.raw # => full payload hash
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Coverage
|
|
124
|
+
|
|
125
|
+
| Accessor | API |
|
|
126
|
+
| --- | --- |
|
|
127
|
+
| `client.account` | account-v1 |
|
|
128
|
+
| `client.summoner` | summoner-v4 |
|
|
129
|
+
| `client.matches` | match-v5 (matches, matchlist, timeline) |
|
|
130
|
+
| `client.champion_masteries` | champion-mastery-v4 |
|
|
131
|
+
| `client.champions` | champion-v3 (rotations) |
|
|
132
|
+
| `client.leagues` / `client.league_exp` | league-v4 / league-exp-v4 |
|
|
133
|
+
| `client.spectator` | spectator-v5 (`active_game` returns `nil` on 404) |
|
|
134
|
+
| `client.lol_status` | lol-status-v4 |
|
|
135
|
+
| `client.clash` | clash-v1 |
|
|
136
|
+
| `client.challenges` | lol-challenges-v1 |
|
|
137
|
+
| `client.tournaments` / `client.tournament_stub` | tournament-v5 / stub-v5 |
|
|
138
|
+
| `client.tft.summoner` / `.leagues` / `.matches` / `.status` / `.spectator` | tft-summoner-v1, tft-league-v1, tft-match-v1, tft-status-v1, spectator-tft-v5 |
|
|
139
|
+
| `client.val.content` / `.matches` / `.console_matches` / `.ranked` / `.status` | val-content-v1, val-match-v1, val-console-match-v1, val-ranked-v1, val-status-v1 |
|
|
140
|
+
| `client.lor.matches` / `.ranked` / `.status` / `.decks` / `.inventory` | lor-match-v1, lor-ranked-v1, lor-status-v1, lor-deck-v1 / lor-inventory-v1 (RSO) |
|
|
141
|
+
| `client.riftbound` | riftbound-content-v1 |
|
|
142
|
+
|
|
143
|
+
Note: VALORANT uses its own platform routing values (`na1, eu, ap, kr, latam, br`;
|
|
144
|
+
console: `na, eu, ap`) — the client validates against the right set per product.
|
|
145
|
+
|
|
146
|
+
## Testing your own app
|
|
147
|
+
|
|
148
|
+
`Rito` plays well with WebMock. Recorded fixtures via VCR:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
bundle exec rake cassettes # re-record (needs RIOT_API_KEY_TEST in .env)
|
|
152
|
+
bundle exec rake test
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Instrumentation
|
|
156
|
+
|
|
157
|
+
If ActiveSupport is present (e.g. in a Rails app), every request emits a
|
|
158
|
+
`request.rito` notification:
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
ActiveSupport::Notifications.subscribe("request.rito") do |*, payload|
|
|
162
|
+
Rails.logger.info(
|
|
163
|
+
"#{payload[:http_method]} #{payload[:url]} -> #{payload[:status]} " \
|
|
164
|
+
"in #{payload[:duration_ms]}ms (attempts: #{payload[:attempts]})"
|
|
165
|
+
)
|
|
166
|
+
end
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Without ActiveSupport, instrumentation is a no-op.
|
|
170
|
+
|
|
171
|
+
## Development
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
bundle install
|
|
175
|
+
bundle exec rake test
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Ruby 3.2+ required (`Data.define`).
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
MIT
|
data/lib/rito/client.rb
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rito
|
|
4
|
+
class Client
|
|
5
|
+
attr_reader :api_key, :bearer_token, :region, :limiter, :max_retries,
|
|
6
|
+
:open_timeout, :timeout, :adapter
|
|
7
|
+
|
|
8
|
+
def initialize(api_key: nil, bearer_token: nil, region: nil, rate_limiter: nil,
|
|
9
|
+
max_retries: nil, open_timeout: nil, timeout: nil, adapter: nil)
|
|
10
|
+
@api_key = api_key || Rito.api_key
|
|
11
|
+
@bearer_token = bearer_token || Rito.bearer_token
|
|
12
|
+
@region = region || Rito.region
|
|
13
|
+
@limiter = rate_limiter || Rito.rate_limiter || RateLimiting::AdaptiveLimiter.new
|
|
14
|
+
@max_retries = max_retries || Rito.max_retries
|
|
15
|
+
@open_timeout = open_timeout || Rito.open_timeout
|
|
16
|
+
@timeout = timeout || Rito.timeout
|
|
17
|
+
@adapter = adapter || Rito.adapter
|
|
18
|
+
@connection = Connection.new(self)
|
|
19
|
+
|
|
20
|
+
validate!
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def account
|
|
24
|
+
Endpoints::AccountV1.new(self)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def summoner
|
|
28
|
+
Endpoints::Lol::SummonerV4.new(self)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def matches
|
|
32
|
+
Endpoints::Lol::MatchV5.new(self)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def champion_masteries
|
|
36
|
+
Endpoints::Lol::ChampionMasteryV4.new(self)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def champions
|
|
40
|
+
Endpoints::Lol::ChampionV3.new(self)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def leagues
|
|
44
|
+
Endpoints::Lol::LeagueV4.new(self)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def league_exp
|
|
48
|
+
Endpoints::Lol::LeagueExpV4.new(self)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def spectator
|
|
52
|
+
Endpoints::Lol::SpectatorV5.new(self)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def lol_status
|
|
56
|
+
Endpoints::Lol::StatusV4.new(self)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def clash
|
|
60
|
+
Endpoints::Lol::ClashV1.new(self)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def challenges
|
|
64
|
+
Endpoints::Lol::ChallengesV1.new(self)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def tournaments
|
|
68
|
+
Endpoints::Lol::TournamentV5.new(self)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def tournament_stub
|
|
72
|
+
Endpoints::Lol::TournamentStubV5.new(self)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def tft
|
|
76
|
+
Rito::Tft::Api.new(self)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def val
|
|
80
|
+
Rito::Val::Api.new(self)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def lor
|
|
84
|
+
Rito::Lor::Api.new(self)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def riftbound
|
|
88
|
+
Endpoints::Riftbound.new(self)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def request(method, path, routing_kind:, routing_value: nil, params: {}, body: nil)
|
|
92
|
+
region = Routing.resolve(routing_kind, routing_value || @region)
|
|
93
|
+
connection = @connection.for_host(Routing.host_for(region))
|
|
94
|
+
response = connection.run_request(method, path, body, {}) do |req|
|
|
95
|
+
req.params.update(params) unless params.empty?
|
|
96
|
+
end
|
|
97
|
+
# 5xx responses reaching here are final: faraday-retry has exhausted
|
|
98
|
+
# its retries (or the status was not retryable).
|
|
99
|
+
raise_server_error!(response) if response.status >= 500
|
|
100
|
+
response
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
def raise_server_error!(response)
|
|
106
|
+
status = response.status
|
|
107
|
+
error_class = status == 503 ? ServiceUnavailable : ServerError
|
|
108
|
+
raise error_class.new("#{status} (after transport retries)", response: response)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def validate!
|
|
112
|
+
raise ConfigError, 'api_key or bearer_token is required' if @api_key.to_s.empty? && @bearer_token.to_s.empty?
|
|
113
|
+
return if Routing.platform?(@region) || Routing.regional?(@region)
|
|
114
|
+
|
|
115
|
+
raise ConfigError, "unknown region: #{@region.inspect}"
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rito
|
|
4
|
+
class Connection
|
|
5
|
+
def initialize(client)
|
|
6
|
+
@client = client
|
|
7
|
+
@connections = {}
|
|
8
|
+
@mutex = Mutex.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def for_host(host)
|
|
12
|
+
@mutex.synchronize do
|
|
13
|
+
@connections[host] ||= build(host)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def build(host)
|
|
20
|
+
Faraday.new("https://#{host}") do |f|
|
|
21
|
+
f.options.open_timeout = @client.open_timeout
|
|
22
|
+
f.options.timeout = @client.timeout
|
|
23
|
+
f.request :json
|
|
24
|
+
f.request :retry,
|
|
25
|
+
max: @client.max_retries,
|
|
26
|
+
retry_statuses: [500, 503, 504],
|
|
27
|
+
interval: 0.5,
|
|
28
|
+
backoff_factor: 2,
|
|
29
|
+
exceptions: [Faraday::ConnectionFailed, Faraday::TimeoutError, Faraday::RetriableResponse]
|
|
30
|
+
f.use Middleware::RateLimitThrottle, client: @client
|
|
31
|
+
f.use Middleware::Authentication, client: @client
|
|
32
|
+
f.use Middleware::RiotErrors, client: @client
|
|
33
|
+
f.use Middleware::RateLimitObserve, client: @client
|
|
34
|
+
f.response :json, content_type: /\bjson$/
|
|
35
|
+
f.adapter @client.adapter
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rito
|
|
4
|
+
module Endpoints
|
|
5
|
+
class AccountV1 < Base
|
|
6
|
+
ROUTING = :regional
|
|
7
|
+
|
|
8
|
+
def by_riot_id(game_name, tag_line, region: nil)
|
|
9
|
+
Models::Account.from_api(
|
|
10
|
+
get("/riot/account/v1/accounts/by-riot-id/#{escape(game_name)}/#{escape(tag_line)}", region)
|
|
11
|
+
)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def by_puuid(puuid, region: nil)
|
|
15
|
+
Models::Account.from_api(
|
|
16
|
+
get("/riot/account/v1/accounts/by-puuid/#{escape(puuid)}", region)
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def active_shard(game, puuid, region: nil)
|
|
21
|
+
Models::ActiveShard.from_api(
|
|
22
|
+
get("/riot/account/v1/active-shards/by-game/#{escape(game)}/by-puuid/#{escape(puuid)}", region)
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def region_by_game(game, puuid, region: nil)
|
|
27
|
+
Models::AccountRegion.from_api(
|
|
28
|
+
get("/riot/account/v1/region/by-game/#{escape(game)}/by-puuid/#{escape(puuid)}", region)
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Requires the client to be configured with a bearer_token (RSO).
|
|
33
|
+
def me(region: nil)
|
|
34
|
+
Models::Account.from_api(get('/riot/account/v1/accounts/me', region))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def get(path, region)
|
|
40
|
+
# account-v1 has no SEA host; normalize it to ASIA.
|
|
41
|
+
super(path, Routing.regional_for_account(region || @client.region))
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rito
|
|
4
|
+
module Endpoints
|
|
5
|
+
class Base
|
|
6
|
+
def initialize(client)
|
|
7
|
+
@client = client
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def get(path, region = nil, params: {})
|
|
13
|
+
@client.request(:get, path, routing_kind: routing_kind, routing_value: region, params: params).body
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def post(path, region = nil, params: {}, body: nil)
|
|
17
|
+
@client.request(:post, path, routing_kind: routing_kind, routing_value: region,
|
|
18
|
+
params: params, body: body).body
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def put(path, region = nil, params: {}, body: nil)
|
|
22
|
+
@client.request(:put, path, routing_kind: routing_kind, routing_value: region,
|
|
23
|
+
params: params, body: body).body
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def routing_kind
|
|
27
|
+
self.class::ROUTING
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def escape(segment)
|
|
31
|
+
ERB::Util.url_encode(segment.to_s)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rito
|
|
4
|
+
module Endpoints
|
|
5
|
+
module Lol
|
|
6
|
+
class ChallengesV1 < Base
|
|
7
|
+
ROUTING = :platform
|
|
8
|
+
|
|
9
|
+
def config(region: nil)
|
|
10
|
+
get('/lol/challenges/v1/challenges/config', region)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def percentiles(region: nil)
|
|
14
|
+
get('/lol/challenges/v1/challenges/percentiles', region)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def leaderboard(challenge_id, level, top: nil, page: nil, region: nil)
|
|
18
|
+
params = {}
|
|
19
|
+
params['top'] = top if top
|
|
20
|
+
params['page'] = page if page
|
|
21
|
+
get("/lol/challenges/v1/challenges/#{escape(challenge_id)}/leaderboards/by-level/#{escape(level)}", region,
|
|
22
|
+
params: params)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def player_data(puuid, region: nil)
|
|
26
|
+
get("/lol/challenges/v1/player-data/#{escape(puuid)}", region)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rito
|
|
4
|
+
module Endpoints
|
|
5
|
+
module Lol
|
|
6
|
+
class ChampionMasteryV4 < Base
|
|
7
|
+
ROUTING = :platform
|
|
8
|
+
|
|
9
|
+
def for_puuid(puuid, region: nil)
|
|
10
|
+
get("/lol/champion-mastery/v4/champion-masteries/by-puuid/#{escape(puuid)}", region)
|
|
11
|
+
.map { |hash| Models::ChampionMastery.from_api(hash) }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def for_puuid_and_champion(puuid, champion_id, region: nil)
|
|
15
|
+
path = "/lol/champion-mastery/v4/champion-masteries/by-puuid/#{escape(puuid)}" \
|
|
16
|
+
"/by-champion/#{escape(champion_id)}"
|
|
17
|
+
Models::ChampionMastery.from_api(get(path, region))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def top_for_puuid(puuid, count: nil, region: nil)
|
|
21
|
+
params = {}
|
|
22
|
+
params['count'] = count if count
|
|
23
|
+
get("/lol/champion-mastery/v4/champion-masteries/by-puuid/#{escape(puuid)}/top", region, params: params)
|
|
24
|
+
.map { |hash| Models::ChampionMastery.from_api(hash) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def score(puuid, region: nil)
|
|
28
|
+
get("/lol/champion-mastery/v4/scores/by-puuid/#{escape(puuid)}", region)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rito
|
|
4
|
+
module Endpoints
|
|
5
|
+
module Lol
|
|
6
|
+
class ChampionV3 < Base
|
|
7
|
+
ROUTING = :platform
|
|
8
|
+
|
|
9
|
+
def rotations(region: nil)
|
|
10
|
+
Models::ChampionInfo.from_api(
|
|
11
|
+
get('/lol/platform/v3/champion-rotations', region)
|
|
12
|
+
)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rito
|
|
4
|
+
module Endpoints
|
|
5
|
+
module Lol
|
|
6
|
+
class ClashV1 < Base
|
|
7
|
+
ROUTING = :platform
|
|
8
|
+
|
|
9
|
+
def players_by_summoner_id(summoner_id, region: nil)
|
|
10
|
+
get("/lol/clash/v1/players/by-summoner/#{escape(summoner_id)}", region)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def team(team_id, region: nil)
|
|
14
|
+
get("/lol/clash/v1/teams/#{escape(team_id)}", region)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def tournaments(region: nil)
|
|
18
|
+
get('/lol/clash/v1/tournaments', region)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def tournament_by_team(team_id, region: nil)
|
|
22
|
+
get("/lol/clash/v1/tournaments/by-team/#{escape(team_id)}", region)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rito
|
|
4
|
+
module Endpoints
|
|
5
|
+
module Lol
|
|
6
|
+
class LeagueV4 < Base
|
|
7
|
+
ROUTING = :platform
|
|
8
|
+
|
|
9
|
+
def challenger(queue, region: nil)
|
|
10
|
+
Models::LeagueList.from_api(
|
|
11
|
+
get("/lol/league/v4/challengerleagues/by-queue/#{escape(queue)}", region)
|
|
12
|
+
)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def grandmaster(queue, region: nil)
|
|
16
|
+
Models::LeagueList.from_api(
|
|
17
|
+
get("/lol/league/v4/grandmasterleagues/by-queue/#{escape(queue)}", region)
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def master(queue, region: nil)
|
|
22
|
+
Models::LeagueList.from_api(
|
|
23
|
+
get("/lol/league/v4/masterleagues/by-queue/#{escape(queue)}", region)
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def entries_by_summoner_id(summoner_id, region: nil)
|
|
28
|
+
get("/lol/league/v4/entries/by-summoner/#{escape(summoner_id)}", region)
|
|
29
|
+
.map { |hash| Models::LeagueEntry.from_api(hash) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def entries(tier, division, page: nil, region: nil)
|
|
33
|
+
params = {}
|
|
34
|
+
params['page'] = page if page
|
|
35
|
+
get("/lol/league/v4/entries/#{escape(tier)}/#{escape(division)}", region, params: params)
|
|
36
|
+
.map { |hash| Models::LeagueEntry.from_api(hash) }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def league(league_id, region: nil)
|
|
40
|
+
Models::LeagueList.from_api(
|
|
41
|
+
get("/lol/league/v4/leagues/#{escape(league_id)}", region)
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class LeagueExpV4 < Base
|
|
47
|
+
ROUTING = :platform
|
|
48
|
+
|
|
49
|
+
def entries(queue, tier, division, page: nil, region: nil)
|
|
50
|
+
params = {}
|
|
51
|
+
params['page'] = page if page
|
|
52
|
+
get("/lol/league-exp/v4/entries/#{escape(queue)}/#{escape(tier)}/#{escape(division)}", region, params: params)
|
|
53
|
+
.map { |hash| Models::LeagueEntry.from_api(hash) }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rito
|
|
4
|
+
module Endpoints
|
|
5
|
+
module Lol
|
|
6
|
+
class MatchV5 < Base
|
|
7
|
+
ROUTING = :regional
|
|
8
|
+
|
|
9
|
+
def by_id(match_id, region: nil)
|
|
10
|
+
Models::Match.from_api(
|
|
11
|
+
get("/lol/match/v5/matches/#{escape(match_id)}", region)
|
|
12
|
+
)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def ids_by_puuid(puuid, region: nil, start: nil, count: nil, queue: nil,
|
|
16
|
+
type: nil, start_time: nil, end_time: nil)
|
|
17
|
+
params = {}
|
|
18
|
+
params['start'] = start if start
|
|
19
|
+
params['count'] = count if count
|
|
20
|
+
params['queue'] = queue if queue
|
|
21
|
+
params['type'] = type if type
|
|
22
|
+
params['startTime'] = start_time if start_time
|
|
23
|
+
params['endTime'] = end_time if end_time
|
|
24
|
+
|
|
25
|
+
get("/lol/match/v5/matches/by-puuid/#{escape(puuid)}/ids", region, params: params)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def timeline_by_id(match_id, region: nil)
|
|
29
|
+
get("/lol/match/v5/matches/#{escape(match_id)}/timeline", region)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|