Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
kindred-api
Advanced tools
Kindred is a thin Node.js wrapper on top of Riot Games API for League of Legends
Kindred is a Node.js wrapper with built-in rate-limiting (enforced per region), caching (Redis), and parameter checking on top of Riot's League of Legends API.
My goal is to make a wrapper that is simple, sensible, and consistent. This project is heavily inspired by psuedonym117's Python wrapper. Look at the Quickstart Section to see what I mean.
yarn add kindred-api
// or npm install kindred-api
Make sure to check the official Riot Documentation to see what query parameters you can pass in to each endpoint (through the options parameter)!
Note: All region
parameters are OPTIONAL. All options
parameters are OPTIONAL unless stated otherwise.
k.ChampionMastery.all({ id: 20026563 }, rprint)
k.ChampionMastery.all({ id: 20026563 }).then(data => console.log(data))
k.ChampionMastery.get({ playerID: 20026563, championID: 203 }, rprint)
k.ChampionMastery.get({ playerID: 20026563, championID: 203 }).then(data => console.log(data))
k.ChampionMastery.score({ id: 20026563 }, rprint)
k.Champion.all({ region: REGIONS.KOREA }, rprint)
k.Champion.get({ championID: 67 }, rprint)
k.Champion.get({ championID: 67 }).then(data => console.log(data))
k.Champion.get({ championID: 67, region: 'kr' }, rprint)
k.Game.get({ summonerID: 20026563 }, rprint)
k.League.getLeagues({ summonerID: 20026563 }, rprint)
k.League.get({ summonerID: 20026563 }, rprint)
k.League.entries({ summonerID: 20026563 }, rprint)
k.League.challengers(rprint)
k.League.challengers({ region: 'na' }, rprint)
k.League.challengers({ options: { type: 'RANKED_FLEX_SR' } }, rprint)
k.League.masters().then(data => console.log(data))
k.Status.get().then(data => console.log(data))
k.Masteries.get({ id: 20026563 }, rprint)
k.Match.get({ id: 1912829920 }, rprint)
k.Match.get({ id: 1912829920, options: { includeTimeline: false } }, rprint)
k.MatchList.get({ id: 20026563 }, rprint)
k.MatchList.get({ id: 20026563, options: { rankedQueues: 'RANKED_FLEX_SR' } }, rprint)
k.Runes.get({ id: 20026563 }, rprint)
k.CurrentGame.get({ name: 'Contractz' }, rprint)
k.FeaturedGames.get().then(data => console.log(data))
k.FeaturedGames.get({ region: 'na' }, rprint)
k.Stats.ranked({ id: 20026563 }, rprint)
k.Stats.ranked({ id: 20026563, options: { season: 'SEASON2016' } }, function(err,data){})
k.Stats.summary({ id: 20026563 }, rprint)
k.Summoner.get({ name: 'caaaaaaaaarIa' }, rprint)
k.Summoner.get({ id: 20026563 }, rprint)
Debug on, dev key rate limiting per region, in-memory cache with default settings on for quick scripts
var KindredAPI = require('kindred-api')
var debug = true
var k = KindredAPI.QuickStart('YOUR_KEY', debug)
/* Summoners! */
k.Summoner.get({ id: 32932398 }, KindredAPI.print)
k.Summoner.get({ name: 'Contractz' }, KindredAPI.print)
/* No need to wrap nested API calls for exclusively by-id endpoints, the client does it for you. */
k.Runes.get({ region: 'na', name: 'Contractz' }).then(data => console.log(data))
/* How to pass in options 101. */
var name = 'caaaaaaaaaria'
var opts = {
region: KindredAPI.REGIONS.NORTH_AMERICA,
options: {
rankedQueues: ['RANKED_SOLO_5x5', 'RANKED_FLEX_SR'], // no need for joins or messy strings
championIDs: '67' // single values can be integers as well
} // option params should be spelled and capitalized the same as it is in Riot's docs!
}
k.Summoner.get({ name: name, region: opts.region })
.then(data => k.MatchList.get(
Object.assign({ id: data.id }, opts)
))
.then(data => console.log(data))
.catch(err => console.err(error))
/*
Instead of chaining requests like in the above, you can simply call
k.MatchList.get with the `name` param.
*/
k.MatchList.get({ name: name, options: opts.options })
.then(data => console.log(data))
.catch(err => console.error(err))
var KindredAPI = require('kindred-api')
// var RIOT_API_KEY = require('whatever')
// or if you're using something like dotenv..
require('dotenv').config()
var RIOT_API_KEY = process.env.RIOT_API_KEY
var REGIONS = KindredAPI.REGIONS
var LIMITS = KindredAPI.LIMITS
var CACHE_TYPES = KindredAPI.CACHE_TYPES
/*
Default region for every method call is NA,
but you can set it during initialization as shown below.
You can also change it with 'setRegion(region)' as well.
To NOT use the built-in rate limiter, do NOT pass in anything
into limits. Same if you don't want to use the cache (cacheOptions).
*/
var k = new KindredAPI.Kindred({
key: RIOT_API_KEY,
defaultRegion: REGIONS.NORTH_AMERICA,
debug: true, // shows status code, urls, and relevant headers
limits: [ [10, 10], [500, 600] ], // user key
// 10 requests per 10 seconds, 500 requests per 10 minutes
// You can just pass in LIMITS.DEV, LIMITS.PROD, 'dev', or 'prod' instead though.
cacheOptions: CACHE_TYPES[0] // in memory
})
console.log(CACHE_TYPES)
// ['in-memory-cache', 'redis']
function rprint(err, data) { console.log(data) }
/*
The important thing about this wrapper is that it does not
take in parameters the usual way. Instead, the only parameter,
excluding the callback parameter, is an object of parameters.
*/
k.Summoner.get({ id: 354959 }, rprint)
k.Summoner.get({ id: 354959 }).then(data => console.log(data))
k.Match.get({ id: 2459973154, options: {
includeTimeline: false // of course, option params must be the same as the ones in Riot Docs
}}, rprint)
k.League.challengers({ region: 'na', options: {
type: 'RANKED_FLEX_SR'
}}, rprint)
/*
All functions essentially have the following form:
functionName({ arg1, arg2... argN, options: {} }, optionalCallback) -> promise
If a method does not have the `options` parameter within my code, that simply means
there are no possible query parameters that you can pass in to that method.
*/
/*
Making any form of parameter error will inform you
what parameters you can pass in so you hopefully
don't have to refer to the documentation as much.
*/
k.Summoner.get(rprint)
// getSummoner request FAILED; required params `id` (int) or `name` (string) not passed in
k.ChampionMastery.get(rprint)
// getChampMastery request FAILED; required params `playerID` (int) AND `championID` (int) not passed in
/*
Notice the OR and the AND!!
Note: getChampMastery is the only method that can't take in an 'id' parameter,
because it requires both a 'playerID' and a 'championID'!
*/
/*
Let me reiterate: the first parameter of all endpoint methods will ALWAYS be an object.
However, when the parameters are satisfied by default parameters and/or
only have optional parameters, you can simply pass your callback in.
*/
k.League.challengers(rprint) // default region, default solo queue mode, valid
k.Static.runes(rprint) // only optional arguments & not passing in any optional arguments, valid
/*
getSummoners & getSummoner target both the by-name and by-id endpoints.
In the case of the summoner endpoints, it made a lot more sense for the two
functions to target both the by-name and by-id summoner endpoints.
*/
k.Summoner.get({ name: 'Contractz' }, rprint)
k.Summoner.get({ id: 354959 }, rprint)
/*
There are aliases for the `id` param.
For example, for summoners, you have summonerID, summonerIDs,
playerID, and playerIDs.
*/
k.Summoner.get({ summonerID: 354959 }, rprint)
k.Summoner.get({ summonerID: 354959 })
.then(json => console.log(json))
.catch(err => console.log(err))
k.Match.get({ id: 2459973154 }, rprint)
k.Match.get({ matchID: 2459973154 })
.then(data => console.log(data))
.catch(err => console.error(err))
/* Every method has an optional 'region' parameter. */
var params = { name: 'sktt1peanut', region: REGIONS.KOREA }
k.Summoner.get(params, rprint) // peanut's data
/* Changing the default region! */
k.setRegion(REGIONS.KOREA)
/* Note that you can use spaces in the name. */
var fakerIgn = { name: 'hide on bush' }
var fakerId
k.Summoner.get(fakerIgn, function (err, data) {
fakerId = data.id
console.log('fakerId:', fakerId)
})
/*
Note that the player runes endpoint only accepts
a comma-separated list of integers.
*/
k.setRegion(REGIONS.NORTH_AMERICA)
k.Runes.get({ id: 354959 }, rprint)
k.Runes.get({ id: 354959 })
.then(json => console.log(json))
.catch(err => console.error(err))
/*
But what if you want to quickly get the rune page of
some random summoner given their name?
You'd chain it like in many other clients:
Get the id from the name, get the runes from the id.
*/
var name = 'Richelle'
k.Summoner.get({ name }, function (err, data) {
if (data) k.Runes.get({ id: data.id }, rprint)
else console.err(err)
})
// or with promises
k.Summoner.get({ name })
.then(data => k.Runes.get({ id: data.id }))
.then(data => console.log(data))
.catch(err => console.log(err))
/* I find that inconvenient, and so I just chain it for you in my code. */
// all methods that target endpoints that only accept ids
k.Runes.get({ name: 'Richelle' }, rprint)
k.Game.get({ name: 'Richelle' }, rprint)
k.League.get({ name: '5tunt' }, rprint)
k.CurrentGame.get({ name: 'Fràe', region: REGIONS.OCEANIA }, rprint)
k.League.get({ name: '5tunt' })
.then(data => console.log(data))
var name = 'Grigne'
k.Runes.get({ name })
.then(data => console.log(data))
k.Masteries.get({ name })
.then(data => console.log(data))
/*
Functions will have an options parameter that you can pass in query
strings when applicable. Values of options should match the
endpoint's 'Query Parameters'. Check the methods to see which methods
you can pass in options to.
Some are required, and some are not. I often take care of the ones
that are required by using the most sensible defaults.
For example, the required parameter for many methods is 'type' (of queue).
I made it so that the default is 'RANKED_SOLO_5x5' (or 'TEAM_BUILDER_RANKED_SOLO')
if 'type' is not passed in.
*/
k.League.challengers({ region: 'na' }, rprint) // get challengers from ranked solo queue ladder
k.League.challengers({ region: 'na', options: {
type: 'RANKED_FLEX_SR'
}}, rprint) // get challengers from ranked flex ladder
k.Match.get({ id: 2459973154 }, rprint) // includes timeline by default
k.Match.get({ id: 2459973154, options: { includeTimeline: false } }, rprint)
/*
However, for getMatchList, the endpoint uses an optional
'rankedQueues' instead of 'type' to allow multiple options.
I still set the default to RANKED_SOLO_5x5 though.
*/
var name = 'caaaaaaaaaria'
k.MatchList.get({ name, region: 'na', options: {
/*
According to Riot API, query parameters that can accept multiple values
must be a comma separated list (or a single value), which is why one can do the below join.
However, both these options are inconvenient, and so I check if you pass in array values
for every option parameter, and manually join it for you. You can still pass in string values
if you want though.
Note, for arrays of values that are conceptually integers,
both strings and integers work because they're joined together as a string anyways.
*/
// rankedQueues: ['RANKED_SOLO_5x5', 'RANKED_FLEX_SR'].join(','), STILL VALID
// championIds: '67' // '267,67' or ['267', '67'].join(',') STILL VALID
rankedQueues: ['RANKED_SOLO_5x5', 'RANKED_FLEX_SR'], // valid
championIDs: [67, 62, '61'] // valid
} }, rprint)
/* The above example with promises. */
var opts = {
name: 'caaaaaaaaaria',
region: 'na',
options: {
rankedQueues: ['RANKED_SOLO_5x5', 'RANKED_FLEX_SR'],
championIDs: '67'
}
}
k.MatchList.get({ name: opts.name, region: opts.region, options: opts.options })
.then(data => console.log(data))
.catch(err => console.err(error))
var furyMasteryId = 6111
k.Static.mastery({ id: furyMasteryId }, rprint)
var msRuneId = 10002
k.Static.rune({ id: msRuneId }, rprint)
April 2 I have added caching support. Right now, the library supports in-memory caching as well as caching with redis. These are the default timers that made sense to me.
const endpointCacheTimers = {
// defaults
CHAMPION: cacheTimers.MONTH,
CHAMPION_MASTERY: cacheTimers.SIX_HOURS,
CURRENT_GAME: cacheTimers.NONE,
FEATURED_GAMES: cacheTimers.NONE,
GAME: cacheTimers.HOUR,
LEAGUE: cacheTimers.SIX_HOURS,
STATIC: cacheTimers.MONTH,
STATUS: cacheTimers.NONE,
MATCH: cacheTimers.MONTH,
MATCH_LIST: cacheTimers.ONE_HOUR,
RUNES_MASTERIES: cacheTimers.WEEK,
STATS: cacheTimers.HOUR,
SUMMONER: cacheTimers.DAY
}
If you pass in cacheOptions, but not how long you want each type of request to be cached (cacheTTL object), then by default you'll use the above timers.
To pass in your own custom timers, initialize Kindred like this:
import TIME_CONSTANTS from KindredAPI.TIME_CONSTANTS // for convenience, has a bunch of set timers in seconds
var k = new KindredAPI.Kindred({
key: RIOT_API_KEY,
defaultRegion: REGIONS.NORTH_AMERICA,
debug: true, // you can see if you're retrieving from cache with lack of requests showing
limits: [ [10, 10], [500, 600] ],
cacheOptions: CACHE_TYPES[0], // in-memory
cacheTTL: {
// All values in SECONDS.
CHAMPION: whatever,
CHAMPION_MASTERY: whatever,
CURRENT_GAME: whatever,
FEATURED_GAMES: whatever,
GAME: whatever,
LEAGUE: whatever,
STATIC: TIME_CONSTANTS.MONTH,
STATUS: whatever,
MATCH: whatever,
MATCH_LIST: whatever,
RUNES_MASTERIES: whatever,
STATS: whatever,
SUMMONER: TIME_CONSTANTS.DAY
}
})
Feel free to make a PR regarding anything (even the smallest typo or inconsistency).
There are a few inconsistencies and weird things within this libary that I don't know how to address since this is my first API wrapper and I'm still quite a big newbie.
For example, the two methods getChamp() and getChampion() are actually different.
getChamp() targets the champ endpoint
getChampion() targets the static endpoint
I didn't want to attach getChampion() with 'static' in any way or form since I thought it looked kind of annoying because then I would want to attach static to the other static methods as well (maybe that's better?).
March 31: I decided to combat the above by just namespacing the functions (k.Static.getChampion vs k.Champion.getChampion/get). The original functions are still usable though.
Right now, the code is also quite messy and there is a lot of repeated code. Function definitions are quite long because I include many aliases as well. I haven't thought of an elegant way to make a magic function that manages to work for every single endpoint request yet.
Any help and/or advice is appreciated!
FAQs
Node.js League of Legends v3 API wrapper with built-in rate-limiting (enforced per region, burst/spread, follows retry headers, app/method rate-limiting), caching (in-memory, Redis), automatic retries, and parameter checking.
The npm package kindred-api receives a total of 51 weekly downloads. As such, kindred-api popularity was classified as not popular.
We found that kindred-api demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.