@timmsy/riftjs 2.1.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -3
- package/dist/endpoints/datadragon.d.ts +4 -0
- package/dist/endpoints/datadragon.js +39 -0
- package/dist/endpoints/riot.d.ts +2 -0
- package/dist/endpoints/riot.js +166 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +103 -0
- package/dist/test-endpoints.d.ts +1 -0
- package/{test-endpoints.js → dist/test-endpoints.js} +28 -39
- package/dist/types.d.ts +60 -0
- package/dist/types.js +2 -0
- package/package.json +23 -3
- package/.github/workflows/publish.yml +0 -52
- package/endpoints/datadragon.js +0 -40
- package/endpoints/riot.js +0 -217
- package/index.js +0 -88
package/README.md
CHANGED
|
@@ -7,14 +7,15 @@ A lightweight Node.js wrapper for the Riot Games API, providing easy access to L
|
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
-
RiftJS simplifies interaction with the Riot Games API and DataDragon static data. It supports fetching account details, summoner info, match history, and game data using a modular endpoint structure. Built with `axios
|
|
10
|
+
RiftJS simplifies interaction with the Riot Games API and DataDragon static data. It supports fetching account details, summoner info, match history, and game data using a modular endpoint structure. Built with `axios`, `dotenv`, and TypeScript, it’s designed for developers building League of Legends tools or applications.
|
|
11
11
|
|
|
12
12
|
## Features
|
|
13
13
|
|
|
14
14
|
- **RiotAPI**: Fetch account data by Riot ID, summoner data by PUUID, match history, and match details.
|
|
15
15
|
- **DataDragon**: Access static game data like champions and items.
|
|
16
16
|
- **Region Support**: Handles platform (e.g., `EUW1`) and shard (e.g., `europe`) routing.
|
|
17
|
-
- **Modular Design**: Endpoints are organized in
|
|
17
|
+
- **Modular Design**: Endpoints are organized in `src/endpoints/` for easy extension.
|
|
18
|
+
- **TypeScript Types**: The package ships with declaration files for typed usage in TS projects.
|
|
18
19
|
|
|
19
20
|
## Installation
|
|
20
21
|
|
|
@@ -178,7 +179,13 @@ To contribute or run locally:
|
|
|
178
179
|
```
|
|
179
180
|
|
|
180
181
|
3. Create a `.env` file (see [Setup](#setup)).
|
|
181
|
-
4.
|
|
182
|
+
4. Build the package:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
npm run build
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
5. Run endpoint checks:
|
|
182
189
|
|
|
183
190
|
```bash
|
|
184
191
|
npm test
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = dataDragonEndpoints;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
function dataDragonEndpoints(baseURLOrResolver) {
|
|
9
|
+
const resolveBaseURL = async () => {
|
|
10
|
+
if (typeof baseURLOrResolver === 'function') {
|
|
11
|
+
return baseURLOrResolver();
|
|
12
|
+
}
|
|
13
|
+
return baseURLOrResolver;
|
|
14
|
+
};
|
|
15
|
+
return {
|
|
16
|
+
async getChampions() {
|
|
17
|
+
try {
|
|
18
|
+
const baseURL = await resolveBaseURL();
|
|
19
|
+
const response = await axios_1.default.get(`${baseURL}/champion.json`);
|
|
20
|
+
return response.data;
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
const message = error instanceof Error ? error.message : 'Unknown DataDragon error';
|
|
24
|
+
throw new Error(`DataDragon error: ${message}`);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
async getItems() {
|
|
28
|
+
try {
|
|
29
|
+
const baseURL = await resolveBaseURL();
|
|
30
|
+
const response = await axios_1.default.get(`${baseURL}/item.json`);
|
|
31
|
+
return response.data;
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
const message = error instanceof Error ? error.message : 'Unknown DataDragon error';
|
|
35
|
+
throw new Error(`DataDragon error: ${message}`);
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = riotEndpoints;
|
|
4
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
5
|
+
const SOLO_QUEUE = 'RANKED_SOLO_5x5';
|
|
6
|
+
const FLEX_QUEUE = 'RANKED_FLEX_SR';
|
|
7
|
+
const withRankMetrics = (entry) => {
|
|
8
|
+
if (!entry)
|
|
9
|
+
return null;
|
|
10
|
+
const wins = Number(entry.wins) || 0;
|
|
11
|
+
const losses = Number(entry.losses) || 0;
|
|
12
|
+
const gamesPlayed = wins + losses;
|
|
13
|
+
const winRate = gamesPlayed > 0 ? Number(((wins / gamesPlayed) * 100).toFixed(2)) : 0;
|
|
14
|
+
return { ...entry, winRate };
|
|
15
|
+
};
|
|
16
|
+
const coerceRegion = (region, regionMap) => {
|
|
17
|
+
const upper = region.toUpperCase();
|
|
18
|
+
if (!regionMap[upper])
|
|
19
|
+
throw new Error(`Invalid region: ${region}`);
|
|
20
|
+
return upper;
|
|
21
|
+
};
|
|
22
|
+
function riotEndpoints({ client, defaultRegion, regionMap, handleError, }) {
|
|
23
|
+
const getAccountByRiotId = async (riotId, tagLine = null, region = defaultRegion) => {
|
|
24
|
+
const resolvedRegion = coerceRegion(region, regionMap);
|
|
25
|
+
let gameName;
|
|
26
|
+
let tag;
|
|
27
|
+
if (riotId.includes('#')) {
|
|
28
|
+
[gameName, tag] = riotId.split('#');
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
gameName = riotId;
|
|
32
|
+
tag = tagLine || '';
|
|
33
|
+
}
|
|
34
|
+
if (!tag)
|
|
35
|
+
throw new Error('TagLine is required for getAccountByRiotId');
|
|
36
|
+
const shard = regionMap[resolvedRegion].shard;
|
|
37
|
+
try {
|
|
38
|
+
const response = await client.get(`/riot/account/v1/accounts/by-riot-id/${encodeURIComponent(gameName)}/${encodeURIComponent(tag)}`, { baseURL: `https://${shard}` });
|
|
39
|
+
return response.data;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
throw handleError(error);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const getSummonerByPuuid = async (puuid, region = defaultRegion) => {
|
|
46
|
+
const resolvedRegion = coerceRegion(region, regionMap);
|
|
47
|
+
const platform = regionMap[resolvedRegion].platform;
|
|
48
|
+
try {
|
|
49
|
+
const response = await client.get(`/lol/summoner/v4/summoners/by-puuid/${encodeURIComponent(puuid)}`, { baseURL: `https://${platform}` });
|
|
50
|
+
return response.data;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
throw handleError(error);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const getRankEntriesByPuuid = async (puuid, region = defaultRegion) => {
|
|
57
|
+
const resolvedRegion = coerceRegion(region, regionMap);
|
|
58
|
+
const platform = regionMap[resolvedRegion].platform;
|
|
59
|
+
try {
|
|
60
|
+
const response = await client.get(`/lol/league/v4/entries/by-puuid/${encodeURIComponent(puuid)}`, {
|
|
61
|
+
baseURL: `https://${platform}`,
|
|
62
|
+
});
|
|
63
|
+
return response.data;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
throw handleError(error);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const getRankByPuuid = async (puuid, region = defaultRegion) => {
|
|
70
|
+
const entries = await getRankEntriesByPuuid(puuid, region);
|
|
71
|
+
const solo = withRankMetrics(entries.find((entry) => entry.queueType === SOLO_QUEUE) || null);
|
|
72
|
+
const flex = withRankMetrics(entries.find((entry) => entry.queueType === FLEX_QUEUE) || null);
|
|
73
|
+
return { solo, flex, entries };
|
|
74
|
+
};
|
|
75
|
+
const getMatchlistByPuuid = async (puuid, options = {}, region = defaultRegion) => {
|
|
76
|
+
const resolvedRegion = coerceRegion(region, regionMap);
|
|
77
|
+
const shard = regionMap[resolvedRegion].shard;
|
|
78
|
+
try {
|
|
79
|
+
const response = await client.get(`/lol/match/v5/matches/by-puuid/${encodeURIComponent(puuid)}/ids`, {
|
|
80
|
+
baseURL: `https://${shard}`,
|
|
81
|
+
params: options,
|
|
82
|
+
});
|
|
83
|
+
return response.data;
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
throw handleError(error);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
const getMatchById = async (matchId, region = defaultRegion) => {
|
|
90
|
+
const resolvedRegion = coerceRegion(region, regionMap);
|
|
91
|
+
const shard = regionMap[resolvedRegion].shard;
|
|
92
|
+
try {
|
|
93
|
+
const response = await client.get(`/lol/match/v5/matches/${matchId}`, {
|
|
94
|
+
baseURL: `https://${shard}`,
|
|
95
|
+
});
|
|
96
|
+
return response.data;
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
throw handleError(error);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const getMatchTimelineById = async (matchId, region = defaultRegion) => {
|
|
103
|
+
const resolvedRegion = coerceRegion(region, regionMap);
|
|
104
|
+
const shard = regionMap[resolvedRegion].shard;
|
|
105
|
+
try {
|
|
106
|
+
const response = await client.get(`/lol/match/v5/matches/${matchId}/timeline`, {
|
|
107
|
+
baseURL: `https://${shard}`,
|
|
108
|
+
});
|
|
109
|
+
return response.data;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
throw handleError(error);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const getMatchlistByPuuidAll = async (puuid, options = {}, region = defaultRegion, pacing = {}) => {
|
|
116
|
+
const baseStart = Number.isInteger(options.start) ? options.start : 0;
|
|
117
|
+
const pageDelayMs = Number.isInteger(pacing.delayMs) ? pacing.delayMs : 1250;
|
|
118
|
+
const maxMatches = Number.isInteger(pacing.maxMatches) && Number(pacing.maxMatches) >= 0 ? Number(pacing.maxMatches) : null;
|
|
119
|
+
const filters = {
|
|
120
|
+
startTime: options.startTime,
|
|
121
|
+
endTime: options.endTime,
|
|
122
|
+
queue: options.queue,
|
|
123
|
+
type: options.type,
|
|
124
|
+
};
|
|
125
|
+
let start = baseStart;
|
|
126
|
+
const allMatchIds = [];
|
|
127
|
+
while (true) {
|
|
128
|
+
const remaining = maxMatches === null ? 100 : Math.min(100, maxMatches - allMatchIds.length);
|
|
129
|
+
if (remaining <= 0)
|
|
130
|
+
break;
|
|
131
|
+
const page = await getMatchlistByPuuid(puuid, { ...filters, start, count: remaining }, region);
|
|
132
|
+
allMatchIds.push(...page);
|
|
133
|
+
if (page.length < remaining)
|
|
134
|
+
break;
|
|
135
|
+
start += page.length;
|
|
136
|
+
if (pageDelayMs > 0)
|
|
137
|
+
await sleep(pageDelayMs);
|
|
138
|
+
}
|
|
139
|
+
return allMatchIds;
|
|
140
|
+
};
|
|
141
|
+
const getMatchesWithDetailsByPuuid = async (puuid, options = {}, region = defaultRegion, pacing = {}) => {
|
|
142
|
+
const pageDelayMs = Number.isInteger(pacing.pageDelayMs) ? pacing.pageDelayMs : 1250;
|
|
143
|
+
const detailDelayMs = Number.isInteger(pacing.detailDelayMs) ? pacing.detailDelayMs : 1250;
|
|
144
|
+
const maxMatches = Number.isInteger(pacing.maxMatches) && Number(pacing.maxMatches) >= 0 ? Number(pacing.maxMatches) : null;
|
|
145
|
+
const matchIds = await getMatchlistByPuuidAll(puuid, options, region, { delayMs: pageDelayMs, maxMatches });
|
|
146
|
+
const matches = [];
|
|
147
|
+
for (let i = 0; i < matchIds.length; i += 1) {
|
|
148
|
+
matches.push(await getMatchById(matchIds[i], region));
|
|
149
|
+
if (detailDelayMs > 0 && i < matchIds.length - 1) {
|
|
150
|
+
await sleep(detailDelayMs);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return { matchIds, matches };
|
|
154
|
+
};
|
|
155
|
+
return {
|
|
156
|
+
getAccountByRiotId,
|
|
157
|
+
getSummonerByPuuid,
|
|
158
|
+
getRankEntriesByPuuid,
|
|
159
|
+
getRankByPuuid,
|
|
160
|
+
getMatchlistByPuuid,
|
|
161
|
+
getMatchById,
|
|
162
|
+
getMatchTimelineById,
|
|
163
|
+
getMatchlistByPuuidAll,
|
|
164
|
+
getMatchesWithDetailsByPuuid,
|
|
165
|
+
};
|
|
166
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import type { DataDragonEndpointMethods, RegionCode, RiotEndpointMethods } from './types';
|
|
3
|
+
export declare class RiotAPI implements RiotEndpointMethods {
|
|
4
|
+
readonly apiKey: string;
|
|
5
|
+
readonly region: RegionCode;
|
|
6
|
+
private readonly client;
|
|
7
|
+
getAccountByRiotId: RiotEndpointMethods['getAccountByRiotId'];
|
|
8
|
+
getSummonerByPuuid: RiotEndpointMethods['getSummonerByPuuid'];
|
|
9
|
+
getRankEntriesByPuuid: RiotEndpointMethods['getRankEntriesByPuuid'];
|
|
10
|
+
getRankByPuuid: RiotEndpointMethods['getRankByPuuid'];
|
|
11
|
+
getMatchlistByPuuid: RiotEndpointMethods['getMatchlistByPuuid'];
|
|
12
|
+
getMatchById: RiotEndpointMethods['getMatchById'];
|
|
13
|
+
getMatchTimelineById: RiotEndpointMethods['getMatchTimelineById'];
|
|
14
|
+
getMatchlistByPuuidAll: RiotEndpointMethods['getMatchlistByPuuidAll'];
|
|
15
|
+
getMatchesWithDetailsByPuuid: RiotEndpointMethods['getMatchesWithDetailsByPuuid'];
|
|
16
|
+
constructor();
|
|
17
|
+
private _handleError;
|
|
18
|
+
}
|
|
19
|
+
export declare class DataDragon implements DataDragonEndpointMethods {
|
|
20
|
+
version: string | null;
|
|
21
|
+
locale: string;
|
|
22
|
+
private baseURL;
|
|
23
|
+
private baseURLPromise;
|
|
24
|
+
getChampions: DataDragonEndpointMethods['getChampions'];
|
|
25
|
+
getItems: DataDragonEndpointMethods['getItems'];
|
|
26
|
+
constructor(version?: string | null, locale?: string);
|
|
27
|
+
private resolveBaseURL;
|
|
28
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DataDragon = exports.RiotAPI = void 0;
|
|
7
|
+
require("dotenv/config");
|
|
8
|
+
const axios_1 = __importDefault(require("axios"));
|
|
9
|
+
const datadragon_1 = __importDefault(require("./endpoints/datadragon"));
|
|
10
|
+
const riot_1 = __importDefault(require("./endpoints/riot"));
|
|
11
|
+
const regionMap = {
|
|
12
|
+
BR1: { platform: 'br1.api.riotgames.com', shard: 'americas.api.riotgames.com' },
|
|
13
|
+
EUN1: { platform: 'eun1.api.riotgames.com', shard: 'europe.api.riotgames.com' },
|
|
14
|
+
EUW1: { platform: 'euw1.api.riotgames.com', shard: 'europe.api.riotgames.com' },
|
|
15
|
+
JP1: { platform: 'jp1.api.riotgames.com', shard: 'asia.api.riotgames.com' },
|
|
16
|
+
KR: { platform: 'kr.api.riotgames.com', shard: 'asia.api.riotgames.com' },
|
|
17
|
+
LA1: { platform: 'la1.api.riotgames.com', shard: 'americas.api.riotgames.com' },
|
|
18
|
+
LA2: { platform: 'la2.api.riotgames.com', shard: 'americas.api.riotgames.com' },
|
|
19
|
+
NA1: { platform: 'na1.api.riotgames.com', shard: 'americas.api.riotgames.com' },
|
|
20
|
+
OC1: { platform: 'oc1.api.riotgames.com', shard: 'sea.api.riotgames.com' },
|
|
21
|
+
TR1: { platform: 'tr1.api.riotgames.com', shard: 'europe.api.riotgames.com' },
|
|
22
|
+
RU: { platform: 'ru.api.riotgames.com', shard: 'europe.api.riotgames.com' },
|
|
23
|
+
PH2: { platform: 'ph2.api.riotgames.com', shard: 'sea.api.riotgames.com' },
|
|
24
|
+
SG2: { platform: 'sg2.api.riotgames.com', shard: 'sea.api.riotgames.com' },
|
|
25
|
+
TH2: { platform: 'th2.api.riotgames.com', shard: 'sea.api.riotgames.com' },
|
|
26
|
+
TW2: { platform: 'tw2.api.riotgames.com', shard: 'sea.api.riotgames.com' },
|
|
27
|
+
VN2: { platform: 'vn2.api.riotgames.com', shard: 'sea.api.riotgames.com' },
|
|
28
|
+
};
|
|
29
|
+
const parseRegion = (region) => {
|
|
30
|
+
const upper = region.toUpperCase();
|
|
31
|
+
if (!regionMap[upper])
|
|
32
|
+
throw new Error(`Invalid region: ${region}`);
|
|
33
|
+
return upper;
|
|
34
|
+
};
|
|
35
|
+
class RiotAPI {
|
|
36
|
+
constructor() {
|
|
37
|
+
this._handleError = (error) => {
|
|
38
|
+
if (axios_1.default.isAxiosError(error)) {
|
|
39
|
+
if (error.response) {
|
|
40
|
+
const status = error.response.status;
|
|
41
|
+
const data = error.response.data;
|
|
42
|
+
return new Error(`API error ${status}: ${data?.status?.message || 'Unknown error'}`);
|
|
43
|
+
}
|
|
44
|
+
if (error.request) {
|
|
45
|
+
return new Error('No response received from the server');
|
|
46
|
+
}
|
|
47
|
+
return new Error(`Request error: ${error.message}`);
|
|
48
|
+
}
|
|
49
|
+
if (error instanceof Error) {
|
|
50
|
+
return new Error(`Request error: ${error.message}`);
|
|
51
|
+
}
|
|
52
|
+
return new Error('Request error: Unknown error');
|
|
53
|
+
};
|
|
54
|
+
this.apiKey = process.env.RIOT_API_KEY || '';
|
|
55
|
+
if (!this.apiKey)
|
|
56
|
+
throw new Error('RIOT_API_KEY is required in .env');
|
|
57
|
+
this.region = parseRegion(process.env.REGION || 'EUW1');
|
|
58
|
+
this.client = axios_1.default.create({
|
|
59
|
+
headers: { 'X-Riot-Token': this.apiKey },
|
|
60
|
+
});
|
|
61
|
+
Object.assign(this, (0, riot_1.default)({
|
|
62
|
+
client: this.client,
|
|
63
|
+
defaultRegion: this.region,
|
|
64
|
+
regionMap,
|
|
65
|
+
handleError: this._handleError,
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
exports.RiotAPI = RiotAPI;
|
|
70
|
+
class DataDragon {
|
|
71
|
+
constructor(version = null, locale = 'en_US') {
|
|
72
|
+
this.version = version;
|
|
73
|
+
this.locale = locale;
|
|
74
|
+
this.baseURL = null;
|
|
75
|
+
this.baseURLPromise = null;
|
|
76
|
+
Object.assign(this, (0, datadragon_1.default)(() => this.resolveBaseURL()));
|
|
77
|
+
}
|
|
78
|
+
async resolveBaseURL() {
|
|
79
|
+
if (this.baseURL)
|
|
80
|
+
return this.baseURL;
|
|
81
|
+
if (this.baseURLPromise)
|
|
82
|
+
return this.baseURLPromise;
|
|
83
|
+
this.baseURLPromise = (async () => {
|
|
84
|
+
if (!this.version) {
|
|
85
|
+
const response = await axios_1.default.get('https://ddragon.leagueoflegends.com/api/versions.json');
|
|
86
|
+
const latestVersion = Array.isArray(response.data) ? response.data[0] : null;
|
|
87
|
+
if (!latestVersion) {
|
|
88
|
+
throw new Error('Could not resolve latest Data Dragon version');
|
|
89
|
+
}
|
|
90
|
+
this.version = latestVersion;
|
|
91
|
+
}
|
|
92
|
+
this.baseURL = `https://ddragon.leagueoflegends.com/cdn/${this.version}/data/${this.locale}`;
|
|
93
|
+
return this.baseURL;
|
|
94
|
+
})();
|
|
95
|
+
try {
|
|
96
|
+
return await this.baseURLPromise;
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
this.baseURLPromise = null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
exports.DataDragon = DataDragon;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
@@ -1,71 +1,60 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
require("dotenv/config");
|
|
4
|
+
const index_1 = require("./index");
|
|
4
5
|
async function run() {
|
|
5
6
|
const riotId = process.env.TEST_RIOT_ID;
|
|
6
7
|
const tagLine = process.env.TEST_TAG_LINE;
|
|
7
|
-
const dd = new DataDragon();
|
|
8
|
-
|
|
8
|
+
const dd = new index_1.DataDragon();
|
|
9
9
|
if (process.env.RIOT_API_KEY && riotId) {
|
|
10
|
-
const riot = new RiotAPI();
|
|
10
|
+
const riot = new index_1.RiotAPI();
|
|
11
11
|
const account = await riot.getAccountByRiotId(riotId, tagLine);
|
|
12
12
|
console.log('[PASS] getAccountByRiotId:', account.puuid);
|
|
13
|
-
|
|
14
|
-
const summoner = await riot.getSummonerByPuuid(
|
|
13
|
+
const puuid = String(account.puuid || '');
|
|
14
|
+
const summoner = await riot.getSummonerByPuuid(puuid);
|
|
15
15
|
console.log('[PASS] getSummonerByPuuid:', {
|
|
16
16
|
puuid: summoner.puuid,
|
|
17
17
|
summonerLevel: summoner.summonerLevel,
|
|
18
18
|
});
|
|
19
|
-
|
|
20
|
-
const rank = await riot.getRankByPuuid(account.puuid);
|
|
19
|
+
const rank = await riot.getRankByPuuid(puuid);
|
|
21
20
|
console.log('[PASS] getRankByPuuid:', {
|
|
22
|
-
solo: rank.solo
|
|
23
|
-
|
|
21
|
+
solo: rank.solo
|
|
22
|
+
? `${rank.solo.tier} ${rank.solo.rank} | ${rank.solo.leaguePoints} LP | ${rank.solo.wins}W-${rank.solo.losses}L | ${rank.solo.winRate}% WR`
|
|
23
|
+
: null,
|
|
24
|
+
flex: rank.flex
|
|
25
|
+
? `${rank.flex.tier} ${rank.flex.rank} | ${rank.flex.leaguePoints} LP | ${rank.flex.wins}W-${rank.flex.losses}L | ${rank.flex.winRate}% WR`
|
|
26
|
+
: null,
|
|
24
27
|
});
|
|
25
|
-
|
|
26
|
-
const matchIds = await riot.getMatchlistByPuuid(account.puuid, { start: 0, count: 3 });
|
|
28
|
+
const matchIds = await riot.getMatchlistByPuuid(puuid, { start: 0, count: 3 });
|
|
27
29
|
console.log('[PASS] getMatchlistByPuuid:', matchIds.length, 'match ids');
|
|
28
|
-
|
|
29
30
|
if (matchIds.length > 0) {
|
|
30
31
|
const match = await riot.getMatchById(matchIds[0]);
|
|
31
|
-
console.log('[PASS] getMatchById:', match.metadata
|
|
32
|
-
|
|
32
|
+
console.log('[PASS] getMatchById:', match.metadata?.matchId);
|
|
33
33
|
const timeline = await riot.getMatchTimelineById(matchIds[0]);
|
|
34
|
-
console.log('[PASS] getMatchTimelineById:', timeline.metadata
|
|
35
|
-
|
|
36
|
-
const allMatchIds = await riot.getMatchlistByPuuidAll(
|
|
37
|
-
account.puuid,
|
|
38
|
-
{ start: 0 },
|
|
39
|
-
undefined,
|
|
40
|
-
{ maxMatches: 2, delayMs: 0 }
|
|
41
|
-
);
|
|
34
|
+
console.log('[PASS] getMatchTimelineById:', timeline.metadata?.matchId);
|
|
35
|
+
const allMatchIds = await riot.getMatchlistByPuuidAll(puuid, { start: 0 }, undefined, { maxMatches: 2, delayMs: 0 });
|
|
42
36
|
console.log('[PASS] getMatchlistByPuuidAll:', allMatchIds.length, 'match ids');
|
|
43
|
-
|
|
44
|
-
const withDetails = await riot.getMatchesWithDetailsByPuuid(
|
|
45
|
-
account.puuid,
|
|
46
|
-
{ start: 0 },
|
|
47
|
-
undefined,
|
|
48
|
-
{ maxMatches: 2, pageDelayMs: 0, detailDelayMs: 0 }
|
|
49
|
-
);
|
|
37
|
+
const withDetails = await riot.getMatchesWithDetailsByPuuid(puuid, { start: 0 }, undefined, { maxMatches: 2, pageDelayMs: 0, detailDelayMs: 0 });
|
|
50
38
|
console.log('[PASS] getMatchesWithDetailsByPuuid:', withDetails.matches.length, 'matches');
|
|
51
|
-
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
52
41
|
console.log('[SKIP] Match-by-id/timeline/all/details checks: no matches returned');
|
|
53
42
|
}
|
|
54
|
-
}
|
|
43
|
+
}
|
|
44
|
+
else if (process.env.RIOT_API_KEY) {
|
|
55
45
|
console.log('[SKIP] Riot endpoints: set TEST_RIOT_ID (and TEST_TAG_LINE if needed)');
|
|
56
|
-
}
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
57
48
|
console.log('[SKIP] Riot endpoints: set RIOT_API_KEY and TEST_RIOT_ID');
|
|
58
49
|
}
|
|
59
|
-
|
|
60
50
|
const champions = await dd.getChampions();
|
|
61
51
|
console.log('[PASS] getChampions:', Object.keys(champions.data || {}).length, 'champions');
|
|
62
52
|
console.log('[INFO] DataDragon version:', dd.version);
|
|
63
|
-
|
|
64
53
|
const items = await dd.getItems();
|
|
65
54
|
console.log('[PASS] getItems:', Object.keys(items.data || {}).length, 'items');
|
|
66
55
|
}
|
|
67
|
-
|
|
68
56
|
run().catch((error) => {
|
|
69
|
-
|
|
57
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
58
|
+
console.error('[FAIL]', message);
|
|
70
59
|
process.exitCode = 1;
|
|
71
60
|
});
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { AxiosInstance } from 'axios';
|
|
2
|
+
export type RegionCode = 'BR1' | 'EUN1' | 'EUW1' | 'JP1' | 'KR' | 'LA1' | 'LA2' | 'NA1' | 'OC1' | 'TR1' | 'RU' | 'PH2' | 'SG2' | 'TH2' | 'TW2' | 'VN2';
|
|
3
|
+
export interface RegionHosts {
|
|
4
|
+
platform: string;
|
|
5
|
+
shard: string;
|
|
6
|
+
}
|
|
7
|
+
export type RegionMap = Record<RegionCode, RegionHosts>;
|
|
8
|
+
export interface MatchlistOptions {
|
|
9
|
+
startTime?: number;
|
|
10
|
+
endTime?: number;
|
|
11
|
+
queue?: number;
|
|
12
|
+
type?: string;
|
|
13
|
+
start?: number;
|
|
14
|
+
count?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface MatchlistAllPacing {
|
|
17
|
+
delayMs?: number;
|
|
18
|
+
maxMatches?: number | null;
|
|
19
|
+
}
|
|
20
|
+
export interface MatchDetailsPacing {
|
|
21
|
+
pageDelayMs?: number;
|
|
22
|
+
detailDelayMs?: number;
|
|
23
|
+
maxMatches?: number | null;
|
|
24
|
+
}
|
|
25
|
+
export interface RankEntry extends Record<string, unknown> {
|
|
26
|
+
queueType?: string;
|
|
27
|
+
wins?: number;
|
|
28
|
+
losses?: number;
|
|
29
|
+
}
|
|
30
|
+
export interface RankEntryWithMetrics extends RankEntry {
|
|
31
|
+
winRate: number;
|
|
32
|
+
}
|
|
33
|
+
export interface RiotEndpointMethods {
|
|
34
|
+
getAccountByRiotId(riotId: string, tagLine?: string | null, region?: RegionCode): Promise<Record<string, unknown>>;
|
|
35
|
+
getSummonerByPuuid(puuid: string, region?: RegionCode): Promise<Record<string, unknown>>;
|
|
36
|
+
getRankEntriesByPuuid(puuid: string, region?: RegionCode): Promise<RankEntry[]>;
|
|
37
|
+
getRankByPuuid(puuid: string, region?: RegionCode): Promise<{
|
|
38
|
+
solo: RankEntryWithMetrics | null;
|
|
39
|
+
flex: RankEntryWithMetrics | null;
|
|
40
|
+
entries: RankEntry[];
|
|
41
|
+
}>;
|
|
42
|
+
getMatchlistByPuuid(puuid: string, options?: MatchlistOptions, region?: RegionCode): Promise<string[]>;
|
|
43
|
+
getMatchById(matchId: string, region?: RegionCode): Promise<Record<string, unknown>>;
|
|
44
|
+
getMatchTimelineById(matchId: string, region?: RegionCode): Promise<Record<string, unknown>>;
|
|
45
|
+
getMatchlistByPuuidAll(puuid: string, options?: MatchlistOptions, region?: RegionCode, pacing?: MatchlistAllPacing): Promise<string[]>;
|
|
46
|
+
getMatchesWithDetailsByPuuid(puuid: string, options?: MatchlistOptions, region?: RegionCode, pacing?: MatchDetailsPacing): Promise<{
|
|
47
|
+
matchIds: string[];
|
|
48
|
+
matches: Record<string, unknown>[];
|
|
49
|
+
}>;
|
|
50
|
+
}
|
|
51
|
+
export interface RiotEndpointsFactoryArgs {
|
|
52
|
+
client: AxiosInstance;
|
|
53
|
+
defaultRegion: RegionCode;
|
|
54
|
+
regionMap: RegionMap;
|
|
55
|
+
handleError: (error: unknown) => Error;
|
|
56
|
+
}
|
|
57
|
+
export interface DataDragonEndpointMethods {
|
|
58
|
+
getChampions(): Promise<Record<string, unknown>>;
|
|
59
|
+
getItems(): Promise<Record<string, unknown>>;
|
|
60
|
+
}
|
package/dist/types.js
ADDED
package/package.json
CHANGED
|
@@ -1,15 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timmsy/riftjs",
|
|
3
|
-
"version": "
|
|
4
|
-
"main": "index.js",
|
|
3
|
+
"version": "3.0.1",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"require": "./dist/index.js",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
5
18
|
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.json",
|
|
6
20
|
"test": "npm run test:endpoints",
|
|
7
|
-
"test:endpoints": "node test-endpoints.js"
|
|
21
|
+
"test:endpoints": "npm run build && node dist/test-endpoints.js",
|
|
22
|
+
"prepublishOnly": "npm run build"
|
|
8
23
|
},
|
|
9
24
|
"dependencies": {
|
|
10
25
|
"axios": "^1.7.7",
|
|
11
26
|
"dotenv": "^16.4.5"
|
|
12
27
|
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^22.13.10",
|
|
30
|
+
"typescript": "^5.8.2"
|
|
31
|
+
},
|
|
13
32
|
"keywords": [
|
|
14
33
|
"riot-api",
|
|
15
34
|
"league-of-legends",
|
|
@@ -20,6 +39,7 @@
|
|
|
20
39
|
"game-data",
|
|
21
40
|
"node-js",
|
|
22
41
|
"javascript",
|
|
42
|
+
"typescript",
|
|
23
43
|
"api-wrapper"
|
|
24
44
|
],
|
|
25
45
|
"author": "James Timms",
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
name: Publish package
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
tags:
|
|
6
|
-
- "v*"
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
publish:
|
|
10
|
-
runs-on: ubuntu-latest
|
|
11
|
-
permissions:
|
|
12
|
-
contents: read
|
|
13
|
-
packages: write
|
|
14
|
-
|
|
15
|
-
steps:
|
|
16
|
-
- name: Checkout
|
|
17
|
-
uses: actions/checkout@v4
|
|
18
|
-
|
|
19
|
-
- name: Setup Node
|
|
20
|
-
uses: actions/setup-node@v4
|
|
21
|
-
with:
|
|
22
|
-
node-version: 20
|
|
23
|
-
registry-url: https://registry.npmjs.org
|
|
24
|
-
|
|
25
|
-
- name: Install dependencies
|
|
26
|
-
run: npm ci
|
|
27
|
-
|
|
28
|
-
- name: Run tests
|
|
29
|
-
run: npm test
|
|
30
|
-
|
|
31
|
-
- name: Validate tag matches package version
|
|
32
|
-
run: |
|
|
33
|
-
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
|
34
|
-
PKG_VERSION="$(node -p "require('./package.json').version")"
|
|
35
|
-
if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
|
|
36
|
-
echo "Tag version v$TAG_VERSION does not match package.json version $PKG_VERSION"
|
|
37
|
-
exit 1
|
|
38
|
-
fi
|
|
39
|
-
|
|
40
|
-
- name: Publish to npm
|
|
41
|
-
run: npm publish --access public
|
|
42
|
-
env:
|
|
43
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
44
|
-
|
|
45
|
-
- name: Publish to GitHub Packages
|
|
46
|
-
run: |
|
|
47
|
-
echo "@timmsy:registry=https://npm.pkg.github.com" >> "$NPM_CONFIG_USERCONFIG"
|
|
48
|
-
echo "//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}" >> "$NPM_CONFIG_USERCONFIG"
|
|
49
|
-
npm pkg set name="@timmsy1998/riftjs"
|
|
50
|
-
npm publish --registry=https://npm.pkg.github.com
|
|
51
|
-
env:
|
|
52
|
-
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
package/endpoints/datadragon.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
const axios = require('axios');
|
|
2
|
-
|
|
3
|
-
module.exports = (baseURLOrResolver) => {
|
|
4
|
-
const resolveBaseURL = async () => {
|
|
5
|
-
if (typeof baseURLOrResolver === 'function') {
|
|
6
|
-
return baseURLOrResolver();
|
|
7
|
-
}
|
|
8
|
-
return baseURLOrResolver;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
return {
|
|
12
|
-
/**
|
|
13
|
-
* Fetch champion metadata for the configured Data Dragon version/locale.
|
|
14
|
-
* @returns {Promise<object>} Champion data.
|
|
15
|
-
*/
|
|
16
|
-
async getChampions() {
|
|
17
|
-
try {
|
|
18
|
-
const baseURL = await resolveBaseURL();
|
|
19
|
-
const response = await axios.get(`${baseURL}/champion.json`);
|
|
20
|
-
return response.data;
|
|
21
|
-
} catch (error) {
|
|
22
|
-
throw new Error(`DataDragon error: ${error.message}`);
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Fetch item metadata for the configured Data Dragon version/locale.
|
|
28
|
-
* @returns {Promise<object>} Item data.
|
|
29
|
-
*/
|
|
30
|
-
async getItems() {
|
|
31
|
-
try {
|
|
32
|
-
const baseURL = await resolveBaseURL();
|
|
33
|
-
const response = await axios.get(`${baseURL}/item.json`);
|
|
34
|
-
return response.data;
|
|
35
|
-
} catch (error) {
|
|
36
|
-
throw new Error(`DataDragon error: ${error.message}`);
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
};
|
package/endpoints/riot.js
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2
|
-
const SOLO_QUEUE = 'RANKED_SOLO_5x5';
|
|
3
|
-
const FLEX_QUEUE = 'RANKED_FLEX_SR';
|
|
4
|
-
const withRankMetrics = (entry) => {
|
|
5
|
-
if (!entry) return null;
|
|
6
|
-
const wins = Number(entry.wins) || 0;
|
|
7
|
-
const losses = Number(entry.losses) || 0;
|
|
8
|
-
const gamesPlayed = wins + losses;
|
|
9
|
-
const winRate = gamesPlayed > 0 ? Number(((wins / gamesPlayed) * 100).toFixed(2)) : 0;
|
|
10
|
-
return { ...entry, winRate };
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
module.exports = (client, defaultRegion, regionMap) => ({
|
|
14
|
-
/**
|
|
15
|
-
* Fetch account data by Riot ID.
|
|
16
|
-
* @param {string} riotId - Riot ID (e.g., "Timmsy#BRUV" or "Timmsy", "BRUV").
|
|
17
|
-
* @param {string} [tagLine] - Optional tagLine if not included in riotId.
|
|
18
|
-
* @param {string} [region] - Region code used to resolve shard routing.
|
|
19
|
-
* @returns {Promise<object>} Account payload including `puuid`.
|
|
20
|
-
*/
|
|
21
|
-
async getAccountByRiotId(riotId, tagLine = null, region = defaultRegion) {
|
|
22
|
-
let gameName, tag;
|
|
23
|
-
if (riotId.includes('#')) {
|
|
24
|
-
[gameName, tag] = riotId.split('#');
|
|
25
|
-
} else {
|
|
26
|
-
gameName = riotId;
|
|
27
|
-
tag = tagLine || '';
|
|
28
|
-
}
|
|
29
|
-
if (!tag) throw new Error('TagLine is required for getAccountByRiotId');
|
|
30
|
-
const shard = regionMap[region].shard;
|
|
31
|
-
try {
|
|
32
|
-
const response = await client.get(`/riot/account/v1/accounts/by-riot-id/${encodeURIComponent(gameName)}/${encodeURIComponent(tag)}`, {
|
|
33
|
-
baseURL: `https://${shard}`,
|
|
34
|
-
});
|
|
35
|
-
return response.data;
|
|
36
|
-
} catch (error) {
|
|
37
|
-
throw this._handleError(error);
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Fetch Summoner-V4 data for a player's PUUID.
|
|
43
|
-
* @param {string} puuid - Encrypted PUUID.
|
|
44
|
-
* @param {string} [region] - Region code used to resolve platform routing.
|
|
45
|
-
* @returns {Promise<object>} Summoner payload.
|
|
46
|
-
*/
|
|
47
|
-
async getSummonerByPuuid(puuid, region = defaultRegion) {
|
|
48
|
-
const platform = regionMap[region].platform;
|
|
49
|
-
try {
|
|
50
|
-
const response = await client.get(`/lol/summoner/v4/summoners/by-puuid/${encodeURIComponent(puuid)}`, {
|
|
51
|
-
baseURL: `https://${platform}`,
|
|
52
|
-
});
|
|
53
|
-
return response.data;
|
|
54
|
-
} catch (error) {
|
|
55
|
-
throw this._handleError(error);
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Fetch all ranked entries for a player PUUID.
|
|
61
|
-
* @param {string} puuid - Player PUUID.
|
|
62
|
-
* @param {string} [region] - Region code used to resolve platform routing.
|
|
63
|
-
* @returns {Promise<object[]>} Ranked entries from League-V4.
|
|
64
|
-
*/
|
|
65
|
-
async getRankEntriesByPuuid(puuid, region = defaultRegion) {
|
|
66
|
-
const platform = regionMap[region].platform;
|
|
67
|
-
try {
|
|
68
|
-
const response = await client.get(`/lol/league/v4/entries/by-puuid/${encodeURIComponent(puuid)}`, {
|
|
69
|
-
baseURL: `https://${platform}`,
|
|
70
|
-
});
|
|
71
|
-
return response.data;
|
|
72
|
-
} catch (error) {
|
|
73
|
-
throw this._handleError(error);
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Fetch ranked info split into Solo and Flex queues.
|
|
79
|
-
* @param {string} puuid - Player PUUID.
|
|
80
|
-
* @param {string} [region] - Region code used to resolve platform routing.
|
|
81
|
-
* @returns {Promise<{solo: object|null, flex: object|null, entries: object[]}>} Queue-split rank payload.
|
|
82
|
-
*/
|
|
83
|
-
async getRankByPuuid(puuid, region = defaultRegion) {
|
|
84
|
-
const entries = await this.getRankEntriesByPuuid(puuid, region);
|
|
85
|
-
const solo = withRankMetrics(entries.find((entry) => entry.queueType === SOLO_QUEUE) || null);
|
|
86
|
-
const flex = withRankMetrics(entries.find((entry) => entry.queueType === FLEX_QUEUE) || null);
|
|
87
|
-
return { solo, flex, entries };
|
|
88
|
-
},
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Fetch match IDs for a PUUID from Match-V5.
|
|
92
|
-
* @param {string} puuid - Player PUUID.
|
|
93
|
-
* @param {object} [options] - Query params such as `start` and `count`.
|
|
94
|
-
* @param {string} [region] - Region code used to resolve shard routing.
|
|
95
|
-
* @returns {Promise<string[]>} Array of match IDs.
|
|
96
|
-
*/
|
|
97
|
-
async getMatchlistByPuuid(puuid, options = {}, region = defaultRegion) {
|
|
98
|
-
const shard = regionMap[region].shard;
|
|
99
|
-
try {
|
|
100
|
-
const response = await client.get(`/lol/match/v5/matches/by-puuid/${encodeURIComponent(puuid)}/ids`, {
|
|
101
|
-
baseURL: `https://${shard}`,
|
|
102
|
-
params: options,
|
|
103
|
-
});
|
|
104
|
-
return response.data;
|
|
105
|
-
} catch (error) {
|
|
106
|
-
throw this._handleError(error);
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Fetch full match details by match ID.
|
|
112
|
-
* @param {string} matchId - Match ID (e.g., "EUW1_1234567890").
|
|
113
|
-
* @param {string} [region] - Region code used to resolve shard routing.
|
|
114
|
-
* @returns {Promise<object>} Match payload.
|
|
115
|
-
*/
|
|
116
|
-
async getMatchById(matchId, region = defaultRegion) {
|
|
117
|
-
const shard = regionMap[region].shard;
|
|
118
|
-
try {
|
|
119
|
-
const response = await client.get(`/lol/match/v5/matches/${matchId}`, {
|
|
120
|
-
baseURL: `https://${shard}`,
|
|
121
|
-
});
|
|
122
|
-
return response.data;
|
|
123
|
-
} catch (error) {
|
|
124
|
-
throw this._handleError(error);
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Fetch match timeline data by match ID.
|
|
130
|
-
* @param {string} matchId - Match ID (e.g., "EUW1_1234567890").
|
|
131
|
-
* @param {string} [region] - Region code used to resolve shard routing.
|
|
132
|
-
* @returns {Promise<object>} Match timeline payload.
|
|
133
|
-
*/
|
|
134
|
-
async getMatchTimelineById(matchId, region = defaultRegion) {
|
|
135
|
-
const shard = regionMap[region].shard;
|
|
136
|
-
try {
|
|
137
|
-
const response = await client.get(`/lol/match/v5/matches/${matchId}/timeline`, {
|
|
138
|
-
baseURL: `https://${shard}`,
|
|
139
|
-
});
|
|
140
|
-
return response.data;
|
|
141
|
-
} catch (error) {
|
|
142
|
-
throw this._handleError(error);
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Fetch all match IDs for a PUUID using Riot's max page size (100).
|
|
148
|
-
* @param {string} puuid - Player PUUID.
|
|
149
|
-
* @param {object} [options] - Riot filters: startTime, endTime, queue, type, start.
|
|
150
|
-
* @param {string} [region] - Region code used to resolve shard routing.
|
|
151
|
-
* @param {object} [pacing] - Pacing controls.
|
|
152
|
-
* @param {number} [pacing.delayMs=1250] - Delay between page requests.
|
|
153
|
-
* @param {number|null} [pacing.maxMatches=null] - Optional cap on total IDs returned.
|
|
154
|
-
* @returns {Promise<string[]>} All matched IDs.
|
|
155
|
-
*/
|
|
156
|
-
async getMatchlistByPuuidAll(puuid, options = {}, region = defaultRegion, pacing = {}) {
|
|
157
|
-
const baseStart = Number.isInteger(options.start) ? options.start : 0;
|
|
158
|
-
const pageDelayMs = Number.isInteger(pacing.delayMs) ? pacing.delayMs : 1250;
|
|
159
|
-
const maxMatches = Number.isInteger(pacing.maxMatches) && pacing.maxMatches >= 0 ? pacing.maxMatches : null;
|
|
160
|
-
const filters = {
|
|
161
|
-
startTime: options.startTime,
|
|
162
|
-
endTime: options.endTime,
|
|
163
|
-
queue: options.queue,
|
|
164
|
-
type: options.type,
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
let start = baseStart;
|
|
168
|
-
const allMatchIds = [];
|
|
169
|
-
|
|
170
|
-
while (true) {
|
|
171
|
-
const remaining = maxMatches === null ? 100 : Math.min(100, maxMatches - allMatchIds.length);
|
|
172
|
-
if (remaining <= 0) break;
|
|
173
|
-
|
|
174
|
-
const page = await this.getMatchlistByPuuid(puuid, { ...filters, start, count: remaining }, region);
|
|
175
|
-
allMatchIds.push(...page);
|
|
176
|
-
|
|
177
|
-
if (page.length < remaining) break;
|
|
178
|
-
start += page.length;
|
|
179
|
-
if (pageDelayMs > 0) await sleep(pageDelayMs);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return allMatchIds;
|
|
183
|
-
},
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Fetch match IDs and full match payloads for each ID.
|
|
187
|
-
* @param {string} puuid - Player PUUID.
|
|
188
|
-
* @param {object} [options] - Riot filters: startTime, endTime, queue, type, start.
|
|
189
|
-
* @param {string} [region] - Region code used to resolve shard routing.
|
|
190
|
-
* @param {object} [pacing] - Pacing controls.
|
|
191
|
-
* @param {number} [pacing.pageDelayMs=1250] - Delay between matchlist page requests.
|
|
192
|
-
* @param {number} [pacing.detailDelayMs=1250] - Delay between match detail requests.
|
|
193
|
-
* @param {number|null} [pacing.maxMatches=null] - Optional cap on total matches fetched.
|
|
194
|
-
* @returns {Promise<{matchIds: string[], matches: object[]}>} IDs and full match payloads.
|
|
195
|
-
*/
|
|
196
|
-
async getMatchesWithDetailsByPuuid(puuid, options = {}, region = defaultRegion, pacing = {}) {
|
|
197
|
-
const pageDelayMs = Number.isInteger(pacing.pageDelayMs) ? pacing.pageDelayMs : 1250;
|
|
198
|
-
const detailDelayMs = Number.isInteger(pacing.detailDelayMs) ? pacing.detailDelayMs : 1250;
|
|
199
|
-
const maxMatches = Number.isInteger(pacing.maxMatches) && pacing.maxMatches >= 0 ? pacing.maxMatches : null;
|
|
200
|
-
const matchIds = await this.getMatchlistByPuuidAll(
|
|
201
|
-
puuid,
|
|
202
|
-
options,
|
|
203
|
-
region,
|
|
204
|
-
{ delayMs: pageDelayMs, maxMatches }
|
|
205
|
-
);
|
|
206
|
-
const matches = [];
|
|
207
|
-
|
|
208
|
-
for (let i = 0; i < matchIds.length; i += 1) {
|
|
209
|
-
matches.push(await this.getMatchById(matchIds[i], region));
|
|
210
|
-
if (detailDelayMs > 0 && i < matchIds.length - 1) {
|
|
211
|
-
await sleep(detailDelayMs);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return { matchIds, matches };
|
|
216
|
-
},
|
|
217
|
-
});
|
package/index.js
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
require('dotenv').config();
|
|
2
|
-
const axios = require('axios');
|
|
3
|
-
const riotEndpoints = require('./endpoints/riot');
|
|
4
|
-
const dataDragonEndpoints = require('./endpoints/datadragon');
|
|
5
|
-
|
|
6
|
-
// Maps region codes to the platform and routing shard hosts required by Riot APIs.
|
|
7
|
-
const regionMap = {
|
|
8
|
-
BR1: { platform: 'br1.api.riotgames.com', shard: 'americas.api.riotgames.com' },
|
|
9
|
-
EUN1: { platform: 'eun1.api.riotgames.com', shard: 'europe.api.riotgames.com' },
|
|
10
|
-
EUW1: { platform: 'euw1.api.riotgames.com', shard: 'europe.api.riotgames.com' },
|
|
11
|
-
JP1: { platform: 'jp1.api.riotgames.com', shard: 'asia.api.riotgames.com' },
|
|
12
|
-
KR: { platform: 'kr.api.riotgames.com', shard: 'asia.api.riotgames.com' },
|
|
13
|
-
LA1: { platform: 'la1.api.riotgames.com', shard: 'americas.api.riotgames.com' },
|
|
14
|
-
LA2: { platform: 'la2.api.riotgames.com', shard: 'americas.api.riotgames.com' },
|
|
15
|
-
NA1: { platform: 'na1.api.riotgames.com', shard: 'americas.api.riotgames.com' },
|
|
16
|
-
OC1: { platform: 'oc1.api.riotgames.com', shard: 'sea.api.riotgames.com' },
|
|
17
|
-
TR1: { platform: 'tr1.api.riotgames.com', shard: 'europe.api.riotgames.com' },
|
|
18
|
-
RU: { platform: 'ru.api.riotgames.com', shard: 'europe.api.riotgames.com' },
|
|
19
|
-
PH2: { platform: 'ph2.api.riotgames.com', shard: 'sea.api.riotgames.com' },
|
|
20
|
-
SG2: { platform: 'sg2.api.riotgames.com', shard: 'sea.api.riotgames.com' },
|
|
21
|
-
TH2: { platform: 'th2.api.riotgames.com', shard: 'sea.api.riotgames.com' },
|
|
22
|
-
TW2: { platform: 'tw2.api.riotgames.com', shard: 'sea.api.riotgames.com' },
|
|
23
|
-
VN2: { platform: 'vn2.api.riotgames.com', shard: 'sea.api.riotgames.com' },
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
class RiotAPI {
|
|
27
|
-
constructor() {
|
|
28
|
-
this.apiKey = process.env.RIOT_API_KEY;
|
|
29
|
-
if (!this.apiKey) throw new Error('RIOT_API_KEY is required in .env');
|
|
30
|
-
this.region = (process.env.REGION || 'EUW1').toUpperCase();
|
|
31
|
-
if (!regionMap[this.region]) throw new Error(`Invalid region: ${this.region}`);
|
|
32
|
-
this.client = axios.create({
|
|
33
|
-
headers: { 'X-Riot-Token': this.apiKey },
|
|
34
|
-
});
|
|
35
|
-
// Attach endpoint methods with a shared axios client and region configuration.
|
|
36
|
-
Object.assign(this, riotEndpoints(this.client, this.region, regionMap));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Normalize axios errors to stable Error messages for consumers.
|
|
40
|
-
_handleError(error) {
|
|
41
|
-
if (error.response) {
|
|
42
|
-
const { status, data } = error.response;
|
|
43
|
-
return new Error(`API error ${status}: ${data.status?.message || 'Unknown error'}`);
|
|
44
|
-
} else if (error.request) {
|
|
45
|
-
return new Error('No response received from the server');
|
|
46
|
-
}
|
|
47
|
-
return new Error(`Request error: ${error.message}`);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
class DataDragon {
|
|
52
|
-
constructor(version = null, locale = 'en_US') {
|
|
53
|
-
this.version = version;
|
|
54
|
-
this.locale = locale;
|
|
55
|
-
this.baseURL = null;
|
|
56
|
-
this._baseURLPromise = null;
|
|
57
|
-
|
|
58
|
-
// Attach static-data endpoint methods that resolve the latest version when needed.
|
|
59
|
-
Object.assign(this, dataDragonEndpoints(() => this._resolveBaseURL()));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async _resolveBaseURL() {
|
|
63
|
-
if (this.baseURL) return this.baseURL;
|
|
64
|
-
if (this._baseURLPromise) return this._baseURLPromise;
|
|
65
|
-
|
|
66
|
-
this._baseURLPromise = (async () => {
|
|
67
|
-
if (!this.version) {
|
|
68
|
-
const response = await axios.get('https://ddragon.leagueoflegends.com/api/versions.json');
|
|
69
|
-
const latestVersion = Array.isArray(response.data) ? response.data[0] : null;
|
|
70
|
-
if (!latestVersion) {
|
|
71
|
-
throw new Error('Could not resolve latest Data Dragon version');
|
|
72
|
-
}
|
|
73
|
-
this.version = latestVersion;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
this.baseURL = `https://ddragon.leagueoflegends.com/cdn/${this.version}/data/${this.locale}`;
|
|
77
|
-
return this.baseURL;
|
|
78
|
-
})();
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
return await this._baseURLPromise;
|
|
82
|
-
} finally {
|
|
83
|
-
this._baseURLPromise = null;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
module.exports = { RiotAPI, DataDragon };
|