UNPKG

@timmsy/riftjs

Version:

TypeScript-first Riot Games API wrapper for Node.js, with built-in Data Dragon support

170 lines (169 loc) 7.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = riotEndpoints; const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const SOLO_QUEUE = 'RANKED_SOLO_5x5'; const FLEX_QUEUE = 'RANKED_FLEX_SR'; const withRankMetrics = (entry) => { if (!entry) return null; const wins = Number(entry.wins) || 0; const losses = Number(entry.losses) || 0; const gamesPlayed = wins + losses; const winRate = gamesPlayed > 0 ? Number(((wins / gamesPlayed) * 100).toFixed(2)) : 0; return { ...entry, winRate }; }; const coerceRegion = (region, regionMap) => { // Maintainer note (Timmsy): region overrides are accepted per-call, but must always match our known map. const upper = region.toUpperCase(); if (!regionMap[upper]) throw new Error(`Invalid region: ${region}`); return upper; }; function riotEndpoints({ client, defaultRegion, regionMap, handleError, }) { const getAccountByRiotId = async (riotId, tagLine = null, region = defaultRegion) => { const resolvedRegion = coerceRegion(region, regionMap); let gameName; let tag; if (riotId.includes('#')) { [gameName, tag] = riotId.split('#'); } else { gameName = riotId; tag = tagLine || ''; } if (!tag) throw new Error('TagLine is required for getAccountByRiotId'); const shard = regionMap[resolvedRegion].shard; try { const response = await client.get(`/riot/account/v1/accounts/by-riot-id/${encodeURIComponent(gameName)}/${encodeURIComponent(tag)}`, { baseURL: `https://${shard}` }); return response.data; } catch (error) { throw handleError(error); } }; const getSummonerByPuuid = async (puuid, region = defaultRegion) => { const resolvedRegion = coerceRegion(region, regionMap); const platform = regionMap[resolvedRegion].platform; try { const response = await client.get(`/lol/summoner/v4/summoners/by-puuid/${encodeURIComponent(puuid)}`, { baseURL: `https://${platform}` }); return response.data; } catch (error) { throw handleError(error); } }; const getRankEntriesByPuuid = async (puuid, region = defaultRegion) => { const resolvedRegion = coerceRegion(region, regionMap); const platform = regionMap[resolvedRegion].platform; try { const response = await client.get(`/lol/league/v4/entries/by-puuid/${encodeURIComponent(puuid)}`, { baseURL: `https://${platform}`, }); return response.data; } catch (error) { throw handleError(error); } }; const getRankByPuuid = async (puuid, region = defaultRegion) => { const entries = await getRankEntriesByPuuid(puuid, region); const solo = withRankMetrics(entries.find((entry) => entry.queueType === SOLO_QUEUE) || null); const flex = withRankMetrics(entries.find((entry) => entry.queueType === FLEX_QUEUE) || null); return { solo, flex, entries }; }; const getMatchlistByPuuid = async (puuid, options = {}, region = defaultRegion) => { const resolvedRegion = coerceRegion(region, regionMap); const shard = regionMap[resolvedRegion].shard; try { const response = await client.get(`/lol/match/v5/matches/by-puuid/${encodeURIComponent(puuid)}/ids`, { baseURL: `https://${shard}`, params: options, }); return response.data; } catch (error) { throw handleError(error); } }; const getMatchById = async (matchId, region = defaultRegion) => { const resolvedRegion = coerceRegion(region, regionMap); const shard = regionMap[resolvedRegion].shard; try { const response = await client.get(`/lol/match/v5/matches/${matchId}`, { baseURL: `https://${shard}`, }); return response.data; } catch (error) { throw handleError(error); } }; const getMatchTimelineById = async (matchId, region = defaultRegion) => { const resolvedRegion = coerceRegion(region, regionMap); const shard = regionMap[resolvedRegion].shard; try { const response = await client.get(`/lol/match/v5/matches/${matchId}/timeline`, { baseURL: `https://${shard}`, }); return response.data; } catch (error) { throw handleError(error); } }; const getMatchlistByPuuidAll = async (puuid, options = {}, region = defaultRegion, pacing = {}) => { // Maintainer note (Timmsy): defaults are intentionally conservative to reduce accidental rate-limit spikes. const baseStart = Number.isInteger(options.start) ? options.start : 0; const pageDelayMs = Number.isInteger(pacing.delayMs) ? pacing.delayMs : 1250; const maxMatches = Number.isInteger(pacing.maxMatches) && Number(pacing.maxMatches) >= 0 ? Number(pacing.maxMatches) : null; const filters = { startTime: options.startTime, endTime: options.endTime, queue: options.queue, type: options.type, }; let start = baseStart; const allMatchIds = []; while (true) { const remaining = maxMatches === null ? 100 : Math.min(100, maxMatches - allMatchIds.length); if (remaining <= 0) break; const page = await getMatchlistByPuuid(puuid, { ...filters, start, count: remaining }, region); allMatchIds.push(...page); if (page.length < remaining) break; start += page.length; if (pageDelayMs > 0) await sleep(pageDelayMs); } return allMatchIds; }; const getMatchesWithDetailsByPuuid = async (puuid, options = {}, region = defaultRegion, pacing = {}) => { // Maintainer note (Timmsy): details are fetched sequentially on purpose for predictable pacing. const pageDelayMs = Number.isInteger(pacing.pageDelayMs) ? pacing.pageDelayMs : 1250; const detailDelayMs = Number.isInteger(pacing.detailDelayMs) ? pacing.detailDelayMs : 1250; const maxMatches = Number.isInteger(pacing.maxMatches) && Number(pacing.maxMatches) >= 0 ? Number(pacing.maxMatches) : null; const matchIds = await getMatchlistByPuuidAll(puuid, options, region, { delayMs: pageDelayMs, maxMatches }); const matches = []; for (let i = 0; i < matchIds.length; i += 1) { matches.push(await getMatchById(matchIds[i], region)); if (detailDelayMs > 0 && i < matchIds.length - 1) { await sleep(detailDelayMs); } } return { matchIds, matches }; }; return { getAccountByRiotId, getSummonerByPuuid, getRankEntriesByPuuid, getRankByPuuid, getMatchlistByPuuid, getMatchById, getMatchTimelineById, getMatchlistByPuuidAll, getMatchesWithDetailsByPuuid, }; }