├── .prettierignore ├── .eslintignore ├── tsconfig.cjs.json ├── .prettierrc.js ├── tsconfig.esm.json ├── src ├── riot │ ├── static │ │ ├── utils.ts │ │ ├── cdragon.type.ts │ │ ├── ddragon.ts │ │ ├── general.type.ts │ │ ├── general.ts │ │ ├── cdragon.ts │ │ └── ddragon.type.ts │ └── apis │ │ ├── utils.ts │ │ ├── enums.ts │ │ ├── query.ts │ │ ├── index.ts │ │ ├── rate-limiter.ts │ │ ├── endpoints.ts │ │ └── dtos.ts ├── types.ts ├── index.ts └── leaguepedia │ ├── generateUrl.ts │ ├── types.ts │ └── index.ts ├── .github └── workflows │ └── release.yml ├── scripts ├── riot.test.ts ├── utils.ts ├── gen.ts ├── cargo.ts ├── cargo.test.ts ├── riot.ts └── __snapshots__ │ ├── endpoints.ts │ └── dtos.ts ├── LICENSE ├── .eslintrc.js ├── .gitignore ├── package.json ├── README.md ├── tests ├── leaguepedia.test.ts └── riot.test.ts └── tsconfig.json /.prettierignore: -------------------------------------------------------------------------------- 1 | __snapshots__ -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | out/**/* 2 | __snapshots__ -------------------------------------------------------------------------------- /tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"] 4 | } 5 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | semi: false, 4 | htmlWhitespaceSensitivity: 'ignore', 5 | trailingComma: 'all', 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.cjs.json", 3 | "compilerOptions": { 4 | "module": "ESNext", 5 | "outDir": "./dist/esm", 6 | "moduleResolution": "node" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/riot/static/utils.ts: -------------------------------------------------------------------------------- 1 | export class Version { 2 | constructor(protected readonly version: string) {} 3 | } 4 | 5 | export class VersionLanguage { 6 | protected language: string 7 | constructor(protected version: string, language: string) { 8 | this.language = language.replace('-', '_') 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import * as DTO from './riot/apis/dtos' 2 | import * as Query from './riot/apis/query' 3 | import * as CData from './riot/static/cdragon.type' 4 | import * as Data from './riot/static/ddragon.type' 5 | import * as General from './riot/static/general.type' 6 | 7 | export * from './riot/apis/enums' 8 | export { DTO, Query, CData, Data, General } 9 | -------------------------------------------------------------------------------- /src/riot/static/cdragon.type.ts: -------------------------------------------------------------------------------- 1 | const queueExamples = [ 2 | { 3 | id: 420, 4 | name: 'Ranked Solo/Duo', 5 | shortName: 'Ranked Solo/Duo', 6 | description: 'Ranked Solo/Duo', 7 | detailedDescription: '', 8 | gameSelectModeGroup: 'kSummonersRift', 9 | gameSelectCategory: 'kPvP', 10 | gameSelectPriority: 20, 11 | isSkillTreeQueue: false, 12 | hidePlayerPosition: false, 13 | }, 14 | ] 15 | 16 | export type QueuesJSON = typeof queueExamples 17 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | push: 8 | tags: 9 | - 'v*' 10 | 11 | jobs: 12 | release: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | 19 | - uses: actions/setup-node@v4 20 | with: 21 | node-version: lts/* 22 | 23 | - run: npx changelogithub 24 | env: 25 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 26 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | CommunityDragon, 3 | CDRAGON_DOMAIN, 4 | RAW_CDRAGON_DOMAIN, 5 | } from './riot/static/cdragon' 6 | export { DataDragon, DDRAGON_DOMAIN } from './riot/static/ddragon' 7 | export { general } from './riot/static/general' 8 | 9 | export { CargoClient } from './leaguepedia' 10 | export { schemaMap } from './leaguepedia/schema' 11 | export * as Cargo from './leaguepedia/types' 12 | 13 | export { RiotClient } from './riot/apis' 14 | export * from './riot/apis/utils' 15 | export * as Riot from './types' 16 | -------------------------------------------------------------------------------- /src/riot/apis/utils.ts: -------------------------------------------------------------------------------- 1 | import { Platform, Region } from './enums' 2 | 3 | export const regionPlatformMap: Record = { 4 | [Region.AMERICAS]: [ 5 | Platform.BR, 6 | Platform.LAN, 7 | Platform.LAS, 8 | Platform.NA, 9 | Platform.OCE, 10 | ], 11 | [Region.ASIA]: [Platform.JP, Platform.KR], 12 | [Region.EUROPE]: [Platform.EUNE, Platform.EUW, Platform.RU, Platform.TR], 13 | } 14 | 15 | export function mapPlatformToRegion(platform: Platform) { 16 | for (const [region, platforms] of Object.entries(regionPlatformMap)) { 17 | if (platforms.includes(platform)) { 18 | return region as Region 19 | } 20 | } 21 | return Region.AMERICAS 22 | } 23 | -------------------------------------------------------------------------------- /src/riot/static/ddragon.ts: -------------------------------------------------------------------------------- 1 | import { Image, MetaFile } from './ddragon.type' 2 | import { VersionLanguage } from './utils' 3 | 4 | export const DDRAGON_DOMAIN = 'ddragon.leagueoflegends.com' 5 | export const DDRAGON_BASE_URL = `https://${DDRAGON_DOMAIN}` 6 | 7 | export class DataDragon extends VersionLanguage { 8 | private readonly prefixImmutable = DDRAGON_BASE_URL + `/cdn` 9 | private readonly prefix = DDRAGON_BASE_URL + `/cdn/${this.version}` 10 | 11 | meta(file: MetaFile) { 12 | return this.prefix + `/data/${this.language}/${file}.json` 13 | } 14 | image(img: Pick) { 15 | return this.prefix + `/img/${img.group}/${img.full}` 16 | } 17 | imageImmutable(path: string) { 18 | return this.prefixImmutable + '/img/' + path 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /scripts/riot.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest' 2 | 3 | import { fetchPageNames, genEndpoints } from './riot' 4 | 5 | describe('riot', () => { 6 | it('should fetch page names', async () => { 7 | expect(fetchPageNames()).resolves.toMatchInlineSnapshot(` 8 | [ 9 | "account-v1", 10 | "champion-mastery-v4", 11 | "champion-v3", 12 | "clash-v1", 13 | "league-v4", 14 | "lol-challenges-v1", 15 | "lol-rso-match-v1", 16 | "lol-status-v4", 17 | "match-v5", 18 | "spectator-v5", 19 | "summoner-v4", 20 | ] 21 | `) 22 | }) 23 | 24 | it( 25 | 'should generate endpoints', 26 | async () => { 27 | const result = await genEndpoints() 28 | expect(result.content).toMatchFileSnapshot('__snapshots__/endpoints.ts') 29 | expect(result.dtos).toMatchFileSnapshot('__snapshots__/dtos.ts') 30 | }, 31 | { timeout: Infinity }, 32 | ) 33 | }) 34 | -------------------------------------------------------------------------------- /src/riot/apis/enums.ts: -------------------------------------------------------------------------------- 1 | export enum Platform { 2 | BR = 'BR1', 3 | EUNE = 'EUN1', 4 | EUW = 'EUW1', 5 | JP = 'JP1', 6 | KR = 'KR', 7 | LAN = 'LA1', 8 | LAS = 'LA2', 9 | NA = 'NA1', 10 | OCE = 'OC1', 11 | TR = 'TR1', 12 | RU = 'RU', 13 | } 14 | 15 | export enum Region { 16 | /** BR, LAN, LAS, NA, OCE */ 17 | AMERICAS = 'AMERICAS', 18 | /** JP, KR */ 19 | ASIA = 'ASIA', 20 | /** EUNE, EUW, RU, TR */ 21 | EUROPE = 'EUROPE', 22 | } 23 | 24 | export enum Queue { 25 | RANKED_SOLO_5x5 = 'RANKED_SOLO_5x5', 26 | RANKED_TFT_PAIRS = 'RANKED_TFT_PAIRS', 27 | RANKED_TFT = 'RANKED_TFT', 28 | RANKED_FLEX_SR = 'RANKED_FLEX_SR', 29 | RANKED_FLEX_TT = 'RANKED_FLEX_TT', 30 | } 31 | 32 | export enum Tier { 33 | CHALLENGER = 'CHALLENGER', 34 | GRANDMASTER = 'GRANDMASTER', 35 | MASTER = 'MASTER', 36 | DIAMOND = 'DIAMOND', 37 | EMERALD = 'EMERALD', 38 | PLATINUM = 'PLATINUM', 39 | GOLD = 'GOLD', 40 | SILVER = 'SILVER', 41 | BRONZE = 'BRONZE', 42 | IRON = 'IRON', 43 | } 44 | 45 | export enum Division { 46 | I = 'I', 47 | II = 'II', 48 | III = 'III', 49 | IV = 'IV', 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 pacexy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/riot/apis/query.ts: -------------------------------------------------------------------------------- 1 | export type LeagueEntryQuery = { 2 | /** Defaults to 1. Starts with page 1. */ 3 | page?: number 4 | } 5 | 6 | export type MatchIdsQuery = { 7 | /** Filter the list of match ids by a specific queue id. This filter is mutually inclusive of the type filter meaning any match ids returned must match both the queue and type filters. */ 8 | queue?: number 9 | /** Filter the list of match ids by the type of match. This filter is mutually inclusive of the queue filter meaning any match ids returned must match both the queue and type filters. */ 10 | type?: string 11 | /** Defaults to 0. Start index. */ 12 | start?: number 13 | /** Defaults to 20. Valid values: 0 to 100. Number of match ids to return. */ 14 | count?: number 15 | } 16 | 17 | export type GetTopChampionMasteriesQuery = { 18 | /** Number of entries to retrieve, defaults to 3. */ 19 | count?: number 20 | } 21 | 22 | export type GetChallengeLeaderboardsQuery = { 23 | limit?: number 24 | } 25 | 26 | export type RsoMatchIdsQuery = MatchIdsQuery & { 27 | /** Epoch timestamp in seconds. The matchlist started storing timestamps on June 16th, 2021. Any matches played before June 16th, 2021 won't be included in the results if the startTime filter is set. */ 28 | startTime?: number 29 | /** Epoch timestamp in seconds. */ 30 | endTime?: number 31 | } 32 | -------------------------------------------------------------------------------- /src/riot/static/general.type.ts: -------------------------------------------------------------------------------- 1 | export type DocFile = keyof DocFileSchemaMap 2 | export type DocFileSchemaMap = { 3 | seasons: Season[] 4 | queues: Queue[] 5 | maps: Map[] 6 | gameModes: GameMode[] 7 | gameTypes: GameType[] 8 | } 9 | 10 | export type Season = { 11 | id: number 12 | season: string 13 | } 14 | 15 | /** 5v5 Blind Pick games, 5v5 Ranked Solo games... */ 16 | export type Queue = { 17 | queueId: number 18 | map: string 19 | description?: string 20 | notes?: string 21 | } 22 | 23 | export type Map = { 24 | mapId: number 25 | mapName: string 26 | notes: string 27 | } 28 | 29 | /** CLASSIC, ARAM, URF... */ 30 | export type GameMode = { 31 | gameMode: string 32 | description: string 33 | } 34 | 35 | /** CUSTOM_GAME, TUTORIAL_GAME, MATCHED_GAME */ 36 | export type GameType = { 37 | gametype: string 38 | description: string 39 | } 40 | 41 | export type Version = string 42 | 43 | export type Realm = { 44 | n: { 45 | item: Version 46 | rune: Version 47 | mastery: Version 48 | summoner: Version 49 | champion: Version 50 | profileicon: Version 51 | map: Version 52 | language: Version 53 | sticker: Version 54 | } 55 | v: Version 56 | l: Language 57 | cdn: string 58 | dd: Version 59 | lg: Version 60 | css: Version 61 | profileiconmax: number 62 | store: any 63 | } 64 | 65 | export type Language = string 66 | -------------------------------------------------------------------------------- /scripts/utils.ts: -------------------------------------------------------------------------------- 1 | import { writeFileSync } from 'fs' 2 | import { join } from 'path' 3 | 4 | import { JSDOM } from 'jsdom' 5 | import prettier from 'prettier' 6 | 7 | // eslint-disable-next-line @typescript-eslint/no-var-requires 8 | const prettierConfig = require('../.prettierrc.js') 9 | 10 | export function createDocument(html: string) { 11 | const jsdom = new JSDOM(html) 12 | return jsdom.window.document 13 | } 14 | 15 | export function removeRedundantSpace(str?: string | null) { 16 | if (!str) return '' 17 | return str.trim().split(' ').filter(Boolean).join(' ') 18 | } 19 | 20 | export function text(node?: Node | null) { 21 | return removeRedundantSpace(node?.textContent) 22 | } 23 | 24 | export function $(el: Element | Document, selector: string) { 25 | return el.querySelector(selector) 26 | } 27 | 28 | export function $$(el: Element | Document, selector: string) { 29 | return Array.from(el.querySelectorAll(selector)) 30 | } 31 | 32 | export function withComment(content: string, comment?: string) { 33 | if (!comment) return content 34 | return [ 35 | `/* ${comment} */`, // 36 | content, 37 | ].join('\n') 38 | } 39 | 40 | export function writeSchema(pathPrefix: string, content: string) { 41 | return writeFileSync( 42 | join(__dirname, '../schemas', pathPrefix + `_${Date.now()}.ts`), 43 | prettier.format(content, { 44 | ...prettierConfig, 45 | parser: 'typescript', 46 | }), 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /scripts/gen.ts: -------------------------------------------------------------------------------- 1 | import { genSchema } from './cargo' 2 | import { genEndpoints } from './riot' 3 | import { writeSchema } from './utils' 4 | 5 | enum Mode { 6 | Cargo = 1 << 0, 7 | RIOT = 1 << 1, 8 | ALL = Cargo | RIOT, 9 | } 10 | 11 | main() 12 | 13 | function main() { 14 | // Extract the command line arguments 15 | const args = process.argv.slice(2) 16 | 17 | // Parse the arguments 18 | const parsedArgs = args.reduce((acc, arg) => { 19 | const [key, value] = arg.split('=') 20 | if (key.startsWith('--')) { 21 | acc[key.slice(2)] = value 22 | } 23 | return acc 24 | }, {} as Record) 25 | 26 | // Use the parsed argument 27 | const mode = Number(parsedArgs.mode) 28 | 29 | if (mode & Mode.Cargo) { 30 | genCargo() 31 | } 32 | if (mode & Mode.RIOT) { 33 | genRiot() 34 | } 35 | } 36 | 37 | export async function genCargo() { 38 | const schema = await genSchema() 39 | writeSchema('./cargo/schema', schema) 40 | } 41 | 42 | export async function genRiot() { 43 | const result = await genEndpoints() 44 | 45 | writeSchema( 46 | './riot/endpoints', 47 | [ 48 | `export function createEndpoints(limiter: RiotRateLimiter) {`, 49 | ` return {`, 50 | result.content, 51 | ` }`, 52 | `}`, 53 | ].join('\n'), 54 | ) 55 | 56 | writeSchema( 57 | './riot/dtos', 58 | [`import { Queue, Tier, Division } from './enums'`, result.dtos].join( 59 | '\n\n', 60 | ), 61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /src/leaguepedia/generateUrl.ts: -------------------------------------------------------------------------------- 1 | import { URLSearchParams } from 'url' 2 | 3 | import { schemaMap } from './schema' 4 | import { Field, Parameter, Table } from './types' 5 | 6 | function removeUndefinedProperty(obj: Record) { 7 | Object.keys(obj).forEach((key) => { 8 | if (obj[key] === undefined) { 9 | delete obj[key] 10 | } 11 | }) 12 | 13 | return obj 14 | } 15 | 16 | export async function generateUrl>({ 17 | tables, 18 | fields, 19 | where, 20 | joinOn, 21 | groupBy, 22 | having, 23 | orderBy = [{ field: `${tables[0]}._pageName` as unknown as Field }], 24 | limit = Number.MAX_SAFE_INTEGER, 25 | offset = 0, 26 | format = 'json', 27 | }: // to make assignment work, specify fields to Field 28 | // cause there is no need to infer what fields exactly is 29 | Parameter, LeftField>) { 30 | if (!fields) { 31 | fields = [] 32 | for (const table of tables) { 33 | fields = fields.concat( 34 | Object.keys(schemaMap[table]).map( 35 | (fieldName) => `${table}.${fieldName}` as Field, 36 | ), 37 | ) 38 | } 39 | } 40 | 41 | const searchParams = new URLSearchParams( 42 | removeUndefinedProperty({ 43 | tables, 44 | fields, 45 | where, 46 | 'join on': joinOn?.map((item) => `${item.left}=${item.right}`), 47 | 'group by': groupBy, 48 | having, 49 | 'order by': orderBy.map( 50 | (item) => `${item.field}${item.desc ? ' DESC' : ''}`, 51 | ), 52 | limit: limit.toString(), 53 | offset: offset.toString(), 54 | format, 55 | }), 56 | ) 57 | 58 | return '/wiki/Special:CargoExport?' + searchParams.toString() 59 | } 60 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | ecmaVersion: 'latest', 5 | sourceType: 'module', 6 | }, 7 | extends: [ 8 | 'eslint:recommended', 9 | 'plugin:node/recommended', 10 | 'plugin:prettier/recommended', 11 | 'plugin:@typescript-eslint/recommended', 12 | ], 13 | plugins: ['import'], 14 | rules: { 15 | // note you must disable the base rule as it can report incorrect errors 16 | 'no-unused-vars': 'off', 17 | '@typescript-eslint/no-unused-vars': 'error', 18 | '@typescript-eslint/no-non-null-assertion': 'off', 19 | '@typescript-eslint/explicit-module-boundary-types': 'off', 20 | '@typescript-eslint/no-empty-interface': 'off', 21 | '@typescript-eslint/no-explicit-any': 'off', 22 | 'node/no-missing-import': 'off', 23 | 'node/no-unsupported-features/es-syntax': 'off', 24 | 'node/no-missing-require': 'off', 25 | 'node/shebang': 'off', 26 | 'no-console': 'error', 27 | 'import/order': [ 28 | 'error', 29 | { 30 | alphabetize: { order: 'asc' }, 31 | 'newlines-between': 'always', 32 | groups: [ 33 | 'builtin', 34 | 'external', 35 | 'internal', 36 | 'parent', 37 | 'sibling', 38 | 'index', 39 | ], 40 | pathGroups: [ 41 | { 42 | pattern: '@/**', 43 | group: 'internal', 44 | }, 45 | ], 46 | pathGroupsExcludedImportTypes: ['builtin'], 47 | }, 48 | ], 49 | }, 50 | overrides: [ 51 | { 52 | files: ['*.js'], 53 | rules: { 54 | '@typescript-eslint/no-var-requires': 'off', 55 | 'node/no-unpublished-require': 'off', 56 | }, 57 | }, 58 | ], 59 | } 60 | -------------------------------------------------------------------------------- /src/riot/static/general.ts: -------------------------------------------------------------------------------- 1 | import { Platform } from '../apis/enums' 2 | 3 | import { DDRAGON_BASE_URL } from './ddragon' 4 | import { DocFile } from './general.type' 5 | 6 | /** 7 | * When looking up specific seasons, queues, maps and modes it is important 8 | * to use the correct ids. 9 | */ 10 | export const general = { 11 | doc(file: DocFile) { 12 | return `https://static.developer.riotgames.com/docs/lol/${file}.json` 13 | }, 14 | /** 15 | * You can find all valid Data Dragon versions in the versions file. 16 | * Typically there's only a single build of Data Dragon for a given patch, 17 | * however occasionally there will be additional builds. This typically 18 | * occurs when there's an error in the original build. As such, you should 19 | * always use the most recent Data Dragon version for a given patch for the 20 | * best results. 21 | */ 22 | versions: DDRAGON_BASE_URL + '/api/versions.json', 23 | /** 24 | * Data Dragon versions aren't always equivalent to the League of Legends 25 | * client version in a region. You can find the version each region is using 26 | * via the realms files. 27 | */ 28 | realm(region: string) { 29 | const platforms = Object.values(Platform) 30 | const platformIndex = platforms.findIndex((platform) => platform === region) 31 | const platformName = Object.keys(Platform)[platformIndex] ?? region 32 | return DDRAGON_BASE_URL + `/realms/${platformName.toLowerCase()}.json` 33 | }, 34 | /** 35 | * Data Dragon provides localized versions of each of the data files in 36 | * languages supported by the client. Below is a list of the languages 37 | * supported by Data Dragon, which you can also retrieved from the Data 38 | * Dragon languages file. 39 | */ 40 | languages: DDRAGON_BASE_URL + '/cdn/languages.json', 41 | } 42 | -------------------------------------------------------------------------------- /src/leaguepedia/types.ts: -------------------------------------------------------------------------------- 1 | import type { schemaMap } from './schema' 2 | 3 | export type SchemaMap = typeof schemaMap 4 | export type Table = keyof SchemaMap 5 | 6 | export interface Options

{ 7 | metadataPrefix?: P 8 | } 9 | 10 | export type PrefixMetadata = S extends `_${infer Name}` 11 | ? `${P}_${Name}` 12 | : S 13 | 14 | export type PrefixMetaProperties = { 15 | // https://github.com/sindresorhus/type-fest/blob/6de66eb8c26cd37adda7213cdbfb5e8246af8328/source/camel-case.d.ts#L47 16 | // https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#key-remapping-via-as 17 | [K in keyof O as PrefixMetadata]: O[K] 18 | } 19 | 20 | export type Field = T extends Table 21 | ? // Type 'keyof T' does not satisfy the constraint 'string'. 22 | // https://github.com/microsoft/TypeScript/issues/25260#issuecomment-548837595 23 | `${T}.${Extract}` 24 | : never 25 | 26 | type ToFieldName = F extends `${Table}.${infer FieldName}` 27 | ? FieldName 28 | : never 29 | type ToFieldType = F extends `${infer T}.${string}` 30 | ? T extends Table 31 | ? K extends keyof SchemaMap[T] 32 | ? SchemaMap[T][K] 33 | : never 34 | : never 35 | : never 36 | 37 | export type Item = { 38 | [K in ToFieldName]: ToFieldType 39 | } 40 | 41 | export type JoinOn< 42 | T extends Table, 43 | LeftField extends Field, 44 | // infer left table with template literal type 45 | > = LeftField extends `${infer LeftTable}.${string}` 46 | ? { 47 | // use type argument inference 48 | left: LeftField 49 | right: Field> 50 | } 51 | : never 52 | 53 | export interface OrderBy { 54 | field: Field 55 | desc?: boolean 56 | } 57 | 58 | export interface Parameter< 59 | T extends Table, 60 | Fields extends Field, 61 | LeftField extends Field, 62 | > { 63 | tables: T[] 64 | fields?: Fields[] 65 | where?: string 66 | joinOn?: JoinOn[] 67 | groupBy?: Field[] 68 | having?: string 69 | orderBy?: OrderBy[] 70 | limit?: number 71 | offset?: number 72 | format?: string 73 | } 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | lerna-debug.log* 6 | 7 | # Diagnostic reports (https://nodejs.org/api/report.html) 8 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | *.lcov 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (https://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # TypeScript v1 declaration files 43 | typings/ 44 | 45 | # TypeScript cache 46 | *.tsbuildinfo 47 | 48 | # Optional npm cache directory 49 | .npm 50 | 51 | # Optional eslint cache 52 | .eslintcache 53 | 54 | # Microbundle cache 55 | .rpt2_cache/ 56 | .rts2_cache_cjs/ 57 | .rts2_cache_es/ 58 | .rts2_cache_umd/ 59 | 60 | # Optional REPL history 61 | .node_repl_history 62 | 63 | # Output of 'npm pack' 64 | *.tgz 65 | 66 | # dotenv environment variables file 67 | .env 68 | .env.test 69 | 70 | # parcel-bundler cache (https://parceljs.org/) 71 | .cache 72 | 73 | # Next.js build output 74 | .next 75 | 76 | # Nuxt.js build / generate output 77 | .nuxt 78 | dist 79 | 80 | # Gatsby files 81 | .cache/ 82 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 83 | # https://nextjs.org/blog/next-9-1#public-directory-support 84 | # public 85 | 86 | # vuepress build output 87 | .vuepress/dist 88 | 89 | # Serverless directories 90 | .serverless/ 91 | 92 | # FuseBox cache 93 | .fusebox/ 94 | 95 | # DynamoDB Local files 96 | .dynamodb/ 97 | 98 | # TernJS port file 99 | .tern-port 100 | 101 | # Output data for testing 102 | output/ 103 | 104 | # Files for testing 105 | test/ 106 | 107 | # vscode 108 | .vscode/ 109 | 110 | out/ 111 | schemas/ 112 | env.ts -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "poro", 3 | "version": "2.4.0", 4 | "description": "Leaguepedia API & Riot League of Legends API & CommunityDragon & Data Dragon", 5 | "main": "./dist/cjs/src/index.js", 6 | "module": "./dist/esm/src/index.js", 7 | "types": "./dist/esm/src/index.d.ts", 8 | "exports": { 9 | "require": "./dist/cjs/src/index.js", 10 | "import": "./dist/esm/src/index.js" 11 | }, 12 | "sideEffects": false, 13 | "scripts": { 14 | "format": "prettier --write src/**/*.ts", 15 | "gen:cargo": "tsx ./scripts/gen.ts --mode=1", 16 | "gen:riot": "tsx ./scripts/gen.ts --mode=2", 17 | "test:cargo": "vitest --update cargo", 18 | "test:riot": "vitest --update riot", 19 | "clean": "rimraf ./dist", 20 | "build": "pnpm clean && tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json", 21 | "lint": "eslint src/**/*.{ts,tsx} --fix", 22 | "version": "pnpm build", 23 | "postversion": "git push --follow-tags && npm publish" 24 | }, 25 | "files": [ 26 | "dist/cjs/src", 27 | "dist/esm/src" 28 | ], 29 | "keywords": [ 30 | "api", 31 | "league of legends", 32 | "lol", 33 | "riot", 34 | "leaguepedia", 35 | "communitydragon", 36 | "data dragon" 37 | ], 38 | "author": "pacexy", 39 | "license": "MIT", 40 | "homepage": "https://github.com/pacexy/poro", 41 | "repository": { 42 | "type": "git", 43 | "url": "https://github.com/pacexy/poro" 44 | }, 45 | "bugs": { 46 | "url": "https://github.com/pacexy/poro/issues" 47 | }, 48 | "dependencies": { 49 | "axios": "^0.21.1", 50 | "axios-retry": "^3.1.9", 51 | "limiter": "^2.1.0", 52 | "lodash": "^4.17.21", 53 | "p-queue": "^6.6.2", 54 | "request": "^2.88.2", 55 | "riot-ratelimiter": "^0.1.5", 56 | "ts-morph": "^11.0.3", 57 | "tslib": "^2.3.1" 58 | }, 59 | "devDependencies": { 60 | "@types/jsdom": "^21.1.6", 61 | "@types/lodash": "^4.14.172", 62 | "@types/node": "^16.4.2", 63 | "@types/prettier": "^2.3.2", 64 | "@typescript-eslint/eslint-plugin": "^6.12.0", 65 | "@typescript-eslint/parser": "^6.12.0", 66 | "eslint": "^7.31.0", 67 | "eslint-config-prettier": "^8.3.0", 68 | "eslint-plugin-import": "^2.23.4", 69 | "eslint-plugin-node": "^11.1.0", 70 | "eslint-plugin-prettier": "^3.4.0", 71 | "jsdom": "^22.1.0", 72 | "lint-staged": "^10.2.11", 73 | "prettier": "^2.3.2", 74 | "release-it": "^14.11.5", 75 | "rimraf": "^3.0.2", 76 | "simple-git-hooks": "^2.9.0", 77 | "tsx": "^4.5.0", 78 | "typescript": "^5.3.2", 79 | "vitest": "^0.34.6" 80 | }, 81 | "release-it": { 82 | "github": { 83 | "release": true, 84 | "web": true 85 | } 86 | }, 87 | "lint-staged": { 88 | "*.{js,json,yml,yaml,css,scss,ts,tsx,md}": "prettier --write", 89 | "*.{js,ts,tsx}": "eslint --fix" 90 | }, 91 | "simple-git-hooks": { 92 | "pre-commit": "npx lint-staged" 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /scripts/cargo.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { isString } from 'lodash' 4 | 5 | import { CargoClient } from '../src/leaguepedia/index' 6 | 7 | import { text, $, createDocument, $$ } from './utils' 8 | 9 | const cargo = new CargoClient() 10 | const axios = cargo.axiosInstance 11 | const BASE_URL = '/wiki/Special:CargoTables' 12 | 13 | const s = { 14 | TABLE_NAMEs: '.cargo-tablelist-tablename', 15 | TABLE_ROWs: '#mw-content-text > ol > li', 16 | ROW_NAME: 'strong', 17 | ROW_TYPE: 'tt', 18 | } as const 19 | 20 | interface Data { 21 | name: string 22 | children: { 23 | name: string 24 | type: string 25 | isArray: boolean 26 | desc: string 27 | }[] 28 | } 29 | 30 | let string = '' 31 | 32 | async function parse(name: string) { 33 | const { data } = await axios.get(BASE_URL + '/' + name) 34 | const document = createDocument(data) 35 | const fieldEls = $$(document, s.TABLE_ROWs) 36 | 37 | return { 38 | name, 39 | children: fieldEls.map((el) => { 40 | const name = text($(el, s.ROW_NAME)) 41 | const type = text($(el, s.ROW_TYPE)) 42 | const isArray = text(el).startsWith(`${name} - List of ${type}`) 43 | return { 44 | name, 45 | type, 46 | isArray, 47 | desc: text($(el, 'span')), 48 | } 49 | }), 50 | } 51 | } 52 | 53 | function transform(data: Data) { 54 | // nothing 55 | return data 56 | } 57 | 58 | function pre() { 59 | string = `export const schemaMap = {` 60 | } 61 | 62 | function post() { 63 | string += '}' 64 | } 65 | 66 | function updateString(data: Data) { 67 | const map: Record = { 68 | String: "''", 69 | Text: "''", 70 | Date: 'new Date()', 71 | Boolean: false, 72 | Integer: 0, 73 | Float: 1, 74 | Wikitext: "''", 75 | 'Wikitext string': "''", 76 | Page: "''", 77 | Datetime: 'new Date()', 78 | } 79 | 80 | const content = data.children 81 | .map(({ name, type, isArray }) => { 82 | return isArray 83 | ? `${name}: [${map[type]}], // List of ${type}` 84 | : `${name}: ${map[type]}, // ${type}` 85 | }) 86 | .join('\n') 87 | 88 | string += ` 89 | ${data.name}: { 90 | ${content} 91 | _pageName: '', 92 | _pageTitle: '', 93 | _pageNamespace: 0, 94 | _pageID: 0, 95 | _ID: 0, 96 | }, 97 | ` 98 | } 99 | 100 | export async function fetchTables() { 101 | const { data } = await axios.get(BASE_URL) 102 | const document = createDocument(data) 103 | return $$(document, s.TABLE_NAMEs).map(text).filter(isString) 104 | } 105 | 106 | export async function genSchema() { 107 | const tables = await fetchTables() 108 | console.log('tables', tables) 109 | 110 | pre() 111 | for (const table of tables) { 112 | const data = await parse(table) 113 | updateString(transform(data)) 114 | console.log('√', table) 115 | } 116 | post() 117 | 118 | return string 119 | } 120 | -------------------------------------------------------------------------------- /scripts/cargo.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest' 2 | 3 | import { fetchTables, genSchema } from './cargo' 4 | 5 | describe('cargo', () => { 6 | it('should fetch tables', async () => { 7 | expect(fetchTables()).resolves.toMatchInlineSnapshot(` 8 | [ 9 | "Alphabets", 10 | "BroadcastMusicTrackTypes", 11 | "BroadcastMusicTracks", 12 | "BroadcastMusicUsages", 13 | "CargoAttachments", 14 | "CargoFields", 15 | "ChampionFlashcards", 16 | "Champions", 17 | "ChromaSets", 18 | "Chromas", 19 | "Contracts", 20 | "CurrentLeagues", 21 | "Disambiguations", 22 | "EmptySubpages", 23 | "Entities", 24 | "ExternalContent", 25 | "GCDArchive", 26 | "Hooks", 27 | "IgnorePagedata", 28 | "IndividualAchievements", 29 | "Items", 30 | "LadderRankingCycles", 31 | "LadderRankings", 32 | "LeagueGroups", 33 | "Leagues", 34 | "ListplayerCurrent", 35 | "LowPriorityRedirects", 36 | "MatchSchedule", 37 | "MatchScheduleGame", 38 | "NASGLadder2018", 39 | "NASGLadder5Cycles", 40 | "NASGLadder7Cycles", 41 | "NTLGlossary", 42 | "NewsItems", 43 | "Organizations", 44 | "ParticipantsArgs", 45 | "PatchNotes", 46 | "Pentakills", 47 | "PicksAndBansCounters", 48 | "PicksAndBansS7", 49 | "PlayerCompetitiveRulings", 50 | "PlayerImages", 51 | "PlayerLeagueHistory", 52 | "PlayerPronunciationFiles", 53 | "PlayerRedirects", 54 | "PlayerRenames", 55 | "Players", 56 | "PostgameJsonMetadata", 57 | "RegionStatuses", 58 | "Regions", 59 | "ResidencyChanges", 60 | "Retirements", 61 | "RosterChangePortalDates", 62 | "RosterChangePortalPages", 63 | "RosterChanges", 64 | "RosterRumors", 65 | "RunesDataPages", 66 | "ScoreboardCounters", 67 | "ScoreboardGames", 68 | "ScoreboardPlayers", 69 | "ScoreboardTeams", 70 | "SisterTeams", 71 | "Skins", 72 | "SkinsUsed", 73 | "Standings", 74 | "StandingsArgs", 75 | "TeamRedirects", 76 | "TeamRegionChanges", 77 | "TeamRenames", 78 | "TeamRosterPhotos", 79 | "Teamnames", 80 | "TeamnamesInputs", 81 | "TeamnamesPages", 82 | "Teams", 83 | "TeamsWithAutoRosters", 84 | "Tenures", 85 | "TenuresUnbroken", 86 | "Tooltips", 87 | "TournamentGroups", 88 | "TournamentPlayers", 89 | "TournamentResults", 90 | "TournamentResults1v1", 91 | "TournamentRosters", 92 | "TournamentScriptsToSkip", 93 | "TournamentTabs", 94 | "Tournaments", 95 | "TwitterArchive", 96 | "TwitterArchivePages", 97 | "UserPredictionGroups", 98 | "UserPredictions", 99 | "_pageData", 100 | "_fileData", 101 | ] 102 | `) 103 | }) 104 | 105 | it( 106 | 'should generate schema', 107 | async () => { 108 | const result = await genSchema() 109 | expect(result).toMatchFileSnapshot('__snapshots__/schema.ts') 110 | }, 111 | { timeout: Infinity }, 112 | ) 113 | }) 114 | -------------------------------------------------------------------------------- /src/riot/apis/index.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import axiosRetry from 'axios-retry' 3 | 4 | import { createEndpoints } from './endpoints' 5 | import { Platform, Region } from './enums' 6 | import { GeneralRegion, LimiterConfig, RiotRateLimiter } from './rate-limiter' 7 | 8 | type Endpoints = ReturnType 9 | 10 | // Input: '/lol/league/v4/entries/{queue}/{tier}/{division}' 11 | // Output: 'queue' | 'tier' | 'division' 12 | type PathParametersUnion

= 13 | P extends `${string}{${infer Parameter}}${infer Tail}` 14 | ? Parameter | PathParametersUnion 15 | : never 16 | 17 | const regionScopedPathPrefix = ['/riot', '/lol/match/v5'] as const 18 | type RegionScopedPathPrefix = typeof regionScopedPathPrefix[number] 19 | type OriginPrefix

= P extends `${RegionScopedPathPrefix}${string}` 20 | ? [prefix?: Region] 21 | : [platform?: Platform] 22 | 23 | // Input: '/lol/league/v4/entries/{queue}/{tier}/{division}' 24 | // Output: [queue: string, tier: string, division: string, platform?: Platform] 25 | type UrlParameters

= PathParametersUnion

extends never 26 | ? [...OriginPrefix

] 27 | : [ 28 | { 29 | [key in PathParametersUnion

]: string | number 30 | }, 31 | ...OriginPrefix

32 | ] 33 | 34 | // type-safe Object.keys 35 | function keys>(obj: T) { 36 | return Object.keys(obj) as (keyof T)[] 37 | } 38 | 39 | interface RiotClientConfig extends LimiterConfig { 40 | auth: string 41 | platform?: Platform 42 | region?: Region 43 | } 44 | 45 | export class RiotClient { 46 | readonly axiosInstance = axios.create() 47 | private readonly limiter 48 | private readonly endpoints 49 | private readonly platform 50 | private readonly region 51 | 52 | constructor({ auth, platform, region, ...limiterConfig }: RiotClientConfig) { 53 | this.axiosInstance.defaults.headers.common['X-Riot-Token'] = auth 54 | this.limiter = new RiotRateLimiter(this.axiosInstance, limiterConfig) 55 | this.endpoints = createEndpoints(this.limiter) 56 | this.platform = platform 57 | this.region = region 58 | 59 | axiosRetry(this.axiosInstance, { 60 | retryCondition(err) { 61 | const errCodes = ['ECONNRESET', 'ETIMEDOUT'] 62 | const statusCodes = [403, 429, 503] 63 | const errCode = err.code ?? '' 64 | const statusCode = err.response?.status ?? 0 65 | 66 | if (errCodes.includes(errCode)) { 67 | return true 68 | } 69 | if (statusCodes.includes(statusCode)) { 70 | return true 71 | } 72 | 73 | return false 74 | }, 75 | retryDelay(retryCount, err) { 76 | if (err.response?.status === 429) { 77 | const retryAfter = err.response.headers['retry-after'] 78 | if (retryAfter) { 79 | return retryAfter * 1000 80 | } 81 | } 82 | return axiosRetry.exponentialDelay(retryCount) 83 | }, 84 | }) 85 | } 86 | 87 | path( 88 | path: Path, 89 | ...urlParameters: UrlParameters 90 | ) { 91 | let realPath: string = path 92 | const pathParam = urlParameters[0] 93 | let originPrefix = urlParameters[1] 94 | 95 | if (typeof pathParam === 'object') { 96 | keys(pathParam).forEach((paramName: PathParametersUnion) => { 97 | realPath = realPath.replace( 98 | `{${paramName}}`, 99 | String(pathParam[paramName]), 100 | ) 101 | }) 102 | } else { 103 | originPrefix = pathParam 104 | } 105 | 106 | const isRegionScoped = regionScopedPathPrefix.some((prefix) => 107 | path.startsWith(prefix), 108 | ) 109 | if (isRegionScoped) { 110 | originPrefix ??= this.region ?? Region.AMERICAS 111 | } else { 112 | originPrefix ??= this.platform ?? Platform.NA 113 | } 114 | 115 | return this.endpoints[path]( 116 | originPrefix as GeneralRegion, 117 | encodeURI(realPath), 118 | path, 119 | ) as ReturnType 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/leaguepedia/index.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import axiosRetry from 'axios-retry' 3 | 4 | import { generateUrl } from './generateUrl' 5 | import { schemaMap } from './schema' 6 | import { 7 | Field, 8 | Item, 9 | Options, 10 | Parameter, 11 | PrefixMetaProperties, 12 | Table, 13 | } from './types' 14 | 15 | const LEAGUEPEDIA_BASE_URL = 'https://lol.fandom.com' 16 | 17 | export class CargoClient

{ 18 | private readonly metadataPrefix: string 19 | readonly axiosInstance = axios.create({ 20 | baseURL: LEAGUEPEDIA_BASE_URL, 21 | }) 22 | 23 | constructor({ metadataPrefix }: Options

= {}) { 24 | this.metadataPrefix = metadataPrefix ?? '' 25 | 26 | axiosRetry(this.axiosInstance, { 27 | retryCondition(err) { 28 | const errCodes = ['ECONNRESET', 'ETIMEDOUT'] 29 | const errCode = err.code ?? '' 30 | 31 | if (errCodes.includes(errCode)) { 32 | return true 33 | } 34 | 35 | return false 36 | }, 37 | }) 38 | } 39 | 40 | private spaceToUnderscore>(obj: T) { 41 | // No index signature with a parameter of type 'string' was found on type '{}'. 42 | // https://stackoverflow.com/a/66406882/13151903 43 | const newObj: Record = {} 44 | Object.entries(obj).forEach(([key, value]) => { 45 | newObj[key.replaceAll(' ', '_')] = value 46 | }) 47 | return newObj as T 48 | } 49 | 50 | private convert, T extends Table>( 51 | obj: O, 52 | tables: T[], 53 | ) { 54 | const newObj: Record = {} 55 | 56 | tables.forEach((t) => { 57 | const schema = schemaMap[t] 58 | Object.entries(obj).forEach(([key, value]) => { 59 | if (!(key in schema)) return 60 | const defaultValue = schema[key as keyof typeof schema] 61 | switch (typeof defaultValue) { 62 | case 'boolean': { 63 | // leaguepedia use bit(1) to store boolean 64 | newObj[key] = typeof value === 'number' ? Boolean(value) : null 65 | break 66 | } 67 | case 'number': { 68 | const n = defaultValue ? parseFloat(value) : parseInt(value) 69 | newObj[key] = isNaN(n) ? null : n 70 | break 71 | } 72 | case 'string': { 73 | newObj[key] = value ? String(value) : null 74 | break 75 | } 76 | case 'object': { 77 | if (defaultValue instanceof Date) { 78 | newObj[key] = value ? new Date(value + 'Z') : null 79 | } else { 80 | newObj[key] = value.map((item: any) => String(item)) 81 | } 82 | break 83 | } 84 | default: { 85 | newObj[key] = value 86 | } 87 | } 88 | }) 89 | }) 90 | 91 | return newObj as O 92 | } 93 | 94 | private addPrefixToMetadata>(obj: T) { 95 | // No index signature with a parameter of type 'string' was found on type '{}'. 96 | // https://stackoverflow.com/a/66406882/13151903 97 | let newObj: Record = {} 98 | 99 | if (this.metadataPrefix === '') { 100 | newObj = obj 101 | } else { 102 | Object.entries(obj).forEach(([key, value]) => { 103 | if (key.startsWith('_')) { 104 | newObj[this.metadataPrefix + key] = value 105 | } else { 106 | newObj[key] = value 107 | } 108 | }) 109 | } 110 | 111 | return newObj as P extends '' ? T : PrefixMetaProperties 112 | } 113 | 114 | async query< 115 | T extends Table, 116 | Fields extends Field, 117 | LeftField extends Field, 118 | >(parameter: Parameter) { 119 | const url = await generateUrl(parameter) 120 | const res = await this.axiosInstance.get[]>(url) 121 | 122 | return { 123 | ...res, 124 | data: res.data 125 | // `spaceToUnderscore` should be executed first to guarantee object shape is as same as schema defined in `schemaMap` 126 | .map(this.spaceToUnderscore.bind(this)) 127 | .map((item) => this.convert(item, parameter.tables)) 128 | .map(this.addPrefixToMetadata.bind(this)), 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # poro 2 | 3 | - Leaguepedia API 4 | - Riot League of Legends API 5 | - Community Dragon 6 | - Data Dragon 7 | 8 | ## Install 9 | 10 | ```sh 11 | npm i poro 12 | ``` 13 | 14 | ## Usage 15 | 16 | ### Leaguepedia 17 | 18 | ```ts 19 | import { CargoClient } from 'poro' 20 | 21 | const cargo = new CargoClient() 22 | // specify fields 23 | const teams = await cargo.query({ 24 | tables: ['Teams'], 25 | fields: ['Teams.Name', 'Teams.Region'], 26 | }) 27 | // filter 28 | const g2 = await cargo.query({ 29 | tables: ['Teams'], 30 | where: 'Teams.Name = "G2 Esports"', 31 | }) 32 | // join on 33 | const broadcastMusicUsages = await cargo.query({ 34 | tables: ['BroadcastMusicUsages', 'BroadcastMusicTracks'], 35 | joinOn: [ 36 | { 37 | left: 'BroadcastMusicUsages.TrackID', 38 | right: 'BroadcastMusicTracks.TrackID', 39 | }, 40 | ], 41 | }) 42 | // group 43 | const proplayers = await cargo.query({ 44 | tables: ['Pentakills'], 45 | groupBy: ['Pentakills.Name'], 46 | having: 'COUNT(DateDisplay) > 10', 47 | }) 48 | ``` 49 | 50 | ### Riot 51 | 52 | ```ts 53 | import { RiotClient, Riot } from 'poro' 54 | 55 | const riot = new RiotClient({ 56 | auth: 'RIOT-API-KEY', 57 | platform: Riot.Platform.KR, 58 | region: Riot.Region.ASIA, 59 | }) 60 | const leagueEntries = await riot 61 | .path('/lol/league-exp/v4/entries/{queue}/{tier}/{division}', { 62 | queue: Riot.Queue.RANKED_SOLO_5x5, 63 | tier: Riot.Tier.CHALLENGER, 64 | division: Riot.Division.I, 65 | }) 66 | .get({ query: { page: 1 } }) 67 | ``` 68 | 69 | ### General Data 70 | 71 | ```ts 72 | import { general, Riot } from 'poro' 73 | 74 | // https://static.developer.riotgames.com/docs/lol/gameModes.json 75 | const gameModes = await axios(general.doc('gameModes')) 76 | 77 | // https://ddragon.leagueoflegends.com/realms/na.json 78 | const realm = await axios(general.realm('na')) 79 | ``` 80 | 81 | ### Data Dragon 82 | 83 | ```ts 84 | import { DataDragon, Riot } from 'poro' 85 | 86 | const ddragon = new DataDragon('11.16.1', 'en_US') 87 | 88 | // https://ddragon.leagueoflegends.com/cdn/11.16.1/data/en_US/summoner.json 89 | const summoner = await axios(ddragon.meta('summoner')) 90 | ``` 91 | 92 | ### Community Dragon 93 | 94 | ```ts 95 | import { CommunityDragon, Riot } from 'poro' 96 | 97 | const cdragon = new CommunityDragon('latest', 'default') 98 | 99 | // https://raw.communitydragon.org/latest/plugins/rcp-fe-lol-static-assets/global/default/images/ranked-mini-regalia/challenger.png 100 | cdragon.tier(Riot.Tier.CHALLENGER) 101 | ``` 102 | 103 | ## APIs 104 | 105 | `CargoClient` and `RiotClient` are designed for server side and use axios to send requests. 106 | 107 | Types are out of box, so you don't have to define them yourself. 108 | 109 | ### Leaguepedia 110 | 111 | > You can get the axios instance with `cargo.axiosInstance`. 112 | 113 | ```ts 114 | interface CargoClientOptions { 115 | // Prefix to field names starting with '_' (like '_ID', '_pageName', etc.) 116 | metadataPrefix?: string 117 | } 118 | ``` 119 | 120 | See the parameters needed for a query in [Cargo query](https://lol.fandom.com/wiki/Special:CargoQuery) (or [API sandbox](https://lol.fandom.com/wiki/Special:ApiSandbox)) 121 | 122 | See all fields of a table in 123 | [Cargo tables](https://lol.fandom.com/wiki/Special:CargoTables). 124 | 125 | ```ts 126 | interface CargoQueryParameter { 127 | tables: Table[] 128 | fields?: Field[] // defaults to all fields 129 | where?: string 130 | joinOn?: JoinOn[] 131 | groupBy?: Field[] 132 | having?: string 133 | orderBy?: OrderBy[] // defaults to `[{ field: `${tables[0]}._pageName` }]` 134 | limit?: number // defaults to `Number.MAX_SAFE_INTEGER` 135 | offset?: number // defaults to `0` 136 | format?: string // defaults to 'json' 137 | } 138 | 139 | interface JoinOn { 140 | left: Field 141 | right: Field 142 | } 143 | 144 | interface OrderBy { 145 | field: Field 146 | desc?: boolean // defaults to `false` 147 | } 148 | ``` 149 | 150 | ### Riot 151 | 152 | > You can get the axios instance with `riot.axiosInstance`. 153 | 154 | ```ts 155 | interface RiotClientConfig extends LimiterConfig { 156 | // Riot API KEY 157 | auth: string 158 | // Instance-scoped platform 159 | platform?: Platform 160 | // Instance-scoped region 161 | region?: Region 162 | } 163 | 164 | interface LimiterConfig { 165 | // Concurrency for each region 166 | concurrency?: number 167 | // If `true`, display debug info. 168 | // If 'mock', display debug info and mock requests. 169 | debug?: boolean | 'mock' 170 | } 171 | ``` 172 | 173 | Sometime you need method-scoped `platform` and `region`. Add it as the last parameter in `path` method and it will override the instance-scoped one. 174 | 175 | ```ts 176 | riot.path( 177 | '/lol/summoner/v4/summoners/{encryptedSummonerId}', 178 | { encryptedSummonerId: '...' }, 179 | Riot.Platform.KR, 180 | ) 181 | ``` 182 | 183 | ## Credits 184 | 185 | The Riot API client is inspired by [Building Strongly Typed REST Clients with TypeScript 186 | ](https://www.youtube.com/watch?v=aZ6nnGlfBG8) ([repository](https://github.com/joheredi/openjs-world-2021)). 187 | -------------------------------------------------------------------------------- /tests/leaguepedia.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest' 2 | 3 | import { CargoClient } from '../src' 4 | 5 | describe('cargoQuery leaguepedia table items', () => { 6 | it('single table', () => { 7 | const cargo = new CargoClient() 8 | return cargo 9 | .query({ 10 | tables: ['Alphabets'], 11 | limit: 3, 12 | offset: 2, 13 | }) 14 | .then(({ data }) => { 15 | expect(data).toHaveLength(3) 16 | }) 17 | }) 18 | 19 | it('specified fields', () => { 20 | const cargo = new CargoClient() 21 | return cargo 22 | .query({ 23 | tables: ['Teams'], 24 | fields: ['Teams.Name', 'Teams.Region'], 25 | limit: 2, 26 | }) 27 | .then(({ data }) => { 28 | data.forEach((item) => { 29 | expect(item).not.toHaveProperty('_pageName') 30 | }) 31 | }) 32 | }) 33 | 34 | it('where', () => { 35 | const cargo = new CargoClient() 36 | return cargo 37 | .query({ 38 | tables: ['Teams'], 39 | where: 'Teams.Name = "G2 Esports"', 40 | }) 41 | .then(({ data }) => { 42 | expect(data).toHaveLength(1) 43 | }) 44 | }) 45 | 46 | it('single join on left table', () => { 47 | const cargo = new CargoClient() 48 | return cargo 49 | .query({ 50 | tables: ['Tournaments', 'ScoreboardGames'], 51 | fields: ['ScoreboardGames.Team1', 'Tournaments.Name'], 52 | joinOn: [ 53 | { 54 | left: 'Tournaments.OverviewPage', 55 | right: 'ScoreboardGames.OverviewPage', 56 | }, 57 | ], 58 | limit: 1, 59 | }) 60 | .then(({ data }) => { 61 | expect(data[0]).toHaveProperty('Team1') 62 | expect(data[0]).toHaveProperty('Name') 63 | }) 64 | }) 65 | 66 | it('multiple join on left tables', () => { 67 | const cargo = new CargoClient() 68 | return cargo 69 | .query({ 70 | tables: [ 71 | 'ScoreboardGames', 72 | 'Tournaments', 73 | 'Leagues', 74 | 'MatchScheduleGame', 75 | 'MatchSchedule', 76 | 'PicksAndBansS7', 77 | ], 78 | joinOn: [ 79 | { 80 | left: 'Tournaments.League', 81 | right: 'Leagues.League', 82 | }, 83 | { 84 | left: 'ScoreboardGames.OverviewPage', 85 | right: 'Tournaments.OverviewPage', 86 | }, 87 | { 88 | left: 'ScoreboardGames.GameId', 89 | right: 'MatchScheduleGame.GameId', 90 | }, 91 | 92 | { 93 | left: 'MatchScheduleGame.MatchId', 94 | right: 'MatchSchedule.MatchId', 95 | }, 96 | { 97 | left: 'ScoreboardGames.GameId', 98 | right: 'PicksAndBansS7.GameId', 99 | }, 100 | ], 101 | fields: [ 102 | 'MatchScheduleGame.OverviewPage', 103 | 'ScoreboardGames.GameId', 104 | 'ScoreboardGames.Team1', 105 | 'ScoreboardGames.Team2', 106 | 'ScoreboardGames.Winner', 107 | 'ScoreboardGames.N_GameInMatch', 108 | 'ScoreboardGames.Gamelength', 109 | 'ScoreboardGames.Patch', 110 | 'ScoreboardGames.RiotPlatformGameId', 111 | 'MatchSchedule.DateTime_UTC', 112 | 'PicksAndBansS7.Team1Role1', // Not in response 113 | ], 114 | limit: 1, 115 | }) 116 | .then(({ data }) => { 117 | expect(data[0]).toHaveProperty('OverviewPage') 118 | expect(data[0]).toHaveProperty('GameId') 119 | expect(data[0]).toHaveProperty('DateTime_UTC') 120 | expect(data[0]).toHaveProperty('Team1Role1') 121 | }) 122 | }) 123 | 124 | it('group by and having', () => { 125 | const cargo = new CargoClient() 126 | return cargo 127 | .query({ 128 | tables: ['Pentakills'], 129 | groupBy: ['Pentakills.Name'], 130 | having: 'COUNT(DateDisplay) > 10', 131 | }) 132 | .then(({ data }) => { 133 | expect(data.length).toBeGreaterThan(0) 134 | }) 135 | }) 136 | 137 | it('spaceToUnderscore', () => { 138 | const cargo = new CargoClient({ metadataPrefix: 'CargoClient' }) 139 | return cargo 140 | .query({ 141 | tables: ['GCDArchive'], 142 | limit: 1, 143 | }) 144 | .then(({ data }) => { 145 | expect(data[0]).toHaveProperty('Diff_URL') 146 | }) 147 | }) 148 | 149 | it('metadataPrefix', () => { 150 | const cargo = new CargoClient({ metadataPrefix: 'cargo' }) 151 | return cargo 152 | .query({ 153 | tables: ['Teams'], 154 | limit: 10, 155 | }) 156 | .then(({ data }) => { 157 | expect(typeof data[0].cargo_ID === 'number').toBe(true) 158 | }) 159 | }) 160 | }) 161 | 162 | describe('data convertion', () => { 163 | const cargo = new CargoClient({ metadataPrefix: 'cargo' }) 164 | const query = cargo.query({ 165 | tables: ['Players'], 166 | limit: 5, 167 | }) 168 | 169 | it('should convert string', () => { 170 | return query.then(({ data }) => { 171 | expect( 172 | data 173 | .map((p) => p.Twitter) 174 | .some((twitter) => twitter === null || typeof twitter === 'string'), 175 | ).toBe(true) 176 | }) 177 | }) 178 | 179 | it('should convert date', () => { 180 | return query.then(({ data }) => { 181 | expect(data.some((player) => player.Birthdate instanceof Date)).toBe(true) 182 | }) 183 | }) 184 | 185 | it('should convert array', () => { 186 | return query.then(({ data }) => { 187 | expect(data.every((player) => Array.isArray(player.FavChamps))).toBe(true) 188 | }) 189 | }) 190 | }) 191 | -------------------------------------------------------------------------------- /src/riot/static/cdragon.ts: -------------------------------------------------------------------------------- 1 | import { Version, VersionLanguage } from './utils' 2 | 3 | export const CDRAGON_DOMAIN = 'cdn.communitydragon.org' 4 | const CDRAGON_BASE_URL = `https://${CDRAGON_DOMAIN}` 5 | 6 | export const RAW_CDRAGON_DOMAIN = 'raw.communitydragon.org' 7 | const RAW_CDRAGON_BASE_URL = `https://${RAW_CDRAGON_DOMAIN}` 8 | 9 | type ChampionId = string 10 | type ChampionKey = number | string 11 | type ChampionIDorKey = ChampionId | ChampionKey 12 | type SkinId = number | string 13 | type HonorId = number | string 14 | type ProfileIconId = number | string 15 | type WardId = number | string 16 | 17 | class Champion extends Version { 18 | private readonly urlPrefix = CDRAGON_BASE_URL + `/${this.version}` 19 | 20 | squareIconPlaceholder = this.urlPrefix + '/champion/generic/square' 21 | 22 | squareIcon(championIDorKey: ChampionIDorKey) { 23 | return this.urlPrefix + `/champion/${championIDorKey}/square` 24 | } 25 | championData(championIDorKey: ChampionIDorKey) { 26 | return this.urlPrefix + `/champion/${championIDorKey}/data` 27 | } 28 | baseSplashArt(championIDorKey: ChampionIDorKey) { 29 | return this.urlPrefix + `/champion/${championIDorKey}/splash-art` 30 | } 31 | splashArtWithSkin(championIDorKey: ChampionIDorKey, skinId: SkinId) { 32 | return ( 33 | this.urlPrefix + `/champion/${championIDorKey}/splash-art/skin/${skinId}` 34 | ) 35 | } 36 | baseCenteredSplashArt(championIDorKey: ChampionIDorKey) { 37 | return this.urlPrefix + `/champion/${championIDorKey}/splash-art/centered` 38 | } 39 | centeredSplashArtWithSkin(championIDorKey: ChampionIDorKey, skinId: SkinId) { 40 | return ( 41 | this.urlPrefix + 42 | `/champion/${championIDorKey}/splash-art/centered/skin/${skinId}` 43 | ) 44 | } 45 | baseTile(championIDorKey: ChampionIDorKey) { 46 | return this.urlPrefix + `/champion/${championIDorKey}/tile` 47 | } 48 | tileWithSkin(championIDorKey: ChampionIDorKey, skinId: SkinId) { 49 | return this.urlPrefix + `/champion/${championIDorKey}/tile/skin/${skinId}` 50 | } 51 | basePortrait(championIDorKey: ChampionIDorKey) { 52 | return this.urlPrefix + `/champion/${championIDorKey}/portrait` 53 | } 54 | portraitWithSkin(championIDorKey: ChampionIDorKey, skinId: SkinId) { 55 | return ( 56 | this.urlPrefix + `/champion/${championIDorKey}/portrait/skin/${skinId}` 57 | ) 58 | } 59 | passiveIcon(championIDorKey: ChampionIDorKey) { 60 | return this.urlPrefix + `/champion/${championIDorKey}/ability-icon/passive` 61 | } 62 | qAbilityIcon(championIDorKey: ChampionIDorKey) { 63 | return this.urlPrefix + `/champion/${championIDorKey}/ability-icon/q` 64 | } 65 | wAbilityIcon(championIDorKey: ChampionIDorKey) { 66 | return this.urlPrefix + `/champion/${championIDorKey}/ability-icon/w` 67 | } 68 | eAbilityIcon(championIDorKey: ChampionIDorKey) { 69 | return this.urlPrefix + `/champion/${championIDorKey}/ability-icon/e` 70 | } 71 | ultimateAbilityIcon(championIDorKey: ChampionIDorKey) { 72 | return this.urlPrefix + `/champion/${championIDorKey}/ability-icon/r` 73 | } 74 | } 75 | 76 | class ChampionSelect extends Version { 77 | private readonly urlPrefix = CDRAGON_BASE_URL + `/${this.version}` 78 | banSound(championIDorKey: ChampionIDorKey) { 79 | return ( 80 | this.urlPrefix + `/champion/${championIDorKey}/champ-select/sounds/ban` 81 | ) 82 | } 83 | lockinSound(championIDorKey: ChampionIDorKey) { 84 | return ( 85 | this.urlPrefix + `/champion/${championIDorKey}/champ-select/sounds/choose` 86 | ) 87 | } 88 | backgroundSound(championIDorKey: ChampionIDorKey) { 89 | return ( 90 | this.urlPrefix + `/champion/${championIDorKey}/champ-select/sounds/sfx` 91 | ) 92 | } 93 | } 94 | 95 | class Summoner extends Version { 96 | private readonly urlPrefix = CDRAGON_BASE_URL + `/${this.version}` 97 | honorEmblemPlaceholder = this.urlPrefix + '/honor/emblem/generic' 98 | 99 | honorEmblem(honorId: HonorId) { 100 | return this.urlPrefix + `/honor/emblem/${honorId}` 101 | } 102 | lockedHonorEmblem(honorId: HonorId) { 103 | return this.urlPrefix + `/honor/emblem/${honorId}/locked` 104 | } 105 | honorEmblemWithCheckpoints(honorId: HonorId, level: number) { 106 | return this.urlPrefix + `/honor/emblem/${honorId}/level/${level}` 107 | } 108 | profileIcon(profileIconId: ProfileIconId) { 109 | return this.urlPrefix + `/profile-icon/${profileIconId}` 110 | } 111 | } 112 | class Game extends Version { 113 | private readonly urlPrefix = CDRAGON_BASE_URL + `/${this.version}` 114 | wardIcon(wardId: WardId) { 115 | return this.urlPrefix + `/ward/${wardId}` 116 | } 117 | wardShadow(wardId: WardId) { 118 | return this.urlPrefix + `/ward/${wardId}/shadow` 119 | } 120 | } 121 | 122 | type MetaFile = 'queues' 123 | 124 | export class CommunityDragon extends VersionLanguage { 125 | constructor(version: string, language: string) { 126 | super(version, language) 127 | // `latest` will not be affected 128 | this.version = this.version.split('.').slice(0, 2).join('.') 129 | this.language = this.language.toLowerCase() 130 | if (this.language === 'en_us') { 131 | this.language = 'default' 132 | } 133 | } 134 | champion = new Champion(this.version) 135 | championSelect = new ChampionSelect(this.version) 136 | summoner = new Summoner(this.version) 137 | game = new Game(this.version) 138 | 139 | meta(file: MetaFile) { 140 | return ( 141 | RAW_CDRAGON_BASE_URL + 142 | `/${this.version}/plugins/rcp-be-lol-game-data/global/${this.language}/v1/${file}.json` 143 | ) 144 | } 145 | 146 | tier(tier: string) { 147 | return ( 148 | RAW_CDRAGON_BASE_URL + 149 | `/${ 150 | this.version 151 | }/plugins/rcp-fe-lol-static-assets/global/default/images/ranked-mini-regalia/${tier.toLowerCase()}.png` 152 | ) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/riot/apis/rate-limiter.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance, AxiosResponse } from 'axios' 2 | import { RateLimiter } from 'limiter' 3 | import PQueue from 'p-queue' 4 | 5 | import { Platform, Region } from './enums' 6 | 7 | export type GeneralRegion = Platform | Region 8 | 9 | export interface LimiterConfig { 10 | /** Concurrency for each region */ 11 | concurrency?: number 12 | debug?: boolean | 'mock' 13 | } 14 | 15 | function parseRateLimitHeaders(str: string) { 16 | return str.split(',').map((rateLimit) => rateLimit.split(':').map(Number)) 17 | } 18 | 19 | function wait(ms: number) { 20 | return new Promise((resolve) => setTimeout(resolve, ms)) 21 | } 22 | 23 | class SpreadRateLimiter extends RateLimiter { 24 | constructor(rateLimitHeader: string) { 25 | // sometimes rate limit header is empty, maybe a bug from Riot 26 | if (!rateLimitHeader) { 27 | throw new Error('Rate limit header is empty') 28 | } 29 | // Get last rate limit 30 | const [tokensPerInterval, intervalInSecond] = 31 | parseRateLimitHeaders(rateLimitHeader).slice(-1)[0] 32 | super({ 33 | tokensPerInterval: 1, 34 | interval: (intervalInSecond * 1000) / tokensPerInterval, 35 | }) 36 | } 37 | } 38 | 39 | type RateLimiterProperty = RateLimiter | null | undefined 40 | 41 | class RegionRateLimiter { 42 | private readonly methodRateLimiterMap = new Map() 43 | private appRateLimiter: RateLimiterProperty 44 | private readonly debug 45 | private readonly queue 46 | 47 | private lastRequestTime = Date.now() 48 | private i = 0 49 | 50 | constructor( 51 | private readonly generalRegion: GeneralRegion, 52 | private readonly axiosInstance: AxiosInstance, 53 | config: LimiterConfig, 54 | ) { 55 | this.debug = config.debug 56 | this.queue = new PQueue({ concurrency: config.concurrency ?? Infinity }) 57 | } 58 | 59 | async execute( 60 | realPath: string, 61 | path: string, 62 | query?: Record, 63 | ) { 64 | await this.preAppRequest() 65 | await this.preMethodRequest(path) 66 | 67 | return this.queue 68 | .add(() => { 69 | if (this.debug) { 70 | const now = Date.now() 71 | // eslint-disable-next-line no-console 72 | console.log( 73 | this.generalRegion, 74 | realPath, 75 | 'request', 76 | this.i++, 77 | now - this.lastRequestTime, 78 | ) 79 | this.lastRequestTime = now 80 | 81 | // Mock request if limiter has created (that's say, the first request for each method are not mocked) 82 | if (this.debug === 'mock') { 83 | if (this.appRateLimiter && this.methodRateLimiterMap.get(path)) { 84 | return Promise.resolve({}) as Promise> 85 | } 86 | } 87 | } 88 | 89 | const origin = `https://${this.generalRegion.toLowerCase()}.api.riotgames.com` 90 | 91 | return this.axiosInstance.get(origin + realPath, { 92 | params: query, 93 | }) 94 | }) 95 | .then(async (res) => { 96 | await this.postAppRequest(res) 97 | await this.postMethodRequest(res, path) 98 | return res 99 | }) 100 | .catch(async (err) => { 101 | if (axios.isAxiosError(err) && err.response) { 102 | await this.postAppRequest(err.response) 103 | await this.postMethodRequest(err.response, path) 104 | } 105 | 106 | throw err 107 | }) 108 | } 109 | 110 | async preAppRequest() { 111 | // Mark the first app request 112 | if (this.appRateLimiter === undefined) { 113 | this.appRateLimiter = null 114 | } else if (!this.appRateLimiter) { 115 | // Wait for the response of the first app request 116 | // eslint-disable-next-line no-constant-condition 117 | while (true) { 118 | await wait(100) 119 | if (this.appRateLimiter) break 120 | } 121 | } 122 | 123 | await this.appRateLimiter?.removeTokens(1) 124 | } 125 | 126 | async postAppRequest(res: AxiosResponse) { 127 | if (this.appRateLimiter === null) { 128 | const appRateLimit = res.headers['x-app-rate-limit'] 129 | this.appRateLimiter = new SpreadRateLimiter(appRateLimit) 130 | await this.appRateLimiter.removeTokens(1) 131 | } 132 | } 133 | 134 | async preMethodRequest(path: string) { 135 | // Mark the first method request 136 | if (this.methodRateLimiterMap.get(path) === undefined) { 137 | this.methodRateLimiterMap.set(path, null) 138 | } else if (!this.methodRateLimiterMap.get(path)) { 139 | // Wait for the response of the first method request 140 | // eslint-disable-next-line no-constant-condition 141 | while (true) { 142 | await wait(100) 143 | if (this.methodRateLimiterMap.get(path)) break 144 | } 145 | } 146 | 147 | await this.methodRateLimiterMap.get(path)?.removeTokens(1) 148 | } 149 | 150 | async postMethodRequest(res: AxiosResponse, path: string) { 151 | if (this.methodRateLimiterMap.get(path) === null) { 152 | const methodRateLimit = res.headers['x-method-rate-limit'] 153 | const methodRateLimiter = new SpreadRateLimiter(methodRateLimit) 154 | this.methodRateLimiterMap.set(path, methodRateLimiter) 155 | await methodRateLimiter.removeTokens(1) 156 | } 157 | } 158 | } 159 | 160 | export class RiotRateLimiter { 161 | private readonly regionRateLimiterMap = new Map< 162 | GeneralRegion, 163 | RegionRateLimiter 164 | >() 165 | 166 | constructor(axiosInstance: AxiosInstance, config: LimiterConfig) { 167 | // eslint-disable-next-line @typescript-eslint/no-extra-semi 168 | ;[...Object.values(Region), ...Object.values(Platform)].forEach( 169 | (generalRegion) => { 170 | this.regionRateLimiterMap.set( 171 | generalRegion, 172 | new RegionRateLimiter(generalRegion, axiosInstance, config), 173 | ) 174 | }, 175 | ) 176 | } 177 | 178 | execute( 179 | generalRegion: GeneralRegion, 180 | realPath: string, 181 | path: string, 182 | query?: Record, 183 | ) { 184 | return this.regionRateLimiterMap 185 | .get(generalRegion)! 186 | .execute(realPath, path, query) 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "ES2015" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */, 8 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, 9 | "lib": [ 10 | "ESNext" 11 | ] /* Specify library files to be included in the compilation. */, 12 | // "allowJs": true, /* Allow javascript files to be compiled. */ 13 | // "checkJs": true, /* Report errors in .js files. */ 14 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ 15 | "declaration": true /* Generates corresponding '.d.ts' file. */, 16 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 17 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 18 | // "outFile": "./", /* Concatenate and emit output to single file. */ 19 | "outDir": "./dist/cjs" /* Redirect output structure to the directory. */, 20 | "rootDir": "./" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, 21 | // "composite": true, /* Enable project compilation */ 22 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 23 | // "removeComments": true, /* Do not emit comments to output. */ 24 | // "noEmit": true, /* Do not emit outputs. */ 25 | "importHelpers": true /* Import emit helpers from 'tslib'. */, 26 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 27 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 28 | 29 | /* Strict Type-Checking Options */ 30 | "strict": true /* Enable all strict type-checking options. */, 31 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 32 | // "strictNullChecks": true, /* Enable strict null checks. */ 33 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 34 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 35 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 36 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 37 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 38 | 39 | /* Additional Checks */ 40 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 41 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 42 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 43 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 44 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 45 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ 46 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ 47 | 48 | /* Module Resolution Options */ 49 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 50 | "baseUrl": "./" /* Base directory to resolve non-absolute module names. */, 51 | // "paths": {} /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */, 52 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 53 | // "typeRoots": [], /* List of folders to include type definitions from. */ 54 | // "types": [], /* Type declaration files to be included in compilation. */ 55 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 56 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 57 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 58 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 59 | 60 | /* Source Map Options */ 61 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 62 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 63 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 64 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 65 | 66 | /* Experimental Options */ 67 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 68 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 69 | 70 | /* Advanced Options */ 71 | "skipLibCheck": true /* Skip type checking of declaration files. */, 72 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 73 | }, 74 | "include": ["src", "tests", "scripts"] 75 | } 76 | -------------------------------------------------------------------------------- /scripts/riot.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { isString } from 'lodash' 3 | import { RiotClient } from 'src/index' 4 | 5 | import { 6 | createDocument, 7 | $, 8 | $$, 9 | text, 10 | removeRedundantSpace, 11 | withComment, 12 | } from './utils' 13 | 14 | const riot = new RiotClient({ 15 | auth: '', 16 | }) 17 | const axios = riot.axiosInstance 18 | const BASE_URL = 'https://developer.riotgames.com' 19 | const ignoredApiPrefixes = [ 20 | 'tft', 21 | 'lor', 22 | 'val', 23 | 'tournament', 24 | 'spectator-tft', 25 | 'league-exp', 26 | ] 27 | 28 | // selectors 29 | const s = { 30 | PAGE_NAMEs: 'ul.app-nav-bar a', // ACCOUNT-V1 31 | ENDPOINTs: '.operation', // GET /lol/account/v1/accounts/{puuid} 32 | ENDPOINT_PATH: '.path', // /lol/account/v1/accounts/{puuid} 33 | ENDPOINT_METHOD: '.http_method', // GET 34 | ENDPOINT_DESC: '.options', // Get account by puuid 35 | ENDPOINT_RETURN: '.response_body:first-of-type', // Return value: AccountDTO 36 | ENDPOINT_DTOs: '.response_body:not(first-of-type)', // AccountDTO , ...other DTOs 37 | ENDPOINT_QUERY: 'form .api_block h4', // LeagueEntryQuery 38 | DTO_NAME: 'h5', // AccountDTO 39 | DTO_PROPs: 'table > tbody > tr', // puuid string PUUID 40 | DTO_COMMENT: '', // - represents a summoner (text node, so not selectable) 41 | } 42 | 43 | export async function fetchPageNames() { 44 | console.time('fetch page names') 45 | const { data } = await axios.get(BASE_URL + '/apis') 46 | const document = createDocument(data) 47 | console.time('fetch page names') 48 | 49 | return $$(document, s.PAGE_NAMEs) 50 | .map((a) => a.getAttribute('api-name')) 51 | .filter(isString) 52 | .filter((n) => !ignoredApiPrefixes.some((p) => n.startsWith(p))) 53 | } 54 | 55 | const QueryMap: Record = { 56 | '/lol/champion-mastery/v4/champion-masteries/by-puuid/{encryptedPUUID}/top': 57 | 'GetTopChampionMasteriesQuery', 58 | '/lol/league-exp/v4/entries/{queue}/{tier}/{division}': 'LeagueEntryQuery', 59 | '/lol/league/v4/entries/{queue}/{tier}/{division}': 'LeagueEntryQuery', 60 | '/lol/challenges/v1/challenges/{challengeId}/leaderboards/by-level/{level}': 61 | 'GetChallengeLeaderboardsQuery', 62 | '/lol/match/v5/matches/by-puuid/{puuid}/ids': 'MatchIdsQuery', 63 | '/lol/rso-match/v1/matches/ids': 'RsoMatchIdsQuery', 64 | } 65 | 66 | function genEndpoint(el: Element, dtoMap: Record) { 67 | const path = text($(el, s.ENDPOINT_PATH)) 68 | const method = text($(el, s.ENDPOINT_METHOD))?.toLowerCase() 69 | const desc = text($(el, s.ENDPOINT_DESC)) 70 | if (desc.endsWith('- ESPORTS')) return 71 | 72 | const returnType = text($(el, s.ENDPOINT_RETURN))?.replace( 73 | /Return value: (\w+)/, 74 | '$1', 75 | ) 76 | $$(el, s.ENDPOINT_DTOs).forEach((el) => Object.assign(dtoMap, parseDto(el))) 77 | const hasQuery = $$(el, s.ENDPOINT_QUERY).some((e) => 78 | e.textContent?.includes('Query Parameters'), 79 | ) 80 | 81 | const generic = returnType ? `<${transformType(returnType)}>` : '' 82 | const params = hasQuery ? `query: ${QueryMap[path]}` : '' 83 | const lastArg = hasQuery ? ', query' : '' 84 | return [ 85 | `'${path}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({`, 86 | ` /* ${desc} */`, 87 | ` ${method}(${params}) {`, 88 | ` return limiter.execute${generic}(generalRegion, realPath, path${lastArg})`, 89 | ` },`, 90 | `}),`, 91 | ].join('\n') 92 | } 93 | 94 | async function fetchPageDocument(page: string) { 95 | console.time(`fetch ${page}`) 96 | const { data } = await axios.get(BASE_URL + `/api-details/${page}`) 97 | return console.timeEnd(`fetch ${page}`), createDocument(data.html) 98 | } 99 | 100 | async function genEndpointsInPage(document: Document, page: string) { 101 | console.time(`gen ${page}`) 102 | // different dto in different page may have the same name, so we use page-specific dtoMap 103 | const dtoMap = {} 104 | const endpoints = $$(document, s.ENDPOINTs) 105 | .map((el) => genEndpoint(el, dtoMap)) 106 | .filter(Boolean) 107 | .join('\n') 108 | console.timeEnd(`gen ${page}`) 109 | 110 | return { 111 | content: [ 112 | `// #region ${page.toUpperCase()}`, // 113 | endpoints, 114 | `// #endregion`, 115 | ].join('\n'), 116 | dtos: [ 117 | `// #region ${page.toUpperCase()}`, // 118 | Object.values(dtoMap).join('\n\n'), 119 | `// #endregion`, 120 | ].join('\n'), 121 | } 122 | } 123 | 124 | export async function genEndpoints() { 125 | const pages = await fetchPageNames() 126 | console.log('pages', pages) 127 | // fetch and parse document in parallel 128 | const documents = await Promise.all(pages.map(fetchPageDocument)) 129 | const results = [] 130 | // execute in sequence to ensure DTOs are generated in order 131 | for (const [i, page] of pages.entries()) { 132 | results.push(await genEndpointsInPage(documents[i], page)) 133 | } 134 | return { 135 | content: results.map((r) => r.content).join('\n\n'), 136 | dtos: results.map((r) => r.dtos).join('\n\n'), 137 | } 138 | } 139 | 140 | function transformType(type: string, name = '') { 141 | const specialTypeMap: Record = { 142 | queue: 'Queue', 143 | queueType: 'Queue', 144 | tier: 'Tier', 145 | rank: 'Division', 146 | } 147 | if (name in specialTypeMap) return specialTypeMap[name] 148 | 149 | let transformed = type 150 | let previous: string 151 | 152 | do { 153 | // eslint-disable-next-line prefer-const 154 | previous = transformed 155 | transformed = removeRedundantSpace( 156 | previous 157 | .replace(/Integer/gi, 'number') 158 | .replace(/int/gi, 'number') 159 | .replace(/float/gi, 'number') 160 | .replace(/long/gi, 'number') 161 | .replace(/double/gi, 'number') 162 | .replace(/String/gi, 'string') 163 | .replace(/Set\[(\w+)\]/g, '$1[]') 164 | .replace(/List\[(\w+)\]/g, '$1[]') 165 | .replace(/Map\[(.+?)\]/g, 'Record<$1>'), 166 | ) 167 | } while (transformed !== previous) 168 | 169 | return transformed 170 | } 171 | 172 | function parseDto(el: Element) { 173 | const name = text($(el, s.DTO_NAME)) 174 | if (!name) return 175 | 176 | const comment = text($(el, s.DTO_NAME)?.nextSibling).replace(/^- /, '') 177 | const props = $$(el, s.DTO_PROPs) 178 | .map((propEl) => Array.from(propEl.children).map(text)) 179 | .map(([n, t, c]) => { 180 | const key = /^\d/.test(n) ? `"${n}"` : n 181 | return withComment(`${key}: ${transformType(t, n)}`, c) 182 | }) 183 | .join('\n') 184 | const value = props ? `{\n${props}\n}` : 'NotMentioned' 185 | const type = `export type ${name} = ${value}` 186 | 187 | return { 188 | [name]: withComment(type, comment), 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /tests/riot.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { describe, it, expect } from 'vitest' 3 | 4 | import { general, DataDragon, CommunityDragon, RiotClient, Riot } from '../src' 5 | 6 | import { auth } from './env' 7 | 8 | describe('static files', () => { 9 | it('general', () => { 10 | expect(general.doc('gameModes')).toBe( 11 | 'https://static.developer.riotgames.com/docs/lol/gameModes.json', 12 | ) 13 | expect(general.realm('na')).toBe( 14 | 'https://ddragon.leagueoflegends.com/realms/na.json', 15 | ) 16 | }) 17 | it('ddragon', () => { 18 | const ddragon = new DataDragon('11.16.1', 'en_US') 19 | expect(ddragon.meta('summoner')).toBe( 20 | 'https://ddragon.leagueoflegends.com/cdn/11.16.1/data/en_US/summoner.json', 21 | ) 22 | }) 23 | it('cdragon', () => { 24 | const cdragon = new CommunityDragon('latest', 'default') 25 | expect(cdragon.tier(Riot.Tier.CHALLENGER)).toBe( 26 | 'https://raw.communitydragon.org/latest/plugins/rcp-fe-lol-static-assets/global/default/images/ranked-mini-regalia/challenger.png', 27 | ) 28 | }) 29 | }) 30 | 31 | describe('api client', () => { 32 | it('league-exp', () => { 33 | const client = new RiotClient({ 34 | auth, 35 | platform: Riot.Platform.KR, 36 | region: Riot.Region.ASIA, 37 | }) 38 | 39 | return client 40 | .path('/lol/league-exp/v4/entries/{queue}/{tier}/{division}', { 41 | queue: Riot.Queue.RANKED_SOLO_5x5, 42 | tier: Riot.Tier.CHALLENGER, 43 | division: Riot.Division.I, 44 | }) 45 | .get({ query: {} }) 46 | .then(({ data }) => { 47 | expect(Array.isArray(data)).toBe(true) 48 | }) 49 | }) 50 | }) 51 | 52 | describe('set region/platform correctly', () => { 53 | it('default region', () => { 54 | const client = new RiotClient({ 55 | auth, 56 | }) 57 | 58 | return client 59 | .path('/riot/account/v1/accounts/me') 60 | .get() 61 | .catch((err) => { 62 | expect(err.config.url).toBe( 63 | `https://${Riot.Region.AMERICAS.toLowerCase()}.api.riotgames.com/riot/account/v1/accounts/me`, 64 | ) 65 | }) 66 | }) 67 | 68 | it('default platform', () => { 69 | const client = new RiotClient({ 70 | auth, 71 | }) 72 | 73 | return client 74 | .path( 75 | '/lol/champion-mastery/v4/champion-masteries/by-puuid/{encryptedPUUID}', 76 | { encryptedPUUID: '123' }, 77 | ) 78 | .get() 79 | .catch((err) => { 80 | expect(err.config.url).toBe( 81 | `https://${Riot.Platform.NA.toLowerCase()}.api.riotgames.com/lol/champion-mastery/v4/champion-masteries/by-puuid/123`, 82 | ) 83 | }) 84 | }) 85 | 86 | it('class-scoped region', () => { 87 | const client = new RiotClient({ 88 | auth, 89 | region: Riot.Region.ASIA, 90 | }) 91 | 92 | return client 93 | .path('/riot/account/v1/accounts/me') 94 | .get() 95 | .catch((err) => { 96 | expect(err.config.url).toBe( 97 | `https://${Riot.Region.ASIA.toLowerCase()}.api.riotgames.com/riot/account/v1/accounts/me`, 98 | ) 99 | }) 100 | }) 101 | 102 | it('class-scoped platform', () => { 103 | const client = new RiotClient({ 104 | auth, 105 | platform: Riot.Platform.KR, 106 | }) 107 | 108 | return client 109 | .path( 110 | '/lol/champion-mastery/v4/champion-masteries/by-puuid/{encryptedPUUID}', 111 | { encryptedPUUID: '123' }, 112 | ) 113 | .get() 114 | .catch((err) => { 115 | expect(err.config.url).toBe( 116 | `https://${Riot.Platform.KR.toLowerCase()}.api.riotgames.com/lol/champion-mastery/v4/champion-masteries/by-puuid/123`, 117 | ) 118 | }) 119 | }) 120 | 121 | it('method-scoped region', () => { 122 | const client = new RiotClient({ 123 | auth, 124 | region: Riot.Region.EUROPE, 125 | }) 126 | 127 | return client 128 | .path('/riot/account/v1/accounts/me', Riot.Region.ASIA) 129 | .get() 130 | .catch((err) => { 131 | expect(err.config.url).toBe( 132 | `https://${Riot.Region.ASIA.toLowerCase()}.api.riotgames.com/riot/account/v1/accounts/me`, 133 | ) 134 | }) 135 | }) 136 | 137 | it('method-scoped platform', () => { 138 | const client = new RiotClient({ 139 | auth, 140 | platform: Riot.Platform.RU, 141 | }) 142 | 143 | return client 144 | .path( 145 | '/lol/champion-mastery/v4/champion-masteries/by-puuid/{encryptedPUUID}', 146 | { encryptedPUUID: '123' }, 147 | Riot.Platform.KR, 148 | ) 149 | .get() 150 | .catch((err) => { 151 | expect(err.config.url).toBe( 152 | `https://${Riot.Platform.KR.toLowerCase()}.api.riotgames.com/lol/champion-mastery/v4/champion-masteries/by-puuid/123`, 153 | ) 154 | }) 155 | }) 156 | }) 157 | 158 | describe( 159 | 'rate limit', 160 | () => { 161 | it('app < method', () => { 162 | // App rate limit: 100:120 spread to 1:1200ms 163 | const n = 5 164 | const client = new RiotClient({ auth }) 165 | const promises: Promise[] = [] 166 | const start = Date.now() 167 | let lastResolveTime = start 168 | 169 | for (let i = 0; i < n; i++) { 170 | // Method rate limit: 1200000:600 spread to 1:0.5ms 171 | const promise = client 172 | .path('/lol/spectator/v5/featured-games') 173 | .get() 174 | .then(() => { 175 | const now = Date.now() 176 | console.log('request', i, now - lastResolveTime) 177 | lastResolveTime = now 178 | }) 179 | promises.push(promise) 180 | } 181 | 182 | return Promise.all(promises).then(() => { 183 | const now = Date.now() 184 | console.log('total', now - start) 185 | expect(now - start).toBeGreaterThanOrEqual(1200 * (n - 1)) 186 | }) 187 | }) 188 | 189 | it('app > method', () => { 190 | // App rate limit: 100:120 spread to 1:1200ms 191 | const n = 5 192 | const client = new RiotClient({ auth }) 193 | const promises: Promise[] = [] 194 | const start = Date.now() 195 | let lastResolveTime = start 196 | 197 | for (let i = 0; i < n; i++) { 198 | // Method rate limit: 10:60 spread to 1:6000ms 199 | const promise = client 200 | .path('/lol/clash/v1/tournaments') 201 | .get() 202 | .then(() => { 203 | const now = Date.now() 204 | console.log('request', i, now - lastResolveTime) 205 | lastResolveTime = now 206 | }) 207 | 208 | promises.push(promise) 209 | } 210 | 211 | return Promise.all(promises).then(() => { 212 | const now = Date.now() 213 | console.log('total', now - start) 214 | expect(now - start).toBeGreaterThanOrEqual(6000 * (n - 1)) 215 | }) 216 | }) 217 | 218 | it('multiple regions', () => { 219 | const n = 5 220 | const client = new RiotClient({ auth }) 221 | const promises: Promise[] = [] 222 | const start = Date.now() 223 | 224 | Object.values(Riot.Platform).forEach((platform) => { 225 | let lastResolveTime = Date.now() 226 | for (let i = 0; i < n; i++) { 227 | const promise = client 228 | .path('/lol/spectator/v5/featured-games', platform) 229 | .get() 230 | .then(() => { 231 | const now = Date.now() 232 | console.log('platform', platform, i, now - lastResolveTime) 233 | lastResolveTime = now 234 | }) 235 | 236 | promises.push(promise) 237 | } 238 | }) 239 | 240 | return Promise.all(promises).then(() => { 241 | const now = Date.now() 242 | console.log('total', now - start) 243 | expect(now - start).toBeGreaterThanOrEqual(1200 * (n - 1)) 244 | }) 245 | }) 246 | 247 | it('multiple methods', () => { 248 | const client = new RiotClient({ auth }) 249 | const promises: Promise[] = [] 250 | 251 | const startTime = Date.now() 252 | 253 | for (let i = 0; i < 5; i++) { 254 | const promise = client.path('/lol/spectator/v5/featured-games').get() 255 | promises.push(promise) 256 | } 257 | 258 | for (let i = 0; i < 5; i++) { 259 | const promise = client.path('/lol/clash/v1/tournaments').get() 260 | promises.push(promise) 261 | } 262 | 263 | return Promise.all(promises).then(() => { 264 | const now = Date.now() 265 | expect(now - startTime).toBeGreaterThan(30 * 1000) 266 | }) 267 | }) 268 | }, 269 | { timeout: Infinity }, 270 | ) 271 | -------------------------------------------------------------------------------- /scripts/__snapshots__/endpoints.ts: -------------------------------------------------------------------------------- 1 | // #region ACCOUNT-V1 2 | '/riot/account/v1/accounts/by-puuid/{puuid}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 3 | /* Get account by puuid */ 4 | get() { 5 | return limiter.execute(generalRegion, realPath, path) 6 | }, 7 | }), 8 | '/riot/account/v1/accounts/by-riot-id/{gameName}/{tagLine}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 9 | /* Get account by riot id */ 10 | get() { 11 | return limiter.execute(generalRegion, realPath, path) 12 | }, 13 | }), 14 | '/riot/account/v1/accounts/me': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 15 | /* Get account by access token */ 16 | get() { 17 | return limiter.execute(generalRegion, realPath, path) 18 | }, 19 | }), 20 | '/riot/account/v1/active-shards/by-game/{game}/by-puuid/{puuid}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 21 | /* Get active shard for a player */ 22 | get() { 23 | return limiter.execute(generalRegion, realPath, path) 24 | }, 25 | }), 26 | '/riot/account/v1/region/by-game/{game}/by-puuid/{puuid}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 27 | /* Get active region (lol and tft) */ 28 | get() { 29 | return limiter.execute(generalRegion, realPath, path) 30 | }, 31 | }), 32 | // #endregion 33 | 34 | // #region CHAMPION-MASTERY-V4 35 | '/lol/champion-mastery/v4/champion-masteries/by-puuid/{encryptedPUUID}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 36 | /* Get all champion mastery entries sorted by number of champion points descending. */ 37 | get() { 38 | return limiter.execute(generalRegion, realPath, path) 39 | }, 40 | }), 41 | '/lol/champion-mastery/v4/champion-masteries/by-puuid/{encryptedPUUID}/by-champion/{championId}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 42 | /* Get a champion mastery by puuid and champion ID. */ 43 | get() { 44 | return limiter.execute(generalRegion, realPath, path) 45 | }, 46 | }), 47 | '/lol/champion-mastery/v4/champion-masteries/by-puuid/{encryptedPUUID}/top': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 48 | /* Get specified number of top champion mastery entries sorted by number of champion points descending. */ 49 | get(query: GetTopChampionMasteriesQuery) { 50 | return limiter.execute(generalRegion, realPath, path, query) 51 | }, 52 | }), 53 | '/lol/champion-mastery/v4/scores/by-puuid/{encryptedPUUID}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 54 | /* Get a player's total champion mastery score, which is the sum of individual champion mastery levels. */ 55 | get() { 56 | return limiter.execute(generalRegion, realPath, path) 57 | }, 58 | }), 59 | // #endregion 60 | 61 | // #region CHAMPION-V3 62 | '/lol/platform/v3/champion-rotations': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 63 | /* Returns champion rotations, including free-to-play and low-level free-to-play rotations (REST) */ 64 | get() { 65 | return limiter.execute(generalRegion, realPath, path) 66 | }, 67 | }), 68 | // #endregion 69 | 70 | // #region CLASH-V1 71 | '/lol/clash/v1/players/by-puuid/{puuid}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 72 | /* Get players by puuid */ 73 | get() { 74 | return limiter.execute(generalRegion, realPath, path) 75 | }, 76 | }), 77 | '/lol/clash/v1/teams/{teamId}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 78 | /* Get team by ID. */ 79 | get() { 80 | return limiter.execute(generalRegion, realPath, path) 81 | }, 82 | }), 83 | '/lol/clash/v1/tournaments': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 84 | /* Get all active or upcoming tournaments. */ 85 | get() { 86 | return limiter.execute(generalRegion, realPath, path) 87 | }, 88 | }), 89 | '/lol/clash/v1/tournaments/by-team/{teamId}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 90 | /* Get tournament by team ID. */ 91 | get() { 92 | return limiter.execute(generalRegion, realPath, path) 93 | }, 94 | }), 95 | '/lol/clash/v1/tournaments/{tournamentId}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 96 | /* Get tournament by ID. */ 97 | get() { 98 | return limiter.execute(generalRegion, realPath, path) 99 | }, 100 | }), 101 | // #endregion 102 | 103 | // #region LEAGUE-V4 104 | '/lol/league/v4/challengerleagues/by-queue/{queue}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 105 | /* Get the challenger league for given queue. */ 106 | get() { 107 | return limiter.execute(generalRegion, realPath, path) 108 | }, 109 | }), 110 | '/lol/league/v4/entries/by-puuid/{encryptedPUUID}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 111 | /* Get league entries in all queues for a given puuid */ 112 | get() { 113 | return limiter.execute(generalRegion, realPath, path) 114 | }, 115 | }), 116 | '/lol/league/v4/entries/{queue}/{tier}/{division}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 117 | /* Get all the league entries. */ 118 | get(query: LeagueEntryQuery) { 119 | return limiter.execute(generalRegion, realPath, path, query) 120 | }, 121 | }), 122 | '/lol/league/v4/grandmasterleagues/by-queue/{queue}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 123 | /* Get the grandmaster league of a specific queue. */ 124 | get() { 125 | return limiter.execute(generalRegion, realPath, path) 126 | }, 127 | }), 128 | '/lol/league/v4/leagues/{leagueId}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 129 | /* Get league with given ID, including inactive entries. */ 130 | get() { 131 | return limiter.execute(generalRegion, realPath, path) 132 | }, 133 | }), 134 | '/lol/league/v4/masterleagues/by-queue/{queue}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 135 | /* Get the master league for given queue. */ 136 | get() { 137 | return limiter.execute(generalRegion, realPath, path) 138 | }, 139 | }), 140 | // #endregion 141 | 142 | // #region LOL-CHALLENGES-V1 143 | '/lol/challenges/v1/challenges/config': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 144 | /* List of all basic challenge configuration information (includes all translations for names and descriptions) */ 145 | get() { 146 | return limiter.execute(generalRegion, realPath, path) 147 | }, 148 | }), 149 | '/lol/challenges/v1/challenges/percentiles': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 150 | /* Map of level to percentile of players who have achieved it - keys: ChallengeId -> Season -> Level -> percentile of players who achieved it */ 151 | get() { 152 | return limiter.execute>>>(generalRegion, realPath, path) 153 | }, 154 | }), 155 | '/lol/challenges/v1/challenges/{challengeId}/config': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 156 | /* Get challenge configuration (REST) */ 157 | get() { 158 | return limiter.execute(generalRegion, realPath, path) 159 | }, 160 | }), 161 | '/lol/challenges/v1/challenges/{challengeId}/leaderboards/by-level/{level}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 162 | /* Return top players for each level. Level must be MASTER, GRANDMASTER or CHALLENGER. */ 163 | get(query: GetChallengeLeaderboardsQuery) { 164 | return limiter.execute(generalRegion, realPath, path, query) 165 | }, 166 | }), 167 | '/lol/challenges/v1/challenges/{challengeId}/percentiles': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 168 | /* Map of level to percentile of players who have achieved it */ 169 | get() { 170 | return limiter.execute>(generalRegion, realPath, path) 171 | }, 172 | }), 173 | '/lol/challenges/v1/player-data/{puuid}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 174 | /* Returns player information with list of all progressed challenges (REST) */ 175 | get() { 176 | return limiter.execute(generalRegion, realPath, path) 177 | }, 178 | }), 179 | // #endregion 180 | 181 | // #region LOL-RSO-MATCH-V1 182 | '/lol/rso-match/v1/matches/ids': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 183 | /* Get a list of match ids by player access token - Includes custom matches */ 184 | get(query: RsoMatchIdsQuery) { 185 | return limiter.execute(generalRegion, realPath, path, query) 186 | }, 187 | }), 188 | '/lol/rso-match/v1/matches/{matchId}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 189 | /* Get a match by match id */ 190 | get() { 191 | return limiter.execute(generalRegion, realPath, path) 192 | }, 193 | }), 194 | '/lol/rso-match/v1/matches/{matchId}/timeline': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 195 | /* Get a match timeline by match id */ 196 | get() { 197 | return limiter.execute(generalRegion, realPath, path) 198 | }, 199 | }), 200 | // #endregion 201 | 202 | // #region LOL-STATUS-V4 203 | '/lol/status/v4/platform-data': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 204 | /* Get League of Legends status for the given platform. */ 205 | get() { 206 | return limiter.execute(generalRegion, realPath, path) 207 | }, 208 | }), 209 | // #endregion 210 | 211 | // #region MATCH-V5 212 | '/lol/match/v5/matches/by-puuid/{puuid}/ids': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 213 | /* Get a list of match ids by puuid */ 214 | get(query: MatchIdsQuery) { 215 | return limiter.execute(generalRegion, realPath, path, query) 216 | }, 217 | }), 218 | '/lol/match/v5/matches/{matchId}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 219 | /* Get a match by match id */ 220 | get() { 221 | return limiter.execute(generalRegion, realPath, path) 222 | }, 223 | }), 224 | '/lol/match/v5/matches/{matchId}/timeline': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 225 | /* Get a match timeline by match id */ 226 | get() { 227 | return limiter.execute(generalRegion, realPath, path) 228 | }, 229 | }), 230 | // #endregion 231 | 232 | // #region SPECTATOR-V5 233 | '/lol/spectator/v5/active-games/by-summoner/{encryptedPUUID}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 234 | /* Get current game information for the given puuid. */ 235 | get() { 236 | return limiter.execute(generalRegion, realPath, path) 237 | }, 238 | }), 239 | '/lol/spectator/v5/featured-games': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 240 | /* Get list of featured games. */ 241 | get() { 242 | return limiter.execute(generalRegion, realPath, path) 243 | }, 244 | }), 245 | // #endregion 246 | 247 | // #region SUMMONER-V4 248 | '/fulfillment/v1/summoners/by-puuid/{rsoPUUID}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 249 | /* Get a summoner by its RSO encrypted PUUID. */ 250 | get() { 251 | return limiter.execute(generalRegion, realPath, path) 252 | }, 253 | }), 254 | '/lol/summoner/v4/summoners/by-puuid/{encryptedPUUID}': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 255 | /* Get a summoner by PUUID. */ 256 | get() { 257 | return limiter.execute(generalRegion, realPath, path) 258 | }, 259 | }), 260 | '/lol/summoner/v4/summoners/me': (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 261 | /* Get a summoner by access token. */ 262 | get() { 263 | return limiter.execute(generalRegion, realPath, path) 264 | }, 265 | }), 266 | // #endregion -------------------------------------------------------------------------------- /src/riot/apis/endpoints.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | AccountDto, 3 | ActiveShardDto, 4 | ApexPlayerInfoDto, 5 | ChallengeConfigInfoDto, 6 | ChampionInfo, 7 | ChampionMasteryDto, 8 | CurrentGameInfo, 9 | FeaturedGames, 10 | LeagueEntryDTO, 11 | LeagueListDTO, 12 | Level, 13 | MatchDto, 14 | PlatformDataDto, 15 | PlayerDto, 16 | PlayerInfoDto, 17 | SummonerDTO, 18 | TeamDto, 19 | TournamentDto, 20 | TimelineDto, 21 | AccountRegionDTO, 22 | } from './dtos' 23 | import type { 24 | GetChallengeLeaderboardsQuery, 25 | GetTopChampionMasteriesQuery, 26 | LeagueEntryQuery, 27 | MatchIdsQuery, 28 | RsoMatchIdsQuery, 29 | } from './query' 30 | import { GeneralRegion, RiotRateLimiter } from './rate-limiter' 31 | 32 | export function createEndpoints(limiter: RiotRateLimiter) { 33 | return { 34 | // #region ACCOUNT-V1 35 | '/riot/account/v1/accounts/by-puuid/{puuid}': ( 36 | generalRegion: GeneralRegion, 37 | realPath: string, 38 | path: string, 39 | ) => ({ 40 | /* Get account by puuid */ 41 | get() { 42 | return limiter.execute(generalRegion, realPath, path) 43 | }, 44 | }), 45 | '/riot/account/v1/accounts/by-riot-id/{gameName}/{tagLine}': ( 46 | generalRegion: GeneralRegion, 47 | realPath: string, 48 | path: string, 49 | ) => ({ 50 | /* Get account by riot id */ 51 | get() { 52 | return limiter.execute(generalRegion, realPath, path) 53 | }, 54 | }), 55 | '/riot/account/v1/accounts/me': ( 56 | generalRegion: GeneralRegion, 57 | realPath: string, 58 | path: string, 59 | ) => ({ 60 | /* Get account by access token */ 61 | get() { 62 | return limiter.execute(generalRegion, realPath, path) 63 | }, 64 | }), 65 | '/riot/account/v1/active-shards/by-game/{game}/by-puuid/{puuid}': ( 66 | generalRegion: GeneralRegion, 67 | realPath: string, 68 | path: string, 69 | ) => ({ 70 | /* Get active shard for a player */ 71 | get() { 72 | return limiter.execute(generalRegion, realPath, path) 73 | }, 74 | }), 75 | '/riot/account/v1/region/by-game/{game}/by-puuid/{puuid}': ( 76 | generalRegion: GeneralRegion, 77 | realPath: string, 78 | path: string, 79 | ) => ({ 80 | /* Get active region (lol and tft) */ 81 | get() { 82 | return limiter.execute(generalRegion, realPath, path) 83 | }, 84 | }), 85 | // #endregion 86 | 87 | // #region CHAMPION-MASTERY-V4 88 | '/lol/champion-mastery/v4/champion-masteries/by-puuid/{encryptedPUUID}': ( 89 | generalRegion: GeneralRegion, 90 | realPath: string, 91 | path: string, 92 | ) => ({ 93 | /* Get all champion mastery entries sorted by number of champion points descending. */ 94 | get() { 95 | return limiter.execute( 96 | generalRegion, 97 | realPath, 98 | path, 99 | ) 100 | }, 101 | }), 102 | '/lol/champion-mastery/v4/champion-masteries/by-puuid/{encryptedPUUID}/by-champion/{championId}': 103 | (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 104 | /* Get a champion mastery by puuid and champion ID. */ 105 | get() { 106 | return limiter.execute( 107 | generalRegion, 108 | realPath, 109 | path, 110 | ) 111 | }, 112 | }), 113 | '/lol/champion-mastery/v4/champion-masteries/by-puuid/{encryptedPUUID}/top': 114 | (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 115 | /* Get specified number of top champion mastery entries sorted by number of champion points descending. */ 116 | get(query: GetTopChampionMasteriesQuery) { 117 | return limiter.execute( 118 | generalRegion, 119 | realPath, 120 | path, 121 | query, 122 | ) 123 | }, 124 | }), 125 | '/lol/champion-mastery/v4/scores/by-puuid/{encryptedPUUID}': ( 126 | generalRegion: GeneralRegion, 127 | realPath: string, 128 | path: string, 129 | ) => ({ 130 | /* Get a player's total champion mastery score, which is the sum of individual champion mastery levels. */ 131 | get() { 132 | return limiter.execute(generalRegion, realPath, path) 133 | }, 134 | }), 135 | // #endregion 136 | 137 | // #region CHAMPION-V3 138 | '/lol/platform/v3/champion-rotations': ( 139 | generalRegion: GeneralRegion, 140 | realPath: string, 141 | path: string, 142 | ) => ({ 143 | /* Returns champion rotations, including free-to-play and low-level free-to-play rotations (REST) */ 144 | get() { 145 | return limiter.execute(generalRegion, realPath, path) 146 | }, 147 | }), 148 | // #endregion 149 | 150 | // #region CLASH-V1 151 | '/lol/clash/v1/players/by-puuid/{puuid}': ( 152 | generalRegion: GeneralRegion, 153 | realPath: string, 154 | path: string, 155 | ) => ({ 156 | /* Get players by puuid */ 157 | get() { 158 | return limiter.execute(generalRegion, realPath, path) 159 | }, 160 | }), 161 | '/lol/clash/v1/teams/{teamId}': ( 162 | generalRegion: GeneralRegion, 163 | realPath: string, 164 | path: string, 165 | ) => ({ 166 | /* Get team by ID. */ 167 | get() { 168 | return limiter.execute(generalRegion, realPath, path) 169 | }, 170 | }), 171 | '/lol/clash/v1/tournaments': ( 172 | generalRegion: GeneralRegion, 173 | realPath: string, 174 | path: string, 175 | ) => ({ 176 | /* Get all active or upcoming tournaments. */ 177 | get() { 178 | return limiter.execute(generalRegion, realPath, path) 179 | }, 180 | }), 181 | '/lol/clash/v1/tournaments/by-team/{teamId}': ( 182 | generalRegion: GeneralRegion, 183 | realPath: string, 184 | path: string, 185 | ) => ({ 186 | /* Get tournament by team ID. */ 187 | get() { 188 | return limiter.execute(generalRegion, realPath, path) 189 | }, 190 | }), 191 | '/lol/clash/v1/tournaments/{tournamentId}': ( 192 | generalRegion: GeneralRegion, 193 | realPath: string, 194 | path: string, 195 | ) => ({ 196 | /* Get tournament by ID. */ 197 | get() { 198 | return limiter.execute(generalRegion, realPath, path) 199 | }, 200 | }), 201 | // #endregion 202 | 203 | // #region LEAGUE-V4 204 | '/lol/league/v4/challengerleagues/by-queue/{queue}': ( 205 | generalRegion: GeneralRegion, 206 | realPath: string, 207 | path: string, 208 | ) => ({ 209 | /* Get the challenger league for given queue. */ 210 | get() { 211 | return limiter.execute(generalRegion, realPath, path) 212 | }, 213 | }), 214 | '/lol/league/v4/entries/by-puuid/{encryptedPUUID}': ( 215 | generalRegion: GeneralRegion, 216 | realPath: string, 217 | path: string, 218 | ) => ({ 219 | /* Get league entries in all queues for a given puuid */ 220 | get() { 221 | return limiter.execute(generalRegion, realPath, path) 222 | }, 223 | }), 224 | '/lol/league/v4/entries/{queue}/{tier}/{division}': ( 225 | generalRegion: GeneralRegion, 226 | realPath: string, 227 | path: string, 228 | ) => ({ 229 | /* Get all the league entries. */ 230 | get(query: LeagueEntryQuery) { 231 | return limiter.execute( 232 | generalRegion, 233 | realPath, 234 | path, 235 | query, 236 | ) 237 | }, 238 | }), 239 | '/lol/league/v4/grandmasterleagues/by-queue/{queue}': ( 240 | generalRegion: GeneralRegion, 241 | realPath: string, 242 | path: string, 243 | ) => ({ 244 | /* Get the grandmaster league of a specific queue. */ 245 | get() { 246 | return limiter.execute(generalRegion, realPath, path) 247 | }, 248 | }), 249 | '/lol/league/v4/leagues/{leagueId}': ( 250 | generalRegion: GeneralRegion, 251 | realPath: string, 252 | path: string, 253 | ) => ({ 254 | /* Get league with given ID, including inactive entries. */ 255 | get() { 256 | return limiter.execute(generalRegion, realPath, path) 257 | }, 258 | }), 259 | '/lol/league/v4/masterleagues/by-queue/{queue}': ( 260 | generalRegion: GeneralRegion, 261 | realPath: string, 262 | path: string, 263 | ) => ({ 264 | /* Get the master league for given queue. */ 265 | get() { 266 | return limiter.execute(generalRegion, realPath, path) 267 | }, 268 | }), 269 | // #endregion 270 | 271 | // #region LOL-CHALLENGES-V1 272 | '/lol/challenges/v1/challenges/config': ( 273 | generalRegion: GeneralRegion, 274 | realPath: string, 275 | path: string, 276 | ) => ({ 277 | /* List of all basic challenge configuration information (includes all translations for names and descriptions) */ 278 | get() { 279 | return limiter.execute( 280 | generalRegion, 281 | realPath, 282 | path, 283 | ) 284 | }, 285 | }), 286 | '/lol/challenges/v1/challenges/percentiles': ( 287 | generalRegion: GeneralRegion, 288 | realPath: string, 289 | path: string, 290 | ) => ({ 291 | /* Map of level to percentile of players who have achieved it - keys: ChallengeId -> Season -> Level -> percentile of players who achieved it */ 292 | get() { 293 | return limiter.execute< 294 | Record>> 295 | >(generalRegion, realPath, path) 296 | }, 297 | }), 298 | '/lol/challenges/v1/challenges/{challengeId}/config': ( 299 | generalRegion: GeneralRegion, 300 | realPath: string, 301 | path: string, 302 | ) => ({ 303 | /* Get challenge configuration (REST) */ 304 | get() { 305 | return limiter.execute( 306 | generalRegion, 307 | realPath, 308 | path, 309 | ) 310 | }, 311 | }), 312 | '/lol/challenges/v1/challenges/{challengeId}/leaderboards/by-level/{level}': 313 | (generalRegion: GeneralRegion, realPath: string, path: string) => ({ 314 | /* Return top players for each level. Level must be MASTER, GRANDMASTER or CHALLENGER. */ 315 | get(query: GetChallengeLeaderboardsQuery) { 316 | return limiter.execute( 317 | generalRegion, 318 | realPath, 319 | path, 320 | query, 321 | ) 322 | }, 323 | }), 324 | '/lol/challenges/v1/challenges/{challengeId}/percentiles': ( 325 | generalRegion: GeneralRegion, 326 | realPath: string, 327 | path: string, 328 | ) => ({ 329 | /* Map of level to percentile of players who have achieved it */ 330 | get() { 331 | return limiter.execute>( 332 | generalRegion, 333 | realPath, 334 | path, 335 | ) 336 | }, 337 | }), 338 | '/lol/challenges/v1/player-data/{puuid}': ( 339 | generalRegion: GeneralRegion, 340 | realPath: string, 341 | path: string, 342 | ) => ({ 343 | /* Returns player information with list of all progressed challenges (REST) */ 344 | get() { 345 | return limiter.execute(generalRegion, realPath, path) 346 | }, 347 | }), 348 | // #endregion 349 | 350 | // #region LOL-RSO-MATCH-V1 351 | '/lol/rso-match/v1/matches/ids': ( 352 | generalRegion: GeneralRegion, 353 | realPath: string, 354 | path: string, 355 | ) => ({ 356 | /* Get a list of match ids by player access token - Includes custom matches */ 357 | get(query: RsoMatchIdsQuery) { 358 | return limiter.execute(generalRegion, realPath, path, query) 359 | }, 360 | }), 361 | '/lol/rso-match/v1/matches/{matchId}': ( 362 | generalRegion: GeneralRegion, 363 | realPath: string, 364 | path: string, 365 | ) => ({ 366 | /* Get a match by match id */ 367 | get() { 368 | return limiter.execute(generalRegion, realPath, path) 369 | }, 370 | }), 371 | '/lol/rso-match/v1/matches/{matchId}/timeline': ( 372 | generalRegion: GeneralRegion, 373 | realPath: string, 374 | path: string, 375 | ) => ({ 376 | /* Get a match timeline by match id */ 377 | get() { 378 | return limiter.execute(generalRegion, realPath, path) 379 | }, 380 | }), 381 | // #endregion 382 | 383 | // #region LOL-STATUS-V4 384 | '/lol/status/v4/platform-data': ( 385 | generalRegion: GeneralRegion, 386 | realPath: string, 387 | path: string, 388 | ) => ({ 389 | /* Get League of Legends status for the given platform. */ 390 | get() { 391 | return limiter.execute(generalRegion, realPath, path) 392 | }, 393 | }), 394 | // #endregion 395 | 396 | // #region MATCH-V5 397 | '/lol/match/v5/matches/by-puuid/{puuid}/ids': ( 398 | generalRegion: GeneralRegion, 399 | realPath: string, 400 | path: string, 401 | ) => ({ 402 | /* Get a list of match ids by puuid */ 403 | get(query: MatchIdsQuery) { 404 | return limiter.execute(generalRegion, realPath, path, query) 405 | }, 406 | }), 407 | '/lol/match/v5/matches/{matchId}': ( 408 | generalRegion: GeneralRegion, 409 | realPath: string, 410 | path: string, 411 | ) => ({ 412 | /* Get a match by match id */ 413 | get() { 414 | return limiter.execute(generalRegion, realPath, path) 415 | }, 416 | }), 417 | '/lol/match/v5/matches/{matchId}/timeline': ( 418 | generalRegion: GeneralRegion, 419 | realPath: string, 420 | path: string, 421 | ) => ({ 422 | /* Get a match timeline by match id */ 423 | get() { 424 | return limiter.execute(generalRegion, realPath, path) 425 | }, 426 | }), 427 | // #endregion 428 | 429 | // #region SPECTATOR-V5 430 | '/lol/spectator/v5/active-games/by-summoner/{encryptedPUUID}': ( 431 | generalRegion: GeneralRegion, 432 | realPath: string, 433 | path: string, 434 | ) => ({ 435 | /* Get current game information for the given puuid. */ 436 | get() { 437 | return limiter.execute(generalRegion, realPath, path) 438 | }, 439 | }), 440 | '/lol/spectator/v5/featured-games': ( 441 | generalRegion: GeneralRegion, 442 | realPath: string, 443 | path: string, 444 | ) => ({ 445 | /* Get list of featured games. */ 446 | get() { 447 | return limiter.execute(generalRegion, realPath, path) 448 | }, 449 | }), 450 | // #endregion 451 | 452 | // #region SUMMONER-V4 453 | '/fulfillment/v1/summoners/by-puuid/{rsoPUUID}': ( 454 | generalRegion: GeneralRegion, 455 | realPath: string, 456 | path: string, 457 | ) => ({ 458 | /* Get a summoner by its RSO encrypted PUUID. */ 459 | get() { 460 | return limiter.execute(generalRegion, realPath, path) 461 | }, 462 | }), 463 | '/lol/summoner/v4/summoners/by-puuid/{encryptedPUUID}': ( 464 | generalRegion: GeneralRegion, 465 | realPath: string, 466 | path: string, 467 | ) => ({ 468 | /* Get a summoner by PUUID. */ 469 | get() { 470 | return limiter.execute(generalRegion, realPath, path) 471 | }, 472 | }), 473 | '/lol/summoner/v4/summoners/me': ( 474 | generalRegion: GeneralRegion, 475 | realPath: string, 476 | path: string, 477 | ) => ({ 478 | /* Get a summoner by access token. */ 479 | get() { 480 | return limiter.execute(generalRegion, realPath, path) 481 | }, 482 | }), 483 | // #endregion 484 | } 485 | } 486 | -------------------------------------------------------------------------------- /src/riot/static/ddragon.type.ts: -------------------------------------------------------------------------------- 1 | export type MetaFile = keyof MetaFileSchemaMap 2 | export type MetaFileSchemaMap = { 3 | [K: `champion/${string}`]: ChampionNameJSON 4 | champion: ChampionJSON 5 | championFull: ChampionFullJSON 6 | item: ItemJSON 7 | language: LanguageJSON 8 | map: MapJSON 9 | 'mission-assets': MissionAssetJSON 10 | profileicon: ProfileIcon 11 | runesReforged: RunesReforgedJSON 12 | sticker: StickerJSON 13 | summoner: SummonerJSON 14 | } 15 | 16 | export type Image = { 17 | full: string 18 | sprite: string 19 | group: string 20 | x: number 21 | y: number 22 | w: number 23 | h: number 24 | } 25 | 26 | type ChampionName = string 27 | 28 | // champion.json 29 | export type ChampionJSON = { 30 | type: string 31 | format: string 32 | version: string 33 | data: { 34 | [championName: string]: BriefChampion 35 | } 36 | } 37 | 38 | export type BriefChampion = { 39 | version: string 40 | id: string 41 | key: string 42 | name: string 43 | title: string 44 | blurb: string 45 | info: ChampionInfo 46 | image: Image 47 | tags: string[] 48 | partype: string 49 | stats: ChampionStats 50 | } 51 | 52 | // champion/[championName].json 53 | export type ChampionNameJSON = { 54 | type: string 55 | format: string 56 | version: string 57 | data: { 58 | [championName: string]: Champion 59 | } 60 | } 61 | 62 | export type Champion = { 63 | id: string 64 | key: string 65 | name: string 66 | title: string 67 | image: Image 68 | skins: Skin[] 69 | lore: string 70 | blurb: string 71 | allytips: string[] 72 | enemytips: string[] 73 | tags: string[] 74 | partype: string 75 | info: ChampionInfo 76 | stats: ChampionStats 77 | spells: Spell[] 78 | passive: Passive 79 | recommended: any[] 80 | } 81 | 82 | export type Skin = { 83 | id: string 84 | num: number 85 | name: string 86 | chromas: boolean 87 | } 88 | 89 | export type ChampionInfo = { 90 | attack: number 91 | defense: number 92 | magic: number 93 | difficulty: number 94 | } 95 | 96 | export type ChampionStats = { 97 | hp: number 98 | hpperlevel: number 99 | mp: number 100 | mpperlevel: number 101 | movespeed: number 102 | armor: number 103 | armorperlevel: number 104 | spellblock: number 105 | spellblockperlevel: number 106 | attackrange: number 107 | hpregen: number 108 | hpregenperlevel: number 109 | mpregen: number 110 | mpregenperlevel: number 111 | crit: number 112 | critperlevel: number 113 | attackdamage: number 114 | attackdamageperlevel: number 115 | attackspeedperlevel: number 116 | attackspeed: number 117 | } 118 | 119 | export type Spell = { 120 | id: string 121 | name: string 122 | description: string 123 | tooltip: string 124 | leveltip: Leveltip 125 | maxrank: number 126 | cooldown: number[] 127 | cooldownBurn: string 128 | cost: number[] 129 | costBurn: string 130 | datavalues: Datavalues 131 | effect: number[] | undefined[] 132 | effectBurn: string | undefined[] 133 | vars: any[] 134 | costType: string 135 | maxammo: string 136 | range: number[] 137 | rangeBurn: string 138 | image: Image 139 | resource: string 140 | } 141 | 142 | export type Leveltip = { 143 | label: string[] 144 | effect: string[] 145 | } 146 | 147 | // eslint-disable-next-line @typescript-eslint/ban-types 148 | export type Datavalues = {} 149 | 150 | export type Passive = { 151 | name: string 152 | description: string 153 | image: Image 154 | } 155 | 156 | // item.json 157 | export type ItemJSON = { 158 | type: string 159 | version: string 160 | basic: { 161 | name: string 162 | rune: { 163 | isrune: boolean 164 | tier: number 165 | type: string 166 | } 167 | gold: Gold 168 | group: string 169 | description: string 170 | colloq: string 171 | plaintext: string 172 | consumed: boolean 173 | stacks: number 174 | depth: number 175 | consumeOnFull: boolean 176 | from: any[] 177 | into: any[] 178 | specialRecipe: number 179 | inStore: boolean 180 | hideFromAll: boolean 181 | requiredChampion: string 182 | requiredAlly: string 183 | stats: ItemStats 184 | tags: any[] 185 | maps: ItemMaps 186 | } 187 | data: { 188 | [itemId: string]: Item 189 | } 190 | groups: { 191 | id: string 192 | MaxGroupOwnable: string 193 | }[] 194 | tree: { 195 | header: string 196 | tags: string[] 197 | }[] 198 | } 199 | 200 | export type Item = { 201 | name: string 202 | description: string 203 | colloq: string 204 | plaintext: string 205 | into: string[] 206 | image: Image 207 | gold: Gold 208 | tags: string[] 209 | maps: ItemMaps 210 | stats: ItemStats 211 | } 212 | 213 | export type Gold = { 214 | base: number 215 | total: number 216 | sell: number 217 | purchasable: boolean 218 | } 219 | 220 | export type ItemStats = { 221 | FlatHPPoolMod: number 222 | rFlatHPModPerLevel: number 223 | FlatMPPoolMod: number 224 | rFlatMPModPerLevel: number 225 | PercentHPPoolMod: number 226 | PercentMPPoolMod: number 227 | FlatHPRegenMod: number 228 | rFlatHPRegenModPerLevel: number 229 | PercentHPRegenMod: number 230 | FlatMPRegenMod: number 231 | rFlatMPRegenModPerLevel: number 232 | PercentMPRegenMod: number 233 | FlatArmorMod: number 234 | rFlatArmorModPerLevel: number 235 | PercentArmorMod: number 236 | rFlatArmorPenetrationMod: number 237 | rFlatArmorPenetrationModPerLevel: number 238 | rPercentArmorPenetrationMod: number 239 | rPercentArmorPenetrationModPerLevel: number 240 | FlatPhysicalDamageMod: number 241 | rFlatPhysicalDamageModPerLevel: number 242 | PercentPhysicalDamageMod: number 243 | FlatMagicDamageMod: number 244 | rFlatMagicDamageModPerLevel: number 245 | PercentMagicDamageMod: number 246 | FlatMovementSpeedMod: number 247 | rFlatMovementSpeedModPerLevel: number 248 | PercentMovementSpeedMod: number 249 | rPercentMovementSpeedModPerLevel: number 250 | FlatAttackSpeedMod: number 251 | PercentAttackSpeedMod: number 252 | rPercentAttackSpeedModPerLevel: number 253 | rFlatDodgeMod: number 254 | rFlatDodgeModPerLevel: number 255 | PercentDodgeMod: number 256 | FlatCritChanceMod: number 257 | rFlatCritChanceModPerLevel: number 258 | PercentCritChanceMod: number 259 | FlatCritDamageMod: number 260 | rFlatCritDamageModPerLevel: number 261 | PercentCritDamageMod: number 262 | FlatBlockMod: number 263 | PercentBlockMod: number 264 | FlatSpellBlockMod: number 265 | rFlatSpellBlockModPerLevel: number 266 | PercentSpellBlockMod: number 267 | FlatEXPBonus: number 268 | PercentEXPBonus: number 269 | rPercentCooldownMod: number 270 | rPercentCooldownModPerLevel: number 271 | rFlatTimeDeadMod: number 272 | rFlatTimeDeadModPerLevel: number 273 | rPercentTimeDeadMod: number 274 | rPercentTimeDeadModPerLevel: number 275 | rFlatGoldPer10Mod: number 276 | rFlatMagicPenetrationMod: number 277 | rFlatMagicPenetrationModPerLevel: number 278 | rPercentMagicPenetrationMod: number 279 | rPercentMagicPenetrationModPerLevel: number 280 | FlatEnergyRegenMod: number 281 | rFlatEnergyRegenModPerLevel: number 282 | FlatEnergyPoolMod: number 283 | rFlatEnergyModPerLevel: number 284 | PercentLifeStealMod: number 285 | PercentSpellVampMod: number 286 | } 287 | 288 | export type ItemMaps = { 289 | '1': boolean 290 | '8': boolean 291 | '10': boolean 292 | '12': boolean 293 | } 294 | 295 | // summoner.json 296 | export type SummonerJSON = { 297 | type: string 298 | version: string 299 | data: { 300 | [spellName: string]: SummonerSpell 301 | } 302 | } 303 | 304 | export type SummonerSpell = { 305 | id: string 306 | name: string 307 | description: string 308 | tooltip: string 309 | maxrank: number 310 | cooldown: number[] 311 | cooldownBurn: string 312 | cost: number[] 313 | costBurn: string 314 | datavalues: Datavalues 315 | effect: number[] | undefined[] 316 | effectBurn: string | undefined[] 317 | vars: any[] 318 | key: string 319 | summonerLevel: number 320 | modes: string[] 321 | costType: string 322 | maxammo: string 323 | range: number[] 324 | rangeBurn: string 325 | image: Image 326 | resource: string 327 | } 328 | 329 | // profileicon.json 330 | export type ProfileIconJSON = { 331 | type: string 332 | version: string 333 | data: { 334 | [profileIconId: string]: ProfileIcon 335 | } 336 | } 337 | 338 | export type ProfileIcon = { 339 | id: number 340 | image: Image 341 | } 342 | 343 | // runesReforged.json 344 | export type RunesReforgedJSON = { 345 | id: number 346 | key: string 347 | icon: string 348 | name: string 349 | slots: { 350 | runes: Rune[] 351 | }[] 352 | }[] 353 | 354 | export type Rune = { 355 | id: number 356 | key: string 357 | icon: string 358 | name: string 359 | shortDesc: string 360 | longDesc: string 361 | } 362 | 363 | // map.json 364 | export type MapJSON = { 365 | type: string 366 | version: string 367 | data: { 368 | [mapId: string]: Map 369 | } 370 | } 371 | 372 | export type Map = { 373 | MapName: string 374 | MapId: string 375 | image: Image 376 | } 377 | 378 | // language.json 379 | export type LanguageJSON = { 380 | type: string 381 | version: string 382 | data: LanguageData 383 | tree: { 384 | searchKeyIgnore: string 385 | searchKeyRemap: any[] 386 | } 387 | } 388 | 389 | export type LanguageData = { 390 | Back: string 391 | Continue: string 392 | Language: string 393 | ItemInfo: string 394 | NextRank_: string 395 | Rank_: string 396 | PlayingAs: string 397 | PlayingAgainst: string 398 | CD_: string 399 | Range: string 400 | Range_: string 401 | Details_: string 402 | PrimaryRole: string 403 | mobileCompanion: string 404 | mobileForum: string 405 | mobileFriends: string 406 | mobilePleaseWait: string 407 | mobileNews: string 408 | modeClassic: string 409 | modeOdin: string 410 | modeAram: string 411 | modeTutorial: string 412 | modeOneforall: string 413 | modeFirstblood: string 414 | mode6v6: string 415 | modeCof: string 416 | Map1: string 417 | Map8: string 418 | Map10: string 419 | Map12: string 420 | categoryChampion: string 421 | categoryItem: string 422 | categoryMastery: string 423 | categoryRune: string 424 | categorySummoner: string 425 | Gold: string 426 | Level: string 427 | Abilities: string 428 | ChampionInfo: string 429 | Lore: string 430 | Stats: string 431 | Tips: string 432 | statAbility: string 433 | statAttack: string 434 | statDefense: string 435 | statDifficulty: string 436 | statUtility: string 437 | Assassin: string 438 | Fighter: string 439 | Marksman: string 440 | Mage: string 441 | Support: string 442 | Tank: string 443 | spells_Self: string 444 | spells_target_0: string 445 | spells_target_1: string 446 | spells_target_2: string 447 | spells_target_3: string 448 | spells_target_4: string 449 | spells_target_5: string 450 | spells_target_6: string 451 | spells_target_7: string 452 | spells_target_8: string 453 | spells_target_100: string 454 | AllItems: string 455 | Armor: string 456 | Attack: string 457 | AttackSpeed: string 458 | Consumable: string 459 | CooldownReduction: string 460 | CriticalStrike: string 461 | Damage: string 462 | Defense: string 463 | Health: string 464 | HealthRegen: string 465 | LifeSteal: string 466 | Magic: string 467 | Mana: string 468 | ManaRegen: string 469 | Movement: string 470 | SpellBlock: string 471 | SpellDamage: string 472 | Boots: string 473 | NonbootsMovement: string 474 | Tenacity: string 475 | SpellVamp: string 476 | GoldPer: string 477 | Slow: string 478 | Aura: string 479 | Active: string 480 | MagicPenetration: string 481 | ArmorPenetration: string 482 | colloq_Armor: string 483 | colloq_Attack: string 484 | colloq_AttackSpeed: string 485 | colloq_Consumables: string 486 | colloq_CriticalStrike: string 487 | colloq_Damage: string 488 | colloq_Defense: string 489 | colloq_Health: string 490 | colloq_HealthRegen: string 491 | colloq_LifeSteal: string 492 | colloq_Magic: string 493 | colloq_Mana: string 494 | colloq_ManaRegen: string 495 | colloq_Movement: string 496 | colloq_SpellBlock: string 497 | colloq_SpellDamage: string 498 | colloq_Consumable: string 499 | colloq_Boots: string 500 | colloq_NonbootsMovement: string 501 | colloq_CooldownReduction: string 502 | colloq_Tenacity: string 503 | colloq_SpellVamp: string 504 | colloq_GoldPer: string 505 | colloq_Slow: string 506 | colloq_Aura: string 507 | colloq_Active: string 508 | colloq_MagicPenetration: string 509 | colloq_ArmorPenetration: string 510 | RecommendedItems: string 511 | recommended_starting: string 512 | recommended_essential: string 513 | recommended_offensive: string 514 | recommended_defensive: string 515 | recommended_consumables: string 516 | Require_: string 517 | Cost_: string 518 | OriginalCost_: string 519 | SellsFor_: string 520 | UpgradeCost_: string 521 | Builds_: string 522 | ButtonBuy: string 523 | ButtonSell: string 524 | SpecialRecipeSmall: string 525 | SpecialRecipeLarge: string 526 | FlatArmorMod: string 527 | FlatAttackSpeedMod: string 528 | FlatBlockMod: string 529 | FlatCritChanceMod: string 530 | FlatCritDamageMod: string 531 | FlatEnergyPoolMod: string 532 | FlatEnergyRegenMod: string 533 | FlatEXPBonus: string 534 | FlatHPPoolMod: string 535 | FlatHPRegenMod: string 536 | FlatMagicDamageMod: string 537 | FlatMovementSpeedMod: string 538 | FlatMPPoolMod: string 539 | FlatMPRegenMod: string 540 | FlatPhysicalDamageMod: string 541 | FlatSpellBlockMod: string 542 | PercentArmorMod: string 543 | PercentAttackSpeedMod: string 544 | PercentBlockMod: string 545 | PercentCritChanceMod: string 546 | PercentCritDamageMod: string 547 | PercentDodgeMod: string 548 | PercentEXPBonus: string 549 | PercentHPPoolMod: string 550 | PercentHPRegenMod: string 551 | PercentMagicDamageMod: string 552 | PercentMovementSpeedMod: string 553 | PercentMPPoolMod: string 554 | PercentMPRegenMod: string 555 | PercentPhysicalDamageMod: string 556 | PercentSpellBlockMod: string 557 | rFlatArmorModPerLevel: string 558 | rFlatArmorPenetrationMod: string 559 | rFlatArmorPenetrationModPerLevel: string 560 | rFlatCritChanceModPerLevel: string 561 | rFlatCritDamageModPerLevel: string 562 | rFlatDodgeMod: string 563 | rFlatDodgeModPerLevel: string 564 | rFlatEnergyModPerLevel: string 565 | rFlatEnergyRegenModPerLevel: string 566 | rFlatGoldPer10Mod: string 567 | rFlatHPModPerLevel: string 568 | rFlatHPRegenModPerLevel: string 569 | rFlatMagicDamageModPerLevel: string 570 | rFlatMagicPenetrationMod: string 571 | rFlatMagicPenetrationModPerLevel: string 572 | rFlatMovementSpeedModPerLevel: string 573 | rFlatMPModPerLevel: string 574 | rFlatMPRegenModPerLevel: string 575 | rFlatPhysicalDamageModPerLevel: string 576 | rFlatSpellBlockModPerLevel: string 577 | rFlatTimeDeadMod: string 578 | rFlatTimeDeadModPerLevel: string 579 | rPercentArmorPenetrationMod: string 580 | rPercentArmorPenetrationModPerLevel: string 581 | rPercentAttackSpeedModPerLevel: string 582 | rPercentCooldownMod: string 583 | rPercentCooldownModPerLevel: string 584 | rPercentMagicPenetrationMod: string 585 | rPercentMagicPenetrationModPerLevel: string 586 | rPercentMovementSpeedModPerLevel: string 587 | rPercentTimeDeadMod: string 588 | rPercentTimeDeadModPerLevel: string 589 | PercentLifeStealMod: string 590 | PercentSpellVampMod: string 591 | masteryFerocity: string 592 | masteryCunning: string 593 | masteryResolve: string 594 | native_ar: string 595 | native_bg: string 596 | native_cs: string 597 | native_de: string 598 | native_el: string 599 | native_en: string 600 | native_es: string 601 | native_fr: string 602 | native_hu: string 603 | native_id: string 604 | native_it: string 605 | native_ja: string 606 | native_ko: string 607 | native_nl: string 608 | native_pl: string 609 | native_pt: string 610 | native_ro: string 611 | native_ru: string 612 | native_th: string 613 | native_tr: string 614 | native_vn: string 615 | native_zh: string 616 | native_zh_CN: string 617 | native_zh_MY: string 618 | native_zh_TW: string 619 | } 620 | 621 | // mission-assets.json 622 | export type MissionAssetJSON = { 623 | type: string 624 | version: string 625 | data: { 626 | [missionName: string]: MissionAsset 627 | } 628 | } 629 | 630 | export type MissionAsset = { 631 | id: number 632 | image: Image 633 | } 634 | 635 | // sticker.json 636 | export type StickerJSON = { 637 | type: string 638 | version: string 639 | // eslint-disable-next-line @typescript-eslint/ban-types 640 | data: {} 641 | } 642 | 643 | // championFull.json 644 | export type ChampionFullJSON = { 645 | type: string 646 | format: string 647 | version: string 648 | data: { 649 | [championName: string]: Champion 650 | } 651 | keys: { 652 | [championKey: string]: ChampionName 653 | } 654 | } 655 | -------------------------------------------------------------------------------- /scripts/__snapshots__/dtos.ts: -------------------------------------------------------------------------------- 1 | // #region ACCOUNT-V1 2 | export type AccountDto = { 3 | /* Encrypted PUUID. Exact length of 78 characters. */ 4 | puuid: string 5 | /* This field may be excluded from the response if the account doesn't have a gameName. */ 6 | gameName: string 7 | /* This field may be excluded from the response if the account doesn't have a tagLine. */ 8 | tagLine: string 9 | } 10 | 11 | /* Account region */ 12 | export type AccountRegionDTO = { 13 | /* Player Universal Unique Identifier. Exact length of 78 characters. (Encrypted) */ 14 | puuid: string 15 | /* Game to lookup active region */ 16 | game: string 17 | /* Player active region */ 18 | region: string 19 | } 20 | // #endregion 21 | 22 | // #region CHAMPION-MASTERY-V4 23 | /* This object contains single Champion Mastery information for player and champion combination. */ 24 | export type ChampionMasteryDto = { 25 | /* Player Universal Unique Identifier. Exact length of 78 characters. (Encrypted) */ 26 | puuid: string 27 | /* Number of points needed to achieve next level. Zero if player reached maximum champion level for this champion. */ 28 | championPointsUntilNextLevel: number 29 | /* Is chest granted for this champion or not in current season. */ 30 | chestGranted: boolean 31 | /* Champion ID for this entry. */ 32 | championId: number 33 | /* Last time this champion was played by this player - in Unix milliseconds time format. */ 34 | lastPlayTime: number 35 | /* Champion level for specified player and champion combination. */ 36 | championLevel: number 37 | /* Total number of champion points for this player and champion combination - they are used to determine championLevel. */ 38 | championPoints: number 39 | /* Number of points earned since current level has been achieved. */ 40 | championPointsSinceLastLevel: number 41 | markRequiredForNextLevel: number 42 | championSeasonMilestone: number 43 | nextSeasonMilestone: NextSeasonMilestonesDto 44 | /* The token earned for this champion at the current championLevel. When the championLevel is advanced the tokensEarned resets to 0. */ 45 | tokensEarned: number 46 | milestoneGrades: string[] 47 | } 48 | 49 | /* This object contains required next season milestone information. */ 50 | export type NextSeasonMilestonesDto = { 51 | requireGradeCounts: object 52 | /* Reward marks. */ 53 | rewardMarks: number 54 | /* Bonus. */ 55 | bonus: boolean 56 | /* Reward configuration. */ 57 | rewardConfig: RewardConfigDto 58 | } 59 | 60 | /* This object contains required reward config information. */ 61 | export type RewardConfigDto = { 62 | /* Reward value */ 63 | rewardValue: string 64 | /* Reward type */ 65 | rewardType: string 66 | /* Maximun reward */ 67 | maximumReward: number 68 | } 69 | // #endregion 70 | 71 | // #region CHAMPION-V3 72 | export type ChampionInfo = { 73 | maxNewPlayerLevel: number 74 | freeChampionIdsForNewPlayers: number[] 75 | freeChampionIds: number[] 76 | } 77 | // #endregion 78 | 79 | // #region CLASH-V1 80 | export type PlayerDto = { 81 | puuid: string 82 | /* (Legal values: UNSELECTED, FILL, TOP, JUNGLE, MIDDLE, BOTTOM, UTILITY) */ 83 | position: string 84 | /* (Legal values: CAPTAIN, MEMBER) */ 85 | role: string 86 | } 87 | 88 | export type TeamDto = { 89 | id: string 90 | tournamentId: number 91 | name: string 92 | iconId: number 93 | tier: Tier 94 | /* Summoner ID of the team captain. */ 95 | captain: string 96 | abbreviation: string 97 | /* Team members. */ 98 | players: PlayerDto[] 99 | } 100 | 101 | export type TournamentDto = { 102 | id: number 103 | themeId: number 104 | nameKey: string 105 | nameKeySecondary: string 106 | /* Tournament phase. */ 107 | schedule: TournamentPhaseDto[] 108 | } 109 | 110 | export type TournamentPhaseDto = { 111 | id: number 112 | registrationTime: number 113 | startTime: number 114 | cancelled: boolean 115 | } 116 | // #endregion 117 | 118 | // #region LEAGUE-V4 119 | export type LeagueListDTO = { 120 | leagueId: string 121 | entries: LeagueItemDTO[] 122 | tier: Tier 123 | name: string 124 | queue: Queue 125 | } 126 | 127 | export type LeagueItemDTO = { 128 | freshBlood: boolean 129 | /* Winning team on Summoners Rift. */ 130 | wins: number 131 | miniSeries: MiniSeriesDTO 132 | inactive: boolean 133 | veteran: boolean 134 | hotStreak: boolean 135 | rank: Division 136 | leaguePoints: number 137 | /* Losing team on Summoners Rift. */ 138 | losses: number 139 | /* Player's encrypted puuid. */ 140 | puuid: string 141 | } 142 | 143 | export type MiniSeriesDTO = { 144 | losses: number 145 | progress: string 146 | target: number 147 | wins: number 148 | } 149 | 150 | export type LeagueEntryDTO = { 151 | leagueId: string 152 | /* Player's encrypted puuid. */ 153 | puuid: string 154 | queueType: Queue 155 | tier: Tier 156 | /* The player's division within a tier. */ 157 | rank: Division 158 | leaguePoints: number 159 | /* Winning team on Summoners Rift. */ 160 | wins: number 161 | /* Losing team on Summoners Rift. */ 162 | losses: number 163 | hotStreak: boolean 164 | veteran: boolean 165 | freshBlood: boolean 166 | inactive: boolean 167 | miniSeries: MiniSeriesDTO 168 | } 169 | // #endregion 170 | 171 | // #region LOL-CHALLENGES-V1 172 | export type ChallengeConfigInfoDto = { 173 | id: number 174 | localizedNames: Record> 175 | state: State 176 | tracking: Tracking 177 | startTimestamp: number 178 | endTimestamp: number 179 | leaderboard: boolean 180 | thresholds: Record 181 | } 182 | 183 | /* DISABLED - not visible and not calculated, 184 | HIDDEN - not visible, but calculated, 185 | ENABLED - visible and calculated, 186 | ARCHIVED - visible, but not calculated */ 187 | export type State = NotMentioned 188 | 189 | /* LIFETIME - stats are incremented without reset, 190 | SEASON - stats are accumulated by season and reset at the beginning of new season */ 191 | export type Tracking = NotMentioned 192 | 193 | export type ApexPlayerInfoDto = { 194 | puuid: string 195 | value: number 196 | position: number 197 | } 198 | 199 | /* 0 NONE, 200 | 1 IRON, 201 | 2 BRONZE, 202 | 3 SILVER, 203 | 4 GOLD, 204 | 5 PLATINUM, 205 | 6 DIAMOND, 206 | 7 MASTER, 207 | 8 GRANDMASTER, 208 | 9 CHALLENGER */ 209 | export type Level = NotMentioned 210 | 211 | export type PlayerInfoDto = { 212 | challenges: ChallengeInfo[] 213 | preferences: PlayerClientPreferences 214 | totalPoints: ChallengePonumbers 215 | categoryPoints: Record 216 | } 217 | // #endregion 218 | 219 | // #region LOL-RSO-MATCH-V1 220 | 221 | // #endregion 222 | 223 | // #region LOL-STATUS-V4 224 | export type PlatformDataDto = { 225 | id: string 226 | name: string 227 | locales: string[] 228 | maintenances: StatusDto[] 229 | incidents: StatusDto[] 230 | } 231 | 232 | export type StatusDto = { 233 | id: number 234 | /* (Legal values: scheduled, in_progress, complete) */ 235 | maintenance_status: string 236 | /* (Legal values: info, warning, critical) */ 237 | incident_severity: string 238 | titles: ContentDto[] 239 | updates: UpdateDto[] 240 | created_at: string 241 | archive_at: string 242 | updated_at: string 243 | /* (Legal values: windows, macos, android, ios, ps4, xbone, switch) */ 244 | platforms: string[] 245 | } 246 | 247 | export type ContentDto = { 248 | locale: string 249 | content: string 250 | } 251 | 252 | export type UpdateDto = { 253 | id: number 254 | author: string 255 | publish: boolean 256 | /* (Legal values: riotclient, riotstatus, game) */ 257 | publish_locations: string[] 258 | translations: ContentDto[] 259 | created_at: string 260 | updated_at: string 261 | } 262 | // #endregion 263 | 264 | // #region MATCH-V5 265 | export type MatchDto = { 266 | /* Match metadata. */ 267 | metadata: MetadataDto 268 | /* Match info. */ 269 | info: InfoDto 270 | } 271 | 272 | export type MetadataDto = { 273 | /* Match data version. */ 274 | dataVersion: string 275 | /* Match id. */ 276 | matchId: string 277 | /* A list of participant PUUIDs. */ 278 | participants: string[] 279 | } 280 | 281 | export type InfoDto = { 282 | /* Refer to indicate if the game ended in termination. */ 283 | endOfGameResult: string 284 | /* Unix timestamp for when the game is created on the game server (i.e., the loading screen). */ 285 | gameCreation: number 286 | /* Prior to patch 11.20, this field returns the game length in milliseconds calculated from gameEndTimestamp - gameStartTimestamp. Post patch 11.20, this field returns the max timePlayed of any participant in the game in seconds, which makes the behavior of this field consistent with that of match-v4. The best way to handling the change in this field is to treat the value as milliseconds if the gameEndTimestamp field isn't in the response and to treat the value as seconds if gameEndTimestamp is in the response. */ 287 | gameDuration: number 288 | /* Unix timestamp for when match ends on the game server. This timestamp can occasionally be significantly longer than when the match "ends". The most reliable way of determining the timestamp for the end of the match would be to add the max time played of any participant to the gameStartTimestamp. This field was added to match-v5 in patch 11.20 on Oct 5th, 2021. */ 289 | gameEndTimestamp: number 290 | gameId: number 291 | /* Refer to the Game Constants documentation. */ 292 | gameMode: string 293 | gameName: string 294 | /* Unix timestamp for when match starts on the game server. */ 295 | gameStartTimestamp: number 296 | gameType: string 297 | /* The first two parts can be used to determine the patch a game was played on. */ 298 | gameVersion: string 299 | /* Refer to the Game Constants documentation. */ 300 | mapId: number 301 | participants: ParticipantDto[] 302 | /* Platform where the match was played. */ 303 | platformId: string 304 | /* Refer to the Game Constants documentation. */ 305 | queueId: number 306 | teams: TeamDto[] 307 | /* Tournament code used to generate the match. This field was added to match-v5 in patch 11.13 on June 23rd, 2021. */ 308 | tournamentCode: string 309 | } 310 | 311 | export type ParticipantDto = { 312 | /* Yellow crossed swords */ 313 | allInPings: number 314 | /* Green flag */ 315 | assistMePings: number 316 | assists: number 317 | baronKills: number 318 | bountyLevel: number 319 | champExperience: number 320 | champLevel: number 321 | /* Prior to patch 11.4, on Feb 18th, 2021, this field returned invalid championIds. We recommend determining the champion based on the championName field for matches played prior to patch 11.4. */ 322 | championId: number 323 | championName: string 324 | /* Blue generic ping (ALT+click) */ 325 | commandPings: number 326 | /* This field is currently only utilized for Kayn's transformations. (Legal values: 0 - None, 1 - Slayer, 2 - Assassin) */ 327 | championTransform: number 328 | consumablesPurchased: number 329 | challenges: ChallengesDto 330 | damageDealtToBuildings: number 331 | damageDealtToObjectives: number 332 | damageDealtToTurrets: number 333 | damageSelfMitigated: number 334 | deaths: number 335 | detectorWardsPlaced: number 336 | doubleKills: number 337 | dragonKills: number 338 | eligibleForProgression: boolean 339 | /* Yellow questionmark */ 340 | enemyMissingPings: number 341 | /* Red eyeball */ 342 | enemyVisionPings: number 343 | firstBloodAssist: boolean 344 | firstBloodKill: boolean 345 | firstTowerAssist: boolean 346 | firstTowerKill: boolean 347 | /* This is an offshoot of the OneStone challenge. The code checks if a spell with the same instance ID does the final point of damage to at least 2 Champions. It doesn't matter if they're enemies, but you cannot hurt your friends. */ 348 | gameEndedInEarlySurrender: boolean 349 | gameEndedInSurrender: boolean 350 | holdPings: number 351 | /* Yellow circle with horizontal line */ 352 | getBackPings: number 353 | goldEarned: number 354 | goldSpent: number 355 | /* Both individualPosition and teamPosition are computed by the game server and are different versions of the most likely position played by a player. The individualPosition is the best guess for which position the player actually played in isolation of anything else. The teamPosition is the best guess for which position the player actually played if we add the constraint that each team must have one top player, one jungle, one middle, etc. Generally the recommendation is to use the teamPosition field over the individualPosition field. */ 356 | individualPosition: string 357 | inhibitorKills: number 358 | inhibitorTakedowns: number 359 | inhibitorsLost: number 360 | item0: number 361 | item1: number 362 | item2: number 363 | item3: number 364 | item4: number 365 | item5: number 366 | item6: number 367 | itemsPurchased: number 368 | killingSprees: number 369 | kills: number 370 | lane: string 371 | largestCriticalStrike: number 372 | largestKillingSpree: number 373 | largestMultiKill: number 374 | longestTimeSpentLiving: number 375 | magicDamageDealt: number 376 | magicDamageDealtToChampions: number 377 | magicDamageTaken: number 378 | missions: MissionsDto 379 | /* neutralMinionsKilled = mNeutralMinionsKilled, which is incremented on kills of kPet and kJungleMonster */ 380 | neutralMinionsKilled: number 381 | /* Green ward */ 382 | needVisionPings: number 383 | nexusKills: number 384 | nexusTakedowns: number 385 | nexusLost: number 386 | objectivesStolen: number 387 | objectivesStolenAssists: number 388 | /* Blue arrow pointing at ground */ 389 | onMyWayPings: number 390 | participantId: number 391 | playerScore0: number 392 | playerScore1: number 393 | playerScore2: number 394 | playerScore3: number 395 | playerScore4: number 396 | playerScore5: number 397 | playerScore6: number 398 | playerScore7: number 399 | playerScore8: number 400 | playerScore9: number 401 | playerScore10: number 402 | playerScore11: number 403 | pentaKills: number 404 | perks: PerksDto 405 | physicalDamageDealt: number 406 | physicalDamageDealtToChampions: number 407 | physicalDamageTaken: number 408 | placement: number 409 | playerAugment1: number 410 | playerAugment2: number 411 | playerAugment3: number 412 | playerAugment4: number 413 | playerSubteamId: number 414 | /* Green minion */ 415 | pushPings: number 416 | profileIcon: number 417 | puuid: string 418 | quadraKills: number 419 | riotIdGameName: string 420 | riotIdTagline: string 421 | role: string 422 | sightWardsBoughtInGame: number 423 | spell1Casts: number 424 | spell2Casts: number 425 | spell3Casts: number 426 | spell4Casts: number 427 | subteamPlacement: number 428 | summoner1Casts: number 429 | summoner1Id: number 430 | summoner2Casts: number 431 | summoner2Id: number 432 | summonerId: string 433 | summonerLevel: number 434 | summonerName: string 435 | teamEarlySurrendered: boolean 436 | teamId: number 437 | /* Both individualPosition and teamPosition are computed by the game server and are different versions of the most likely position played by a player. The individualPosition is the best guess for which position the player actually played in isolation of anything else. The teamPosition is the best guess for which position the player actually played if we add the constraint that each team must have one top player, one jungle, one middle, etc. Generally the recommendation is to use the teamPosition field over the individualPosition field. */ 438 | teamPosition: string 439 | timeCCingOthers: number 440 | timePlayed: number 441 | totalAllyJungleMinionsKilled: number 442 | totalDamageDealt: number 443 | totalDamageDealtToChampions: number 444 | totalDamageShieldedOnTeammates: number 445 | totalDamageTaken: number 446 | totalEnemyJungleMinionsKilled: number 447 | /* Whenever positive health is applied (which translates to all heals in the game but not things like regeneration), totalHeal is incremented by the amount of health received. This includes healing enemies, jungle monsters, yourself, etc */ 448 | totalHeal: number 449 | /* Whenever positive health is applied (which translates to all heals in the game but not things like regeneration), totalHealsOnTeammates is incremented by the amount of health received. This is post modified, so if you heal someone missing 5 health for 100 you will get +5 totalHealsOnTeammates */ 450 | totalHealsOnTeammates: number 451 | /* totalMillionsKilled = mMinionsKilled, which is only incremented on kills of kTeamMinion, kMeleeLaneMinion, kSuperLaneMinion, kRangedLaneMinion and kSiegeLaneMinion */ 452 | totalMinionsKilled: number 453 | totalTimeCCDealt: number 454 | totalTimeSpentDead: number 455 | totalUnitsHealed: number 456 | tripleKills: number 457 | trueDamageDealt: number 458 | trueDamageDealtToChampions: number 459 | trueDamageTaken: number 460 | turretKills: number 461 | turretTakedowns: number 462 | turretsLost: number 463 | unrealKills: number 464 | visionScore: number 465 | visionClearedPings: number 466 | visionWardsBoughtInGame: number 467 | wardsKilled: number 468 | wardsPlaced: number 469 | win: boolean 470 | } 471 | 472 | /* Challenges DTO */ 473 | export type ChallengesDto = { 474 | "12AssistStreakCount": number 475 | baronBuffGoldAdvantageOverThreshold: number 476 | controlWardTimeCoverageInRiverOrEnemyHalf: number 477 | earliestBaron: number 478 | earliestDragonTakedown: number 479 | earliestElderDragon: number 480 | earlyLaningPhaseGoldExpAdvantage: number 481 | fasterSupportQuestCompletion: number 482 | fastestLegendary: number 483 | hadAfkTeammate: number 484 | highestChampionDamage: number 485 | highestCrowdControlScore: number 486 | highestWardKills: number 487 | junglerKillsEarlyJungle: number 488 | killsOnLanersEarlyJungleAsJungler: number 489 | laningPhaseGoldExpAdvantage: number 490 | legendaryCount: number 491 | maxCsAdvantageOnLaneOpponent: number 492 | maxLevelLeadLaneOpponent: number 493 | mostWardsDestroyedOneSweeper: number 494 | mythicItemUsed: number 495 | playedChampSelectPosition: number 496 | soloTurretsLategame: number 497 | takedownsFirst25Minutes: number 498 | teleportTakedowns: number 499 | thirdInhibitorDestroyedTime: number 500 | threeWardsOneSweeperCount: number 501 | visionScoreAdvantageLaneOpponent: number 502 | InfernalScalePickup: number 503 | fistBumpParticipation: number 504 | voidMonsterKill: number 505 | abilityUses: number 506 | acesBefore15Minutes: number 507 | alliedJungleMonsterKills: number 508 | baronTakedowns: number 509 | blastConeOppositeOpponentCount: number 510 | bountyGold: number 511 | buffsStolen: number 512 | completeSupportQuestInTime: number 513 | controlWardsPlaced: number 514 | damagePerMinute: number 515 | damageTakenOnTeamPercentage: number 516 | dancedWithRiftHerald: number 517 | deathsByEnemyChamps: number 518 | dodgeSkillShotsSmallWindow: number 519 | doubleAces: number 520 | dragonTakedowns: number 521 | legendaryItemUsed: number[] 522 | effectiveHealAndShielding: number 523 | elderDragonKillsWithOpposingSoul: number 524 | elderDragonMultikills: number 525 | enemyChampionImmobilizations: number 526 | enemyJungleMonsterKills: number 527 | epicMonsterKillsNearEnemyJungler: number 528 | epicMonsterKillsWithin30SecondsOfSpawn: number 529 | epicMonsterSteals: number 530 | epicMonsterStolenWithoutSmite: number 531 | firstTurretKilled: number 532 | firstTurretKilledTime: number 533 | flawlessAces: number 534 | fullTeamTakedown: number 535 | gameLength: number 536 | getTakedownsInAllLanesEarlyJungleAsLaner: number 537 | goldPerMinute: number 538 | hadOpenNexus: number 539 | immobilizeAndKillWithAlly: number 540 | initialBuffCount: number 541 | initialCrabCount: number 542 | jungleCsBefore10Minutes: number 543 | junglerTakedownsNearDamagedEpicMonster: number 544 | kda: number 545 | killAfterHiddenWithAlly: number 546 | killedChampTookFullTeamDamageSurvived: number 547 | killingSprees: number 548 | killParticipation: number 549 | killsNearEnemyTurret: number 550 | killsOnOtherLanesEarlyJungleAsLaner: number 551 | killsOnRecentlyHealedByAramPack: number 552 | killsUnderOwnTurret: number 553 | killsWithHelpFromEpicMonster: number 554 | knockEnemyIntoTeamAndKill: number 555 | kTurretsDestroyedBeforePlatesFall: number 556 | landSkillShotsEarlyGame: number 557 | laneMinionsFirst10Minutes: number 558 | lostAnInhibitor: number 559 | maxKillDeficit: number 560 | mejaisFullStackInTime: number 561 | moreEnemyJungleThanOpponent: number 562 | /* This is an offshoot of the OneStone challenge. The code checks if a spell with the same instance ID does the final point of damage to at least 2 Champions. It doesn't matter if they're enemies, but you cannot hurt your friends. */ 563 | multiKillOneSpell: number 564 | multikills: number 565 | multikillsAfterAggressiveFlash: number 566 | multiTurretRiftHeraldCount: number 567 | outerTurretExecutesBefore10Minutes: number 568 | outnumberedKills: number 569 | outnumberedNexusKill: number 570 | perfectDragonSoulsTaken: number 571 | perfectGame: number 572 | pickKillWithAlly: number 573 | poroExplosions: number 574 | quickCleanse: number 575 | quickFirstTurret: number 576 | quickSoloKills: number 577 | riftHeraldTakedowns: number 578 | saveAllyFromDeath: number 579 | scuttleCrabKills: number 580 | shortestTimeToAceFromFirstTakedown: number 581 | skillshotsDodged: number 582 | skillshotsHit: number 583 | snowballsHit: number 584 | soloBaronKills: number 585 | SWARM_DefeatAatrox: number 586 | SWARM_DefeatBriar: number 587 | SWARM_DefeatMiniBosses: number 588 | SWARM_EvolveWeapon: number 589 | SWARM_Have3Passives: number 590 | SWARM_KillEnemy: number 591 | SWARM_PickupGold: number 592 | SWARM_ReachLevel50: number 593 | SWARM_Survive15Min: number 594 | SWARM_WinWith5EvolvedWeapons: number 595 | soloKills: number 596 | stealthWardsPlaced: number 597 | survivedSingleDigitHpCount: number 598 | survivedThreeImmobilizesInFight: number 599 | takedownOnFirstTurret: number 600 | takedowns: number 601 | takedownsAfterGainingLevelAdvantage: number 602 | takedownsBeforeJungleMinionSpawn: number 603 | takedownsFirstXMinutes: number 604 | takedownsInAlcove: number 605 | takedownsInEnemyFountain: number 606 | teamBaronKills: number 607 | teamDamagePercentage: number 608 | teamElderDragonKills: number 609 | teamRiftHeraldKills: number 610 | tookLargeDamageSurvived: number 611 | turretPlatesTaken: number 612 | /* Any player who damages a tower that is destroyed within 30 seconds of a Rift Herald charge will receive credit. A player who does not damage the tower will not receive credit. */ 613 | turretsTakenWithRiftHerald: number 614 | turretTakedowns: number 615 | twentyMinionsIn3SecondsCount: number 616 | twoWardsOneSweeperCount: number 617 | unseenRecalls: number 618 | visionScorePerMinute: number 619 | wardsGuarded: number 620 | wardTakedowns: number 621 | wardTakedownsBefore20M: number 622 | } 623 | 624 | /* Missions DTO */ 625 | export type MissionsDto = { 626 | playerScore0: number 627 | playerScore1: number 628 | playerScore2: number 629 | playerScore3: number 630 | playerScore4: number 631 | playerScore5: number 632 | playerScore6: number 633 | playerScore7: number 634 | playerScore8: number 635 | playerScore9: number 636 | playerScore10: number 637 | playerScore11: number 638 | } 639 | 640 | export type PerksDto = { 641 | statPerks: PerkStatsDto 642 | styles: PerkStyleDto[] 643 | } 644 | 645 | export type PerkStatsDto = { 646 | defense: number 647 | flex: number 648 | offense: number 649 | } 650 | 651 | export type PerkStyleDto = { 652 | description: string 653 | selections: PerkStyleSelectionDto[] 654 | style: number 655 | } 656 | 657 | export type PerkStyleSelectionDto = { 658 | perk: number 659 | var1: number 660 | var2: number 661 | var3: number 662 | } 663 | 664 | export type TeamDto = { 665 | bans: BanDto[] 666 | objectives: ObjectivesDto 667 | teamId: number 668 | win: boolean 669 | } 670 | 671 | export type BanDto = { 672 | championId: number 673 | pickTurn: number 674 | } 675 | 676 | export type ObjectivesDto = { 677 | baron: ObjectiveDto 678 | champion: ObjectiveDto 679 | dragon: ObjectiveDto 680 | horde: ObjectiveDto 681 | inhibitor: ObjectiveDto 682 | riftHerald: ObjectiveDto 683 | tower: ObjectiveDto 684 | } 685 | 686 | export type ObjectiveDto = { 687 | first: boolean 688 | kills: number 689 | } 690 | 691 | export type TimelineDto = { 692 | /* Match metadata. */ 693 | metadata: MetadataTimeLineDto 694 | /* Match info. */ 695 | info: InfoTimeLineDto 696 | } 697 | 698 | export type MetadataTimeLineDto = { 699 | /* Match data version. */ 700 | dataVersion: string 701 | /* Match id. */ 702 | matchId: string 703 | /* A list of participant PUUIDs. */ 704 | participants: string[] 705 | } 706 | 707 | export type InfoTimeLineDto = { 708 | /* Refer to indicate if the game ended in termination. */ 709 | endOfGameResult: string 710 | frameInterval: number 711 | gameId: number 712 | participants: ParticipantTimeLineDto[] 713 | frames: FramesTimeLineDto[] 714 | } 715 | 716 | export type ParticipantTimeLineDto = { 717 | participantId: number 718 | puuid: string 719 | } 720 | 721 | export type FramesTimeLineDto = { 722 | events: EventsTimeLineDto[] 723 | participantFrames: ParticipantFramesDto 724 | timestamp: number 725 | } 726 | 727 | export type EventsTimeLineDto = { 728 | timestamp: number 729 | realTimestamp: number 730 | type: string 731 | } 732 | 733 | export type ParticipantFramesDto = { 734 | /* Key value mapping for each participant */ 735 | "1-9": ParticipantFrameDto 736 | } 737 | 738 | export type ParticipantFrameDto = { 739 | championStats: ChampionStatsDto 740 | currentGold: number 741 | damageStats: DamageStatsDto 742 | goldPerSecond: number 743 | jungleMinionsKilled: number 744 | level: number 745 | minionsKilled: number 746 | participantId: number 747 | position: PositionDto 748 | timeEnemySpentControlled: number 749 | totalGold: number 750 | xp: number 751 | } 752 | 753 | export type ChampionStatsDto = { 754 | abilityHaste: number 755 | abilityPower: number 756 | armor: number 757 | armorPen: number 758 | armorPenPercent: number 759 | attackDamage: number 760 | attackSpeed: number 761 | bonusArmorPenPercent: number 762 | bonusMagicPenPercent: number 763 | ccReduction: number 764 | cooldownReduction: number 765 | health: number 766 | healthMax: number 767 | healthRegen: number 768 | lifesteal: number 769 | magicPen: number 770 | magicPenPercent: number 771 | magicResist: number 772 | movementSpeed: number 773 | omnivamp: number 774 | physicalVamp: number 775 | power: number 776 | powerMax: number 777 | powerRegen: number 778 | spellVamp: number 779 | } 780 | 781 | export type DamageStatsDto = { 782 | magicDamageDone: number 783 | magicDamageDoneToChampions: number 784 | magicDamageTaken: number 785 | physicalDamageDone: number 786 | physicalDamageDoneToChampions: number 787 | physicalDamageTaken: number 788 | totalDamageDone: number 789 | totalDamageDoneToChampions: number 790 | totalDamageTaken: number 791 | trueDamageDone: number 792 | trueDamageDoneToChampions: number 793 | trueDamageTaken: number 794 | } 795 | 796 | export type PositionDto = { 797 | x: number 798 | y: number 799 | } 800 | // #endregion 801 | 802 | // #region SPECTATOR-V5 803 | export type CurrentGameInfo = { 804 | /* The ID of the game */ 805 | gameId: number 806 | /* The game type */ 807 | gameType: string 808 | /* The game start time represented in epoch milliseconds */ 809 | gameStartTime: number 810 | /* The ID of the map */ 811 | mapId: number 812 | /* The amount of time in seconds that has passed since the game started */ 813 | gameLength: number 814 | /* The ID of the platform on which the game is being played */ 815 | platformId: string 816 | /* The game mode */ 817 | gameMode: string 818 | /* Banned champion information */ 819 | bannedChampions: BannedChampion[] 820 | /* The queue type (queue types are documented on the Game Constants page) */ 821 | gameQueueConfigId: number 822 | /* The observer information */ 823 | observers: Observer 824 | /* The participant information */ 825 | participants: CurrentGameParticipant[] 826 | } 827 | 828 | export type BannedChampion = { 829 | /* The turn during which the champion was banned */ 830 | pickTurn: number 831 | /* The ID of the banned champion */ 832 | championId: number 833 | /* The ID of the team that banned the champion */ 834 | teamId: number 835 | } 836 | 837 | export type Observer = { 838 | /* Key used to decrypt the spectator grid game data for playback */ 839 | encryptionKey: string 840 | } 841 | 842 | export type CurrentGameParticipant = { 843 | /* The ID of the champion played by this participant */ 844 | championId: number 845 | /* Perks/Runes Reforged Information */ 846 | perks: Perks 847 | /* The ID of the profile icon used by this participant */ 848 | profileIconId: number 849 | /* Flag indicating whether or not this participant is a bot */ 850 | bot: boolean 851 | /* The team ID of this participant, indicating the participant's team */ 852 | teamId: number 853 | /* The encrypted puuid of this participant */ 854 | puuid: string 855 | /* The ID of the first summoner spell used by this participant */ 856 | spell1Id: number 857 | /* The ID of the second summoner spell used by this participant */ 858 | spell2Id: number 859 | /* List of Game Customizations */ 860 | gameCustomizationObjects: GameCustomizationObject[] 861 | } 862 | 863 | export type Perks = { 864 | /* IDs of the perks/runes assigned. */ 865 | perkIds: number[] 866 | /* Primary runes path */ 867 | perkStyle: number 868 | /* Secondary runes path */ 869 | perkSubStyle: number 870 | } 871 | 872 | export type GameCustomizationObject = { 873 | /* Category identifier for Game Customization */ 874 | category: string 875 | /* Game Customization content */ 876 | content: string 877 | } 878 | 879 | export type FeaturedGames = { 880 | /* The list of featured games */ 881 | gameList: FeaturedGameInfo[] 882 | /* The suggested interval to wait before requesting FeaturedGames again */ 883 | clientRefreshInterval: number 884 | } 885 | 886 | export type FeaturedGameInfo = { 887 | /* The game mode 888 | (Legal values: CLASSIC, ODIN, ARAM, TUTORIAL, ONEFORALL, ASCENSION, FIRSTBLOOD, KINGPORO) */ 889 | gameMode: string 890 | /* The amount of time in seconds that has passed since the game started */ 891 | gameLength: number 892 | /* The ID of the map */ 893 | mapId: number 894 | /* The game type 895 | (Legal values: CUSTOM_GAME, MATCHED_GAME, TUTORIAL_GAME) */ 896 | gameType: string 897 | /* Banned champion information */ 898 | bannedChampions: BannedChampion[] 899 | /* The ID of the game */ 900 | gameId: number 901 | /* The observer information */ 902 | observers: Observer 903 | /* The queue type (queue types are documented on the Game Constants page) */ 904 | gameQueueConfigId: number 905 | /* The participant information */ 906 | participants: Participant[] 907 | /* The ID of the platform on which the game is being played */ 908 | platformId: string 909 | } 910 | 911 | export type Participant = { 912 | /* Flag indicating whether or not this participant is a bot */ 913 | bot: boolean 914 | /* The ID of the second summoner spell used by this participant */ 915 | spell2Id: number 916 | /* The ID of the profile icon used by this participant */ 917 | profileIconId: number 918 | /* Encrypted puuid of this participant */ 919 | puuid: string 920 | /* The ID of the champion played by this participant */ 921 | championId: number 922 | /* The team ID of this participant, indicating the participant's team */ 923 | teamId: number 924 | /* The ID of the first summoner spell used by this participant */ 925 | spell1Id: number 926 | } 927 | // #endregion 928 | 929 | // #region SUMMONER-V4 930 | /* represents a summoner */ 931 | export type SummonerDTO = { 932 | /* ID of the summoner icon associated with the summoner. */ 933 | profileIconId: number 934 | /* Date summoner was last modified specified as epoch milliseconds. The following events will update this timestamp: profile icon change, playing the tutorial or advanced tutorial, finishing a game, summoner name change. */ 935 | revisionDate: number 936 | /* Encrypted PUUID. Exact length of 78 characters. */ 937 | puuid: string 938 | /* Summoner level associated with the summoner. */ 939 | summonerLevel: number 940 | } 941 | // #endregion -------------------------------------------------------------------------------- /src/riot/apis/dtos.ts: -------------------------------------------------------------------------------- 1 | import { Queue, Tier, Division } from './enums' 2 | 3 | // not mentioned in the docs 4 | export type NotMentioned = any 5 | export type ChallengeInfo = NotMentioned 6 | export type ChallengePonumbers = NotMentioned 7 | export type PlayerClientPreferences = NotMentioned 8 | export type ActiveShardDto = NotMentioned 9 | export type MatchTimelineDto = NotMentioned 10 | 11 | // #region ACCOUNT-V1 12 | export type AccountDto = { 13 | /* Encrypted PUUID. Exact length of 78 characters. */ 14 | puuid: string 15 | /* This field may be excluded from the response if the account doesn't have a gameName. */ 16 | gameName: string 17 | /* This field may be excluded from the response if the account doesn't have a tagLine. */ 18 | tagLine: string 19 | } 20 | 21 | /* Account region */ 22 | export type AccountRegionDTO = { 23 | /* Player Universal Unique Identifier. Exact length of 78 characters. (Encrypted) */ 24 | puuid: string 25 | /* Game to lookup active region */ 26 | game: string 27 | /* Player active region */ 28 | region: string 29 | } 30 | // #endregion 31 | 32 | // #region CHAMPION-MASTERY-V4 33 | /* This object contains single Champion Mastery information for player and champion combination. */ 34 | export type ChampionMasteryDto = { 35 | /* Player Universal Unique Identifier. Exact length of 78 characters. (Encrypted) */ 36 | puuid: string 37 | /* Number of points needed to achieve next level. Zero if player reached maximum champion level for this champion. */ 38 | championPointsUntilNextLevel: number 39 | /* Is chest granted for this champion or not in current season. */ 40 | chestGranted: boolean 41 | /* Champion ID for this entry. */ 42 | championId: number 43 | /* Last time this champion was played by this player - in Unix milliseconds time format. */ 44 | lastPlayTime: number 45 | /* Champion level for specified player and champion combination. */ 46 | championLevel: number 47 | /* Total number of champion points for this player and champion combination - they are used to determine championLevel. */ 48 | championPoints: number 49 | /* Number of points earned since current level has been achieved. */ 50 | championPointsSinceLastLevel: number 51 | markRequiredForNextLevel: number 52 | championSeasonMilestone: number 53 | nextSeasonMilestone: NextSeasonMilestonesDto 54 | /* The token earned for this champion at the current championLevel. When the championLevel is advanced the tokensEarned resets to 0. */ 55 | tokensEarned: number 56 | milestoneGrades: string[] 57 | } 58 | 59 | /* This object contains required next season milestone information. */ 60 | export type NextSeasonMilestonesDto = { 61 | requireGradeCounts: object 62 | /* Reward marks. */ 63 | rewardMarks: number 64 | /* Bonus. */ 65 | bonus: boolean 66 | /* Reward configuration. */ 67 | rewardConfig: RewardConfigDto 68 | } 69 | 70 | /* This object contains required reward config information. */ 71 | export type RewardConfigDto = { 72 | /* Reward value */ 73 | rewardValue: string 74 | /* Reward type */ 75 | rewardType: string 76 | /* Maximun reward */ 77 | maximumReward: number 78 | } 79 | // #endregion 80 | 81 | // #region CHAMPION-V3 82 | export type ChampionInfo = { 83 | maxNewPlayerLevel: number 84 | freeChampionIdsForNewPlayers: number[] 85 | freeChampionIds: number[] 86 | } 87 | // #endregion 88 | 89 | // #region CLASH-V1 90 | export type PlayerDto = { 91 | puuid: string 92 | /* (Legal values: UNSELECTED, FILL, TOP, JUNGLE, MIDDLE, BOTTOM, UTILITY) */ 93 | position: string 94 | /* (Legal values: CAPTAIN, MEMBER) */ 95 | role: string 96 | } 97 | 98 | export type ClashTeamDto = { 99 | id: string 100 | tournamentId: number 101 | name: string 102 | iconId: number 103 | tier: Tier 104 | /* Summoner ID of the team captain. */ 105 | captain: string 106 | abbreviation: string 107 | /* Team members. */ 108 | players: PlayerDto[] 109 | } 110 | 111 | export type TournamentDto = { 112 | id: number 113 | themeId: number 114 | nameKey: string 115 | nameKeySecondary: string 116 | /* Tournament phase. */ 117 | schedule: TournamentPhaseDto[] 118 | } 119 | 120 | export type TournamentPhaseDto = { 121 | id: number 122 | registrationTime: number 123 | startTime: number 124 | cancelled: boolean 125 | } 126 | // #endregion 127 | 128 | // #region LEAGUE-V4 129 | export type LeagueListDTO = { 130 | leagueId: string 131 | entries: LeagueItemDTO[] 132 | tier: Tier 133 | name: string 134 | queue: Queue 135 | } 136 | 137 | export type LeagueItemDTO = { 138 | freshBlood: boolean 139 | /* Winning team on Summoners Rift. */ 140 | wins: number 141 | miniSeries: MiniSeriesDTO 142 | inactive: boolean 143 | veteran: boolean 144 | hotStreak: boolean 145 | rank: Division 146 | leaguePoints: number 147 | /* Losing team on Summoners Rift. */ 148 | losses: number 149 | /* Player's encrypted puuid. */ 150 | puuid: string 151 | } 152 | 153 | export type MiniSeriesDTO = { 154 | losses: number 155 | progress: string 156 | target: number 157 | wins: number 158 | } 159 | 160 | export type LeagueEntryDTO = { 161 | leagueId: string 162 | /* Player's encrypted puuid. */ 163 | puuid: string 164 | queueType: Queue 165 | tier: Tier 166 | /* The player's division within a tier. */ 167 | rank: Division 168 | leaguePoints: number 169 | /* Winning team on Summoners Rift. */ 170 | wins: number 171 | /* Losing team on Summoners Rift. */ 172 | losses: number 173 | hotStreak: boolean 174 | veteran: boolean 175 | freshBlood: boolean 176 | inactive: boolean 177 | miniSeries: MiniSeriesDTO 178 | } 179 | // #endregion 180 | 181 | // #region LOL-CHALLENGES-V1 182 | export type ChallengeConfigInfoDto = { 183 | id: number 184 | localizedNames: Record> 185 | state: State 186 | tracking: Tracking 187 | startTimestamp: number 188 | endTimestamp: number 189 | leaderboard: boolean 190 | thresholds: Record 191 | } 192 | 193 | /* DISABLED - not visible and not calculated, 194 | HIDDEN - not visible, but calculated, 195 | ENABLED - visible and calculated, 196 | ARCHIVED - visible, but not calculated */ 197 | export type State = NotMentioned 198 | 199 | /* LIFETIME - stats are incremented without reset, 200 | SEASON - stats are accumulated by season and reset at the beginning of new season */ 201 | export type Tracking = NotMentioned 202 | 203 | export type ApexPlayerInfoDto = { 204 | puuid: string 205 | value: number 206 | position: number 207 | } 208 | 209 | /* 0 NONE, 210 | 1 IRON, 211 | 2 BRONZE, 212 | 3 SILVER, 213 | 4 GOLD, 214 | 5 PLATINUM, 215 | 6 DIAMOND, 216 | 7 MASTER, 217 | 8 GRANDMASTER, 218 | 9 CHALLENGER */ 219 | export type Level = NotMentioned 220 | 221 | export type PlayerInfoDto = { 222 | challenges: ChallengeInfo[] 223 | preferences: PlayerClientPreferences 224 | totalPoints: ChallengePonumbers 225 | categoryPoints: Record 226 | } 227 | // #endregion 228 | 229 | // #region LOL-RSO-MATCH-V1 230 | 231 | // #endregion 232 | 233 | // #region LOL-STATUS-V4 234 | export type PlatformDataDto = { 235 | id: string 236 | name: string 237 | locales: string[] 238 | maintenances: StatusDto[] 239 | incidents: StatusDto[] 240 | } 241 | 242 | export type StatusDto = { 243 | id: number 244 | /* (Legal values: scheduled, in_progress, complete) */ 245 | maintenance_status: string 246 | /* (Legal values: info, warning, critical) */ 247 | incident_severity: string 248 | titles: ContentDto[] 249 | updates: UpdateDto[] 250 | created_at: string 251 | archive_at: string 252 | updated_at: string 253 | /* (Legal values: windows, macos, android, ios, ps4, xbone, switch) */ 254 | platforms: string[] 255 | } 256 | 257 | export type ContentDto = { 258 | locale: string 259 | content: string 260 | } 261 | 262 | export type UpdateDto = { 263 | id: number 264 | author: string 265 | publish: boolean 266 | /* (Legal values: riotclient, riotstatus, game) */ 267 | publish_locations: string[] 268 | translations: ContentDto[] 269 | created_at: string 270 | updated_at: string 271 | } 272 | // #endregion 273 | 274 | // #region MATCH-V5 275 | export type MatchDto = { 276 | /* Match metadata. */ 277 | metadata: MetadataDto 278 | /* Match info. */ 279 | info: InfoDto 280 | } 281 | 282 | export type MetadataDto = { 283 | /* Match data version. */ 284 | dataVersion: string 285 | /* Match id. */ 286 | matchId: string 287 | /* A list of participant PUUIDs. */ 288 | participants: string[] 289 | } 290 | 291 | export type InfoDto = { 292 | /* Refer to indicate if the game ended in termination. */ 293 | endOfGameResult: string 294 | /* Unix timestamp for when the game is created on the game server (i.e., the loading screen). */ 295 | gameCreation: number 296 | /* Prior to patch 11.20, this field returns the game length in milliseconds calculated from gameEndTimestamp - gameStartTimestamp. Post patch 11.20, this field returns the max timePlayed of any participant in the game in seconds, which makes the behavior of this field consistent with that of match-v4. The best way to handling the change in this field is to treat the value as milliseconds if the gameEndTimestamp field isn't in the response and to treat the value as seconds if gameEndTimestamp is in the response. */ 297 | gameDuration: number 298 | /* Unix timestamp for when match ends on the game server. This timestamp can occasionally be significantly longer than when the match "ends". The most reliable way of determining the timestamp for the end of the match would be to add the max time played of any participant to the gameStartTimestamp. This field was added to match-v5 in patch 11.20 on Oct 5th, 2021. */ 299 | gameEndTimestamp: number 300 | gameId: number 301 | /* Refer to the Game Constants documentation. */ 302 | gameMode: string 303 | gameName: string 304 | /* Unix timestamp for when match starts on the game server. */ 305 | gameStartTimestamp: number 306 | gameType: string 307 | /* The first two parts can be used to determine the patch a game was played on. */ 308 | gameVersion: string 309 | /* Refer to the Game Constants documentation. */ 310 | mapId: number 311 | participants: ParticipantDto[] 312 | /* Platform where the match was played. */ 313 | platformId: string 314 | /* Refer to the Game Constants documentation. */ 315 | queueId: number 316 | teams: TeamDto[] 317 | /* Tournament code used to generate the match. This field was added to match-v5 in patch 11.13 on June 23rd, 2021. */ 318 | tournamentCode: string 319 | } 320 | 321 | export type ParticipantDto = { 322 | /* Yellow crossed swords */ 323 | allInPings: number 324 | /* Green flag */ 325 | assistMePings: number 326 | assists: number 327 | baronKills: number 328 | bountyLevel: number 329 | champExperience: number 330 | champLevel: number 331 | /* Prior to patch 11.4, on Feb 18th, 2021, this field returned invalid championIds. We recommend determining the champion based on the championName field for matches played prior to patch 11.4. */ 332 | championId: number 333 | championName: string 334 | /* Blue generic ping (ALT+click) */ 335 | commandPings: number 336 | /* This field is currently only utilized for Kayn's transformations. (Legal values: 0 - None, 1 - Slayer, 2 - Assassin) */ 337 | championTransform: number 338 | consumablesPurchased: number 339 | challenges: ChallengesDto 340 | damageDealtToBuildings: number 341 | damageDealtToObjectives: number 342 | damageDealtToTurrets: number 343 | damageSelfMitigated: number 344 | deaths: number 345 | detectorWardsPlaced: number 346 | doubleKills: number 347 | dragonKills: number 348 | eligibleForProgression: boolean 349 | /* Yellow questionmark */ 350 | enemyMissingPings: number 351 | /* Red eyeball */ 352 | enemyVisionPings: number 353 | firstBloodAssist: boolean 354 | firstBloodKill: boolean 355 | firstTowerAssist: boolean 356 | firstTowerKill: boolean 357 | /* This is an offshoot of the OneStone challenge. The code checks if a spell with the same instance ID does the final point of damage to at least 2 Champions. It doesn't matter if they're enemies, but you cannot hurt your friends. */ 358 | gameEndedInEarlySurrender: boolean 359 | gameEndedInSurrender: boolean 360 | holdPings: number 361 | /* Yellow circle with horizontal line */ 362 | getBackPings: number 363 | goldEarned: number 364 | goldSpent: number 365 | /* Both individualPosition and teamPosition are computed by the game server and are different versions of the most likely position played by a player. The individualPosition is the best guess for which position the player actually played in isolation of anything else. The teamPosition is the best guess for which position the player actually played if we add the constraint that each team must have one top player, one jungle, one middle, etc. Generally the recommendation is to use the teamPosition field over the individualPosition field. */ 366 | individualPosition: string 367 | inhibitorKills: number 368 | inhibitorTakedowns: number 369 | inhibitorsLost: number 370 | item0: number 371 | item1: number 372 | item2: number 373 | item3: number 374 | item4: number 375 | item5: number 376 | item6: number 377 | itemsPurchased: number 378 | killingSprees: number 379 | kills: number 380 | lane: string 381 | largestCriticalStrike: number 382 | largestKillingSpree: number 383 | largestMultiKill: number 384 | longestTimeSpentLiving: number 385 | magicDamageDealt: number 386 | magicDamageDealtToChampions: number 387 | magicDamageTaken: number 388 | missions: MissionsDto 389 | /* neutralMinionsKilled = mNeutralMinionsKilled, which is incremented on kills of kPet and kJungleMonster */ 390 | neutralMinionsKilled: number 391 | /* Green ward */ 392 | needVisionPings: number 393 | nexusKills: number 394 | nexusTakedowns: number 395 | nexusLost: number 396 | objectivesStolen: number 397 | objectivesStolenAssists: number 398 | /* Blue arrow pointing at ground */ 399 | onMyWayPings: number 400 | participantId: number 401 | playerScore0: number 402 | playerScore1: number 403 | playerScore2: number 404 | playerScore3: number 405 | playerScore4: number 406 | playerScore5: number 407 | playerScore6: number 408 | playerScore7: number 409 | playerScore8: number 410 | playerScore9: number 411 | playerScore10: number 412 | playerScore11: number 413 | pentaKills: number 414 | perks: PerksDto 415 | physicalDamageDealt: number 416 | physicalDamageDealtToChampions: number 417 | physicalDamageTaken: number 418 | placement: number 419 | playerAugment1: number 420 | playerAugment2: number 421 | playerAugment3: number 422 | playerAugment4: number 423 | playerSubteamId: number 424 | /* Green minion */ 425 | pushPings: number 426 | profileIcon: number 427 | puuid: string 428 | quadraKills: number 429 | riotIdGameName: string 430 | riotIdTagline: string 431 | role: string 432 | sightWardsBoughtInGame: number 433 | spell1Casts: number 434 | spell2Casts: number 435 | spell3Casts: number 436 | spell4Casts: number 437 | subteamPlacement: number 438 | summoner1Casts: number 439 | summoner1Id: number 440 | summoner2Casts: number 441 | summoner2Id: number 442 | summonerId: string 443 | summonerLevel: number 444 | summonerName: string 445 | teamEarlySurrendered: boolean 446 | teamId: number 447 | /* Both individualPosition and teamPosition are computed by the game server and are different versions of the most likely position played by a player. The individualPosition is the best guess for which position the player actually played in isolation of anything else. The teamPosition is the best guess for which position the player actually played if we add the constraint that each team must have one top player, one jungle, one middle, etc. Generally the recommendation is to use the teamPosition field over the individualPosition field. */ 448 | teamPosition: string 449 | timeCCingOthers: number 450 | timePlayed: number 451 | totalAllyJungleMinionsKilled: number 452 | totalDamageDealt: number 453 | totalDamageDealtToChampions: number 454 | totalDamageShieldedOnTeammates: number 455 | totalDamageTaken: number 456 | totalEnemyJungleMinionsKilled: number 457 | /* Whenever positive health is applied (which translates to all heals in the game but not things like regeneration), totalHeal is incremented by the amount of health received. This includes healing enemies, jungle monsters, yourself, etc */ 458 | totalHeal: number 459 | /* Whenever positive health is applied (which translates to all heals in the game but not things like regeneration), totalHealsOnTeammates is incremented by the amount of health received. This is post modified, so if you heal someone missing 5 health for 100 you will get +5 totalHealsOnTeammates */ 460 | totalHealsOnTeammates: number 461 | /* totalMillionsKilled = mMinionsKilled, which is only incremented on kills of kTeamMinion, kMeleeLaneMinion, kSuperLaneMinion, kRangedLaneMinion and kSiegeLaneMinion */ 462 | totalMinionsKilled: number 463 | totalTimeCCDealt: number 464 | totalTimeSpentDead: number 465 | totalUnitsHealed: number 466 | tripleKills: number 467 | trueDamageDealt: number 468 | trueDamageDealtToChampions: number 469 | trueDamageTaken: number 470 | turretKills: number 471 | turretTakedowns: number 472 | turretsLost: number 473 | unrealKills: number 474 | visionScore: number 475 | visionClearedPings: number 476 | visionWardsBoughtInGame: number 477 | wardsKilled: number 478 | wardsPlaced: number 479 | win: boolean 480 | } 481 | 482 | /* Challenges DTO */ 483 | export type ChallengesDto = { 484 | '12AssistStreakCount': number 485 | baronBuffGoldAdvantageOverThreshold: number 486 | controlWardTimeCoverageInRiverOrEnemyHalf: number 487 | earliestBaron: number 488 | earliestDragonTakedown: number 489 | earliestElderDragon: number 490 | earlyLaningPhaseGoldExpAdvantage: number 491 | fasterSupportQuestCompletion: number 492 | fastestLegendary: number 493 | hadAfkTeammate: number 494 | highestChampionDamage: number 495 | highestCrowdControlScore: number 496 | highestWardKills: number 497 | junglerKillsEarlyJungle: number 498 | killsOnLanersEarlyJungleAsJungler: number 499 | laningPhaseGoldExpAdvantage: number 500 | legendaryCount: number 501 | maxCsAdvantageOnLaneOpponent: number 502 | maxLevelLeadLaneOpponent: number 503 | mostWardsDestroyedOneSweeper: number 504 | mythicItemUsed: number 505 | playedChampSelectPosition: number 506 | soloTurretsLategame: number 507 | takedownsFirst25Minutes: number 508 | teleportTakedowns: number 509 | thirdInhibitorDestroyedTime: number 510 | threeWardsOneSweeperCount: number 511 | visionScoreAdvantageLaneOpponent: number 512 | InfernalScalePickup: number 513 | fistBumpParticipation: number 514 | voidMonsterKill: number 515 | abilityUses: number 516 | acesBefore15Minutes: number 517 | alliedJungleMonsterKills: number 518 | baronTakedowns: number 519 | blastConeOppositeOpponentCount: number 520 | bountyGold: number 521 | buffsStolen: number 522 | completeSupportQuestInTime: number 523 | controlWardsPlaced: number 524 | damagePerMinute: number 525 | damageTakenOnTeamPercentage: number 526 | dancedWithRiftHerald: number 527 | deathsByEnemyChamps: number 528 | dodgeSkillShotsSmallWindow: number 529 | doubleAces: number 530 | dragonTakedowns: number 531 | legendaryItemUsed: number[] 532 | effectiveHealAndShielding: number 533 | elderDragonKillsWithOpposingSoul: number 534 | elderDragonMultikills: number 535 | enemyChampionImmobilizations: number 536 | enemyJungleMonsterKills: number 537 | epicMonsterKillsNearEnemyJungler: number 538 | epicMonsterKillsWithin30SecondsOfSpawn: number 539 | epicMonsterSteals: number 540 | epicMonsterStolenWithoutSmite: number 541 | firstTurretKilled: number 542 | firstTurretKilledTime: number 543 | flawlessAces: number 544 | fullTeamTakedown: number 545 | gameLength: number 546 | getTakedownsInAllLanesEarlyJungleAsLaner: number 547 | goldPerMinute: number 548 | hadOpenNexus: number 549 | immobilizeAndKillWithAlly: number 550 | initialBuffCount: number 551 | initialCrabCount: number 552 | jungleCsBefore10Minutes: number 553 | junglerTakedownsNearDamagedEpicMonster: number 554 | kda: number 555 | killAfterHiddenWithAlly: number 556 | killedChampTookFullTeamDamageSurvived: number 557 | killingSprees: number 558 | killParticipation: number 559 | killsNearEnemyTurret: number 560 | killsOnOtherLanesEarlyJungleAsLaner: number 561 | killsOnRecentlyHealedByAramPack: number 562 | killsUnderOwnTurret: number 563 | killsWithHelpFromEpicMonster: number 564 | knockEnemyIntoTeamAndKill: number 565 | kTurretsDestroyedBeforePlatesFall: number 566 | landSkillShotsEarlyGame: number 567 | laneMinionsFirst10Minutes: number 568 | lostAnInhibitor: number 569 | maxKillDeficit: number 570 | mejaisFullStackInTime: number 571 | moreEnemyJungleThanOpponent: number 572 | /* This is an offshoot of the OneStone challenge. The code checks if a spell with the same instance ID does the final point of damage to at least 2 Champions. It doesn't matter if they're enemies, but you cannot hurt your friends. */ 573 | multiKillOneSpell: number 574 | multikills: number 575 | multikillsAfterAggressiveFlash: number 576 | multiTurretRiftHeraldCount: number 577 | outerTurretExecutesBefore10Minutes: number 578 | outnumberedKills: number 579 | outnumberedNexusKill: number 580 | perfectDragonSoulsTaken: number 581 | perfectGame: number 582 | pickKillWithAlly: number 583 | poroExplosions: number 584 | quickCleanse: number 585 | quickFirstTurret: number 586 | quickSoloKills: number 587 | riftHeraldTakedowns: number 588 | saveAllyFromDeath: number 589 | scuttleCrabKills: number 590 | shortestTimeToAceFromFirstTakedown: number 591 | skillshotsDodged: number 592 | skillshotsHit: number 593 | snowballsHit: number 594 | soloBaronKills: number 595 | SWARM_DefeatAatrox: number 596 | SWARM_DefeatBriar: number 597 | SWARM_DefeatMiniBosses: number 598 | SWARM_EvolveWeapon: number 599 | SWARM_Have3Passives: number 600 | SWARM_KillEnemy: number 601 | SWARM_PickupGold: number 602 | SWARM_ReachLevel50: number 603 | SWARM_Survive15Min: number 604 | SWARM_WinWith5EvolvedWeapons: number 605 | soloKills: number 606 | stealthWardsPlaced: number 607 | survivedSingleDigitHpCount: number 608 | survivedThreeImmobilizesInFight: number 609 | takedownOnFirstTurret: number 610 | takedowns: number 611 | takedownsAfterGainingLevelAdvantage: number 612 | takedownsBeforeJungleMinionSpawn: number 613 | takedownsFirstXMinutes: number 614 | takedownsInAlcove: number 615 | takedownsInEnemyFountain: number 616 | teamBaronKills: number 617 | teamDamagePercentage: number 618 | teamElderDragonKills: number 619 | teamRiftHeraldKills: number 620 | tookLargeDamageSurvived: number 621 | turretPlatesTaken: number 622 | /* Any player who damages a tower that is destroyed within 30 seconds of a Rift Herald charge will receive credit. A player who does not damage the tower will not receive credit. */ 623 | turretsTakenWithRiftHerald: number 624 | turretTakedowns: number 625 | twentyMinionsIn3SecondsCount: number 626 | twoWardsOneSweeperCount: number 627 | unseenRecalls: number 628 | visionScorePerMinute: number 629 | wardsGuarded: number 630 | wardTakedowns: number 631 | wardTakedownsBefore20M: number 632 | } 633 | 634 | /* Missions DTO */ 635 | export type MissionsDto = { 636 | playerScore0: number 637 | playerScore1: number 638 | playerScore2: number 639 | playerScore3: number 640 | playerScore4: number 641 | playerScore5: number 642 | playerScore6: number 643 | playerScore7: number 644 | playerScore8: number 645 | playerScore9: number 646 | playerScore10: number 647 | playerScore11: number 648 | } 649 | 650 | export type PerksDto = { 651 | statPerks: PerkStatsDto 652 | styles: PerkStyleDto[] 653 | } 654 | 655 | export type PerkStatsDto = { 656 | defense: number 657 | flex: number 658 | offense: number 659 | } 660 | 661 | export type PerkStyleDto = { 662 | description: string 663 | selections: PerkStyleSelectionDto[] 664 | style: number 665 | } 666 | 667 | export type PerkStyleSelectionDto = { 668 | perk: number 669 | var1: number 670 | var2: number 671 | var3: number 672 | } 673 | 674 | export type TeamDto = { 675 | bans: BanDto[] 676 | objectives: ObjectivesDto 677 | teamId: number 678 | win: boolean 679 | } 680 | 681 | export type BanDto = { 682 | championId: number 683 | pickTurn: number 684 | } 685 | 686 | export type ObjectivesDto = { 687 | baron: ObjectiveDto 688 | champion: ObjectiveDto 689 | dragon: ObjectiveDto 690 | horde: ObjectiveDto 691 | inhibitor: ObjectiveDto 692 | riftHerald: ObjectiveDto 693 | tower: ObjectiveDto 694 | } 695 | 696 | export type ObjectiveDto = { 697 | first: boolean 698 | kills: number 699 | } 700 | 701 | export type TimelineDto = { 702 | /* Match metadata. */ 703 | metadata: MetadataTimeLineDto 704 | /* Match info. */ 705 | info: InfoTimeLineDto 706 | } 707 | 708 | export type MetadataTimeLineDto = { 709 | /* Match data version. */ 710 | dataVersion: string 711 | /* Match id. */ 712 | matchId: string 713 | /* A list of participant PUUIDs. */ 714 | participants: string[] 715 | } 716 | 717 | export type InfoTimeLineDto = { 718 | /* Refer to indicate if the game ended in termination. */ 719 | endOfGameResult: string 720 | frameInterval: number 721 | gameId: number 722 | participants: ParticipantTimeLineDto[] 723 | frames: FramesTimeLineDto[] 724 | } 725 | 726 | export type ParticipantTimeLineDto = { 727 | participantId: number 728 | puuid: string 729 | } 730 | 731 | export type FramesTimeLineDto = { 732 | events: EventsTimeLineDto[] 733 | participantFrames: ParticipantFramesDto 734 | timestamp: number 735 | } 736 | 737 | export type EventsTimeLineDto = { 738 | timestamp: number 739 | realTimestamp: number 740 | type: string 741 | } 742 | 743 | export type ParticipantFramesDto = { 744 | /* Key value mapping for each participant */ 745 | '1-9': ParticipantFrameDto 746 | } 747 | 748 | export type ParticipantFrameDto = { 749 | championStats: ChampionStatsDto 750 | currentGold: number 751 | damageStats: DamageStatsDto 752 | goldPerSecond: number 753 | jungleMinionsKilled: number 754 | level: number 755 | minionsKilled: number 756 | participantId: number 757 | position: PositionDto 758 | timeEnemySpentControlled: number 759 | totalGold: number 760 | xp: number 761 | } 762 | 763 | export type ChampionStatsDto = { 764 | abilityHaste: number 765 | abilityPower: number 766 | armor: number 767 | armorPen: number 768 | armorPenPercent: number 769 | attackDamage: number 770 | attackSpeed: number 771 | bonusArmorPenPercent: number 772 | bonusMagicPenPercent: number 773 | ccReduction: number 774 | cooldownReduction: number 775 | health: number 776 | healthMax: number 777 | healthRegen: number 778 | lifesteal: number 779 | magicPen: number 780 | magicPenPercent: number 781 | magicResist: number 782 | movementSpeed: number 783 | omnivamp: number 784 | physicalVamp: number 785 | power: number 786 | powerMax: number 787 | powerRegen: number 788 | spellVamp: number 789 | } 790 | 791 | export type DamageStatsDto = { 792 | magicDamageDone: number 793 | magicDamageDoneToChampions: number 794 | magicDamageTaken: number 795 | physicalDamageDone: number 796 | physicalDamageDoneToChampions: number 797 | physicalDamageTaken: number 798 | totalDamageDone: number 799 | totalDamageDoneToChampions: number 800 | totalDamageTaken: number 801 | trueDamageDone: number 802 | trueDamageDoneToChampions: number 803 | trueDamageTaken: number 804 | } 805 | 806 | export type PositionDto = { 807 | x: number 808 | y: number 809 | } 810 | // #endregion 811 | 812 | // #region SPECTATOR-V5 813 | export type CurrentGameInfo = { 814 | /* The ID of the game */ 815 | gameId: number 816 | /* The game type */ 817 | gameType: string 818 | /* The game start time represented in epoch milliseconds */ 819 | gameStartTime: number 820 | /* The ID of the map */ 821 | mapId: number 822 | /* The amount of time in seconds that has passed since the game started */ 823 | gameLength: number 824 | /* The ID of the platform on which the game is being played */ 825 | platformId: string 826 | /* The game mode */ 827 | gameMode: string 828 | /* Banned champion information */ 829 | bannedChampions: BannedChampion[] 830 | /* The queue type (queue types are documented on the Game Constants page) */ 831 | gameQueueConfigId: number 832 | /* The observer information */ 833 | observers: Observer 834 | /* The participant information */ 835 | participants: CurrentGameParticipant[] 836 | } 837 | 838 | export type BannedChampion = { 839 | /* The turn during which the champion was banned */ 840 | pickTurn: number 841 | /* The ID of the banned champion */ 842 | championId: number 843 | /* The ID of the team that banned the champion */ 844 | teamId: number 845 | } 846 | 847 | export type Observer = { 848 | /* Key used to decrypt the spectator grid game data for playback */ 849 | encryptionKey: string 850 | } 851 | 852 | export type CurrentGameParticipant = { 853 | /* The ID of the champion played by this participant */ 854 | championId: number 855 | /* Perks/Runes Reforged Information */ 856 | perks: Perks 857 | /* The ID of the profile icon used by this participant */ 858 | profileIconId: number 859 | /* Flag indicating whether or not this participant is a bot */ 860 | bot: boolean 861 | /* The team ID of this participant, indicating the participant's team */ 862 | teamId: number 863 | /* The encrypted puuid of this participant */ 864 | puuid: string 865 | /* The ID of the first summoner spell used by this participant */ 866 | spell1Id: number 867 | /* The ID of the second summoner spell used by this participant */ 868 | spell2Id: number 869 | /* List of Game Customizations */ 870 | gameCustomizationObjects: GameCustomizationObject[] 871 | } 872 | 873 | export type Perks = { 874 | /* IDs of the perks/runes assigned. */ 875 | perkIds: number[] 876 | /* Primary runes path */ 877 | perkStyle: number 878 | /* Secondary runes path */ 879 | perkSubStyle: number 880 | } 881 | 882 | export type GameCustomizationObject = { 883 | /* Category identifier for Game Customization */ 884 | category: string 885 | /* Game Customization content */ 886 | content: string 887 | } 888 | 889 | export type FeaturedGames = { 890 | /* The list of featured games */ 891 | gameList: FeaturedGameInfo[] 892 | /* The suggested interval to wait before requesting FeaturedGames again */ 893 | clientRefreshInterval: number 894 | } 895 | 896 | export type FeaturedGameInfo = { 897 | /* The game mode 898 | (Legal values: CLASSIC, ODIN, ARAM, TUTORIAL, ONEFORALL, ASCENSION, FIRSTBLOOD, KINGPORO) */ 899 | gameMode: string 900 | /* The amount of time in seconds that has passed since the game started */ 901 | gameLength: number 902 | /* The ID of the map */ 903 | mapId: number 904 | /* The game type 905 | (Legal values: CUSTOM_GAME, MATCHED_GAME, TUTORIAL_GAME) */ 906 | gameType: string 907 | /* Banned champion information */ 908 | bannedChampions: BannedChampion[] 909 | /* The ID of the game */ 910 | gameId: number 911 | /* The observer information */ 912 | observers: Observer 913 | /* The queue type (queue types are documented on the Game Constants page) */ 914 | gameQueueConfigId: number 915 | /* The participant information */ 916 | participants: Participant[] 917 | /* The ID of the platform on which the game is being played */ 918 | platformId: string 919 | } 920 | 921 | export type Participant = { 922 | /* Flag indicating whether or not this participant is a bot */ 923 | bot: boolean 924 | /* The ID of the second summoner spell used by this participant */ 925 | spell2Id: number 926 | /* The ID of the profile icon used by this participant */ 927 | profileIconId: number 928 | /* Encrypted puuid of this participant */ 929 | puuid: string 930 | /* The ID of the champion played by this participant */ 931 | championId: number 932 | /* The team ID of this participant, indicating the participant's team */ 933 | teamId: number 934 | /* The ID of the first summoner spell used by this participant */ 935 | spell1Id: number 936 | } 937 | // #endregion 938 | 939 | // #region SUMMONER-V4 940 | /* represents a summoner */ 941 | export type SummonerDTO = { 942 | /* ID of the summoner icon associated with the summoner. */ 943 | profileIconId: number 944 | /* Date summoner was last modified specified as epoch milliseconds. The following events will update this timestamp: profile icon change, playing the tutorial or advanced tutorial, finishing a game, summoner name change. */ 945 | revisionDate: number 946 | /* Encrypted PUUID. Exact length of 78 characters. */ 947 | puuid: string 948 | /* Summoner level associated with the summoner. */ 949 | summonerLevel: number 950 | } 951 | // #endregion 952 | --------------------------------------------------------------------------------