UNPKG

pubg-royale

Version:

A pubg api wrapper with built-in caching

503 lines (436 loc) 15.4 kB
/* eslint class-methods-use-this: "off" */ /** * Options object to specify ttl for cached objects. * @typedef {Object} CacheOptions * @property {number} player ttl in ms for player objects to be cached * @property {number} playerStats ttl in ms for player statistic objects to be cached * @property {number} seasons ttl in ms for seasons objects to be cached * @property {number} status ttl in ms for status objects to be cached * @property {number} match ttl in ms for match objects to be cached */ /** * Options object for pubg api client constructor. * @typedef {Object} PubgRoyaleClientOptions * @property {string} key Your PUBG API key. Required. * @property {string} defaultRegion Default region used for api request. If omitted, defaults to * 'pc-na'. * @property {CacheOptions} cache Object to configure ttl for cached objects. */ /** * Options object for player request. Either "id" or "name" property is required. Omitting * both causes a rejected promise. If both are specified the id will be used. * @typedef {Object} PlayerOptions * @property {string} region Region used for finding player. If omitted, client default will be * used. * @property {string} id Pubg api player id used for finding player. * @property {string} name Pubg player name used for finding player. */ /** * Options object for player statistics request. * @typedef {Object} PlayerStatsOptions * @property {string} region Region of player. If omitted, client default is used. * @property {string} playerId Pubg api player id. Required. * @property {string} seasonId Pubg api season id. Required. */ /** * Options object for player lifetime statistics request. * @typedef {Object} PlayerLifetimeStatsOptions * @property {string} region Region of player. If omitted, client default is used. * @property {string} playerId Pubg api player id. Required. */ /** * Options object for Telemetry. * @typedef {Object} TelemetryOptions * @property {string} region Region of player. If omitted, client default is used. * @property {string} matchId Id obtained from player() which has this format * "1ad97f85-cf9b-11e7-b84e-0a586460f004". Required */ /** * Options object for seasons request. * @typedef {Object} SeasonsOptions * @property {string} region Region used to get seasons. If omitted, client default is used. */ /** * Options object for match request. * @typedef {Object} MatchOptions * @property {string} region Region used to get match. If omitted, client default is used. * @property {string} id Pubg api match id. Required. */ /** * Options for getting telemetry URL * @typedef {Object} TelemetryURLOptions * @property {JSON} jsonResponse returned value from telemetry() */ const https = require('https'); const { Cache } = require('clean-cache'); const PUBG_API_HOST_NAME = 'api.pubg.com'; const REGIONS = { PC: { STEAM: 'steam', KAKAO: 'kakao', }, }; /** * Creates HTTP request options for api call. * @param {string} apiKey pubg api key * @param {string} path sub url path for api request */ function getApiOptions(apiKey, path) { return { path, hostname: PUBG_API_HOST_NAME, method: 'GET', headers: { Authorization: `Bearer ${apiKey}`, Accept: 'application/vnd.api+json', }, }; } /** * Makes api request. Caches objects based on requested path. * @param {Object} options Request options object * @param {Function} resolve Callback that is called, when the api request succeeded * @param {Function} reject Callback that is called, when an error occures * @param {Cache} cache Cache to store and retrieve results. */ function apiRequest(options, cache, resolve, reject) { // first, check whether there already is the requested object in cache const cachedObject = cache.retrieve(options.path); if (cachedObject !== null) { if (cachedObject instanceof Error) { // reject promise if request caused error previously reject(cachedObject); } else { // resolve the request with cached object resolve(cachedObject); } } else { // ... no cached object, so start the http get request https.get(options, (resp) => { let data = ''; // a chunk of data has been recieved. resp.on('data', (chunk) => { data += chunk; }); // the whole response has been received. resp.on('end', () => { const apiData = JSON.parse(data); // if an api error object was received... if (apiData.errors !== undefined && apiData.errors.length > 0) { // ...gather error info... let errorMessage = apiData.errors[0].title; if (apiData.errors[0].detail !== undefined) { errorMessage += `. Details: ${apiData.errors[0].detail}`; } // ... and reject promise with proper error const apiError = new Error(errorMessage); cache.add(options.path, apiError); reject(apiError); } else { cache.add(options.path, apiData); resolve(apiData); } }); }).on('error', (err) => { cache.add(options.path, err); reject(err); }); } } /** * Client class for access to pubg api. */ class PubgRoyaleClient { /** * Constructor * @param {PubgRoyaleClientOptions} options object to customize behaviour */ constructor(options) { if (options === undefined) { throw new Error('No options parameter specified.'); } if (options.key === undefined || options.key === null) { throw new Error('Api key must be specified'); } else { this.apiKey = options.key; } if (options.defaultRegion !== undefined) { this.defaultRegion = options.defaultRegion; } else { this.defaultRegion = REGIONS.PC.STEAM; } const defaultTtl = 60 * 1000; if (options.cache !== undefined) { const { cache: cacheSettings } = options; if (cacheSettings.player !== undefined) { this.playerCache = new Cache(cacheSettings.player); } else { this.playerCache = new Cache(defaultTtl); } if (cacheSettings.playerStats !== undefined) { this.playerStatsCache = new Cache(cacheSettings.playerStats); } else { this.playerStatsCache = new Cache(defaultTtl); } if (cacheSettings.status !== undefined) { this.statusCache = new Cache(cacheSettings.status); } else { this.statusCache = new Cache(defaultTtl); } if (cacheSettings.seasons !== undefined) { this.seasonsCache = new Cache(cacheSettings.seasons); } else { this.seasonsCache = new Cache(defaultTtl); } if (cacheSettings.match !== undefined) { this.matchCache = new Cache(cacheSettings.match); } else { this.matchCache = new Cache(defaultTtl); } if (cacheSettings.tournaments !== undefined) { this.tournamentsCache = new Cache(cacheSettings.tournaments); } else { this.tournamentsCache = new Cache(defaultTtl); } if (cacheSettings.tournament !== undefined) { this.tournamentCache = new Cache(cacheSettings.tournament); } else { this.tournamentCache = new Cache(defaultTtl); } } else { this.playerCache = new Cache(defaultTtl); this.playerStatsCache = new Cache(defaultTtl); this.statusCache = new Cache(defaultTtl); this.seasonsCache = new Cache(defaultTtl); this.matchCache = new Cache(defaultTtl); this.tournamentsCache = new Cache(defaultTtl); this.tournamentCache = new Cache(defaultTtl); } } /** * Creates a promise to get PUBG api info about player * @param {PlayerOptions} options Options for api call. * @returns {Promise} Promise to get player */ player(options) { let region = ''; if (options === undefined) { return Promise.reject(new Error('No options parameter defined for player api request')); } if (options.region !== undefined) { ({ region } = options); } else { region = this.defaultRegion; } if (options.id === undefined && options.name === undefined) { return Promise.reject(new Error('No player id or name specified through "id" or "name" option')); } if (options.id !== undefined) { return new Promise((resolve, reject) => { const apiOptions = getApiOptions(this.apiKey, `/shards/${region}/players/${options.id}`); return apiRequest(apiOptions, this.playerCache, resolve, reject); }); } // use name instead return new Promise((resolve, reject) => { const apiOptions = getApiOptions( this.apiKey, `/shards/${region}/players?filter[playerNames]=${options.name}`, ); return apiRequest(apiOptions, this.playerCache, resolve, reject); }); } /** * Creates a promise to get some telemetry info. * @param {TelemetryOptions} options Options for api call. * @returns {Promise} Promise to get player stats. */ telemetry(options) { let region = ''; if (options === undefined) { return Promise.reject(new Error('No options parameter defined for telemetry api request')); } if (options.region !== undefined) { ({ region } = options); } else { region = this.defaultRegion; } if (options.matchId === undefined) { return Promise.reject(new Error('No "matchId" passed as an option')); } // Using matchId return new Promise((resolve, reject) => { const apiOptions = getApiOptions( this.apiKey, `/shards/${region}/matches/${options.matchId}`, ); return apiRequest(apiOptions, this.playerCache, resolve, reject); }); } /** * Creates a promise to get the stats of a player during the given season. * @param {PlayerStatsOptions} options Options for api call. * @returns {Promise} Promise to get player stats. */ playerStats(options) { let region = ''; let playerId = ''; let seasonId = ''; if (options === undefined) { return Promise.reject(new Error('No options parameter defined for player stats api request')); } if (options.region !== undefined) { ({ region } = options); } else { region = this.defaultRegion; } if (options.playerId === undefined) { return Promise.reject(new Error('No player id specified through "playerId" option')); } if (options.seasonId === undefined) { return Promise.reject(new Error('No season id specified through "seasonId" option')); } ({ playerId, seasonId } = options); return new Promise((resolve, reject) => { const apiOptions = getApiOptions( this.apiKey, `/shards/${region}/players/${playerId}/seasons/${seasonId}`, ); apiRequest(apiOptions, this.playerStatsCache, resolve, reject); }); } /** * Creates a promise to get the lifetime stats of the player. * @param {PlayerLifetimeStatsOptions} options Options for api call * @returns {Promise} Promise to get player lifetime stats. */ lifetimeStats(options) { let region = ''; let playerId = ''; if (options === undefined) { return Promise.reject(new Error('No options parameter defined for player stats api request')); } if (options.region !== undefined) { ({ region } = options); } else { region = this.defaultRegion; } if (options.playerId === undefined) { return Promise.reject(new Error('No player id specified through "playerId" option')); } ({ playerId } = options); return new Promise((resolve, reject) => { const apiOptions = getApiOptions( this.apiKey, `/shards/${region}/players/${playerId}/seasons/lifetime`, ); apiRequest(apiOptions, this.playerStatsCache, resolve, reject); }); } /** * Creates a promise to get the current status of the pubg api. * @returns {Promise} Promise to get api status */ status() { return new Promise((resolve, reject) => { const options = getApiOptions(this.apiKey, '/status'); return apiRequest(options, this.statusCache, resolve, reject); }); } /** * Creates a promise to get all seasons. * @param {SeasonsOptions} options Options for api call. * @returns {Promise} Promise to get seasons */ seasons(options) { let region = ''; if (options === undefined) { region = this.defaultRegion; } else if (options.region !== undefined) { ({ region } = options); } else { region = this.defaultRegion; } return new Promise((resolve, reject) => { const apiOptions = getApiOptions(this.apiKey, `/shards/${region}/seasons`); return apiRequest(apiOptions, this.seasonsCache, resolve, reject); }); } /** * Creates a promise to get infos about match identified by the * given id. * @param {MatchOptions} options Options for api call. * @returns {Promise} Promise to get match */ match(options) { let region = ''; let matchId = ''; if (options === undefined) { return Promise.reject(new Error('No options parameter defined for match api request')); } if (options.region !== undefined) { ({ region } = options); } else { region = this.defaultRegion; } if (options.id === undefined) { return Promise.reject(new Error('No match id specified through "id" option')); } ({ id: matchId } = options); return new Promise((resolve, reject) => { const apiOptions = getApiOptions(this.apiKey, `/shards/${region}/matches/${matchId}`); apiRequest(apiOptions, this.matchCache, resolve, reject); }); } /** * Returns the json file URL from telemetry * Use it after calling telemetry() in your code * @param {JSON} jsonReturned * @returns {String} Telemetry file URL */ getTelemetryURL(jsonReturned) { const asset = jsonReturned.included.filter((val, index, arr) => { if (val.type === 'asset') { return arr; } return undefined; }); if (asset !== undefined) { return asset[0].attributes.URL; } return undefined; } /** * Creates a promise to get all tournaments. * @param {TournamentsOptions} options Options for api call. * @returns {Promise} Promise to get tournaments */ tournaments() { return new Promise((resolve, reject) => { const apiOptions = getApiOptions(this.apiKey, '/tournaments'); return apiRequest(apiOptions, this.tournamentsCache, resolve, reject); }); } /** * Creates a promise to get infos about a tournament identified * by the given id. * @param {TournamentOptions} options Options for api call. * @returns {Promise} Promise to get tournament */ tournament(options) { let tournamentId = ''; if (options === undefined) { return Promise.reject(new Error('No options parameter defined for tournament api request')); } if (options.id === undefined) { return Promise.reject(new Error('No tournament id specified through "id" option')); } ({ id: tournamentId } = options); return new Promise((resolve, reject) => { const apiOptions = getApiOptions(this.apiKey, `/tournaments/${tournamentId}`); apiRequest(apiOptions, this.tournamentCache, resolve, reject); }); } } exports.Client = PubgRoyaleClient; exports.REGIONS = REGIONS;