UNPKG

twisted

Version:
402 lines (284 loc) 12.3 kB
<div align="center"> # 🎮 Twisted ### A fully‑typed Riot Games API wrapper for Node.js League of Legends · Teamfight Tactics · Riot Account · Data Dragon [![npm version](https://img.shields.io/npm/v/twisted?color=cb3837&logo=npm)](https://www.npmjs.com/package/twisted) [![npm downloads](https://img.shields.io/npm/dm/twisted?color=blue&logo=npm)](https://www.npmjs.com/package/twisted) [![node](https://img.shields.io/node/v/twisted?color=339933&logo=node.js&logoColor=white)](https://nodejs.org) [![types](https://img.shields.io/npm/types/twisted?logo=typescript&logoColor=white)](https://www.typescriptlang.org) [![license](https://img.shields.io/npm/l/twisted?color=green)](./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>