From 6ce4330e09792e438bcfe5f932bbe12e79baedad Mon Sep 17 00:00:00 2001 From: Julio Sansossio Date: Wed, 10 Jun 2026 06:42:38 -0400 Subject: [PATCH] feat: drop axios/lodash/dotenv for native fetch, bump to 1.80.0 Remove the three runtime dependencies in favor of platform features: - axios -> native fetch (new request pipeline in src/base/request.base.ts with an HttpError that mirrors the previous axios error shape, so error handling and rate-limit retries are unchanged). Query params are serialized with URLSearchParams (repeated keys, skipping null/undefined). DataDragon and Seed now use fetch directly. - lodash -> native JS (Object.entries-based champion id map, a small camelCase helper, object spreads instead of cloneDeep, direct property access instead of _.get/_.set). - dotenv -> removed auto-loading; the live test uses a shared test/utils/loadEnv.ts helper instead. Because the client now relies on a global fetch, the minimum supported Node.js version is raised to >= 18 (engines field updated). Also: - Export the error classes from the package barrel (src/index.ts). - Refactor README.md to be more visual and clearer. - Enrich every example under example/ (comments, logging, error handling), fix stale imports/types and the removed Summoner.getByName usages. - Bump version to 1.80.0 and regenerate yarn.lock. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 578 +++++++++++------- example/README.md | 53 +- example/clash/ClashTournamentById.example.ts | 38 +- example/clash/ClashTournamentList.example.ts | 26 +- example/config/config.ts | 45 +- .../lol/ChallengerLeagueByQueue.example.ts | 30 +- example/lol/Challenges.example.ts | 75 ++- example/lol/ChampionMasteryByPUUID.example.ts | 37 +- .../ChampionMasteryByPUUIDChampion.example.ts | 40 +- example/lol/ChampionRotation.example.ts | 27 +- example/lol/ChampionsScore.example.ts | 35 +- example/lol/GameModes.DataDragon.ts | 20 +- example/lol/GameTypes.DataDragon.ts | 20 +- example/lol/GetChampion.DataDragon.ts | 25 +- example/lol/GetChampionList.DataDragon.ts | 25 +- .../lol/GrandMasterLeagueByQueue.example.ts | 30 +- example/lol/Languages.DataDragon.ts | 20 +- example/lol/League.example.ts | 46 +- example/lol/LeagueByPUUID.example.ts | 34 +- example/lol/LeagueEntries.example.ts | 28 +- example/lol/LeagueExp.example.ts | 30 +- example/lol/Maps.DataDragon.ts | 20 +- example/lol/MasterLeagueByQueue.example.ts | 30 +- example/lol/MatchV5Replays.example.ts | 39 +- .../lol/MatchV5TimelineLatestMatch.example.ts | 56 +- example/lol/Queues.DataDragon.ts | 23 +- example/lol/Realms.DataDragon.ts | 25 +- example/lol/RunesReforged.DataDragon.ts | 23 +- example/lol/Seasons.DataDragon.ts | 20 +- example/lol/SeedMatches.DataDragon.ts | 25 +- .../lol/SpectatorFeaturedGamesV5.example.ts | 23 +- example/lol/SpectatorSummonerV5.example.ts | 38 +- example/lol/StatusV4.example.ts | 27 +- example/lol/SummonerByPUUID.example.ts | 34 +- example/lol/SummonerLeague.example.ts | 40 +- example/lol/Versions.DataDragon.ts | 20 +- example/lol/deprecated/LolStatus.example.ts | 22 +- example/lol/deprecated/Match.example.ts | 46 +- .../lol/deprecated/MatchListing.example.ts | 38 +- .../MatchListingFiltering.example.ts | 40 +- .../lol/deprecated/MatchTimeline.example.ts | 44 +- .../SpectatorFeaturedGames.example.ts | 26 +- .../deprecated/SpectatorSummoner.example.ts | 47 +- .../deprecated/SummonerByAccountID.example.ts | 39 +- .../lol/deprecated/SummonerById.example.ts | 39 +- .../lol/deprecated/SummonerByName.example.ts | 38 +- .../lol/deprecated/ThirdPartyCode.example.ts | 42 +- example/lol/index.ts | 2 + example/riot/Account.examples.ts | 39 +- example/riot/AccountRegion.examples.ts | 37 +- example/tft/StaticFiles.example.ts | 33 +- example/tft/SummonerTFT.example.ts | 33 +- example/tft/TftLeagueBySummoner.example.ts | 36 +- .../tft/TftLeagueByTierDivision.example.ts | 37 +- example/tft/TftMatchDetails.example.ts | 49 +- example/tft/TftMatchList.example.ts | 34 +- example/tft/TftMatchListDetails.example.ts | 38 +- .../tft/TftSpectatorActiveGames.example.ts | 35 +- .../tft/TftSpectatorFeaturedGames.example.ts | 26 +- package.json | 9 +- runExamples.ts | 3 +- src/apis/lol/dataDragon/DataDragonService.ts | 9 +- src/apis/lol/seed/seed.ts | 9 +- src/apis/lol/summoner/summoner.ts | 3 +- src/apis/tft/summoner/summoner.ts | 3 +- src/base/base.ts | 38 +- src/base/base.utils.ts | 55 +- src/base/logger.base.ts | 5 +- src/base/request.base.ts | 73 ++- src/constants/champions.ts | 55 +- src/errors/Generic.error.ts | 10 +- src/index.ts | 1 + test/live.test.ts | 4 +- test/utils/loadEnv.ts | 30 + yarn.lock | 178 ------ 75 files changed, 2156 insertions(+), 854 deletions(-) create mode 100644 test/utils/loadEnv.ts diff --git a/README.md b/README.md index 680e5716..c1ddcdc6 100644 --- a/README.md +++ b/README.md @@ -1,231 +1,401 @@ -# Twisted -League of Legends API Wrapper
-![https://www.npmjs.com/package/twisted](https://nodei.co/npm/twisted.png) +
-# Simple example -RIOT: -```js +# 🎮 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) + +
+ +--- + +## ✨ 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`. + +
+Riot Account — resolve a Riot ID into a PUUID + +```ts import { RiotApi, Constants } from 'twisted' -const api = new RiotApi() +const api = new RiotApi({ key: 'RGAPI-xxxxxxxx' }) -export async function getAccount () { - // Recommended to use the nearest routing value to your server: americas, asia, europe - return (await api.Account.getByRiotId("Hide on bush", "KR1", Constants.RegionGroups.AMERICAS)).response +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 } } ``` -LOL: -```js + +
+ +
+League of Legends — summoner, ranked & matches + +```ts import { LolApi, Constants } from 'twisted' -const api = new LolApi() +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 -export async function getSummoner () { - const user = await getAccount() - return await api.Summoner.getByPUUID(user.puuid, Constants.Regions.KOREA) + return { summoner, ranked, lastGame } } ``` -TFT: -```js + +
+ +
+Teamfight Tactics — TFT summoner & matches + +```ts import { TftApi, Constants } from 'twisted' -const api = new TftApi() +const api = new TftApi({ key: 'RGAPI-xxxxxxxx' }) -export async function matchListTft () { - const user = await getAccount() - return api.Match.list(user.puuid, Constants.RegionGroups.KOREA) +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 } } +``` + +
+ +--- + +## 🧠 Core concepts + +### Response shape + +Every API method (except Data Dragon) resolves to an `ApiResponseDTO`: + +```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 ``` -[More examples](https://github.com/justadev-afk/twisted/tree/master/example) -# Automatic rate limits reattempts -```js +> 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({ - /** - * If api response is 429 (rate limits) try reattempt after needed time (default true) - */ - rateLimitRetry: true - /** - * Number of time to retry after rate limit response (default 1) - */ - rateLimitRetryAttempts: 1 - /** - * Concurrency calls to riot (default infinity) - * Concurrency per method (example: summoner api, match api, etc) - */ + key: 'RGAPI-xxxxxxxx', + rateLimitRetry: true, + rateLimitRetryAttempts: 1, concurrency: undefined, - /** - * Riot games api key - */ - key: '', - /** - * BaseURL for a rate limiting proxy (default: "https://$(region).api.riotgames.com/:game") - * Using this field is for a very advanced use case and in most cases not necessary - * ${region} and :game are expected but not required variables - */ - baseURL: "http://localhost:8080/${region}/:game", - /** - * Debug methods - */ debug: { - /** - * Log methods execution time (default false) - */ - logTime: false - /** - * Log urls (default false) - */ - logUrls: false - /** - * Log when is waiting for rate limits (default false) - */ - logRatelimit?: false + logTime: false, + logUrls: false, + logRatelimits: false } }) ``` -# Endpoints -Everything should be in the same order as in the official docs. - -# Riot Endpoints -## ACCOUNT-V1 -- [x] `Get account by puuid` -- [ ] `Get account by puuid - ESPORTS` -- [x] `Get account by riot id` -- [ ] `Get account by riot id - ESPORTS` -- [ ] `Get active shard for a player` -- [x] `Get active region (lol and tft)` -- [ ] `Get account by access token` -- [ ] `Get account by access token - ESPORTS` - -# LOL Endpoints -## CHAMPION-MASTERY-V4 -- [x] `Get all champion mastery entries sorted by number of champion points descending.` -- [x] `Get a champion mastery by player ID and champion ID.` -- [x] `Get a player's total champion mastery score, which is the sum of individual champion mastery levels.` -## CHAMPION-V3 -- [x] `Retrieve all champions.` -- [x] `Retrieve champion by ID.` -## CLASH -- [x] `Get players by summoner id` -- [x] `Get team` -- [x] `Get tournaments` -- [x] `Get tournaments by team id` -- [x] `Get tournament by id` -## MATCH-V5 -- [x] `Get match by id` -- [x] `Get matches by summoner id` -- [x] `Get match timeline by id` -- [x] `Get available match replays by PUUID.` -## MATCH-V4 (deprecated) -- [x] `Get matches id by tournament code` -- [x] `Get match by id` -- [x] `Get match by tournament code` -- [x] `Get matches by summoner id` -- [x] `Get match timeline by id` -## LEAGUE-V4 -- [x] `Get the challenger league for given queue.` -- [x] `Get league entries in all queues by PUUID.` -- [x] `Get league entries in all queues for a given summoner ID.` -- [x] `Get all the league entries.` -- [x] `Get the grandmaster league of a specific queue.` -- [x] `Get league with given ID, including inactive entries.` -- [x] `Get the master league for given queue.` -- [x] `Get the queues that have positional ranks enabled.` (deprecated June 17th and in `v0.9.10`) -- [x] `Get league positions in all queues for a given summoner ID.` (deprecated June 17th and in `v0.9.10`) -- [x] `Get all the positional league entries.` (deprecated June 17th and in `v0.9.10`) -## LOL-CHALLENGES-V1 -- [x] `Get all challenge configurations.` -- [x] `Get all challenge percentile distributions.` -- [x] `Get a challenge configuration.` -- [x] `Get Leaderboards for a challenge (Chall, GM, Masters).` -- [x] `Get a challenge percentile distribution.` -- [x] `Get player challenge information.` -## LOL-STATUS-V3 -- [x] `Get League of Legends status for the given shard.` -- [x] `Get matchlist for games played on given account ID and platform ID and filtered using given filter parameters, if any.` -- [x] `Get match timeline by match ID.` -- [x] `Get match IDs by tournament code.` -- [x] `Get match by match ID and tournament code.` -## LOL-STATUS-V4 -- [x] `Get League of Legends status for the given platform.` -## SPECTATOR-V5 -- [x] `Get current game information for the given summoner ID.` -- [x] `Get list of featured games.` -## SPECTATOR-V4 (deprecated [April 5](https://twitter.com/RiotGamesDevRel/status/1764780016640852222?t=pHB1GpVotgKnNYU-OH_1HQ&s=19)) -- [x] `Get current game information for the given summoner ID.` -- [x] `Get list of featured games.` -## SUMMONER-V4 -- [x] `Get a summoner by account ID.` -- [x] `Get a summoner by summoner name.` (deprecated Oct 16th, 2023) -- [x] `Get a summoner by PUUID.` -- [x] `Get a summoner by summoner ID.` -## TOURNAMENT-STUB-V4 -- [ ] `Create a mock tournament code for the given tournament.` -- [ ] `Gets a mock list of lobby events by tournament code.` -- [ ] `Creates a mock tournament provider and returns its ID.` -- [ ] `Creates a mock tournament and returns its ID.` -## TOURNAMENT-V4 -- [ ] `Create a tournament code for the given tournament.` -- [ ] `Returns the tournament code DTO associated with a tournament code string.` -- [ ] `Update the pick type, map, spectator type, or allowed summoners for a code.` -- [ ] `Gets a list of lobby events by tournament code.` -- [ ] `Creates a tournament provider and returns its ID.` -- [ ] `Creates a tournament and returns its ID.` - -# TFT Endpoints -## TFT-SPECTATOR-V5 -- [x] `Get current game information for the given puuid.` -- [x] `Get list of featured games.` -## TFT-SUMMONER-V1 -- [x] `Get a summoner by account ID.` -- [x] `Get a summoner by summoner name.` (deprecated Oct 16th, 2023) -- [x] `Get a summoner by PUUID.` -- [x] `Get a summoner by summoner ID.` -## TFT-MATCH-V1 -- [x] `Get match list by summoner PUUID.` -- [x] `Get match list details.` -## TFT-LEAGUE-V1 -- [x] `Get the challenger league for given queue.` -- [x] `Get the grandmaster league for given queue.` -- [x] `Get the master league for given queue.` -- [x] `Get league entries in all queues for a given summoner ID.` -- [ ] `Get all the league entries.` -- [ ] `Get league with given ID, including inactive entries.` - -# Run all examples - -Download code from git and: - -## Simple -```$ RIOT_API_KEY={YOUR_KEY} yarn example``` - -## Specific examples -```$ RIOT_API_KEY={YOUR_KEY} yarn example {exampleFunctionName}``` - -## With docker -Edit docker-compose.yml with your api key and: -```$ docker-compose up``` - -## Real project -We did a project based on a "twisted" package, this project is not finished but it is a very good example
-Github: https://github.com/twisted-gg - -# Options - -The following environment variables can be set either in the ```.env``` file or as shown in the examples: - -## ```RIOT_API_KEY``` - -Obtained from the Riot Games developer page(https://developer.riotgames.com/) -Necessary to use this library. - -## ```UPDATE_CHAMPION_IDS``` - -This library has an option to fetch an actual version of champion IDs regularly. This is useful in case a new champion -gets added, while the application runs. E.g. data crawlers, or services which aren't supposed to be restarted regularly. - -Set the value to ```true``` or ```1``` to enable this feature. +| 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). + +
+Riot Account + +#### 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 + +
+ +
+League of Legends + +#### 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 + +
+ +
+Teamfight Tactics + +#### 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 + +
+ +--- + +## 🔀 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). + +--- + +
+ +Released under the [MIT License](./LICENSE). + +
diff --git a/example/README.md b/example/README.md index 7de2e550..e52aa00e 100644 --- a/example/README.md +++ b/example/README.md @@ -1,3 +1,52 @@ -# Examples +# Twisted Examples -Here you are example of each api endpoint of league of legends +A runnable example for (almost) every Riot API endpoint that [`twisted`](../) wraps — +League of Legends, Teamfight Tactics, Riot Account, Data Dragon and Clash. + +Each file exports a single named `async` function that performs a small, self-contained +call and logs a friendly summary of the result. They double as living documentation: +read them to see the exact shape of a request and its response. + +## How to run + +Examples need a Riot API key, read from `process.env.RIOT_API_KEY`. Grab one from the +[Riot Developer Portal](https://developer.riotgames.com/). + +```bash +# Run every example +RIOT_API_KEY=RGAPI-xxxx yarn example + +# Run a single example by name (case-insensitive substring match) +RIOT_API_KEY=RGAPI-xxxx yarn example account +RIOT_API_KEY=RGAPI-xxxx yarn example datadragon +``` + +When you pass a name, the runner matches every exported function whose name *contains* +it, runs them, and prints the JSON they return. With no argument, it runs them all in +sequence (with a short delay between calls to stay friendly to rate limits). + +> **Use your own account.** The examples target a few demo Riot IDs defined in +> [`config/config.ts`](./config/config.ts). Edit that file to point at your own +> account (`summonerName` / `tagLine` / `region` / `regionGroup`) before running. + +## What's inside + +| Folder | Category | Examples | Highlights | +| --- | --- | --- | --- | +| [`riot/`](./riot) | **Riot Account** (Account-V1) | 2 | Resolve a Riot ID ↔ PUUID; the entry point for everything else. Routed by region *group*. | +| [`lol/`](./lol) | **League of Legends** | 19 | Summoner, League/ranked, Champion mastery & rotation, Match-V5, Spectator-V5, Challenges, Status. | +| [`lol/*.DataDragon.ts`](./lol) | **Data Dragon** (static CDN) | 12 | Champions, runes, maps, queues, versions and more — no API key, no rate limits. | +| [`lol/deprecated/`](./lol/deprecated) | **LoL (deprecated)** | 11 | Older endpoints kept for reference (summoner-by-name, Match-V4, Spectator-V4, …). Prefer the current ones. | +| [`tft/`](./tft) | **Teamfight Tactics** | 9 | Summoner, league by tier/summoner, Match-V1, Spectator, static files. | +| [`clash/`](./clash) | **Clash** | 2 | List tournaments and fetch one by id. | + +## Regions vs region groups + +Riot uses two routing concepts, both demonstrated in the examples and documented in +[`config/config.ts`](./config/config.ts): + +- **`Regions`** — platform routing (`EUW1`, `KR`, `NA1`, …) used by most LoL/TFT endpoints. +- **`RegionGroups`** — routing groups (`AMERICAS`, `ASIA`, `EUROPE`, `SEA`) used by + Match-V5, TFT-Match and the Account API. + +The library's types enforce which one each endpoint accepts. diff --git a/example/clash/ClashTournamentById.example.ts b/example/clash/ClashTournamentById.example.ts index 220ddd52..05040f16 100644 --- a/example/clash/ClashTournamentById.example.ts +++ b/example/clash/ClashTournamentById.example.ts @@ -1,16 +1,32 @@ import { LolApi } from '../../src' import { config } from '../config/config' -const api = new LolApi() - +/** + * CLASH-V1 — Fetch a single Clash tournament by its id. + * + * There is no fixed tournament id to demo, so we first list the active + * tournaments and reuse the first one's id. Clash is often idle, so we guard + * against an empty list. This endpoint uses the platform `Regions` value. + */ export async function clashTournamentById () { - const { region } = config - const { - response: [ - { - id - } - ] - } = await api.Clash.getTournaments(region) - return api.Clash.getTournamentById(id, region) + const lolApi = new LolApi() + + // 1. List the active Clash tournaments to discover a valid id + const { response: tournaments } = await lolApi.Clash.getTournaments(config.region) + + // 2. Bail out gracefully when no tournament is currently scheduled + if (tournaments.length === 0) { + console.log(`No active Clash tournaments on ${config.region} right now.`) + return null + } + + // 3. Reuse the first tournament's id to fetch its full detail + const [{ id }] = tournaments + const { response: tournament } = await lolApi.Clash.getTournamentById(id, config.region) + + console.log(`Tournament #${tournament.id} — ${tournament.nameKey}`) + console.log(`Theme : ${tournament.themeId}`) + console.log(`Schedule: ${tournament.schedule.length} phase(s)`) + + return tournament } diff --git a/example/clash/ClashTournamentList.example.ts b/example/clash/ClashTournamentList.example.ts index aaff1708..60e5d401 100644 --- a/example/clash/ClashTournamentList.example.ts +++ b/example/clash/ClashTournamentList.example.ts @@ -1,12 +1,24 @@ import { LolApi } from '../../src' import { config } from '../config/config' -const api = new LolApi() - +/** + * CLASH-V1 — List the active Clash tournaments for a platform region. + * + * Clash is a scheduled competitive mode, so the list is often empty when no + * tournament is running. This endpoint uses the platform `Regions` value. + */ export async function clashTournamentList () { - const { region } = config - const { - response - } = await api.Clash.getTournaments(region) - return response + const lolApi = new LolApi() + + // 1. Fetch every active/scheduled Clash tournament on the platform region + const { response: tournaments } = await lolApi.Clash.getTournaments(config.region) + + console.log(`Active Clash tournaments on ${config.region}: ${tournaments.length}`) + + // 2. Print a short summary for each tournament (id + theme) + for (const tournament of tournaments) { + console.log(`#${tournament.id} — themeId ${tournament.themeId} (${tournament.nameKey})`) + } + + return tournaments } diff --git a/example/config/config.ts b/example/config/config.ts index 259ab4cb..86dd1279 100644 --- a/example/config/config.ts +++ b/example/config/config.ts @@ -1,14 +1,42 @@ import { Constants } from '../../src' +import { Regions, RegionGroups, AccountAPIRegionGroups } from '../../src/constants' import { Levels } from '../../src/constants/levels' -export const config = { +/** + * Shared configuration used across the examples. + * + * Riot IDs are made of a `gameName` + `tagLine` (the value after the `#`), + * e.g. `Thebausffs#COOL`. + * + * Three different "region" concepts show up in the Riot API: + * - `Regions` (platform routing, e.g. `EUW1`, `KR`, `NA1`) — most LoL/TFT endpoints. + * - `RegionGroups` (`AMERICAS` | `ASIA` | `EUROPE` | `SEA`) — Match-V5 / TFT-Match. + * - `AccountAPIRegionGroups` (`RegionGroups` without `SEA`) — the Account-V1 API. + */ +interface ExampleAccount { + /** Riot ID game name (the part before the `#`). */ + summonerName: string + /** Riot ID tag line (the part after the `#`). */ + tagLine: string + /** Platform region for LoL/TFT platform endpoints. */ + region: Regions + /** Routing value for the Account-V1 API (americas / asia / europe). */ + regionGroup: AccountAPIRegionGroups +} + +export const config: ExampleAccount = { summonerName: 'Thebausffs', tagLine: 'COOL', region: Constants.Regions.EU_WEST, - regionGroup: Constants.RegionGroups.AMERICAS, + regionGroup: Constants.RegionGroups.EUROPE +} + +interface ExampleTftAccount extends ExampleAccount { + /** Routing value for the TFT Match-V1 API. */ + tftRegion: RegionGroups } -export const configTft = { +export const configTft: ExampleTftAccount = { summonerName: 'Meinya', tagLine: 'NA1', region: Constants.Regions.AMERICA_NORTH, @@ -16,12 +44,19 @@ export const configTft = { tftRegion: Constants.RegionGroups.AMERICAS } +interface ExampleChallengesAccount extends ExampleAccount { + /** A challenge id (see the Challenges-V1 config endpoint). */ + challengeId: number + /** Tier used when querying challenge leaderboards. */ + level: Levels +} + // Used for challengeID -export const configChallenges = { +export const configChallenges: ExampleChallengesAccount = { summonerName: 'Night Owl', tagLine: 'ryi', region: Constants.Regions.AMERICA_NORTH, regionGroup: Constants.RegionGroups.AMERICAS, challengeId: 101106, // ARAM Eradication level: Levels.CHALLENGER -} \ No newline at end of file +} diff --git a/example/lol/ChallengerLeagueByQueue.example.ts b/example/lol/ChallengerLeagueByQueue.example.ts index 6fde91fd..40362b37 100644 --- a/example/lol/ChallengerLeagueByQueue.example.ts +++ b/example/lol/ChallengerLeagueByQueue.example.ts @@ -1,9 +1,29 @@ -import { LolApi } from '../../src' +import { LolApi, Constants } from '../../src' import { config } from '../config/config' -import { Queues } from '../../src/constants' - -const api = new LolApi() +/** + * LEAGUE-V4 — Get the Challenger league for a given ranked queue. + * + * Returns the single apex league for the region/queue, with every Challenger + * player listed under `entries`. + */ export async function challengerLeagueByQueueExample () { - return await api.League.getChallengerLeaguesByQueue(Queues.RANKED_SOLO_5x5, config.region) + const lolApi = new LolApi() + + // Fetch the Challenger ladder for solo/duo queue on this region + const { response: league } = await lolApi.League.getChallengerLeaguesByQueue( + Constants.Queues.RANKED_SOLO_5x5, + config.region + ) + + console.log(`Challenger league "${league.name}" (${league.queue})`) + console.log(`Players: ${league.entries.length}`) + + // Highlight the highest-LP player in the ladder + const top = [...league.entries].sort((a, b) => b.leaguePoints - a.leaguePoints)[0] + if (top) { + console.log(`Top player: ${top.leaguePoints} LP — ${top.wins}W/${top.losses}L`) + } + + return league } diff --git a/example/lol/Challenges.example.ts b/example/lol/Challenges.example.ts index c6c84435..a8c2d104 100644 --- a/example/lol/Challenges.example.ts +++ b/example/lol/Challenges.example.ts @@ -1,28 +1,65 @@ -import { RiotApi } from '../../src' -import { LolApi } from '../../src/apis/lol/lol' +import { LolApi, RiotApi } from '../../src' import { configChallenges } from '../config/config' -export async function challengesV1Example() { - const rApi = new RiotApi() - const api = new LolApi() +/** + * LOL-CHALLENGES-V1 — Explore the Challenges system. + * + * Challenges are achievement-style goals with levels (IRON … CHALLENGER) and + * leaderboards. This example walks the main read endpoints: + * - PlayerChallenges : a single player's progress (needs a PUUID) + * - Leaderboards : the top players for one challenge at a level + * - Configs : the static config for every challenge + * - ChallengeConfig : the static config for one challenge + * - Percentiles : value distribution across ALL challenges + * - ChallengePercentiles: value distribution for one challenge + */ +export async function challengesV1Example () { + const riotApi = new RiotApi() + const lolApi = new LolApi() - const { response: { puuid } } = await rApi.Account.getByRiotId(configChallenges.summonerName, configChallenges.tagLine, configChallenges.regionGroup) + // 1. Resolve the Riot ID into a PUUID (PlayerChallenges is keyed by PUUID) + const { response: account } = await riotApi.Account.getByRiotId( + configChallenges.summonerName, + configChallenges.tagLine, + configChallenges.regionGroup + ) - const playerChallenges = (await api.Challenges.PlayerChallenges(puuid, configChallenges.region)).response - // console.log('Found total challenge points:', playerChallenges.totalPoints) + // 2. The player's own challenge progress and summed points + const { response: playerChallenges } = await lolApi.Challenges.PlayerChallenges( + account.puuid, + configChallenges.region + ) + console.log(`${account.gameName}#${account.tagLine} total points: ${playerChallenges.totalPoints.current}/${playerChallenges.totalPoints.max} (${playerChallenges.totalPoints.level})`) - const leaderboards = (await api.Challenges.Leaderboards(configChallenges.challengeId, configChallenges.level, configChallenges.region, { limit: 5 })).response - // console.log(`Top 5 ${configChallenges.level} for ARAM Eradication for `, leaderboards) + // 3. Top 5 players for our example challenge at the configured level + const { response: leaderboards } = await lolApi.Challenges.Leaderboards( + configChallenges.challengeId, + configChallenges.level, + configChallenges.region, + { limit: 5 } + ) + console.log(`Leaderboard entries for challenge ${configChallenges.challengeId}: ${leaderboards.length}`) - const configs = (await api.Challenges.Configs(configChallenges.region)).response - // console.log("Config thresholds for the first of all basic challenges:", configs[0]) + // 4. Static config for every challenge on this region + const { response: configs } = await lolApi.Challenges.Configs(configChallenges.region) + console.log(`Total challenges configured: ${configs.length}`) - const config = (await api.Challenges.ChallengeConfig(configChallenges.challengeId, configChallenges.region)).response - // console.log('Challenge Configuration for ARAM Eradication', config) + // 5. Static config for just our example challenge + const { response: challengeConfig } = await lolApi.Challenges.ChallengeConfig( + configChallenges.challengeId, + configChallenges.region + ) + console.log(`Challenge ${challengeConfig.id} has leaderboard: ${challengeConfig.leaderboard}`) - const distributions = (await api.Challenges.Percentiles(configChallenges.region)).response - // console.log("Distribution for ARAM Eradication:", distributions[configChallenges.challengeId]) + // 6. Value distribution across ALL challenges, then narrowed to our challenge + const { response: percentiles } = await lolApi.Challenges.Percentiles(configChallenges.region) + console.log(`Percentile data available for ${Object.keys(percentiles).length} challenges`) - const distribution = (await api.Challenges.ChallengePercentiles(configChallenges.challengeId, configChallenges.region)).response - // console.log("Distribution for ARAM Eradication:", distribution) -} \ No newline at end of file + const { response: challengePercentiles } = await lolApi.Challenges.ChallengePercentiles( + configChallenges.challengeId, + configChallenges.region + ) + console.log(`Levels with a percentile for challenge ${configChallenges.challengeId}: ${Object.keys(challengePercentiles).join(', ')}`) + + return playerChallenges +} diff --git a/example/lol/ChampionMasteryByPUUID.example.ts b/example/lol/ChampionMasteryByPUUID.example.ts index d8d2f38d..19bf0f67 100644 --- a/example/lol/ChampionMasteryByPUUID.example.ts +++ b/example/lol/ChampionMasteryByPUUID.example.ts @@ -1,11 +1,34 @@ -import { RiotApi } from '../../src' -import { LolApi } from '../../src' +import { LolApi, RiotApi } from '../../src' import { config } from '../config/config' -const rApi = new RiotApi() -const api = new LolApi() - +/** + * CHAMPION-MASTERY-V4 — Get all champion mastery entries for a player by PUUID. + * + * Mastery is keyed by PUUID, so we first resolve the account's PUUID through + * the Riot Account API, then fetch every champion the player has points on. + */ export async function championMasteryByPUUID () { - const { response: { puuid } } = await rApi.Account.getByRiotId(config.summonerName, config.tagLine, config.regionGroup) - return await api.Champion.masteryByPUUID(puuid, config.region) + const riotApi = new RiotApi() + const lolApi = new LolApi() + + // 1. Resolve the Riot ID (gameName#tagLine) into a PUUID + const { response: account } = await riotApi.Account.getByRiotId( + config.summonerName, + config.tagLine, + config.regionGroup + ) + + // 2. Fetch every champion mastery entry on the player's platform region + const { response: masteries } = await lolApi.Champion.masteryByPUUID(account.puuid, config.region) + + console.log(`Player : ${account.gameName}#${account.tagLine}`) + console.log(`Champions : ${masteries.length}`) + + // 3. The API already returns the list sorted by championPoints (highest first) + const top = masteries[0] + if (top) { + console.log(`Top champion : id ${top.championId} (level ${top.championLevel}, ${top.championPoints} pts)`) + } + + return masteries } diff --git a/example/lol/ChampionMasteryByPUUIDChampion.example.ts b/example/lol/ChampionMasteryByPUUIDChampion.example.ts index 30b1e54d..ab9983e1 100644 --- a/example/lol/ChampionMasteryByPUUIDChampion.example.ts +++ b/example/lol/ChampionMasteryByPUUIDChampion.example.ts @@ -1,11 +1,37 @@ -import { RiotApi } from '../../src' -import { LolApi } from '../../src' +import { LolApi, RiotApi } from '../../src' import { config } from '../config/config' -const rApi = new RiotApi() -const api = new LolApi() - +/** + * CHAMPION-MASTERY-V4 — Get a single champion's mastery for a player by PUUID. + * + * Like the full-list endpoint, this is keyed by PUUID, so we resolve the + * account first and then ask for one specific champion id (here: Annie, id 1). + */ export async function championMasteryByPUUIDByChampion () { - const { response: { puuid } } = await rApi.Account.getByRiotId(config.summonerName, config.tagLine, config.regionGroup) - return await api.Champion.masteryByPUUIDChampion(puuid, 1, config.region) + const riotApi = new RiotApi() + const lolApi = new LolApi() + + const championId = 1 // Annie + + // 1. Resolve the Riot ID (gameName#tagLine) into a PUUID + const { response: account } = await riotApi.Account.getByRiotId( + config.summonerName, + config.tagLine, + config.regionGroup + ) + + // 2. Fetch this single champion's mastery on the player's platform region + const { response: mastery } = await lolApi.Champion.masteryByPUUIDChampion( + account.puuid, + championId, + config.region + ) + + console.log(`Player : ${account.gameName}#${account.tagLine}`) + console.log(`Champion id : ${mastery.championId}`) + console.log(`Mastery level : ${mastery.championLevel}`) + console.log(`Mastery points : ${mastery.championPoints}`) + console.log(`Until next level : ${mastery.championPointsUntilNextLevel}`) + + return mastery } diff --git a/example/lol/ChampionRotation.example.ts b/example/lol/ChampionRotation.example.ts index 47a567cf..04d39257 100644 --- a/example/lol/ChampionRotation.example.ts +++ b/example/lol/ChampionRotation.example.ts @@ -1,7 +1,30 @@ import { LolApi } from '../../src' -import { Regions } from '../../src/constants' +import { config } from '../config/config' +/** Shape of the CHAMPION-V3 rotation payload (the call itself is untyped on the wire). */ +interface ChampionRotation { + freeChampionIds: number[] + freeChampionIdsForNewPlayers: number[] + maxNewPlayerLevel: number[] +} + +/** + * CHAMPION-V3 — Get the current free champion rotation. + * + * Returns the champions that are free-to-play this week (for all players and + * for players who have not yet reached level 11), keyed by numeric champion id. + */ export async function championRotationExample () { const api = new LolApi() - return await api.Champion.rotation(Regions.LAT_NORTH) + + // Champion rotation is a platform endpoint, so it takes a platform `Regions` value. + // `rotation()` is untyped on the wire, so we annotate the expected DTO here. + const { response } = await api.Champion.rotation(config.region) + const rotation = response as ChampionRotation + + console.log(`Free champions this week : ${rotation.freeChampionIds.length}`) + console.log(`Free for new players (<11) : ${rotation.freeChampionIdsForNewPlayers.length}`) + console.log(`Max new player levels : ${rotation.maxNewPlayerLevel.join(', ')}`) + + return rotation } diff --git a/example/lol/ChampionsScore.example.ts b/example/lol/ChampionsScore.example.ts index c1ecd1b6..c1b0270b 100644 --- a/example/lol/ChampionsScore.example.ts +++ b/example/lol/ChampionsScore.example.ts @@ -1,11 +1,32 @@ -import { RiotApi } from '../../src' -import { LolApi } from '../../src' +import { LolApi, RiotApi } from '../../src' import { config } from '../config/config' -const rApi = new RiotApi() -const api = new LolApi() - +/** + * CHAMPION-MASTERY-V4 — Get a player's total champion mastery score by PUUID. + * + * The mastery score is the sum of a player's individual champion mastery + * levels. We resolve the account's PUUID first, then read the score. + * + * Note: `championsScore` returns the `ChampionsScoreDTO` directly (no + * `{ response }` wrapper). + */ export async function championsScoreExample () { - const { response: { puuid } } = await rApi.Account.getByRiotId(config.summonerName, config.region, config.regionGroup) - return await api.Champion.championsScore(puuid, config.region) + const riotApi = new RiotApi() + const lolApi = new LolApi() + + // 1. Resolve the Riot ID (gameName#tagLine) into a PUUID. + // getByRiotId takes (gameName, tagLine, accountRegionGroup). + const { response: account } = await riotApi.Account.getByRiotId( + config.summonerName, + config.tagLine, + config.regionGroup + ) + + // 2. Fetch the total mastery score on the player's platform region + const score = await lolApi.Champion.championsScore(account.puuid, config.region) + + console.log(`Player : ${account.gameName}#${account.tagLine}`) + console.log(`Mastery score : ${score.score}`) + + return score } diff --git a/example/lol/GameModes.DataDragon.ts b/example/lol/GameModes.DataDragon.ts index 22db7c43..ccd98650 100644 --- a/example/lol/GameModes.DataDragon.ts +++ b/example/lol/GameModes.DataDragon.ts @@ -1,7 +1,21 @@ import { LolApi } from '../../src' -const api = new LolApi() - +/** + * DATA DRAGON — Game modes (static). + * + * Returns the static game-mode reference: each `gameMode` code mapped to a + * human description (e.g. CLASSIC -> "Classic Summoner's Rift and Twisted + * Treeline games"). Served from the static CDN: no key, no rate limits. + */ export async function gameModesDataDragon () { - return api.DataDragon.getGameModes() + // Data Dragon needs no key + const api = new LolApi() + + // 1. Fetch the static game-mode table + const gameModes = await api.DataDragon.getGameModes() + + console.log(`Game modes defined: ${gameModes.length}`) + console.log(`Codes : ${gameModes.map((g) => g.gameMode).join(', ')}`) + + return gameModes } diff --git a/example/lol/GameTypes.DataDragon.ts b/example/lol/GameTypes.DataDragon.ts index 7f59edf4..75795145 100644 --- a/example/lol/GameTypes.DataDragon.ts +++ b/example/lol/GameTypes.DataDragon.ts @@ -1,7 +1,21 @@ import { LolApi } from '../../src' -const api = new LolApi() - +/** + * DATA DRAGON — Game types (static). + * + * Returns the static game-type reference: each `gametype` code mapped to a + * human description (e.g. CUSTOM_GAME, MATCHED_GAME, TUTORIAL_GAME). + * Served from the static CDN: no key, no rate limits. + */ export async function gameTypessDataDragon () { - return api.DataDragon.getGameTypes() + // Data Dragon needs no key + const api = new LolApi() + + // 1. Fetch the static game-type table + const gameTypes = await api.DataDragon.getGameTypes() + + console.log(`Game types defined: ${gameTypes.length}`) + console.log(`Codes : ${gameTypes.map((g) => g.gametype).join(', ')}`) + + return gameTypes } diff --git a/example/lol/GetChampion.DataDragon.ts b/example/lol/GetChampion.DataDragon.ts index f7b23e1f..abe0d9d6 100644 --- a/example/lol/GetChampion.DataDragon.ts +++ b/example/lol/GetChampion.DataDragon.ts @@ -1,8 +1,23 @@ -import { LolApi } from '../../src' -import { RealmServers, Champions } from '../../src/constants' - -const api = new LolApi() +import { LolApi, Constants } from '../../src' +/** + * DATA DRAGON — Single champion details. + * + * Passing a `Constants.Champions` value returns the *full* champion blob for + * that one champion: lore, spells, passive, skins and base stats. Raw CDN + * data: no key, no rate limits and no `{ response }` wrapper. + */ export async function getChampionDetailsDataDragon () { - return api.DataDragon.getChampion(Champions.TWISTED_FATE) + // Data Dragon needs no key + const api = new LolApi() + + // 1. Fetch the detailed payload for a single champion (Twisted Fate here) + const champion = await api.DataDragon.getChampion(Constants.Champions.TWISTED_FATE) + + console.log(`Champion: ${champion.name} — ${champion.title}`) + console.log(`Tags : ${champion.tags.join(', ')}`) + console.log(`Spells : ${champion.spells.map((s) => s.name).join(', ')}`) + console.log(`Skins : ${champion.skins.length}`) + + return champion } diff --git a/example/lol/GetChampionList.DataDragon.ts b/example/lol/GetChampionList.DataDragon.ts index 115a919f..bfcc0a56 100644 --- a/example/lol/GetChampionList.DataDragon.ts +++ b/example/lol/GetChampionList.DataDragon.ts @@ -1,8 +1,25 @@ import { LolApi } from '../../src' -import { RealmServers, Champions } from '../../src/constants' - -const api = new LolApi() +/** + * DATA DRAGON — Champion list. + * + * Calling `getChampion()` with no argument returns the *summary* list of all + * champions for the latest patch, keyed by champion id under `data`. Each + * entry is a lightweight blurb (no spells/skins). Raw CDN data: no key. + */ export async function getChampionListDataDragon () { - return api.DataDragon.getChampion() + // Data Dragon needs no key + const api = new LolApi() + + // 1. Fetch the full champion roster for the latest patch + const championList = await api.DataDragon.getChampion() + + // 2. `data` is an object keyed by champion id (e.g. "Aatrox", "Ahri", ...) + const ids = Object.keys(championList.data) + + console.log(`Patch : ${championList.version}`) + console.log(`Champions : ${ids.length}`) + console.log(`First few : ${ids.slice(0, 5).join(', ')}`) + + return championList } diff --git a/example/lol/GrandMasterLeagueByQueue.example.ts b/example/lol/GrandMasterLeagueByQueue.example.ts index 74bc55ea..26264c6a 100644 --- a/example/lol/GrandMasterLeagueByQueue.example.ts +++ b/example/lol/GrandMasterLeagueByQueue.example.ts @@ -1,9 +1,29 @@ -import { LolApi } from '../../src' +import { LolApi, Constants } from '../../src' import { config } from '../config/config' -import { Queues } from '../../src/constants' - -const api = new LolApi() +/** + * LEAGUE-V4 — Get the Grandmaster league for a given ranked queue. + * + * Returns the single apex league for the region/queue, with every Grandmaster + * player listed under `entries`. + */ export async function grandmasterLeagueByQueueExample () { - return await api.League.getGrandMasterLeagueByQueue(Queues.RANKED_SOLO_5x5, config.region) + const lolApi = new LolApi() + + // Fetch the Grandmaster ladder for solo/duo queue on this region + const { response: league } = await lolApi.League.getGrandMasterLeagueByQueue( + Constants.Queues.RANKED_SOLO_5x5, + config.region + ) + + console.log(`Grandmaster league "${league.name}" (${league.queue})`) + console.log(`Players: ${league.entries.length}`) + + // Highlight the highest-LP player in the ladder + const top = [...league.entries].sort((a, b) => b.leaguePoints - a.leaguePoints)[0] + if (top) { + console.log(`Top player: ${top.leaguePoints} LP — ${top.wins}W/${top.losses}L`) + } + + return league } diff --git a/example/lol/Languages.DataDragon.ts b/example/lol/Languages.DataDragon.ts index 85d65275..efe10877 100644 --- a/example/lol/Languages.DataDragon.ts +++ b/example/lol/Languages.DataDragon.ts @@ -1,7 +1,21 @@ import { LolApi } from '../../src' -const api = new LolApi() - +/** + * DATA DRAGON — Languages. + * + * Returns the list of locale codes (e.g. `en_US`, `es_ES`, `ko_KR`) that + * Data Dragon ships localized data for. Pass any of these as the `lang` + * argument of `getChampion`, `getChampionList`, etc. Raw CDN data: no key. + */ export async function languagesDataDragon () { - return api.DataDragon.getLanguages() + // Data Dragon needs no key + const api = new LolApi() + + // 1. Fetch every supported locale code + const languages = await api.DataDragon.getLanguages() + + console.log(`Locales available: ${languages.length}`) + console.log(`Examples : ${languages.slice(0, 5).join(', ')}`) + + return languages } diff --git a/example/lol/League.example.ts b/example/lol/League.example.ts index b4a8eeb6..6ddefdb3 100644 --- a/example/lol/League.example.ts +++ b/example/lol/League.example.ts @@ -1,14 +1,40 @@ -import { RiotApi } from '../../src' -import { LolApi } from '../../src' +import { LolApi, RiotApi } from '../../src' import { config } from '../config/config' -const rApi = new RiotApi() -const api = new LolApi() - +/** + * LEAGUE-V4 — Get a full league by its leagueId. + * + * A leagueId identifies a whole division group; the response lists every + * member in it. To find a leagueId we resolve our account, look up its ranked + * entries, and then fetch the league one of those entries belongs to. + */ export async function leagueExample () { - const { response: { puuid } } = await rApi.Account.getByRiotId(config.summonerName, config.tagLine, config.regionGroup) - const { response: { id }} = await api.Summoner.getByPUUID(puuid, config.region) - // For below, response does not guarantee order for { response: [league]} destructuring - const league = (await api.League.bySummoner(id, config.region)).response.find(o => o.leagueId) - return league ? await api.League.get(league.leagueId, config.region) : null + const riotApi = new RiotApi() + const lolApi = new LolApi() + + // 1. Resolve the Riot ID into a PUUID, then into a summonerId + const { response: account } = await riotApi.Account.getByRiotId( + config.summonerName, + config.tagLine, + config.regionGroup + ) + const { response: summoner } = await lolApi.Summoner.getByPUUID(account.puuid, config.region) + + // 2. Fetch the player's ranked entries and pick one that exposes a leagueId. + // The order of entries is not guaranteed, so search rather than destructure. + const { response: entries } = await lolApi.League.bySummoner(summoner.id, config.region) + const entry = entries.find(o => o.leagueId) + + if (!entry) { + console.log(`${account.gameName}#${account.tagLine} has no ranked league entries.`) + return null + } + + // 3. Fetch the full league that entry belongs to + const { response: league } = await lolApi.League.get(entry.leagueId, config.region) + + console.log(`League "${league.name}" — ${league.tier} (${league.queue})`) + console.log(`Members: ${league.entries.length}`) + + return league } diff --git a/example/lol/LeagueByPUUID.example.ts b/example/lol/LeagueByPUUID.example.ts index 490c0a5a..ad5b024c 100644 --- a/example/lol/LeagueByPUUID.example.ts +++ b/example/lol/LeagueByPUUID.example.ts @@ -1,12 +1,32 @@ import { LolApi, RiotApi } from '../../src' -import { Regions } from '../../src/constants' import { config } from '../config/config' -export async function leaguesByPUUIDExample() { - const api = new LolApi() - const rApi = new RiotApi() +/** + * LEAGUE-V4 — Get ranked entries for a summoner by PUUID. + * + * This is the modern replacement for the summonerId-based lookup: resolve the + * account's PUUID through ACCOUNT-V1, then read its ranked entries directly. + */ +export async function leaguesByPUUIDExample () { + const riotApi = new RiotApi() + const lolApi = new LolApi() - const { response: { puuid }} = await rApi.Account.getByRiotId(config.summonerName, config.tagLine, config.regionGroup) - const { response: data } = await api.League.byPUUID(puuid, Regions.EU_WEST) - return data; + // 1. Resolve the Riot ID into a PUUID + const { response: account } = await riotApi.Account.getByRiotId( + config.summonerName, + config.tagLine, + config.regionGroup + ) + + // 2. Fetch every ranked entry for that PUUID (one per ranked queue) + const { response: leagues } = await lolApi.League.byPUUID(account.puuid, config.region) + + console.log(`Ranked entries for ${account.gameName}#${account.tagLine}: ${leagues.length}`) + for (const league of leagues) { + console.log( + ` ${league.queueType}: ${league.tier} ${league.rank} (${league.leaguePoints} LP)` + ) + } + + return leagues } diff --git a/example/lol/LeagueEntries.example.ts b/example/lol/LeagueEntries.example.ts index 0f5a0cc9..8dc76eeb 100644 --- a/example/lol/LeagueEntries.example.ts +++ b/example/lol/LeagueEntries.example.ts @@ -1,9 +1,27 @@ -import { LolApi } from '../../src' -import { Queues, Tiers, Divisions, Regions } from '../../src/constants' +import { LolApi, Constants } from '../../src' +import { config } from '../config/config' +/** + * LEAGUE-V4 — Get all (paginated) league entries for a queue / tier / division. + * + * Unlike LEAGUE-EXP-V4, this endpoint does not cover the apex tiers + * (MASTER / GRANDMASTER / CHALLENGER); use the *ByQueue helpers for those. + */ export async function leagueEntriesExample () { - const api = new LolApi() - const { response: entries } = await api.League.entries(Queues.RANKED_SOLO_5x5, Tiers.BRONZE, Divisions.I, Regions.LAT_NORTH) + const lolApi = new LolApi() - return entries; + // Fetch page 1 of every Bronze I solo-queue player on this region + const { response: entries } = await lolApi.League.entries( + Constants.Queues.RANKED_SOLO_5x5, + Constants.Tiers.BRONZE, + Constants.Divisions.I, + config.region + ) + + console.log(`Bronze I (${Constants.Queues.RANKED_SOLO_5x5}) entries on page 1: ${entries.length}`) + for (const entry of entries.slice(0, 5)) { + console.log(` ${entry.leaguePoints} LP — ${entry.wins}W/${entry.losses}L (puuid ${entry.puuid})`) + } + + return entries } diff --git a/example/lol/LeagueExp.example.ts b/example/lol/LeagueExp.example.ts index 858c445b..8c21e6c9 100644 --- a/example/lol/LeagueExp.example.ts +++ b/example/lol/LeagueExp.example.ts @@ -1,7 +1,29 @@ -import { LolApi } from '../../src' -import { Queues, Tiers, Divisions, Regions } from '../../src/constants' +import { LolApi, Constants } from '../../src' +import { config } from '../config/config' +/** + * LEAGUE-EXP-V4 — Experimental, paginated league entries for a given + * queue / tier / division. + * + * Same data shape as LEAGUE-V4 entries, but this endpoint also returns the + * apex tiers (MASTER / GRANDMASTER / CHALLENGER), which the classic entries + * endpoint does not. + */ export async function leagueExpExample () { - const api = new LolApi() - return await api.League.exp(Queues.RANKED_SOLO_5x5, Tiers.BRONZE, Divisions.I, Regions.AMERICA_NORTH) + const lolApi = new LolApi() + + // Query page 1 of every Bronze I solo-queue player on this region + const { response: entries } = await lolApi.League.exp( + Constants.Queues.RANKED_SOLO_5x5, + Constants.Tiers.BRONZE, + Constants.Divisions.I, + config.region + ) + + console.log(`Bronze I (${Constants.Queues.RANKED_SOLO_5x5}) entries on page 1: ${entries.length}`) + for (const entry of entries.slice(0, 5)) { + console.log(` ${entry.leaguePoints} LP — ${entry.wins}W/${entry.losses}L (puuid ${entry.puuid})`) + } + + return entries } diff --git a/example/lol/Maps.DataDragon.ts b/example/lol/Maps.DataDragon.ts index d8b95f18..f653042b 100644 --- a/example/lol/Maps.DataDragon.ts +++ b/example/lol/Maps.DataDragon.ts @@ -1,7 +1,21 @@ import { LolApi } from '../../src' -const api = new LolApi() - +/** + * DATA DRAGON — Maps (static). + * + * Returns the static map reference: each `mapId` mapped to its name + * (e.g. 11 -> "Summoner's Rift", 12 -> "Howling Abyss"). Useful for decoding + * the `mapId` on matches. Served from the static CDN: no key. + */ export async function mapsDataDragon () { - return api.DataDragon.getMaps() + // Data Dragon needs no key + const api = new LolApi() + + // 1. Fetch the static map table + const maps = await api.DataDragon.getMaps() + + console.log(`Maps defined: ${maps.length}`) + maps.forEach((m) => console.log(` ${m.mapId} -> ${m.mapName}`)) + + return maps } diff --git a/example/lol/MasterLeagueByQueue.example.ts b/example/lol/MasterLeagueByQueue.example.ts index 86383684..de3c3822 100644 --- a/example/lol/MasterLeagueByQueue.example.ts +++ b/example/lol/MasterLeagueByQueue.example.ts @@ -1,9 +1,29 @@ -import { LolApi } from '../../src' +import { LolApi, Constants } from '../../src' import { config } from '../config/config' -import { Queues } from '../../src/constants' - -const api = new LolApi() +/** + * LEAGUE-V4 — Get the Master league for a given ranked queue. + * + * Returns the single apex league for the region/queue, with every Master + * player listed under `entries`. + */ export async function masterLeagueByQueue () { - return await api.League.getMasterLeagueByQueue(Queues.RANKED_SOLO_5x5, config.region) + const lolApi = new LolApi() + + // Fetch the Master ladder for solo/duo queue on this region + const { response: league } = await lolApi.League.getMasterLeagueByQueue( + Constants.Queues.RANKED_SOLO_5x5, + config.region + ) + + console.log(`Master league "${league.name}" (${league.queue})`) + console.log(`Players: ${league.entries.length}`) + + // Highlight the highest-LP player in the ladder + const top = [...league.entries].sort((a, b) => b.leaguePoints - a.leaguePoints)[0] + if (top) { + console.log(`Top player: ${top.leaguePoints} LP — ${top.wins}W/${top.losses}L`) + } + + return league } diff --git a/example/lol/MatchV5Replays.example.ts b/example/lol/MatchV5Replays.example.ts index 35e101f5..589eec6a 100644 --- a/example/lol/MatchV5Replays.example.ts +++ b/example/lol/MatchV5Replays.example.ts @@ -1,15 +1,32 @@ -import { RiotApi } from '../../src' -import { LolApi } from '../../src' -import { AccountAPIRegionGroups, RegionGroups } from '../../src/constants' +import { LolApi, RiotApi } from '../../src' import { config } from '../config/config' -export async function matchV5ReplaysExample() { - const api = new LolApi() - const rApi = new RiotApi() +/** + * MATCH-V5 — Get replay (ROFL) file URLs for a player's recent matches. + * + * Replays are routed by region group (AMERICAS / ASIA / EUROPE), so we resolve + * the account's PUUID via the Account API, then request the downloadable + * replay file URLs. + */ +export async function matchV5ReplaysExample () { + const riotApi = new RiotApi() + const lolApi = new LolApi() - const { response: { puuid }} = await rApi.Account.getByRiotId(config.summonerName, config.tagLine, config.regionGroup as AccountAPIRegionGroups) - const { response: data } = await api.MatchV5.replays(puuid, RegionGroups.EUROPE) - console.log(data); -} + // 1. Resolve the Riot ID (gameName#tagLine) into a PUUID + const { response: account } = await riotApi.Account.getByRiotId( + config.summonerName, + config.tagLine, + config.regionGroup + ) + + // 2. Replays are a Match-V5 endpoint -> use the account's region GROUP + const { response: replays } = await lolApi.MatchV5.replays(account.puuid, config.regionGroup) -matchV5ReplaysExample() + console.log(`Player : ${account.gameName}#${account.tagLine}`) + console.log(`Replay files : ${replays.total}`) + if (replays.matchFileURLs[0]) { + console.log(`First replay : ${replays.matchFileURLs[0]}`) + } + + return replays +} diff --git a/example/lol/MatchV5TimelineLatestMatch.example.ts b/example/lol/MatchV5TimelineLatestMatch.example.ts index 268db9aa..7ec4f931 100644 --- a/example/lol/MatchV5TimelineLatestMatch.example.ts +++ b/example/lol/MatchV5TimelineLatestMatch.example.ts @@ -1,22 +1,46 @@ -import { LolApi } from '../../src/apis/lol/lol' -import { Regions, RegionGroups } from '../../src/constants' +import { LolApi, RiotApi } from '../../src' +import { config } from '../config/config' -export async function matchV5TimelineLatestMatchExample() { - const api = new LolApi() +/** + * MATCH-V5 — Walk from a Riot ID to a match timeline. + * + * Summoner-V4 no longer accepts a name, so we resolve the PUUID through the + * Account API and then chain the Match-V5 endpoints: + * list (match ids) -> get (match details) -> timeline (per-frame events). + * Match-V5 is routed by region GROUP (AMERICAS / ASIA / EUROPE). + */ +export async function matchV5TimelineLatestMatchExample () { + const riotApi = new RiotApi() + const lolApi = new LolApi() - const summonerName = "Cookie Hater" - const summoner = (await api.Summoner.getByName(summonerName, Regions.EU_WEST)).response - console.log("Found summoner:", summoner.puuid) + // 1. Resolve the Riot ID (gameName#tagLine) into a PUUID + const { response: account } = await riotApi.Account.getByRiotId( + config.summonerName, + config.tagLine, + config.regionGroup + ) + console.log(`Player : ${account.gameName}#${account.tagLine}`) - const matchlist = (await api.MatchV5.list(summoner.puuid, RegionGroups.EUROPE, { queue: 450 })).response - console.log("Matchlist length:", matchlist.length) + // 2. List the player's most recent ARAM matches (queue 450), routed by region group + const { response: matchIds } = await lolApi.MatchV5.list(account.puuid, config.regionGroup, { + queue: 450, + count: 5 + }) + console.log(`Match ids : ${matchIds.length}`) - const matchId = matchlist[0] - const match = (await api.MatchV5.get(matchId, RegionGroups.EUROPE)).response - console.log("Found match with id:", match.metadata.matchId) + const matchId = matchIds[0] + if (!matchId) { + console.log('No matches found for this player') + return undefined + } - const timeline = (await api.MatchV5.timeline(matchId, RegionGroups.EUROPE)).response - console.log("Timeline length:", timeline.info.frames.length) -} + // 3. Fetch the match details for the most recent match + const { response: match } = await lolApi.MatchV5.get(matchId, config.regionGroup) + console.log(`Match : ${match.metadata.matchId} (queue ${match.info.queueId}, ${match.info.gameDuration}s)`) + + // 4. Fetch the timeline (frames of per-participant state and events) + const { response: timeline } = await lolApi.MatchV5.timeline(matchId, config.regionGroup) + console.log(`Frames : ${timeline.info.frames.length}`) -matchV5TimelineLatestMatchExample() + return timeline +} diff --git a/example/lol/Queues.DataDragon.ts b/example/lol/Queues.DataDragon.ts index 3aee8faf..9acb5556 100644 --- a/example/lol/Queues.DataDragon.ts +++ b/example/lol/Queues.DataDragon.ts @@ -1,7 +1,24 @@ import { LolApi } from '../../src' -const api = new LolApi() - +/** + * DATA DRAGON — Queues (static). + * + * Returns the static queue reference: every `queueId` mapped to the map it is + * played on plus a human description (e.g. 420 -> "5v5 Ranked Solo games"). + * Served from the static CDN: no key, no rate limits. + */ export async function queuesDataDragon () { - return api.DataDragon.getQueues() + // Data Dragon needs no key + const api = new LolApi() + + // 1. Fetch the static queue table + const queues = await api.DataDragon.getQueues() + + console.log(`Queues defined: ${queues.length}`) + const ranked = queues.find((q) => q.queueId === 420) + if (ranked) { + console.log(`Queue 420 : ${ranked.description ?? 'n/a'} on ${ranked.map}`) + } + + return queues } diff --git a/example/lol/Realms.DataDragon.ts b/example/lol/Realms.DataDragon.ts index 9065f06e..d0c879f3 100644 --- a/example/lol/Realms.DataDragon.ts +++ b/example/lol/Realms.DataDragon.ts @@ -1,8 +1,23 @@ -import { LolApi } from '../../src' -import { RealmServers } from '../../src/constants' - -const api = new LolApi() +import { LolApi, Constants } from '../../src' +/** + * DATA DRAGON — Realms. + * + * Returns the per-server Data Dragon realm config: the live data version, + * the CDN base URL and the latest patch used for each asset family. + * Raw CDN data, so no API key, no rate limits and no `{ response }` wrapper. + */ export async function realmsDataDragon () { - return api.DataDragon.getRealms(RealmServers.AMERICA_NORTH) + // Data Dragon needs no key — instantiate the client and read straight from the CDN + const api = new LolApi() + + // 1. Fetch the realm config for a specific server (here North America) + const realm = await api.DataDragon.getRealms(Constants.RealmServers.AMERICA_NORTH) + + console.log(`Data version : ${realm.v}`) + console.log(`Default lang : ${realm.l}`) + console.log(`CDN base : ${realm.cdn}`) + console.log(`Champion patch: ${realm.n.champion}`) + + return realm } diff --git a/example/lol/RunesReforged.DataDragon.ts b/example/lol/RunesReforged.DataDragon.ts index c839f90e..1e0a604f 100644 --- a/example/lol/RunesReforged.DataDragon.ts +++ b/example/lol/RunesReforged.DataDragon.ts @@ -1,7 +1,24 @@ import { LolApi } from '../../src' -const api = new LolApi() - +/** + * DATA DRAGON — Runes Reforged (perks). + * + * Returns the rune trees (Precision, Domination, Sorcery, Resolve, Inspiration) + * for the latest patch. Each tree exposes `slots`, each slot a list of runes. + * Raw CDN data: no key, no rate limits and no `{ response }` wrapper. + */ export async function runesReforgedDataDragon () { - return api.DataDragon.getRunesReforged() + // Data Dragon needs no key + const api = new LolApi() + + // 1. Fetch the rune trees (defaults to the latest patch + en_US locale) + const trees = await api.DataDragon.getRunesReforged() + + console.log(`Rune trees: ${trees.length}`) + trees.forEach((tree) => { + const runeCount = tree.slots.reduce((total, slot) => total + slot.runes.length, 0) + console.log(` ${tree.name} (${tree.key}) — ${runeCount} runes`) + }) + + return trees } diff --git a/example/lol/Seasons.DataDragon.ts b/example/lol/Seasons.DataDragon.ts index a4021921..ce9e170e 100644 --- a/example/lol/Seasons.DataDragon.ts +++ b/example/lol/Seasons.DataDragon.ts @@ -1,7 +1,21 @@ import { LolApi } from '../../src' -const api = new LolApi() - +/** + * DATA DRAGON — Seasons (static). + * + * Returns the static season reference: each numeric `id` mapped to its season + * label (e.g. `{ id: 0, season: "PRESEASON 3" }`). Useful for decoding the + * `seasonId` found on matches. Served from the static CDN: no key. + */ export async function seasonsDataDragon () { - return api.DataDragon.getSeasons() + // Data Dragon needs no key + const api = new LolApi() + + // 1. Fetch the static season table + const seasons = await api.DataDragon.getSeasons() + + console.log(`Seasons defined: ${seasons.length}`) + console.log(`Latest season : ${seasons[seasons.length - 1]?.season}`) + + return seasons } diff --git a/example/lol/SeedMatches.DataDragon.ts b/example/lol/SeedMatches.DataDragon.ts index 23ce42db..9cc254f0 100644 --- a/example/lol/SeedMatches.DataDragon.ts +++ b/example/lol/SeedMatches.DataDragon.ts @@ -1,8 +1,27 @@ import { LolApi } from '../../src' -const api = new LolApi() - +/** + * DATA DRAGON — Seed match data. + * + * Riot ships 10 sample match-v4 payload files (ids 1..10) for testing without + * burning real API quota. `Seed.matches(id)` returns `{ matches }` raw — no + * key, no rate limits and no `{ response }` wrapper. + */ export async function matchesSeedData () { + // Seed data needs no key + const api = new LolApi() + + // 1. Pick one of the 10 seed files (valid ids are 1..10) const id = 1 - return api.Seed.matches(id) + + // 2. Fetch it — the payload is wrapped in a `matches` array + const { matches } = await api.Seed.matches(id) + + console.log(`Seed file ${id}: ${matches.length} matches`) + const first = matches[0] + if (first) { + console.log(`First match: gameId ${first.gameId} (${first.gameMode}, queue ${first.queueId})`) + } + + return matches } diff --git a/example/lol/SpectatorFeaturedGamesV5.example.ts b/example/lol/SpectatorFeaturedGamesV5.example.ts index cb9831b7..b0c7839d 100644 --- a/example/lol/SpectatorFeaturedGamesV5.example.ts +++ b/example/lol/SpectatorFeaturedGamesV5.example.ts @@ -1,7 +1,26 @@ import { LolApi } from '../../src' -import { Regions } from '../../src/constants' +import { config } from '../config/config' +/** + * SPECTATOR-V5 — List the featured games currently being played on a platform. + * + * Riot picks a handful of live games (no key-specific player needed) that can + * be spectated. Also returns the suggested polling interval. + */ export async function spectatorV5FeaturedGames () { const api = new LolApi() - return await api.SpectatorV5.featuredGames(Regions.LAT_NORTH) + + // Featured games is a platform endpoint, so it takes a platform `Regions` value + const { response: featured } = await api.SpectatorV5.featuredGames(config.region) + + console.log(`Featured games : ${featured.gameList.length}`) + console.log(`Refresh interval : ${featured.clientRefreshInterval}s`) + + // Highlight the first featured game, if any + const first = featured.gameList[0] + if (first) { + console.log(`First game : ${first.gameMode} on map ${first.mapId} (${first.participants.length} players)`) + } + + return featured } diff --git a/example/lol/SpectatorSummonerV5.example.ts b/example/lol/SpectatorSummonerV5.example.ts index 97a7199a..24e7103d 100644 --- a/example/lol/SpectatorSummonerV5.example.ts +++ b/example/lol/SpectatorSummonerV5.example.ts @@ -1,10 +1,36 @@ -import { RiotApi } from '../../src' -import { LolApi } from '../../src' +import { LolApi, RiotApi } from '../../src' import { config } from '../config/config' +/** + * SPECTATOR-V5 — Get the active (live) game for a player by PUUID. + * + * We resolve the account's PUUID through the Account API, then ask the + * spectator endpoint for the game they are currently in. When the player is + * NOT in a game the endpoint returns 404, so the call is wrapped in try/catch. + */ export async function spectatorV5SummonerExample () { - const rApi = new RiotApi() - const api = new LolApi() - const { response: { puuid } } = await rApi.Account.getByRiotId(config.summonerName, config.region, config.regionGroup) - return await api.SpectatorV5.activeGame(puuid, config.region) + const riotApi = new RiotApi() + const lolApi = new LolApi() + + // 1. Resolve the Riot ID (gameName#tagLine) into a PUUID + const { response: account } = await riotApi.Account.getByRiotId( + config.summonerName, + config.tagLine, + config.regionGroup + ) + + // 2. Ask for the live game — this 404s when the player is not currently playing + try { + const { response: game } = await lolApi.SpectatorV5.activeGame(account.puuid, config.region) + + console.log(`${account.gameName}#${account.tagLine} is in a live game`) + console.log(`Game id : ${game.gameId}`) + console.log(`Game mode : ${game.gameMode}`) + console.log(`Players : ${game.participants.length}`) + + return game + } catch (e) { + console.log(`${account.gameName}#${account.tagLine} is not currently in a game`) + return undefined + } } diff --git a/example/lol/StatusV4.example.ts b/example/lol/StatusV4.example.ts index 3d245b0d..46373ca8 100644 --- a/example/lol/StatusV4.example.ts +++ b/example/lol/StatusV4.example.ts @@ -1,11 +1,22 @@ -import { LolApi } from "../../src/apis/lol/lol"; -import { Regions } from "../../src/constants"; +import { LolApi } from '../../src' +import { config } from '../config/config' -export async function statusV4Example() { - const api = new LolApi(); +/** + * LOL-STATUS-V4 — Get the platform status for a region. + * + * Reports the server's supported locales plus any ongoing maintenances and + * incidents (useful for surfacing "servers are down" messages to users). + */ +export async function statusV4Example () { + const api = new LolApi() - const status = (await api.StatusV4.get(Regions.EU_WEST)).response; - console.log(status.locales); - console.log(status.maintenances) - console.log(status.incidents) + // Platform status is a platform endpoint, so it takes a platform `Regions` value + const { response: status } = await api.StatusV4.get(config.region) + + console.log(`Region : ${status.name} (${status.id})`) + console.log(`Locales : ${status.locales.join(', ')}`) + console.log(`Maintenance : ${status.maintenances.maintenance_status ?? 'none'}`) + console.log(`Incident sev. : ${status.incidents.incident_severity ?? 'none'}`) + + return status } diff --git a/example/lol/SummonerByPUUID.example.ts b/example/lol/SummonerByPUUID.example.ts index 8f6cbe95..aa0a39e1 100644 --- a/example/lol/SummonerByPUUID.example.ts +++ b/example/lol/SummonerByPUUID.example.ts @@ -1,11 +1,31 @@ -import { RiotApi } from '../../src' -import { LolApi } from '../../src' +import { LolApi, RiotApi } from '../../src' import { config } from '../config/config' -const rApi = new RiotApi() -const api = new LolApi() - +/** + * SUMMONER-V4 — Get a summoner by PUUID. + * + * Summoner-V4 no longer accepts a name, so we first resolve the account's + * PUUID through the Riot Account API (ACCOUNT-V1), then look the summoner up + * on its platform region. + */ export async function summonerByPUUIDExample () { - const { response: { puuid }} = await rApi.Account.getByRiotId(config.summonerName, config.tagLine, config.regionGroup) - return await api.Summoner.getByPUUID(puuid, config.region) + const riotApi = new RiotApi() + const lolApi = new LolApi() + + // 1. Resolve the Riot ID (gameName#tagLine) into a PUUID via ACCOUNT-V1 + const { response: account } = await riotApi.Account.getByRiotId( + config.summonerName, + config.tagLine, + config.regionGroup + ) + + // 2. Fetch the summoner on its platform region using that PUUID + const { response: summoner } = await lolApi.Summoner.getByPUUID(account.puuid, config.region) + + console.log(`Summoner : ${account.gameName}#${account.tagLine}`) + console.log(`PUUID : ${summoner.puuid}`) + console.log(`Level : ${summoner.summonerLevel}`) + console.log(`Profile icon: ${summoner.profileIconId}`) + + return summoner } diff --git a/example/lol/SummonerLeague.example.ts b/example/lol/SummonerLeague.example.ts index 7dee6651..823019cc 100644 --- a/example/lol/SummonerLeague.example.ts +++ b/example/lol/SummonerLeague.example.ts @@ -1,12 +1,36 @@ -import { RiotApi } from '../../src' -import { LolApi } from '../../src' +import { LolApi, RiotApi } from '../../src' import { config } from '../config/config' -const rApi = new RiotApi() -const api = new LolApi() - +/** + * LEAGUE-V4 — Get ranked entries for a summoner (bySummoner). + * + * This is the legacy lookup keyed by the encrypted summonerId. We first + * resolve the account's PUUID (ACCOUNT-V1), then the summonerId (SUMMONER-V4), + * and finally fetch every ranked queue the player has entries in. + */ export async function summonerLeagueExample () { - const { response: { puuid } } = await rApi.Account.getByRiotId(config.summonerName, config.tagLine, config.regionGroup) - const { response: { id }} = await api.Summoner.getByPUUID(puuid, config.region) - return await api.League.bySummoner(id, config.region) + const riotApi = new RiotApi() + const lolApi = new LolApi() + + // 1. Resolve the Riot ID into a PUUID + const { response: account } = await riotApi.Account.getByRiotId( + config.summonerName, + config.tagLine, + config.regionGroup + ) + + // 2. Resolve the summoner to obtain its encrypted summonerId + const { response: summoner } = await lolApi.Summoner.getByPUUID(account.puuid, config.region) + + // 3. Fetch all ranked entries for that summoner (one per ranked queue) + const { response: leagues } = await lolApi.League.bySummoner(summoner.id, config.region) + + console.log(`Ranked entries for ${account.gameName}#${account.tagLine}: ${leagues.length}`) + for (const league of leagues) { + console.log( + ` ${league.queueType}: ${league.tier} ${league.rank} (${league.leaguePoints} LP) — ${league.wins}W/${league.losses}L` + ) + } + + return leagues } diff --git a/example/lol/Versions.DataDragon.ts b/example/lol/Versions.DataDragon.ts index 71003ddb..31cdd42c 100644 --- a/example/lol/Versions.DataDragon.ts +++ b/example/lol/Versions.DataDragon.ts @@ -1,7 +1,21 @@ import { LolApi } from '../../src' -const api = new LolApi() - +/** + * DATA DRAGON — Versions. + * + * Returns the full list of Data Dragon versions, newest first. The first + * entry (`[0]`) is the latest patch and is what every other Data Dragon + * asset is fetched against. Raw CDN data: no key, no rate limits. + */ export async function versionsDataDragon () { - return api.DataDragon.getVersions() + // Data Dragon needs no key + const api = new LolApi() + + // 1. Fetch every published Data Dragon version (descending order) + const versions = await api.DataDragon.getVersions() + + console.log(`Versions available: ${versions.length}`) + console.log(`Latest patch : ${versions[0]}`) + + return versions } diff --git a/example/lol/deprecated/LolStatus.example.ts b/example/lol/deprecated/LolStatus.example.ts index cf18beac..72ea65cb 100644 --- a/example/lol/deprecated/LolStatus.example.ts +++ b/example/lol/deprecated/LolStatus.example.ts @@ -1,7 +1,21 @@ -import { LolApi } from '../../src' -import { Regions } from '../../src/constants' +import { LolApi, Constants } from '../../../src' +/** + * DEPRECATED — LOL-STATUS-V3 (`/lol/status/v3/shard-data`). + * + * This shard-data endpoint has been removed by Riot in favour of + * `lol-status-v4` (use `LolApi.StatusV4.get` instead). It returned the + * platform name, hostname and the per-service status for a given region. + */ export async function lolStatusExample () { - const api = new LolApi() - return await api.Status.get(Regions.LAT_NORTH) + const lolApi = new LolApi() + + // 1. Query the (deprecated) shard status for a platform region + const { response: status } = await lolApi.Status.get(Constants.Regions.LAT_NORTH) + + console.log(`Platform : ${status.name} (${status.slug})`) + console.log(`Hostname : ${status.hostname}`) + console.log(`Locales : ${status.locales.join(', ')}`) + + return status } diff --git a/example/lol/deprecated/Match.example.ts b/example/lol/deprecated/Match.example.ts index 9b75dbae..630260ff 100644 --- a/example/lol/deprecated/Match.example.ts +++ b/example/lol/deprecated/Match.example.ts @@ -1,19 +1,37 @@ -import { LolApi } from '../../src' -import { config } from '../config/config' +import { LolApi, RiotApi } from '../../../src' +import { config } from '../../config/config' +/** + * DEPRECATED — MATCH-V4 (`/lol/match/v4/matches/{matchId}`). + * + * Match-V4 was replaced by Match-V5 (use `LolApi.MatchV5`). It is kept here + * for reference: note that V4 keys games by a numeric `gameId`, whereas V5 + * uses string match ids. We resolve the account through the Riot Account API + * (Summoner name lookup was removed) and read its match history. + */ export async function matchExample () { - const api = new LolApi() + const riotApi = new RiotApi() + const lolApi = new LolApi() const { region } = config - const user = await api.Summoner.getByName(config.summonerName, region) - console.log() - const { - response: { - matches - } - } = await api.Match.list(user.response.accountId, region) - const { gameId } = matches[0] - const match = await api.Match.get(gameId, region) + + // 1. Resolve the Riot ID into a PUUID, then load the summoner + const { response: account } = await riotApi.Account.getByRiotId( + config.summonerName, + config.tagLine, + config.regionGroup + ) + const { response: summoner } = await lolApi.Summoner.getByPUUID(account.puuid, region) + + // 2. List the match history for this account (V4 uses the encrypted accountId) + const { response: listing } = await lolApi.Match.list(summoner.accountId, region) + console.log(`Found ${listing.matches.length} matches (total ${listing.totalGames})`) + + // 3. Fetch the full details of the most recent game (V4 get takes a numeric gameId) + const { gameId } = listing.matches[0] + const { response: match } = await lolApi.Match.get(gameId, region) + + console.log(`Game ${match.gameId} | mode ${match.gameMode} | queue ${match.queueId}`) + console.log(`Duration: ${Math.round(match.gameDuration / 60)} min | ${match.participants.length} players`) + return match } - -matchExample() diff --git a/example/lol/deprecated/MatchListing.example.ts b/example/lol/deprecated/MatchListing.example.ts index 73bd3c4a..8178da8d 100644 --- a/example/lol/deprecated/MatchListing.example.ts +++ b/example/lol/deprecated/MatchListing.example.ts @@ -1,11 +1,35 @@ -import { LolApi } from '../../src' -import { config } from '../config/config' - -const api = new LolApi() +import { LolApi, RiotApi } from '../../../src' +import { config } from '../../config/config' +/** + * DEPRECATED — MATCH-V4 match listing (`/lol/match/v4/matchlists/by-account/{accountId}`). + * + * Replaced by Match-V5 `LolApi.MatchV5.list(puuid, ...)`, which returns plain + * string match ids. The V4 listing instead returns rich reference objects + * (gameId, champion, queue, role, lane, timestamp...) keyed by the encrypted + * accountId, which we resolve via the Riot Account API. + */ export async function matchListingExample () { + const riotApi = new RiotApi() + const lolApi = new LolApi() const { region } = config - const user = await api.Summoner.getByName(config.summonerName, region) - const matchList = await api.Match.list(user.response.accountId, region) - return matchList + + // 1. Resolve the Riot ID into a PUUID, then load the summoner + const { response: account } = await riotApi.Account.getByRiotId( + config.summonerName, + config.tagLine, + config.regionGroup + ) + const { response: summoner } = await lolApi.Summoner.getByPUUID(account.puuid, region) + + // 2. List the match history (V4 keys this by the encrypted accountId) + const { response: listing } = await lolApi.Match.list(summoner.accountId, region) + + console.log(`Returned ${listing.matches.length} of ${listing.totalGames} games`) + const first = listing.matches[0] + if (first) { + console.log(`Most recent: game ${first.gameId} | champion ${first.champion} | queue ${first.queue}`) + } + + return listing } diff --git a/example/lol/deprecated/MatchListingFiltering.example.ts b/example/lol/deprecated/MatchListingFiltering.example.ts index 8decf16a..81533250 100644 --- a/example/lol/deprecated/MatchListingFiltering.example.ts +++ b/example/lol/deprecated/MatchListingFiltering.example.ts @@ -1,16 +1,36 @@ -import { LolApi } from '../../src' -import { config } from '../config/config' -import { Champions } from '../../src/constants' -import { MatchQueryDTO } from '../../src/models-dto' - -const api = new LolApi() +import { LolApi, RiotApi, Constants } from '../../../src' +import { config } from '../../config/config' +import { MatchQueryDTO } from '../../../src/models-dto' +/** + * DEPRECATED — MATCH-V4 match listing with filters. + * + * Replaced by Match-V5 `LolApi.MatchV5.list`. The V4 listing accepted a rich + * query (`MatchQueryDTO`) letting you filter by champion, queue, season and a + * time window — here we ask only for games played on a specific champion. + */ export async function matchListingFilteringExample () { + const riotApi = new RiotApi() + const lolApi = new LolApi() const { region } = config - const user = await api.Summoner.getByName(config.summonerName, region) + + // 1. Resolve the Riot ID into a PUUID, then load the summoner + const { response: account } = await riotApi.Account.getByRiotId( + config.summonerName, + config.tagLine, + config.regionGroup + ) + const { response: summoner } = await lolApi.Summoner.getByPUUID(account.puuid, region) + + // 2. Build a filter: only games where the player picked Twisted Fate const filter: MatchQueryDTO = { - champion: Champions.TWISTED_FATE + champion: Constants.Champions.TWISTED_FATE } - const matchList = await api.Match.list(user.response.accountId, region, filter) - return matchList + + // 3. List the filtered match history (V4 keys this by the encrypted accountId) + const { response: listing } = await lolApi.Match.list(summoner.accountId, region, filter) + + console.log(`Twisted Fate games: ${listing.matches.length}`) + + return listing } diff --git a/example/lol/deprecated/MatchTimeline.example.ts b/example/lol/deprecated/MatchTimeline.example.ts index 1f526b0c..b30f21b2 100644 --- a/example/lol/deprecated/MatchTimeline.example.ts +++ b/example/lol/deprecated/MatchTimeline.example.ts @@ -1,17 +1,35 @@ -import { LolApi } from '../../src' -import { config } from '../config/config' - -const api = new LolApi() +import { LolApi, RiotApi } from '../../../src' +import { config } from '../../config/config' +/** + * DEPRECATED — MATCH-V4 timeline (`/lol/match/v4/timelines/by-match/{matchId}`). + * + * Replaced by Match-V5 `LolApi.MatchV5.timeline`. The timeline breaks a game + * into fixed-interval frames (events + per-participant state), and V4 keys it + * by the same numeric `gameId` used by the match listing. + */ export async function matchTimeLineExample () { + const riotApi = new RiotApi() + const lolApi = new LolApi() const { region } = config - const user = await api.Summoner.getByName(config.summonerName, region) - const { - response: { - matches - } - } = await api.Match.list(user.response.accountId, region) - const { gameId } = matches[0] - const matchTimeline = await api.Match.timeline(gameId, region) - return matchTimeline + + // 1. Resolve the Riot ID into a PUUID, then load the summoner + const { response: account } = await riotApi.Account.getByRiotId( + config.summonerName, + config.tagLine, + config.regionGroup + ) + const { response: summoner } = await lolApi.Summoner.getByPUUID(account.puuid, region) + + // 2. Find the most recent game id from the match listing + const { response: listing } = await lolApi.Match.list(summoner.accountId, region) + const { gameId } = listing.matches[0] + + // 3. Fetch the timeline for that game (V4 timeline takes the numeric gameId) + const { response: timeline } = await lolApi.Match.timeline(gameId, region) + + console.log(`Timeline for game ${gameId}`) + console.log(`Frames: ${timeline.frames.length} | interval: ${timeline.frameInterval}ms`) + + return timeline } diff --git a/example/lol/deprecated/SpectatorFeaturedGames.example.ts b/example/lol/deprecated/SpectatorFeaturedGames.example.ts index a1383b8b..cc08b4e9 100644 --- a/example/lol/deprecated/SpectatorFeaturedGames.example.ts +++ b/example/lol/deprecated/SpectatorFeaturedGames.example.ts @@ -1,7 +1,25 @@ -import { LolApi } from '../../src' -import { Regions } from '../../src/constants' +import { LolApi, Constants } from '../../../src' +/** + * DEPRECATED — SPECTATOR-V4 featured games. + * + * Use `LolApi.SpectatorV5.featuredGames` instead. This returns a small list + * of currently live games that Riot is highlighting for the given platform + * region, plus the suggested refresh interval. + */ export async function spectatorFeaturedGames () { - const api = new LolApi() - return await api.Spectator.featuredGames(Regions.LAT_NORTH) + const lolApi = new LolApi() + + // 1. Fetch the featured (live) games for a platform region + const { response: featured } = await lolApi.Spectator.featuredGames(Constants.Regions.LAT_NORTH) + + console.log(`Featured games: ${featured.gameList.length}`) + console.log(`Refresh in : ${featured.clientRefreshInterval}s`) + + const first = featured.gameList[0] + if (first) { + console.log(`First game: id ${first.gameId} | mode ${first.gameMode} | ${first.participants.length} players`) + } + + return featured } diff --git a/example/lol/deprecated/SpectatorSummoner.example.ts b/example/lol/deprecated/SpectatorSummoner.example.ts index 7fb8479f..6d25a595 100644 --- a/example/lol/deprecated/SpectatorSummoner.example.ts +++ b/example/lol/deprecated/SpectatorSummoner.example.ts @@ -1,12 +1,43 @@ -import { LolApi } from '../../src' -import { config } from '../config/config' +import { LolApi, RiotApi } from '../../../src' +import { config } from '../../config/config' +/** + * DEPRECATED — SPECTATOR-V4 active game (`/lol/spectator/v4/active-games/by-summoner/{summonerId}`). + * + * Use `LolApi.SpectatorV5.activeGame(puuid, region)` instead. The V4 endpoint + * looked a player up by their encrypted summoner id and returned their live + * game — or nothing when the player is not currently in a game. + */ export async function spectatorSummonerExample () { - const api = new LolApi() - const { summonerName, region } = config - const { response: { id } } = await api.Summoner.getByName(summonerName, region) + const riotApi = new RiotApi() + const lolApi = new LolApi() + const { region } = config - return await api.Spectator.activeGame(id, region) -} + // 1. Resolve the Riot ID into a PUUID, then load the summoner (for its id) + const { response: account } = await riotApi.Account.getByRiotId( + config.summonerName, + config.tagLine, + config.regionGroup + ) + const { response: summoner } = await lolApi.Summoner.getByPUUID(account.puuid, region) + + // 2. Look up the live game by the encrypted summoner id. + // The deprecated wrapper returns a "not available" object when not in a game; + // a found game is returned wrapped as { response, rateLimits }. + try { + const result = await lolApi.Spectator.activeGame(summoner.id, region) -spectatorSummonerExample() + if ('response' in result) { + const game = result.response + console.log(`${account.gameName} is in game ${game.gameId} (mode ${game.gameMode})`) + console.log(`Players: ${game.participants.length} | live for ${game.gameLength}s`) + return game + } + + console.log(`${account.gameName} is not currently in a game (${result.message})`) + return result + } catch (e) { + console.log('Could not fetch active game (player likely offline / not in game)') + return null + } +} diff --git a/example/lol/deprecated/SummonerByAccountID.example.ts b/example/lol/deprecated/SummonerByAccountID.example.ts index 1589387c..80e87ce1 100644 --- a/example/lol/deprecated/SummonerByAccountID.example.ts +++ b/example/lol/deprecated/SummonerByAccountID.example.ts @@ -1,14 +1,35 @@ -import { RiotApi } from '../../../src' -import { LolApi } from '../../../src' +import { LolApi, RiotApi } from '../../../src' import { config } from '../../config/config' -const rApi = new RiotApi() -const api = new LolApi() - +/** + * DEPRECATED-style lookup — SUMMONER-V4 by account id + * (`/lol/summoner/v4/summoners/by-account/{accountId}`). + * + * The encrypted accountId is a legacy identifier (the modern key is the PUUID). + * Since name lookup was removed, we first resolve the account through the Riot + * Account API to obtain a PUUID, fetch the summoner to read its `accountId`, + * then demonstrate the by-account lookup. + */ export async function summonerByAccountIDExample () { + const riotApi = new RiotApi() + const lolApi = new LolApi() const { region } = config - // const { response: { accountId } } = await api.Summoner.getByName(config.summonerName, region) - const { response: { puuid } } = await rApi.Account.getByRiotId(config.summonerName, config.tagLine, config.regionGroup) - const { response: { accountId } } = await api.Summoner.getByPUUID(puuid, region) - return await api.Summoner.getByAccountID(accountId, region) + + // 1. Resolve the Riot ID into a PUUID + const { response: account } = await riotApi.Account.getByRiotId( + config.summonerName, + config.tagLine, + config.regionGroup + ) + + // 2. Fetch the summoner by PUUID to obtain its encrypted accountId + const { response: byPuuid } = await lolApi.Summoner.getByPUUID(account.puuid, region) + + // 3. Look the same summoner up by its accountId (the deprecated identifier) + const { response: summoner } = await lolApi.Summoner.getByAccountID(byPuuid.accountId, region) + + console.log(`Summoner: ${account.gameName}#${account.tagLine}`) + console.log(`Level : ${summoner.summonerLevel} | accountId ${summoner.accountId}`) + + return summoner } diff --git a/example/lol/deprecated/SummonerById.example.ts b/example/lol/deprecated/SummonerById.example.ts index 87540293..3de66f14 100644 --- a/example/lol/deprecated/SummonerById.example.ts +++ b/example/lol/deprecated/SummonerById.example.ts @@ -1,12 +1,35 @@ -import { RiotApi } from '../../../src' -import { LolApi } from '../../../src' +import { LolApi, RiotApi } from '../../../src' import { config } from '../../config/config' -const rApi = new RiotApi() -const api = new LolApi() - +/** + * DEPRECATED-style lookup — SUMMONER-V4 by summoner id + * (`/lol/summoner/v4/summoners/{summonerId}`). + * + * The encrypted summoner id is a legacy identifier (the modern key is the + * PUUID). Since name lookup was removed, we resolve the account through the + * Riot Account API, fetch the summoner by PUUID to obtain its `id`, then + * demonstrate the by-id lookup. + */ export async function summonerByIdExample () { - const { response: { puuid } } = await rApi.Account.getByRiotId(config.summonerName, config.region, config.regionGroup) - const { response: { id } } = await api.Summoner.getByPUUID(puuid, config.region) - return await api.Summoner.getById(id, config.region) + const riotApi = new RiotApi() + const lolApi = new LolApi() + const { region } = config + + // 1. Resolve the Riot ID into a PUUID (Account API uses a region GROUP) + const { response: account } = await riotApi.Account.getByRiotId( + config.summonerName, + config.tagLine, + config.regionGroup + ) + + // 2. Fetch the summoner by PUUID to obtain its encrypted summoner id + const { response: byPuuid } = await lolApi.Summoner.getByPUUID(account.puuid, region) + + // 3. Look the same summoner up by its id (the deprecated identifier) + const { response: summoner } = await lolApi.Summoner.getById(byPuuid.id, region) + + console.log(`Summoner: ${account.gameName}#${account.tagLine}`) + console.log(`Level : ${summoner.summonerLevel} | id ${summoner.id}`) + + return summoner } diff --git a/example/lol/deprecated/SummonerByName.example.ts b/example/lol/deprecated/SummonerByName.example.ts index c06b7a30..d73017e7 100644 --- a/example/lol/deprecated/SummonerByName.example.ts +++ b/example/lol/deprecated/SummonerByName.example.ts @@ -1,14 +1,30 @@ -import { LolApi } from '../../src' -import { config } from '../config/config' - -const api = new LolApi({ - debug: { - logRatelimits: true, - logTime: true, - logUrls: true - } -}) +import { LolApi, RiotApi } from '../../../src' +import { config } from '../../config/config' +/** + * DEPRECATED — SUMMONER-V4 by name (`/lol/summoner/v4/summoners/by-name/{name}`). + * + * Riot REMOVED summoner-name lookup: `LolApi.Summoner.getByName` no longer + * exists. The modern replacement is to resolve the Riot ID (gameName#tagLine) + * to a PUUID through the Account API, then fetch the summoner by PUUID. This + * example demonstrates that replacement flow. + */ export async function summonerByNameExample () { - return await api.Summoner.getByName(config.summonerName, config.region) + const riotApi = new RiotApi() + const lolApi = new LolApi() + + // 1. Resolve the Riot ID (gameName#tagLine) into a PUUID via the Account API + const { response: account } = await riotApi.Account.getByRiotId( + config.summonerName, + config.tagLine, + config.regionGroup + ) + + // 2. Fetch the summoner on its platform region using the PUUID + const { response: summoner } = await lolApi.Summoner.getByPUUID(account.puuid, config.region) + + console.log(`Summoner: ${account.gameName}#${account.tagLine}`) + console.log(`Level : ${summoner.summonerLevel}`) + + return summoner } diff --git a/example/lol/deprecated/ThirdPartyCode.example.ts b/example/lol/deprecated/ThirdPartyCode.example.ts index 8cdfbbbf..9132af5c 100644 --- a/example/lol/deprecated/ThirdPartyCode.example.ts +++ b/example/lol/deprecated/ThirdPartyCode.example.ts @@ -1,13 +1,41 @@ -import { LolApi } from '../../src' -import { config } from '../config/config' +import { LolApi, RiotApi } from '../../../src' +import { config } from '../../config/config' +// NOTE: ThirdPartyCode is a sunset endpoint and is NOT wired into `LolApi`, +// so it is not reachable from the package barrel — we import the service class +// from its module to demonstrate the (now removed) call. +import { ThirdPartyCode } from '../../../src/apis/lol/thirdPartyCode/thirdPartyCode' +/** + * DEPRECATED — THIRD-PARTY-CODE-V4 + * (`/lol/platform/v4/third-party-code/by-summoner/{summonerId}`). + * + * This endpoint was sunset by Riot on March 11th, 2024 and is no longer + * available. It returned the verification code a player had set so third-party + * apps could prove account ownership. It was keyed by the encrypted summoner + * id, which we resolve via the Account API -> Summoner-by-PUUID flow. + */ export async function thirdPartyExample () { - const riot = new LolApi() - const { response: { id } } = await riot.Summoner.getByName(config.summonerName, config.region) + const riotApi = new RiotApi() + const lolApi = new LolApi() + const thirdPartyApi = new ThirdPartyCode() + const { region } = config + + // 1. Resolve the Riot ID into a PUUID, then load the summoner (for its id) + const { response: account } = await riotApi.Account.getByRiotId( + config.summonerName, + config.tagLine, + config.regionGroup + ) + const { response: summoner } = await lolApi.Summoner.getByPUUID(account.puuid, region) + + // 2. Read the third-party verification code by summoner id. + // Wrapped in try/catch since the endpoint is sunset / commonly 404s. try { - return await riot.ThirdPartyCode.get(id, config.region) + const { response: thirdParty } = await thirdPartyApi.get(summoner.id, region) + console.log(`Verification code: ${thirdParty.code ?? '(none set / endpoint unavailable)'}`) + return thirdParty } catch (e) { - console.error(e) - return {} + console.log('Third-party-code endpoint is no longer available (sunset 2024-03-11)') + return { code: null } } } diff --git a/example/lol/index.ts b/example/lol/index.ts index 2ab1621b..ddc5f91d 100644 --- a/example/lol/index.ts +++ b/example/lol/index.ts @@ -14,6 +14,8 @@ export * from './ChampionMasteryByPUUIDChampion.example' export * from './ChampionsScore.example' export * from './SpectatorFeaturedGamesV5.example' export * from './SpectatorSummonerV5.example' +export * from './MatchV5TimelineLatestMatch.example' +export * from './MatchV5Replays.example' export * from './Realms.DataDragon' export * from './Versions.DataDragon' export * from './Languages.DataDragon' diff --git a/example/riot/Account.examples.ts b/example/riot/Account.examples.ts index 7b3a58f3..c0c23193 100644 --- a/example/riot/Account.examples.ts +++ b/example/riot/Account.examples.ts @@ -1,13 +1,34 @@ -import { RiotApi } from '../../src/apis/riot/riot' -import { RegionGroups } from '../../src/constants' +import { RiotApi, Constants } from '../../src' import { config } from '../config/config' -export async function accountV1Examples() { - const api = new RiotApi() +/** + * ACCOUNT-V1 — Look up a Riot account. + * + * The Riot Account API is the entry point for almost everything else: it maps a + * human-friendly Riot ID (gameName#tagLine) to the stable PUUID used by every + * other endpoint. Here we resolve an account both ways: by Riot ID and by PUUID. + * + * Account endpoints are routed by region GROUP (AMERICAS / ASIA / EUROPE), + * never by platform region. + */ +export async function accountV1Examples () { + const riotApi = new RiotApi() - const resByRiotId = (await api.Account.getByRiotId(config.summonerName, config.tagLine, RegionGroups.AMERICAS)).response - console.log('Account info by Riot Id: ', resByRiotId) + // 1. Resolve the Riot ID (gameName#tagLine) into an account (and its PUUID) + const { response: byRiotId } = await riotApi.Account.getByRiotId( + config.summonerName, + config.tagLine, + config.regionGroup + ) + console.log(`Riot ID : ${byRiotId.gameName}#${byRiotId.tagLine}`) + console.log(`PUUID : ${byRiotId.puuid}`) - const resByPuuid = (await api.Account.getByPUUID(resByRiotId.puuid, RegionGroups.AMERICAS)).response - console.log('Account info by PUUID: ', resByPuuid) -} \ No newline at end of file + // 2. Reverse the lookup: fetch the same account from its PUUID + const { response: byPuuid } = await riotApi.Account.getByPUUID( + byRiotId.puuid, + config.regionGroup + ) + console.log(`Round-trip Riot ID: ${byPuuid.gameName}#${byPuuid.tagLine}`) + + return byPuuid +} diff --git a/example/riot/AccountRegion.examples.ts b/example/riot/AccountRegion.examples.ts index 7f1d6fe5..4f715e8e 100644 --- a/example/riot/AccountRegion.examples.ts +++ b/example/riot/AccountRegion.examples.ts @@ -1,11 +1,34 @@ -import { RiotApi } from '../../src/apis/riot/riot' -import { RegionGroups, Games } from '../../src/constants' +import { RiotApi, Constants } from '../../src' import { config } from '../config/config' -export async function accountRegionV1Examples() { - const api = new RiotApi() +/** + * ACCOUNT-V1 — Get the active shard / region for a player and game. + * + * Given a PUUID and a game (LoL / TFT / LoR), this returns the platform region + * where that player is currently active. Useful when you only know a Riot ID + * but not which shard the account lives on. + * + * Like the rest of Account-V1, it is routed by region GROUP (AMERICAS / ASIA / + * EUROPE), not by platform region. + */ +export async function accountRegionV1Examples () { + const riotApi = new RiotApi() - const { puuid } = (await api.Account.getByRiotId(config.summonerName, config.tagLine, RegionGroups.AMERICAS)).response + // 1. Resolve the Riot ID (gameName#tagLine) into a PUUID + const { puuid } = ( + await riotApi.Account.getByRiotId(config.summonerName, config.tagLine, config.regionGroup) + ).response - return (await api.Account.getActiveRegion(puuid, Games.LOL, RegionGroups.AMERICAS)).response -} \ No newline at end of file + // 2. Ask which region this PUUID is active in for League of Legends + const { response: activeRegion } = await riotApi.Account.getActiveRegion( + puuid, + Constants.Games.LOL, + config.regionGroup + ) + + console.log(`Account : ${config.summonerName}#${config.tagLine}`) + console.log(`Game : ${activeRegion.game}`) + console.log(`Active region: ${activeRegion.region}`) + + return activeRegion +} diff --git a/example/tft/StaticFiles.example.ts b/example/tft/StaticFiles.example.ts index f1dd3f3b..0d04a5fa 100644 --- a/example/tft/StaticFiles.example.ts +++ b/example/tft/StaticFiles.example.ts @@ -1,11 +1,30 @@ import { TftApi } from '../../src' +/** + * TFT STATIC FILES — Local Teamfight Tactics game data. + * + * Unlike every other endpoint, `StaticFiles` reads bundled JSON shipped with + * the library. The methods are SYNCHRONOUS (no `await`, no API key, no rate + * limits): champions, hexes, items and traits for the current set. + */ export function staticFilesExample () { - const api = new TftApi().StaticFiles - return [ - api.Champions(), - api.Hexes(), - api.Items(), - api.Traits() - ] + const staticFiles = new TftApi().StaticFiles + + // All four readers return arrays straight from local JSON + const champions = staticFiles.Champions() + const hexes = staticFiles.Hexes() + const items = staticFiles.Items() + const traits = staticFiles.Traits() + + console.log(`Champions: ${champions.length}`) + console.log(`Hexes : ${hexes.length}`) + console.log(`Items : ${items.length}`) + console.log(`Traits : ${traits.length}`) + + // Peek at one champion to show the shape of the data + if (champions[0]) { + console.log(`Example champion: ${champions[0].champion} (cost ${champions[0].cost})`) + } + + return { champions, hexes, items, traits } } diff --git a/example/tft/SummonerTFT.example.ts b/example/tft/SummonerTFT.example.ts index eb010785..6f3b4fe2 100644 --- a/example/tft/SummonerTFT.example.ts +++ b/example/tft/SummonerTFT.example.ts @@ -1,11 +1,30 @@ -import { RiotApi } from '../../src' -import { TftApi } from '../../src' +import { RiotApi, TftApi } from '../../src' import { configTft } from '../config/config' -const rApi = new RiotApi() -const api = new TftApi() - +/** + * TFT-SUMMONER-V1 — Get a Teamfight Tactics summoner by PUUID. + * + * The summoner endpoint is platform-region based (e.g. NA1), so we resolve + * the PUUID through the Account API and then look the summoner up on its + * platform region. + */ export async function getSummonerTft () { - const { response: { puuid } } = await rApi.Account.getByRiotId(configTft.summonerName, configTft.region, configTft.regionGroup) - return api.Summoner.getByPUUID(puuid, configTft.region) + const riotApi = new RiotApi() + const tftApi = new TftApi() + + // 1. Resolve the Riot ID (gameName#tagLine) into a PUUID + const { response: account } = await riotApi.Account.getByRiotId( + configTft.summonerName, + configTft.tagLine, + configTft.regionGroup + ) + + // 2. Fetch the TFT summoner on its platform region (NOT the region group) + const { response: summoner } = await tftApi.Summoner.getByPUUID(account.puuid, configTft.region) + + console.log(`Summoner : ${account.gameName}#${account.tagLine}`) + console.log(`Level : ${summoner.summonerLevel}`) + console.log(`Icon id : ${summoner.profileIconId}`) + + return summoner } diff --git a/example/tft/TftLeagueBySummoner.example.ts b/example/tft/TftLeagueBySummoner.example.ts index 91fb8431..6d704169 100644 --- a/example/tft/TftLeagueBySummoner.example.ts +++ b/example/tft/TftLeagueBySummoner.example.ts @@ -1,12 +1,32 @@ -import { RiotApi } from '../../src' -import { TftApi } from '../../src' +import { RiotApi, TftApi } from '../../src' import { configTft } from '../config/config' -const rApi = new RiotApi() -const api = new TftApi() - +/** + * TFT-LEAGUE-V1 — Get the ranked league entries for a player. + * + * We resolve the PUUID via the Account API and read the ranked entries with + * `getByPUUID` (the non-deprecated counterpart of the summoner-id based call). + * League entries are platform-region based, so we use `configTft.region`. + */ export async function TftLeagueBySummoner () { - const { response: { puuid } } = await rApi.Account.getByRiotId(configTft.summonerName, configTft.region, configTft.regionGroup) - const { response: { id } } = await api.Summoner.getByPUUID(puuid, configTft.region) - const league = await api.League.get(id, configTft.region) + const riotApi = new RiotApi() + const tftApi = new TftApi() + + // 1. Resolve the Riot ID into a PUUID + const { response: account } = await riotApi.Account.getByRiotId( + configTft.summonerName, + configTft.tagLine, + configTft.regionGroup + ) + + // 2. Fetch every ranked TFT league entry for this PUUID on its platform region + const { response: entries } = await tftApi.League.getByPUUID(account.puuid, configTft.region) + + console.log(`Player : ${account.gameName}#${account.tagLine}`) + console.log(`Ranked queues: ${entries.length}`) + for (const entry of entries) { + console.log(` ${entry.queueType}: ${entry.tier} ${entry.rank} - ${entry.leaguePoints} LP (${entry.wins}W/${entry.losses}L)`) + } + + return entries } diff --git a/example/tft/TftLeagueByTierDivision.example.ts b/example/tft/TftLeagueByTierDivision.example.ts index ad055193..58de8989 100644 --- a/example/tft/TftLeagueByTierDivision.example.ts +++ b/example/tft/TftLeagueByTierDivision.example.ts @@ -1,14 +1,31 @@ -import { TftApi } from "../../src"; -import { configTft } from "../config/config"; -import { Tiers } from "../../src/constants"; -import { Divisions } from "../../src/constants"; +import { TftApi, Constants } from '../../src' +import { configTft } from '../config/config' -const api = new TftApi(); +/** + * TFT-LEAGUE-V1 — List the ranked entries for a given tier + division. + * + * This returns a page of players sitting in the requested bracket + * (e.g. DIAMOND I) on the given platform region. + */ +export async function TftLeagueByTierDivision () { + const tftApi = new TftApi() -export async function TftLeagueByTierDivision() { - const league = await api.League.getByTierDivision( + // Page through the DIAMOND I ladder on the configured platform region. + // Signature: getByTierDivision(region, tier, division, page = 1, queue = 'RANKED_TFT') + const { response: entries } = await tftApi.League.getByTierDivision( configTft.region, - Tiers.DIAMOND, - Divisions.I - ); + Constants.Tiers.DIAMOND, + Constants.Divisions.I + ) + + console.log(`Bracket: DIAMOND I (${configTft.region})`) + console.log(`Entries: ${entries.length}`) + + // Show the top few players on this page by league points + const top = [...entries].sort((a, b) => b.leaguePoints - a.leaguePoints).slice(0, 5) + for (const entry of top) { + console.log(` ${entry.leaguePoints} LP - ${entry.wins}W/${entry.losses}L`) + } + + return entries } diff --git a/example/tft/TftMatchDetails.example.ts b/example/tft/TftMatchDetails.example.ts index cd94135b..96ec4e88 100644 --- a/example/tft/TftMatchDetails.example.ts +++ b/example/tft/TftMatchDetails.example.ts @@ -1,13 +1,44 @@ -import { RiotApi } from '../../src' -import { TftApi } from '../../src' +import { RiotApi, TftApi } from '../../src' import { configTft } from '../config/config' -const rApi = new RiotApi() -const api = new TftApi() - +/** + * TFT-MATCH-V1 — Get the full detail of a single Teamfight Tactics match. + * + * We resolve the account, grab its most recent match id, then fetch the + * match info (participants, placements, set number, ...). + */ export async function matchDetailsTft () { - const { response: { puuid } } = await rApi.Account.getByRiotId(configTft.summonerName, configTft.region, configTft.regionGroup) - const { response: [matchId] } = await api.Match.list(puuid, configTft.tftRegion) - console.log(matchId) - return api.Match.get(matchId, configTft.tftRegion) + const riotApi = new RiotApi() + const tftApi = new TftApi() + + // 1. Resolve the Riot ID into a PUUID + const { response: account } = await riotApi.Account.getByRiotId( + configTft.summonerName, + configTft.tagLine, + configTft.regionGroup + ) + + // 2. List the match ids and pick the most recent one + const { response: matchIds } = await tftApi.Match.list(account.puuid, configTft.tftRegion) + const [matchId] = matchIds + if (!matchId) { + console.log('No TFT matches found for this player.') + return undefined + } + + // 3. Fetch the full match detail (same RegionGroups routing as the list) + const { response: match } = await tftApi.Match.get(matchId, configTft.tftRegion) + + console.log(`Match id : ${match.metadata.match_id}`) + console.log(`Set number : ${match.info.tft_set_number}`) + console.log(`Game length : ${Math.round(match.info.game_length)}s`) + console.log(`Participants : ${match.info.participants.length}`) + + // Find our player's row to show their placement + const me = match.info.participants.find(p => p.puuid === account.puuid) + if (me) { + console.log(`Your placement: #${me.placement} (level ${me.level})`) + } + + return match } diff --git a/example/tft/TftMatchList.example.ts b/example/tft/TftMatchList.example.ts index 365b60c3..f99a77d8 100644 --- a/example/tft/TftMatchList.example.ts +++ b/example/tft/TftMatchList.example.ts @@ -1,13 +1,29 @@ -import { RiotApi } from '../../src' -import { TftApi } from '../../src' +import { RiotApi, TftApi } from '../../src' import { configTft } from '../config/config' -const rApi = new RiotApi() -const api = new TftApi() - +/** + * TFT-MATCH-V1 — List a player's recent Teamfight Tactics match ids. + * + * TFT matches are routed by `RegionGroups` (AMERICAS / ASIA / EUROPE / SEA), + * but the account still has to be resolved through the Account API first. + */ export async function matchListTft () { - const { response: { puuid } } = await rApi.Account.getByRiotId(configTft.summonerName, configTft.region, configTft.regionGroup) - const x = await api.Match.list(puuid, configTft.tftRegion) - console.log(puuid) - console.log(x) + const riotApi = new RiotApi() + const tftApi = new TftApi() + + // 1. Resolve the Riot ID (gameName#tagLine) into a PUUID via the Account API + const { response: account } = await riotApi.Account.getByRiotId( + configTft.summonerName, + configTft.tagLine, + configTft.regionGroup + ) + + // 2. Fetch the match ids on the TFT routing region (a RegionGroups value) + const { response: matchIds } = await tftApi.Match.list(account.puuid, configTft.tftRegion) + + console.log(`Player : ${account.gameName}#${account.tagLine}`) + console.log(`Match ids : ${matchIds.length} found`) + console.log(`Most recent: ${matchIds[0] ?? 'no matches'}`) + + return matchIds } diff --git a/example/tft/TftMatchListDetails.example.ts b/example/tft/TftMatchListDetails.example.ts index 7ddd916e..6f8a25b7 100644 --- a/example/tft/TftMatchListDetails.example.ts +++ b/example/tft/TftMatchListDetails.example.ts @@ -1,11 +1,35 @@ -import { RiotApi } from '../../src' -import { TftApi } from '../../src' +import { RiotApi, TftApi } from '../../src' import { configTft } from '../config/config' -const rApi = new RiotApi() -const api = new TftApi() - +/** + * TFT-MATCH-V1 — List a player's recent matches WITH their full detail. + * + * `listWithDetails` is a convenience helper: it lists the match ids and then + * fetches each match, so you get an array of full match objects in one call. + */ export async function matchListDetailsTft () { - const { response: { puuid } } = await rApi.Account.getByRiotId(configTft.summonerName, configTft.region, configTft.regionGroup) - return api.Match.listWithDetails(puuid, configTft.tftRegion) + const riotApi = new RiotApi() + const tftApi = new TftApi() + + // 1. Resolve the Riot ID into a PUUID + const { response: account } = await riotApi.Account.getByRiotId( + configTft.summonerName, + configTft.tagLine, + configTft.regionGroup + ) + + // 2. Fetch the recent matches already expanded to their full detail. + // Note: listWithDetails returns the match array DIRECTLY (no { response } wrapper). + const matches = await tftApi.Match.listWithDetails(account.puuid, configTft.tftRegion) + + console.log(`Player : ${account.gameName}#${account.tagLine}`) + console.log(`Matches loaded: ${matches.length}`) + + // Summarise this player's placement in each loaded match + for (const match of matches) { + const me = match.info.participants.find(p => p.puuid === account.puuid) + console.log(` ${match.metadata.match_id} -> placement #${me?.placement ?? '?'}`) + } + + return matches } diff --git a/example/tft/TftSpectatorActiveGames.example.ts b/example/tft/TftSpectatorActiveGames.example.ts index a8eec642..16228df8 100644 --- a/example/tft/TftSpectatorActiveGames.example.ts +++ b/example/tft/TftSpectatorActiveGames.example.ts @@ -1,11 +1,32 @@ -import { RiotApi } from '../../src' -import { TftApi } from '../../src' -import { RegionGroups, Regions } from '../../src/constants' +import { RiotApi, TftApi } from '../../src' +import { configTft } from '../config/config' +/** + * TFT-SPECTATOR-V5 — Get the live game a player is currently in. + * + * This 404s when the player is not in a game, so the call is wrapped in a + * try/catch. The active-game endpoint is platform-region based. + */ export async function tftSpectatorActiveGames () { - const rApi = new RiotApi() - const tApi = new TftApi() + const riotApi = new RiotApi() + const tftApi = new TftApi() - const { puuid } = (await rApi.Account.getByRiotId('Mimoru', '6129', RegionGroups.AMERICAS)).response - return await tApi.SpectatorV5.activeGame(puuid, Regions.AMERICA_NORTH) + // 1. Resolve the Riot ID into a PUUID + const { response: account } = await riotApi.Account.getByRiotId( + configTft.summonerName, + configTft.tagLine, + configTft.regionGroup + ) + + // 2. Look up the player's current game (404 if they are not in one) + try { + const { response: game } = await tftApi.SpectatorV5.activeGame(account.puuid, configTft.region) + console.log(`${account.gameName}#${account.tagLine} is in game ${game.gameId}`) + console.log(`Mode : ${game.gameMode} (queue ${game.gameQueueConfigId})`) + console.log(`Players: ${game.participants.length}`) + return game + } catch (e) { + console.log(`${account.gameName}#${account.tagLine} is not currently in a TFT game.`) + return undefined + } } diff --git a/example/tft/TftSpectatorFeaturedGames.example.ts b/example/tft/TftSpectatorFeaturedGames.example.ts index 69dc4cc0..e8e884fc 100644 --- a/example/tft/TftSpectatorFeaturedGames.example.ts +++ b/example/tft/TftSpectatorFeaturedGames.example.ts @@ -1,7 +1,23 @@ import { TftApi } from '../../src' -import { Regions } from '../../src/constants' +import { configTft } from '../config/config' -export async function spectatorTFTV5FeaturedGames() { - const api = new TftApi() - return await api.SpectatorV5.featuredGames(Regions.AMERICA_NORTH) -} \ No newline at end of file +/** + * TFT-SPECTATOR-V5 — List the games Riot is currently featuring. + * + * Featured games are platform-region based and need no account: Riot returns + * a curated list of in-progress games plus a suggested refresh interval. + */ +export async function spectatorTFTV5FeaturedGames () { + const tftApi = new TftApi() + + // Fetch the featured games on the configured platform region + const { response: featured } = await tftApi.SpectatorV5.featuredGames(configTft.region) + + console.log(`Featured games : ${featured.gameList.length}`) + console.log(`Refresh interval: ${featured.clientRefreshInterval}s`) + for (const game of featured.gameList) { + console.log(` game ${game.gameId} - ${game.participants.length} players (queue ${game.gameQueueConfigId})`) + } + + return featured +} diff --git a/package.json b/package.json index d06c1d1d..bcb4deba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "twisted", - "version": "1.75.1", + "version": "1.80.0", "description": "Fetching riot games api data", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,7 +14,7 @@ "prepublishOnly": "npm run build" }, "engines": { - "node": ">=8.6.0" + "node": ">=18.0.0" }, "keywords": [ "riot", @@ -41,9 +41,7 @@ "license": "MIT", "devDependencies": { "@types/chai": "^4.3.11", - "@types/dotenv": "^6.1.1", "@types/jest": "^29.5.10", - "@types/lodash": "^4.14.202", "@types/node": "^20.10.0", "@types/promise-queue": "^2.2.3", "@types/sinon": "^17.0.2", @@ -58,10 +56,7 @@ "typescript": "^5.3.2" }, "dependencies": { - "axios": "^1.6.2", - "dotenv": "^16.3.1", "http-status-codes": "^2.3.0", - "lodash": "^4.17.21", "promise-queue": "^2.2.5", "uuid": "^14.0.0" }, diff --git a/runExamples.ts b/runExamples.ts index 3461c79d..1875ca70 100644 --- a/runExamples.ts +++ b/runExamples.ts @@ -1,5 +1,4 @@ import * as allExamples from './example' -import * as _ from 'lodash' const interval = 1000 @@ -17,7 +16,7 @@ async function runExamples () { console.log('------------------------------------------------') for (const key of examples) { console.log(`Run ${key}`) - const method = _.get(allExamples, key) + const method = (allExamples as Record)[key] if (typeof method !== 'function') { console.error(`Method ${key} isn't a function`) continue diff --git a/src/apis/lol/dataDragon/DataDragonService.ts b/src/apis/lol/dataDragon/DataDragonService.ts index 5351ac71..233c67c1 100644 --- a/src/apis/lol/dataDragon/DataDragonService.ts +++ b/src/apis/lol/dataDragon/DataDragonService.ts @@ -1,4 +1,3 @@ -import Axios, { AxiosRequestConfig } from 'axios' import { DataDragonEnum } from '../../../constants/dataDragon' import { RealmServers } from '../../../constants/realmServers' import { RealmDTO, ChampionsDataDragon, QueuesDataDragonDTO, GameModesDataDragonDTO } from '../../../models-dto' @@ -18,11 +17,11 @@ const defaultLang = 'en_US' export class DataDragonService { // Internal methods private async request (path: string, base: DataDragonEnum = DataDragonEnum.BASE): Promise { - const options: AxiosRequestConfig = { - url: `${base}/${path}`, - method: 'GET' + const response = await fetch(`${base}/${path}`) + if (!response.ok) { + throw new Error(`Data Dragon request failed (${response.status} ${response.statusText}): ${base}/${path}`) } - return (await Axios(options)).data + return await response.json() as T } // Riot requests // Data dragon diff --git a/src/apis/lol/seed/seed.ts b/src/apis/lol/seed/seed.ts index eda1a4e4..c35aa3ad 100644 --- a/src/apis/lol/seed/seed.ts +++ b/src/apis/lol/seed/seed.ts @@ -1,4 +1,3 @@ -import Axios, { AxiosRequestConfig } from 'axios' import { DataSeed } from '../../../constants/dataSeed' import { MatchDto } from '../../../models-dto/matches/match/match.dto' @@ -7,11 +6,11 @@ export class SeedApi { private async request (path: string): Promise { const url = `${this.baseUrl}/${path}` - const options: AxiosRequestConfig = { - url, - method: 'GET' + const response = await fetch(url) + if (!response.ok) { + throw new Error(`Seed request failed (${response.status} ${response.statusText}): ${url}`) } - return (await Axios(options)).data + return await response.json() as T } async matches (id: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10): Promise<{ matches: MatchDto[] }> { diff --git a/src/apis/lol/summoner/summoner.ts b/src/apis/lol/summoner/summoner.ts index c9ad477b..dd31cac0 100644 --- a/src/apis/lol/summoner/summoner.ts +++ b/src/apis/lol/summoner/summoner.ts @@ -1,4 +1,3 @@ -import * as _ from 'lodash' import { endpointsV4, IEndpoint } from '../../../endpoints/endpoints' import { SummonerV4DTO } from '../../../models-dto/summoners/summoner.dto' import { Regions } from '../../../constants' @@ -17,7 +16,7 @@ export class SummonerApi extends BaseApiLol { return path } private genericRequest (by: FindSummonerBy, value: string, region: Regions) { - const endpoint = _.cloneDeep(endpointsV4.Summoner) + const endpoint = { ...endpointsV4.Summoner } endpoint.path = this.parsePath(endpoint, by) const params = { summonerName: value, diff --git a/src/apis/tft/summoner/summoner.ts b/src/apis/tft/summoner/summoner.ts index 00186d03..8dafaa99 100644 --- a/src/apis/tft/summoner/summoner.ts +++ b/src/apis/tft/summoner/summoner.ts @@ -1,4 +1,3 @@ -import * as _ from 'lodash' import { endpointsTFTV1, IEndpoint } from '../../../endpoints/endpoints' import { SummonerV4DTO } from '../../../models-dto/summoners/summoner.dto' import { Regions } from '../../../constants' @@ -17,7 +16,7 @@ export class SummonerTftApi extends BaseApiTft { return path } private genericRequest (by: FindSummonerBy, value: string, region: Regions) { - const endpoint = _.cloneDeep(endpointsTFTV1.Summoner) + const endpoint = { ...endpointsTFTV1.Summoner } endpoint.path = this.parsePath(endpoint, by) const params = { summonerName: value, diff --git a/src/base/base.ts b/src/base/base.ts index a31c310c..10a1003c 100644 --- a/src/base/base.ts +++ b/src/base/base.ts @@ -1,22 +1,17 @@ -import { AxiosRequestConfig, AxiosResponse } from 'axios' -import * as _ from 'lodash' import { ApiKeyNotFound } from '../errors' import { IEndpoint } from '../endpoints' import { TOO_MANY_REQUESTS, SERVICE_UNAVAILABLE } from 'http-status-codes' -import { config } from 'dotenv' import { ApiResponseDTO } from '../models-dto/api-response/api-response' import { RateLimitDto } from '../models-dto/rate-limit/rate-limit.dto' import { GenericError } from '../errors/Generic.error' import { RateLimitError } from '../errors/rate-limit.error' -import { IBaseApiParams, IParams, waiter } from './base.utils' +import { IBaseApiParams, IParams, RequestOptions, waiter } from './base.utils' import { ServiceUnavailable } from '../errors/service-unavailable.error' import { BaseConstants, BaseApiGames } from './base.const' import { Logger } from './logger.base' -import { RequestBase } from './request.base' +import { RequestBase, HttpResponse } from './request.base' import { RegionGroups } from '../constants' -config() - export class BaseApi { protected readonly game: BaseApiGames = BaseApiGames.LOL private baseUrl: string = BaseConstants.BASE_URL @@ -58,13 +53,13 @@ export class BaseApi { } if (typeof param.debug !== 'undefined') { if (typeof param.debug.logTime !== 'undefined') { - _.set(this.debug, 'logTime', param.debug.logTime) + this.debug.logTime = param.debug.logTime } if (typeof param.debug.logUrls !== 'undefined') { - _.set(this.debug, 'logUrls', param.debug.logUrls) + this.debug.logUrls = param.debug.logUrls } if (typeof param.debug.logRatelimits !== 'undefined') { - _.set(this.debug, 'logRatelimits', param.debug.logRatelimits) + this.debug.logRatelimits = param.debug.logRatelimits } } if(typeof param.baseURL !== 'undefined') { @@ -79,14 +74,15 @@ export class BaseApi { } private getRateLimits (headers: any): RateLimitDto { + const h = headers || {} return { - Type: _.get(headers, 'x-rate-limit-type', null), - AppRateLimit: _.get(headers, 'x-app-rate-limit', null), - AppRateLimitCount: _.get(headers, 'x-app-rate-limit-count', null), - MethodRateLimit: _.get(headers, 'x-method-rate-limit'), - MethodRatelimitCount: _.get(headers, 'x-method-rate-limit-count', null), - RetryAfter: +_.get(headers, 'retry-after', 0), - EdgeTraceId: _.get(headers, 'x-riot-edge-trace-id') + Type: h['x-rate-limit-type'] ?? null, + AppRateLimit: h['x-app-rate-limit'] ?? null, + AppRateLimitCount: h['x-app-rate-limit-count'] ?? null, + MethodRateLimit: h['x-method-rate-limit'], + MethodRatelimitCount: h['x-method-rate-limit-count'] ?? null, + RetryAfter: +(h['retry-after'] ?? 0), + EdgeTraceId: h['x-riot-edge-trace-id'] } } @@ -129,7 +125,7 @@ export class BaseApi { } private getError (e: any) { - const headers = this.getRateLimits(_.get(e, 'response.headers')) + const headers = this.getRateLimits(e?.response?.headers) if (this.isRateLimitError(e)) { return new RateLimitError(headers) } @@ -140,7 +136,7 @@ export class BaseApi { return new GenericError(headers, e) } - private internalRequest (options: AxiosRequestConfig): Promise { + private internalRequest (options: RequestOptions): Promise { return RequestBase.request(options) } @@ -208,7 +204,7 @@ export class BaseApi { if (this.debug.logTime) { Logger.start(endpoint, url) } - const options: AxiosRequestConfig = { + const options: RequestOptions = { url, method: 'GET', headers: { @@ -220,7 +216,7 @@ export class BaseApi { Logger.uri(options, endpoint) } try { - const apiResponse = await this.internalRequest>(options) + const apiResponse = await this.internalRequest>(options) const { data, headers } = apiResponse return { rateLimits: this.getRateLimits(headers), diff --git a/src/base/base.utils.ts b/src/base/base.utils.ts index bf7cfa75..736563e9 100644 --- a/src/base/base.utils.ts +++ b/src/base/base.utils.ts @@ -1,10 +1,21 @@ -import { AxiosRequestConfig } from 'axios' -import qs from 'querystring' - export interface IParams { [key: string]: string | number } +/** + * Minimal request options shared across the request pipeline. + * Replaces axios' `AxiosRequestConfig` now that the library uses native `fetch`. + */ +export interface RequestOptions { + url: string + method?: string + headers?: Record + /** + * Query string values. Serialized with {@link stringifyParams}. + */ + params?: Record +} + export interface IBaseApiParams { /** * If api response is 429 (rate limits) try reattempt after needed time (default true) @@ -55,11 +66,41 @@ export function waiter (ms: number) { }) } -export function getUrlFromOptions (options: AxiosRequestConfig): string { - let uri = options.url as string +/** + * Serialize query params into a query string. + * + * - `null`/`undefined` values are skipped (matching axios behaviour). + * - Arrays are expanded into repeated keys (`queue=420&queue=440`), which is + * the format the Riot API expects. + */ +export function stringifyParams (params: Record): string { + const search = new URLSearchParams() + for (const key of Object.keys(params)) { + const value = params[key] + if (value === undefined || value === null) { + continue + } + if (Array.isArray(value)) { + for (const item of value) { + if (item === undefined || item === null) { + continue + } + search.append(key, String(item)) + } + } else { + search.append(key, String(value)) + } + } + return search.toString() +} + +export function getUrlFromOptions (options: RequestOptions): string { + let uri = options.url if (options.params) { - uri += '?' - uri += qs.stringify(options.params) + const query = stringifyParams(options.params) + if (query) { + uri += `?${query}` + } } return uri } diff --git a/src/base/logger.base.ts b/src/base/logger.base.ts index b745336e..a3fba473 100644 --- a/src/base/logger.base.ts +++ b/src/base/logger.base.ts @@ -1,6 +1,5 @@ import { IEndpoint } from '../endpoints/endpoints' -import { AxiosRequestConfig } from 'axios' -import { getUrlFromOptions } from './base.utils' +import { RequestOptions, getUrlFromOptions } from './base.utils' export class Logger { // Private methods @@ -22,7 +21,7 @@ export class Logger { console.timeEnd(name) } - static uri (options: AxiosRequestConfig, endpoint: IEndpoint) { + static uri (options: RequestOptions, endpoint: IEndpoint) { const uri = getUrlFromOptions(options) console.log(`Calling method url: ${uri} (${endpoint.path})`) } diff --git a/src/base/request.base.ts b/src/base/request.base.ts index 4f46bd33..c181ff89 100644 --- a/src/base/request.base.ts +++ b/src/base/request.base.ts @@ -1,15 +1,74 @@ -import Axios, { AxiosRequestConfig } from 'axios' import PromiseQueue from 'promise-queue' +import { RequestOptions, getUrlFromOptions } from './base.utils' + +/** + * Successful response shape returned by the request pipeline. + * Mirrors the small subset of axios' response that the library relies on. + */ +export interface HttpResponse { + data: T + headers: Record + status: number +} + +/** + * Error thrown when the Riot API responds with a non 2xx status code. + * Keeps the same shape the rest of the codebase used to read from axios errors + * (`error.status` and `error.response.{status,data,headers}`). + */ +export class HttpError extends Error { + readonly status: number + readonly response: { + status: number + data: any + headers: Record + } + + constructor (status: number, statusText: string, data: any, headers: Record) { + super(statusText || `Request failed with status code ${status}`) + this.name = 'HttpError' + this.status = status + this.response = { status, data, headers } + Object.setPrototypeOf(this, HttpError.prototype) + } +} + +function headersToObject (headers: Headers): Record { + const result: Record = {} + headers.forEach((value, key) => { + result[key] = value + }) + return result +} + +async function parseBody (response: Response): Promise { + const text = await response.text() + if (!text) { + return undefined + } + try { + return JSON.parse(text) + } catch (e) { + // Some endpoints (e.g. third party code) reply with plain text + return text + } +} export class RequestBase { static queue: PromiseQueue - private static sendRequest (options: AxiosRequestConfig) { - return new Promise((resolve, reject) => { - Axios(options) - .then(resolve) - .catch(reject) + private static async sendRequest (options: RequestOptions): Promise> { + const url = getUrlFromOptions(options) + const response = await fetch(url, { + method: options.method || 'GET', + headers: options.headers }) + const headers = headersToObject(response.headers) + const data = await parseBody(response) + if (!response.ok) { + throw new HttpError(response.status, response.statusText, data, headers) + } + return { data, headers, status: response.status } } private static getQueue () { @@ -23,7 +82,7 @@ export class RequestBase { RequestBase.queue = new PromiseQueue(concurrency, Infinity) } - static request (options: AxiosRequestConfig): Promise { + static request (options: RequestOptions): Promise { return RequestBase.getQueue().add(() => RequestBase.sendRequest(options) as any) } } diff --git a/src/constants/champions.ts b/src/constants/champions.ts index f96b44b8..2ea307d6 100644 --- a/src/constants/champions.ts +++ b/src/constants/champions.ts @@ -1,7 +1,3 @@ -import { invert } from 'lodash' -import Axios from 'axios' -import _ from 'lodash' - /** * Champions - Used as fallback */ @@ -180,7 +176,26 @@ export enum Champions { YUNARA = 804 } -const championIdMap = invert(Champions) +/** + * Bidirectional id <-> name map (replaces lodash `invert`): + * a numeric enum produces both `ANNIE -> 1` and `1 -> ANNIE` entries, so the + * resulting map can resolve a champion in either direction. + */ +const championIdMap: { [key: string]: string } = {} +for (const [key, value] of Object.entries(Champions)) { + championIdMap[String(value)] = key +} + +/** + * camelCase a snake_case / spaced string (replaces lodash `camelCase`). + */ +function camelCase (value: string): string { + return value + .split(/[^a-zA-Z0-9]+/) + .filter((part) => part.length > 0) + .map((part, index) => (index === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1))) + .join('') +} /** * Fetching champion IDs from CommunityDragon's PBE content. See https://www.communitydragon.org/ @@ -188,20 +203,20 @@ const championIdMap = invert(Champions) if (process.env.UPDATE_CHAMPION_IDS) { const updateChampionIDs = () => { const CD_CHAMPIONS = 'https://raw.communitydragon.org/pbe/plugins/rcp-be-lol-game-data/global/default/v1/champion-summary.json' - try { - void Axios(CD_CHAMPIONS) - .then(({ data: cdChamps }) => { - cdChamps.forEach(({ id, alias }: {id: number, alias: string}) => { - const championAlias = alias.replace(/[a-z][A-Z]/g, letter => letter[0] + '_' + letter[1]).toUpperCase() - if (!championIdMap[id]) { - championIdMap[id] = championIdMap[id] || championAlias - championIdMap[championAlias] = championIdMap[championAlias] || '' + id - } - }) - }) - } catch (e) { - console.warn('Updating champion IDs failed') - } + void fetch(CD_CHAMPIONS) + .then((response) => response.json()) + .then((cdChamps: { id: number, alias: string }[]) => { + cdChamps.forEach(({ id, alias }) => { + const championAlias = alias.replace(/[a-z][A-Z]/g, letter => letter[0] + '_' + letter[1]).toUpperCase() + if (!championIdMap[id]) { + championIdMap[id] = championIdMap[id] || championAlias + championIdMap[championAlias] = championIdMap[championAlias] || '' + id + } + }) + }) + .catch(() => { + console.warn('Updating champion IDs failed') + }) } // Schedule once every day. setInterval(updateChampionIDs, 1000 * 60 * 60 * 24) @@ -224,7 +239,7 @@ export function getChampionName (champ: number): string { */ export function getChampionNameCapital (champ: number | string): string { let name = typeof champ === 'number' ? getChampionName(champ) : champ - name = _.camelCase(name.toLowerCase()) + name = camelCase(name.toLowerCase()) name = name.charAt(0).toUpperCase() + name.slice(1) switch (name) { case 'Reksai': diff --git a/src/errors/Generic.error.ts b/src/errors/Generic.error.ts index 1ad5765e..9104a021 100644 --- a/src/errors/Generic.error.ts +++ b/src/errors/Generic.error.ts @@ -1,10 +1,16 @@ import { IErrors } from '.' import { RateLimitDto } from '../models-dto/rate-limit/rate-limit.dto' -import * as Axios from 'axios' import HttpStatusCodes from 'http-status-codes' const message = 'Generic error' +interface HttpErrorLike extends Error { + response?: { + status?: number + data?: any + } +} + /** * Not api key found */ @@ -15,7 +21,7 @@ export class GenericError extends Error implements IErrors { readonly body?: any readonly name = 'GenericError' - constructor (rateLimits: RateLimitDto, error: Axios.AxiosError) { + constructor (rateLimits: RateLimitDto, error: HttpErrorLike) { super(error.message || message) this.status = error.response?.status || HttpStatusCodes.INTERNAL_SERVER_ERROR this.body = error.response?.data diff --git a/src/index.ts b/src/index.ts index d0808f4e..17582c1a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,5 +2,6 @@ import * as constants from './constants' import * as dto from './models-dto' export * from './apis' +export * from './errors' export const Constants = constants export const Dto = dto diff --git a/test/live.test.ts b/test/live.test.ts index 80164fc0..1fbc4140 100644 --- a/test/live.test.ts +++ b/test/live.test.ts @@ -1,9 +1,9 @@ import { LolApi, RiotApi } from '../src/index'; import { Regions, AccountAPIRegionGroups, regionToRegionGroupForAccountAPI } from '../src/constants/regions'; -import * as dotenv from 'dotenv'; import * as fs from 'fs'; import * as path from 'path'; import { CurrentGameInfoDTO } from '../src/models-dto'; +import { loadEnv } from './utils/loadEnv'; describe('Live API Tests', () => { let RIOT_API_KEY: string; @@ -24,7 +24,7 @@ describe('Live API Tests', () => { throw new Error(`Missing .env file at ${envPath}. Please create it with RIOT_API_KEY, RIOT_GAME_NAME, RIOT_TAG, RIOT_REGION.`); } - dotenv.config({ path: envPath }); + loadEnv(envPath); RIOT_API_KEY = process.env.RIOT_API_KEY as string; RIOT_GAME_NAME = process.env.RIOT_GAME_NAME as string; diff --git a/test/utils/loadEnv.ts b/test/utils/loadEnv.ts new file mode 100644 index 00000000..d81c135f --- /dev/null +++ b/test/utils/loadEnv.ts @@ -0,0 +1,30 @@ +import * as fs from 'fs' + +/** + * Minimal `.env` loader shared across the test suite (replaces the `dotenv` + * dependency). + * + * Parses `KEY=value` lines and populates `process.env` without overriding + * variables that are already set. Blank lines and `#` comments are ignored, + * and surrounding single/double quotes are stripped from values. + * + * @param envPath Absolute path to the `.env` file. + */ +export function loadEnv (envPath: string): void { + const content = fs.readFileSync(envPath, 'utf-8') + for (const rawLine of content.split('\n')) { + const line = rawLine.trim() + if (!line || line.startsWith('#')) { + continue + } + const eqIndex = line.indexOf('=') + if (eqIndex === -1) { + continue + } + const key = line.slice(0, eqIndex).trim() + const value = line.slice(eqIndex + 1).trim().replace(/^["']|["']$/g, '') + if (!(key in process.env)) { + process.env[key] = value + } + } +} diff --git a/yarn.lock b/yarn.lock index 1db48fa3..023c96bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -715,13 +715,6 @@ resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.11.tgz#e95050bf79a932cb7305dd130254ccdf9bde671c" integrity sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ== -"@types/dotenv@^6.1.1": - version "6.1.1" - resolved "https://registry.yarnpkg.com/@types/dotenv/-/dotenv-6.1.1.tgz#f7ce1cc4fe34f0a4373ba99fefa437b0bec54b46" - integrity sha512-ftQl3DtBvqHl9L16tpqqzA4YzCSXZfi7g8cQceTz5rOlYtk/IZbFjAv3mLOQlNIgOaylCQWQoBdDQHPgEBJPHg== - dependencies: - "@types/node" "*" - "@types/graceful-fs@^4.1.3": version "4.1.9" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" @@ -766,11 +759,6 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== -"@types/lodash@^4.14.202": - version "4.14.202" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.202.tgz#f09dbd2fb082d507178b2f2a5c7e74bd72ff98f8" - integrity sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ== - "@types/node@*", "@types/node@^20.10.0": version "20.10.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.0.tgz#16ddf9c0a72b832ec4fcce35b8249cf149214617" @@ -1061,25 +1049,11 @@ arraybuffer.prototype.slice@^1.0.2: is-array-buffer "^3.0.2" is-shared-array-buffer "^1.0.2" -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - available-typed-arrays@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== -axios@^1.6.2: - version "1.16.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.16.0.tgz#f8e5dd931cef2a5f8c32216d5784eda2f8750eb7" - integrity sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w== - dependencies: - follow-redirects "^1.16.0" - form-data "^4.0.5" - proxy-from-env "^2.1.0" - babel-jest@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" @@ -1194,14 +1168,6 @@ builtin-modules@^3.3.0: resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== -call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" - integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.4, call-bind@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513" @@ -1306,13 +1272,6 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - comment-parser@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.4.1.tgz#bdafead37961ac079be11eb7ec65c4d021eaf9cc" @@ -1402,11 +1361,6 @@ define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: has-property-descriptors "^1.0.0" object-keys "^1.1.1" -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -1443,20 +1397,6 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dotenv@^16.3.1: - version "16.3.1" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" - integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== - -dunder-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" - integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-errors "^1.3.0" - gopd "^1.2.0" - electron-to-chromium@^1.4.535: version "1.4.595" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.595.tgz#fa33309eb9aabb7426915f8e166ec60f664e9ad4" @@ -1524,23 +1464,6 @@ es-abstract@^1.22.1: unbox-primitive "^1.0.2" which-typed-array "^1.1.13" -es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" - integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== - dependencies: - es-errors "^1.3.0" - es-set-tostringtag@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz#11f7cc9f63376930a5f20be4915834f4bc74f9c9" @@ -1550,16 +1473,6 @@ es-set-tostringtag@^2.0.1: has-tostringtag "^1.0.0" hasown "^2.0.0" -es-set-tostringtag@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" - integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== - dependencies: - es-errors "^1.3.0" - get-intrinsic "^1.2.6" - has-tostringtag "^1.0.2" - hasown "^2.0.2" - es-shim-unscopables@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" @@ -1860,11 +1773,6 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== -follow-redirects@^1.16.0: - version "1.16.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.16.0.tgz#28474a159d3b9d11ef62050a14ed60e4df6d61bc" - integrity sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw== - for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -1872,17 +1780,6 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" -form-data@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.5.tgz#b49e48858045ff4cbf6b03e1805cebcad3679053" - integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - es-set-tostringtag "^2.1.0" - hasown "^2.0.2" - mime-types "^2.1.12" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -1933,35 +1830,11 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" -get-intrinsic@^1.2.6: - version "1.3.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" - integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== - dependencies: - call-bind-apply-helpers "^1.0.2" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.1.1" - function-bind "^1.1.2" - get-proto "^1.0.1" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.1.0" - get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" - integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== - dependencies: - dunder-proto "^1.0.1" - es-object-atoms "^1.0.0" - get-stream@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" @@ -2039,11 +1912,6 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" @@ -2086,11 +1954,6 @@ has-symbols@^1.0.2, has-symbols@^1.0.3: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== -has-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - has-tostringtag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" @@ -2098,13 +1961,6 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" -has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - dependencies: - has-symbols "^1.0.3" - hasown@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" @@ -2112,13 +1968,6 @@ hasown@^2.0.0: dependencies: function-bind "^1.1.2" -hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" @@ -2866,11 +2715,6 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash@^4.17.21: - version "4.17.23" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a" - integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w== - lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -2904,11 +2748,6 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" -math-intrinsics@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" - integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== - merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -2927,18 +2766,6 @@ micromatch@^4.0.4: braces "^3.0.3" picomatch "^2.3.1" -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -3191,11 +3018,6 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -proxy-from-env@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz#a7487568adad577cfaaa7e88c49cab3ab3081aba" - integrity sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA== - punycode@^2.1.0: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"