twisted
Version:
Fetching riot games api data
402 lines (284 loc) • 12.3 kB
Markdown
<div align="center">
# 🎮 Twisted
### A fully‑typed Riot Games API wrapper for Node.js
League of Legends · Teamfight Tactics · Riot Account · Data Dragon
[](https://www.npmjs.com/package/twisted)
[](https://www.npmjs.com/package/twisted)
[](https://nodejs.org)
[](https://www.typescriptlang.org)
[](./LICENSE)
</div>
---
## ✨ Highlights
- 🧩 **Complete coverage** — League of Legends, Teamfight Tactics, Riot Account and Data Dragon in one package.
- 🪶 **Lightweight** — built on the **native `fetch`** API. No `axios`, no `lodash`, no `dotenv`.
- 🔤 **First‑class TypeScript** — every endpoint, parameter and response is typed. Great autocompletion out of the box.
- 🔁 **Automatic rate‑limit retries** — `429`/`503` responses are retried honoring Riot's `Retry-After` header.
- 🚦 **Concurrency control** — cap how many requests hit Riot in parallel.
- 🧪 **Battle‑tested** — used in production by real projects.
> [!IMPORTANT]
> **v1.80** drops the `axios`, `lodash` and `dotenv` dependencies in favor of the platform.
> The minimum supported Node.js version is now **18** (the first LTS shipping a global `fetch`).
> See [Migrating to v1.80](#-migrating-to-v180).
---
## 📚 Table of contents
- [Installation](#-installation)
- [Quick start](#-quick-start)
- [Core concepts](#-core-concepts)
- [Configuration](#%EF%B8%8F-configuration)
- [Rate limiting & retries](#-rate-limiting--retries)
- [Error handling](#-error-handling)
- [Data Dragon](#-data-dragon)
- [Examples](#-examples)
- [Endpoint coverage](#-endpoint-coverage)
- [Migrating to v1.80](#-migrating-to-v180)
- [Contributing](#-contributing)
---
## 📦 Installation
```bash
npm install twisted
# or
yarn add twisted
# or
pnpm add twisted
```
**Requirements:** Node.js **≥ 18**. Get your API key at the [Riot Developer Portal](https://developer.riotgames.com/).
---
## 🚀 Quick start
The three entry points are `RiotApi` (account), `LolApi` (League of Legends) and `TftApi` (Teamfight Tactics).
Every call returns `{ response, rateLimits }` — your data lives in `response`.
<details open>
<summary><b>Riot Account</b> — resolve a Riot ID into a PUUID</summary>
```ts
import { RiotApi, Constants } from 'twisted'
const api = new RiotApi({ key: 'RGAPI-xxxxxxxx' })
async function getAccount () {
// Use the routing value closest to your server: AMERICAS, ASIA or EUROPE
const { response } = await api.Account.getByRiotId(
'Hide on bush', // gameName
'KR1', // tagLine (the part after the #)
Constants.RegionGroups.ASIA
)
return response // -> { puuid, gameName, tagLine }
}
```
</details>
<details open>
<summary><b>League of Legends</b> — summoner, ranked & matches</summary>
```ts
import { LolApi, Constants } from 'twisted'
const api = new LolApi({ key: 'RGAPI-xxxxxxxx' })
async function getRanked (puuid: string) {
const summoner = (await api.Summoner.getByPUUID(puuid, Constants.Regions.KOREA)).response
const ranked = (await api.League.byPUUID(puuid, Constants.Regions.KOREA)).response
const matchIds = (await api.MatchV5.list(puuid, Constants.RegionGroups.ASIA, { count: 5 })).response
const lastGame = (await api.MatchV5.get(matchIds[0], Constants.RegionGroups.ASIA)).response
return { summoner, ranked, lastGame }
}
```
</details>
<details>
<summary><b>Teamfight Tactics</b> — TFT summoner & matches</summary>
```ts
import { TftApi, Constants } from 'twisted'
const api = new TftApi({ key: 'RGAPI-xxxxxxxx' })
async function tftHistory (puuid: string) {
const summoner = (await api.Summoner.getByPUUID(puuid, Constants.Regions.AMERICA_NORTH)).response
const matchIds = (await api.Match.list(puuid, Constants.RegionGroups.AMERICAS, { count: 5 })).response
return { summoner, matchIds }
}
```
</details>
---
## 🧠 Core concepts
### Response shape
Every API method (except Data Dragon) resolves to an `ApiResponseDTO<T>`:
```ts
{
response: T // the parsed payload
rateLimits: { // parsed from Riot's response headers
AppRateLimit, AppRateLimitCount,
MethodRateLimit, MethodRatelimitCount,
RetryAfter, Type, EdgeTraceId
}
}
```
### Regions vs. region groups
Riot exposes three different routing concepts. Twisted enforces the right one at the **type level**, so the compiler tells you when you pass the wrong kind.
| Concept | Type | Values | Used by |
| --- | --- | --- | --- |
| **Platform region** | `Regions` | `NA1`, `EUW1`, `KR`, `BR1`, … | Summoner, League, Champion Mastery, Spectator, Status |
| **Region group** | `RegionGroups` | `AMERICAS`, `ASIA`, `EUROPE`, `SEA` | Match‑V5, TFT Match |
| **Account routing** | `AccountAPIRegionGroups` | `AMERICAS`, `ASIA`, `EUROPE` | Account‑V1 |
```ts
import { Constants } from 'twisted'
Constants.Regions.EU_WEST // 'EUW1' — platform region
Constants.RegionGroups.EUROPE // 'EUROPE' — routing value
```
### Providing your API key
The key is read from `process.env.RIOT_API_KEY`, or you can pass it explicitly:
```ts
new LolApi('RGAPI-xxxxxxxx') // shorthand
new LolApi({ key: 'RGAPI-xxxxxxxx' }) // with options
```
> Since `dotenv` is no longer bundled, load a `.env` file with Node's built‑in flag
> (Node ≥ 20.6): `node --env-file=.env app.js`, or set the variable in your shell.
---
## ⚙️ Configuration
```ts
import { LolApi } from 'twisted'
const api = new LolApi({
key: 'RGAPI-xxxxxxxx',
rateLimitRetry: true,
rateLimitRetryAttempts: 1,
concurrency: undefined,
debug: {
logTime: false,
logUrls: false,
logRatelimits: false
}
})
```
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `key` | `string` | `process.env.RIOT_API_KEY` | Your Riot Games API key. |
| `rateLimitRetry` | `boolean` | `true` | Retry the request when Riot answers `429` / `503`. |
| `rateLimitRetryAttempts` | `number` | `1` | How many times to retry after a rate‑limit response. |
| `concurrency` | `number` | `Infinity` | Max concurrent requests **per service** (Summoner, Match, …). |
| `baseURL` | `string` | `https://$(region).api.riotgames.com/:game` | Point requests at a rate‑limiting proxy. `$(region)` and `:game` are substituted. |
| `debug.logTime` | `boolean` | `false` | Log each method's execution time. |
| `debug.logUrls` | `boolean` | `false` | Log the URL of every request. |
| `debug.logRatelimits` | `boolean` | `false` | Log whenever the client is waiting on a rate limit. |
---
## 🔁 Rate limiting & retries
When Riot returns **`429 Too Many Requests`** or **`503 Service Unavailable`**, Twisted automatically waits
(honoring the `Retry-After` header) and re‑issues the request up to `rateLimitRetryAttempts` times — query
parameters included. Disable it with `rateLimitRetry: false` if you manage limits yourself.
### Concurrency
```ts
// Never fire more than 10 concurrent requests per service
const api = new LolApi({ key, concurrency: 10 })
```
---
## 🧯 Error handling
Failed requests throw typed errors you can branch on:
```ts
import { LolApi, Constants, GenericError, RateLimitError, ServiceUnavailable, ApiKeyNotFound } from 'twisted'
try {
await api.Summoner.getByPUUID(puuid, Constants.Regions.KOREA)
} catch (e) {
if (e instanceof RateLimitError) { /* 429 — retries exhausted */ }
if (e instanceof ServiceUnavailable) { /* 503 */ }
if (e instanceof ApiKeyNotFound) { /* missing key */ }
if (e instanceof GenericError) { console.log(e.status, e.body) }
}
```
| Error | When |
| --- | --- |
| `ApiKeyNotFound` | No API key was provided. |
| `RateLimitError` | `429` and retries are exhausted/disabled. |
| `ServiceUnavailable` | `503` from the Riot API. |
| `GenericError` | Any other non‑2xx response (`status` and `body` attached). |
---
## 🐉 Data Dragon
Static game assets (champions, items, runes, versions…). Data Dragon hits the public CDN directly — **no API key,
no rate limiting** — so these methods return the raw payload instead of an `ApiResponseDTO`.
```ts
const api = new LolApi()
const versions = await api.DataDragon.getVersions() // ['15.x.1', …]
const champs = await api.DataDragon.getChampionList() // all champions
const aatrox = await api.DataDragon.getChampion(Constants.Champions.AATROX)
const runes = await api.DataDragon.getRunesReforged()
```
---
## 💡 Examples
A runnable example exists for **every endpoint** under [`/example`](./example).
```bash
# Run them all
RIOT_API_KEY=RGAPI-xxxx yarn example
# Run a subset by (case-insensitive) name match
RIOT_API_KEY=RGAPI-xxxx yarn example summoner
```
---
## 📋 Endpoint coverage
> Listed in the same order as the [official Riot documentation](https://developer.riotgames.com/apis).
<details>
<summary><b>Riot Account</b></summary>
#### ACCOUNT-V1
- [x] Get account by puuid
- [x] Get account by riot id
- [x] Get active region (lol and tft)
- [ ] Get account by puuid — ESPORTS
- [ ] Get account by riot id — ESPORTS
- [ ] Get active shard for a player
- [ ] Get account by access token
</details>
<details>
<summary><b>League of Legends</b></summary>
#### CHAMPION-MASTERY-V4
- [x] All champion mastery entries
- [x] Champion mastery by player & champion id
- [x] Total champion mastery score
#### CHAMPION-V3
- [x] Champion rotation
#### CLASH
- [x] Players by summoner id · Team · Tournaments · Tournament by team id · Tournament by id
#### MATCH-V5
- [x] Match by id · Matches by puuid · Match timeline · Available replays by puuid
#### MATCH-V4 *(deprecated)*
- [x] Matches by tournament code · Match by id · Match by tournament code · Matches by summoner id · Match timeline
#### LEAGUE-V4
- [x] Challenger / Grandmaster / Master leagues by queue
- [x] League entries by PUUID · by summoner id · all entries
- [x] League by id · Experimental league entries
#### LOL-CHALLENGES-V1
- [x] Config · Percentiles · Challenge config · Leaderboards · Challenge percentiles · Player challenges
#### LOL-STATUS-V4
- [x] Platform status (v4) · Shard status (v3, deprecated)
#### SPECTATOR-V5
- [x] Current game by summoner id · Featured games *(v4 deprecated)*
#### SUMMONER-V4
- [x] By account id · By PUUID · By summoner id
#### TOURNAMENT(-STUB)-V4
- [ ] Not yet implemented
</details>
<details>
<summary><b>Teamfight Tactics</b></summary>
#### TFT-SUMMONER-V1
- [x] By account id · By PUUID · By summoner id
#### TFT-MATCH-V1
- [x] Match list by PUUID · Match details
#### TFT-LEAGUE-V1
- [x] Challenger / Grandmaster / Master leagues
- [x] Entries by summoner id · By tier & division
- [ ] All entries · League by id
#### TFT-SPECTATOR-V5
- [x] Current game by puuid · Featured games
</details>
---
## 🔀 Migrating to v1.80
This release removes three runtime dependencies in favor of native platform features:
| Removed | Replaced by |
| --- | --- |
| `axios` | Native `fetch` (Node ≥ 18) |
| `lodash` | Native JS (`Object.entries`, spreads, …) |
| `dotenv` | `node --env-file=.env` or your own loader |
**What you need to do**
- Run on **Node 18 or newer**.
- If you relied on Twisted auto‑loading a `.env`, load it yourself — e.g. `node --env-file=.env`, or pass `new LolApi({ key })`.
The public API is otherwise **unchanged** — your existing calls keep working.
---
## 🤝 Contributing
```bash
yarn install # install dependencies
yarn build # compile TypeScript -> dist/
yarn lint # eslint
yarn jest # run the test suite (coverage is always collected)
```
PRs are welcome! For new endpoints: declare it in `src/endpoints`, add the service method, model the response DTO,
add an example, and wire it into the relevant entry class.
A larger real‑world project built on Twisted lives at [twisted‑gg](https://github.com/twisted-gg).
---
<div align="center">
Released under the [MIT License](./LICENSE).
</div>