Giter Club home page Giter Club logo

kayn's Introduction

A small Node.js library to work with Riot's League of Legend's API.

NPM

Build Status API Cov. Badge codecov dependencies Status

Simple example using promises and callbacks

const _kayn = require('kayn')
const Kayn = _kayn.Kayn
const REGIONS = _kayn.REGIONS

const kayn = Kayn(/* process.env.RIOT_LOL_API_KEY */)(/* optional config */)

kayn.Summoner.by
    .name('Contractz')
    .region(REGIONS.NORTH_AMERICA) // same as 'na'
    .callback(function(unhandledError, summoner) {
        kayn.Matchlist.by
            .accountID(summoner.accountId)
            /* Note that region falls back to default if unused. */
            .query({
                season: 11,
                queue: [420, 440],
            })
            .then(function(matchlist) {
                console.log('actual matches:', matchlist.matches)
                console.log('total number of games:', matchlist.totalGames)
            })
            .catch(console.error)
    })

Same example (as the above) using async/await, destructuring, and template strings

import { Kayn, REGIONS } from 'kayn'

const kayn = Kayn(/* process.env.RIOT_LOL_API_KEY */)(/* optional config */)

const main = async () => {
    const { accountId } = await kayn.Summoner.by.name('Contractz')
    // ^ default region is used, which is `na` unless specified in config
    const { matches, totalGames } = await kayn.Matchlist.by
        .accountID(accountId)
        .query({ season: 11, champion: 67 })
        .region(REGIONS.NORTH_AMERICA)

    console.log('actual matches:', matches)
    console.log(`total number of games: ${totalGames}`)
}

main()

Example of getting match information from 100 matches at once

const getChampionIdFromMatch = (match, accountId) => {
    for (let i in match.participantIdentities) {
        if (
            match.participantIdentities[i].player.currentAccountId ===
            accountId
        ) {
            return match.participants[parseInt(i)].championId
        }
    }
}

const main = async kayn => {
    const { accountId } = await kayn.SummonerV4.by.name('Contractz')
    const rankGameIds = (await kayn.MatchlistV4.by
        .accountID(accountId)
        .query({ queue: 420 })).matches.map(el => el.gameId)
    const championIds = await Promise.all(
        rankGameIds.map(async gameId => {
            const matchDetail = await kayn.MatchV4.get(gameId).region('na')
            return getChampionIdFromMatch(matchDetail, accountId)
        }),
    )
    console.log(championIds.slice(0, 5), championIds.length)
}

Example of getting DDragon information of banned champions in a game

const main = async (kayn) => {
    const match = await kayn.Match.get(2877485196)
    const bans = match.teams.map(m => m.bans).reduce((t, c) => t.concat(c), [])
    const ids = bans.map(b => b.championId)
    const ddragonChampions = await kayn.DDragon.Champion.listDataByIdWithParentAsId()
    const champions = ids.map(id => ddragonChampions.data[id])
    console.log(champions)
}

More Examples

Example Selected Implementations

... More Examples

Table of Contents:

Features

Rate Limiting

Handled by Colorfulstan's wonderful riot-ratelimiter.

See RATELIMITING.md.

All Endpoints Covered

Caching

Currently supports a basic JS cache (for simple scripts), node-lru-cache, and Redis.

Compatible with Callbacks, Promises, Async / Await

TypeScript Support

Works immediately upon installation.

As of v0.8.0, full DTO's are provided thanks to MingweiSamuel's auto-updated Swagger JSON.

Methods

Check out ENDPOINTS.md to see kayn's methods, as well as the endpoints covered.

Documentation

The auto-generated ESDoc documentation can be found here.

Installation and Usage

The minimum required Node.js version is v7.6.0 for native async/await support (there's only a mere line in the codebase, though).

npm

npm i --save kayn

yarn

yarn add kayn

Quick Setup with Default Config

const { Kayn, REGIONS } = require('kayn')
const kayn = Kayn('RGAPI-my-api-key')(/*{
    region: REGIONS.NORTH_AMERICA,
    apiURLPrefix: 'https://%s.api.riotgames.com',
    locale: 'en_US',
    debugOptions: {
        isEnabled: true,
        showKey: false,
    },
    requestOptions: {
        shouldRetry: true,
        numberOfRetriesBeforeAbort: 3,
        delayBeforeRetry: 1000,
        burst: false,
        shouldExitOn403: false,
    },
    cacheOptions: {
        cache: null,
        timeToLives: {
            useDefault: false,
            byGroup: {},
            byMethod: {},
        },
    },
}*/)

Note: Any config passed in is deeply merged with the default config.

Environment Variables

const kayn = Kayn(/* process.env.RIOT_LOL_API_KEY */)(myConfig)

Although it is possible to manually pass in the API key, it is preferable to store the key in a secret file (which should not be committed).

This allows kayn to be constructed like in the above code.

# filename: .env
RIOT_LOL_API_KEY=RGAPI-my-api-key

Callbacks

kayn.Summoner.by.name('Contractz').callback(function(err, summoner) {
    // do something
})

Promises

kayn.Summoner.by.name('Contractz')
    .then(summoner => doSomething(summoner))
    .then(console.log)
    .catch(error => console.error(error))

Async / Await

const main = async () => {
    const ctz = await kayn.Summoner.by.name('Contractz')
}

Region

This forces a request to target a specific region instead of the default region set in kayn's config. If .region() is not used, kayn will use the default region to make requests.

kayn.Summoner.by.name('hide on bush')
    .region(REGIONS.KOREA)
    .callback(function(error, summoner) {
        doSomething(summoner)
    })

Region without Throwing

There is another utility method in case if you want to avoid handling exceptions caused by .region(). This method simply catches .region()'s exception, and so it will fall back to the default region as well.

kayn.Summoner.by.name('hide on bush')
    .regionNoThrow(null) // No error thrown. Uses default region.

kayn.Summoner.by.name('hide on bush')
    .regionNoThrow(3) // Same as above.

kayn.Summoner.by.name('hide on bush')
    .regionNoThrow('kr524') // Same as above.

Query Parameters

You can pass in strings, numbers, or arrays as values. Just pass in whatever Riot expects. :)

kayn.Matchlist.by.accountID(3440481)
    .region(REGIONS.KOREA)
    .query({
        season: 9,
        queue: [420, 440],
    })
    .callback(function(err, matchlist) {
        console.log(matchlist.matches.length)
    })

Request Errors

Errors as of v0.8.7 return the following error object:

{
    statusCode: 42, // some random number
    url: '', // the debug URL that is used in logging as well
    error: {} // the rest of the error object
}

DDragon Usage

Version

This forces a request to target a specific version and is no longer mandatory as of v0.8.22.

kayn.DDragon.Champion.list()
    .version('8.15.1') /* Explicit */
    .callback(function(error, champions) {
        console.log(champions)
    })

// Let's say the config region is North America and that the latest version
// for the champion endpoint in NA is 8.24.1
kayn.DDragon.Champion.list() // Implicitly targets 8.24.1
    .callback(function(error, champions) {
        console.log(champions)
    })

// Same thing as above, but gets the versions for a different region from the configuration.
kayn.DDragon.Champion.list().region('br')
    .callback(function(error, champions) {
        console.log(champions)
    })

Notes about Optional Version Argument

Whenever you make a request that does not have a version passed in, kayn will automatically grab all the JSON versions associated with your default region or through the region() method.

If you do not have caching enabled, note that each request with no version passed will always send an additional request for grabbing the version. Otherwise, it follows standard caching.

No Cache Example
// Calls the Realm list endpoint to get the version for North America's champion data. It then gets the champions.
kaynWithNoCache.DDragon.Champion.list()

// Gets versions for 'kr' instead of default region.
kaynWithNoCache.DDragon.Champion.list().region('kr')
Cache Example
// Calls Kayn.Realm.list(), caches it, and then gets the version for North America's champion data. It then gets the champions. 
kaynWithCache.DDragon.Champion.list()
// Retrieves the cached version (because we already called the realm endpoint under the hood) for North America's champion data and then gets the champions.
kaynWithCache.DDragon.Champion.list()

Region

This is only for /cdn/data//.json-esque requests. It is a helper method that allows kayn to not force the user to have to pass in a version.

kayn.DDragon.Champion.list()
    .region('kr')
    .locale('ko_KR')

Locale

This forces a request to target a specific locale instead of the default locale set in kayn's config. If .locale() is not used, kayn will use the default locale to make requests.

kayn.DDragon.Champion.list()
    .version('8.15.1')
    .locale('sg_SG')
    .callback(function(error, champions) {
        console.log(champions)
    })

kayn.DDragon.Champion.list()
    .version('8.15.1')
    /* Locale not specified. Uses default locale, which is 'en_US' in the config and is adjustable. */
    .callback(function(error, champions) {
        console.log(champions)
    })

Realm -> Version Example

This example firstly hits the Realm endpoint, which grabs a list of versions where each version corresponds with some type of DDragon endpoint (Champion, Item, etc). I then grab the version associated with the Champion endpoint to get the latest static champion list for the NA region. Note that kayn.DDragon.Realm.list uses the default region or takes in a region specified, which is why I am able to avoid passing in extra arguments.

const main = async () => {
    const kayn = Kayn('RGAPI-my-api-key')({
        region: REGIONS.NORTH_AMERICA,
        locale: 'en_US',
        debugOptions: {
            isEnabled: true,
            showKey: false,
        },
        requestOptions: {}, // Doesn't apply to DDragon requests
        cacheOptions: {
            cache: new LRUCache({ max: 5000 }),
            timeToLives: {
                useDefault: true, // Cache DDragon by default!
            },
        },
    })

    /*
        kayn.DDragon.Realm.list('na') =>
        {
        	"n": {
        		"item": "8.17.1",
        		"rune": "7.23.1",
        		"mastery": "7.23.1",
        		"summoner": "8.17.1",
          		"champion": "8.17.1",
          		"profileicon": "8.17.1",
        		"map": "8.17.1",
          		"language": "8.17.1",
        		"sticker": "8.17.1"
          	},
        	"v": "8.17.1",
          	"l": "en_US",
          	"cdn": "https://ddragon.leagueoflegends.com/cdn",
          	"dd": "8.17.1",
          	"lg": "8.17.1",
          	"css": "8.17.1",
          	"profileiconmax": 28,
          	"store": null
        }
    */

    // Same as `const championVersion = data.n.champion`.
    const { n: { champion: championVersion } } = await kayn.DDragon.Realm.list(/* default region */)
    const championList = await kayn.DDragon.Champion.list().version(championVersion)
    console.log(championList)
}

dataById and dataByIdWithParentAsId

As of v0.8.19, the following DDragon.Champion functions have been added:

DDragon.Champion.getDataById(championName: string)
DDragon.Champion.getDataByIdWithParentAsId(championName: string)
DDragon.Champion.listDataById()
DDragon.Champion.listDataByIdWithParentAsId()
DDragon.Champion.listFullDataById()
DDragon.Champion.listFullDataByIdWithParentAsId()

Given:

{
  ...
	"data": {
        ...
		"Aatrox": {
            ...
			"id": "Aatrox",
			"key": "266"
		}
	}
}

someFunctionDataById changes the shape to:

{
  ...
	"data": {
        ...
		"Aatrox": {
            ...
			"id": "266",
			"key": "Aatrox"
		}
	}
}

while someFunctionDataByIdWithParentAsId changes the shape to:

{
  ...
	"data": {
    ...
		"266": {
            ...
			"id": "266",
			"key": "Aatrox"
		}
	}
}

These functions also cache their own data, separate from the functions that make the actual HTTP requests. They also have their own method names, and are cached under the 'DDRAGON' namespace.

More Examples

Configuration

region

Default: 'na'

locale

Default: 'en_US'

apiURLPrefix

Default: 'https://%s.api.riotgames.com'

Request Options

numberOfRetriesBeforeAbort

Default: 3 attempts.

delayBeforeRetry

Default: 1000 ms (1 second).

This option will be scrapped in the future in favor for more flexibility (linear, exponential, random, etc).

burst

Default: false.

Disabled by default in favor of spread.

true => riotratelimiter will use its burst strategy.

false => riotratelimiter will use its spread strategy.

shouldExitOn403

Default: false.

This option will force the process to quit if your API key is blacklisted or invalid.

Cache Options

To cache, firstly create some cache that implements the get and set functions that kayn interfaces with, and then pass that cache instance to cacheOptions.cache.

ttls are method ttls. This part is pretty inconvenient right now. Suggestions are welcome.

Current caches:

For the last two caches, the options that they take are the same options that their respective docs list out. In other words, I basically export wrappers that takes in the options and just passes it to the actual cache client.

import { Kayn, REGIONS, METHOD_NAMES, BasicJSCache, LRUCache, RedisCache } from 'kayn'

const redisCache = new RedisCache({
    host: 'localhost',
    port: 5000,
    keyPrefix: 'kayn',
    password: 'hello-world',
    // etc...
})

const lruCache = new LRUCache({
    max: 500,
    dispose: (key, value) => {},
    length: (value, key) => 1,
    // maxAge intentionally is disabled
})

const basicCache = new BasicJSCache()

const myCache = redisCache // or basicCache/lruCache

const kayn = Kayn(/* optional key */)({
    region: 'na',
    locale: 'en_US',
    debugOptions: {
        isEnabled: true,
        showKey: false,
    },
    requestOptions: {
        shouldRetry: true,
        numberOfRetriesBeforeAbort: 3,
        delayBeforeRetry: 1000,
    },
    cacheOptions: {
        cache: myCache,
        timeToLives: {
            useDefault: true,
            byGroup: {
                DDRAGON: 1000 * 60 * 60 * 24 * 30, // cache for a month
            },
            byMethod: {
                [METHOD_NAMES.SUMMONER.GET_BY_SUMMONER_NAME]: 1000, // ms
            },
        },
    },
})

kayn.Summoner.by
    .name('Contractz')
    .then(() => kayn.Summoner.by.name('Contractz'))

/*
200 @ https://na1.api.riotgames.com/lol/summoner/v3/summoners/by-name/Contractz
CACHE HIT @ https://na1.api.riotgames.com/lol/summoner/v3/summoners/by-name/Contractz
*/

TTLs???

Check out Enums/default-ttls and Enums/method-names to find the constants that you can use as keys within byGroup and byMethod.

Here is the order in which ttl's resolve (highest-priority first):

  1. byMethod
  2. byGroup
  3. useDefault

Note that if you're using the ttls prop before v0.8.9, you're perfectly fine. ttls is the source of truth, and has the highest priotity over the above 3 ways.

byMethod

byMethod takes pairs of Enums/method-names, which are just unique (string-type) identifiers and the ttl value you desire.

cacheOptions: {
    timeToLives: {
        byMethod: {
            [METHOD_NAMES.SUMMONER.GET_BY_SUMMONER_NAME]: 1000,
        }
    }
}

// Same as passing this:
// 'SUMMONER.GET_BY_SUMMONER_NAME': 1000,
// Constants just help for auto-complete.

byGroup

byGroup takes pairs as well. The key difference is that it follows how Riot groups their API methods on their developer interface. You can check Enums/method-names once again to see how methods are grouped toegher.

byGroup has lower priority than byMethod. This as you'll see is flexible.

cacheOptions: {
    timeToLives: {
        byGroup: {
            MATCH: 60 * 60 * 24 * 30, // 30 days
        }
        byMethod: {
            [METHOD_NAMES.MATCH.GET_MATCHLIST]: 1000,
        }
    }
}

// Enums/method-names
/*
const MATCH = {
    GET_MATCH: 'MATCH.GET_MATCH',
    GET_MATCHLIST: 'MATCH.GET_MATCHLIST',
    GET_RECENT_MATCHLIST: 'MATCH.GET_RECENT_MATCHLIST',
    GET_MATCH_TIMELINE: 'MATCH.GET_MATCH_TIMELINE',
    GET_MATCH_IDS_BY_TOURNAMENT_CODE: 'MATCH.GET_MATCH_IDS_BY_TOURNAMENT_CODE',
    GET_MATCH_BY_TOURNAMENT_CODE: 'MATCH.GET_MATCH_BY_TOURNAMENT_CODE',
}
*/

What this does is set the cache ttl of every single one of the above match method to 30 days. However, since byMethod has higher priority, we are then able to overwrite the Matchlist.by.accountID ttl, making it only be cached for a second instead.

This is good because the other match methods rarely change, while matchlists can change every 20 minutes.

useDefault

Simply set useDefault in timeToLives to true. This option basically sets ttls I thought made some sense. useDefault has the lowest priority, which means you can set it to true, and then overwrite it on a case-by-case basis using byGroup and byMethod.

Flushing the Cache

// BasicJSCache O(1)
// synchronous
kayn.flushCache()
// this has been turned into a promise so that it can be chained.
// still can just be called normally though.
// the `data` parameter returns "OK" just like in the RedisCache.
async1
  .then(() => kayn.flushCache())
  .then(console.log) // prints OK always. there's no way to get an error.
  .catch(console.err)

// RedisCache O(N)
// asynchronous
kayn.flushCache(function (err, ok) {
  console.log(ok === "OK")
})

const flush = async () => {
  try {
    await kayn.flushCache() // returns "OK", but not really necessary to store.
  } catch (exception) {
    console.log(exception)
  }
}

async1
  .then(() => async2())
  .then(() => kayn.flushCache())
  .then(console.log)
  .catch(console.log)

Debug Options

showKey

When logging, URLs printed out on the screen will also have the API key query string attached to it, allowing the user to conveniently inspect the response if necessary.

loggers

kayn now uses debug for all logging purposes.

Here are the current namespaces:

kayn

  • init
  • request
    • incoming
      • success
      • error
    • outgoing
  • cache
    • set
    • get

To enable debugging, firstly make sure config.debugOptions.isEnabled is true. Then, run your program with the desired DEBUG environment variables.

For example, if you wish to only see the request errors (404, 420, 503, 500, etc), run:

DEBUG=kayn:request:incoming:error <command>
# DEBUG=kayn:*:error works too.

...where command runs the script/server/whatever (npm run start, yarn start, node index.js).

To enable all loggers, simply run:

DEBUG=kayn:* <command>

My Project

If you're interested in what I have built using this library, here's a small web application I made, along with the original reddit post.

One Tricks:

Here are the requests stats for anyone interested.

Note that my requests stats are inflated since I'm not really caching at the moment (lol).

Alt text

Alt text

Bugs

Feel free to make an issue (bug, typos, questions, suggestions, whatever) or pull request to fix an issue. Just remember to run prettier (via yarn lint).

Package commands:

  • yarn lint for prettier (will add eslint to prettier soon)
  • yarn example to run the various files in ./examples
  • yarn build to build. yarn example runs this command
  • yarn test

General Workflow

Here's my general workflow when it comes to kayn.

Note: You don't have to worry about editor configuration as long as you follow the steps.

  • If possible, create a unit test (make more if applicable)
  • Set describe.only on your test(s)
  • Run yarn test to make sure test is failing
  • Write implementation and run yarn test on completion
  • You can manually test requests in example.js using your own API key
    • Preferrable just to use a .env file with kayn's default key (RIOT_LOL_API_KEY)
    • Run yarn example and verify behavior manually
  • Remove describe.only from your tests and run the entire test suite
  • When tests pass and manual testing went well, run yarn lint
  • Commit and push! For forks, make sure you check out a new branch

Changelog

CHANGELOG.md.

As long this library is pre-1.0.0, breaking changes may be made, but will be documented and will generally not be drastic. Upon 1.0.0, SemVer will be followed strictly.

Disclaimer

kayn isn't endorsed by Riot Games and doesn't reflect the views or opinions of Riot Games or anyone officially involved in producing or managing League of Legends. League of Legends and Riot Games are trademarks or registered trademarks of Riot Games, Inc. League of Legends © Riot Games, Inc.

FAQ

My requests seem to take a long time.

You are most likely using the default spread rate limiting strategy, which spreads out your requests over rate limit periods.

Set requestOptions.burst to true to burst your requests instead.

I'm getting (a lot of) 429's.

If you're getting 429's, you're most likely processing huge amounts of requests that probably needs to be broken into smaller pieces (while also setting requestOptions.burst to false), needs effective caching, and/or requires a more powerful, but smaller library like riot-lol-api, which also happens to be made by a Riot employee IIRC. TeemoJS would probably work well too!

Occasionally, if requestOptions.burst = true, the rate limiter may get out of sync if you're running thousands of concurrent requests (like onetricks.net when building stats), which can cause 429's that will propagate until you're blacklisted.

It is thus ideal to use the spread strategy when working on apps that process a ton of requests because there is a much lower risk of getting a 429. However, for small or dev scripts, bursting your requests is a lot better.

External Links

kayn's People

Contributors

cnguy avatar ilshidur avatar namtsua avatar oiyouyeahyou avatar raitono avatar scotsoo avatar turtlehero avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

kayn's Issues

How to check if a summoner is or has been recently online?

Hi, I know that this is not really an issue but wanted to know if there is a way to check if the summoner is playing now? I saw this website called lolnexus.com where you type the summoner and its region and they display the match list. I think that this could be achieved with the Spectator V3 endpoint but I am not so sure. Could you please give me some hint? Will appreciate it! Thanks!

Update Examples

Delete old examples, add new & better ones, dot dot dot.

Untagged old commits:
6eab42e refactor: prettier everything / clean up examples folder
e6aada4 docs (examples): Add verification example
c80a636 fix (examples): beginIndex should start at 100 since we already proce…

API Calls not asynchronous?

After upgrading to kayn from kindred, I noticed that performing a few api calls at once was vastly slower than it was on kindred. I ran some tests with the following calls: https://pastebin.com/2BkFDw6Q (pastebin because code wasnt formatting correctly)
What I noticed were as follows: The kayn api takes ~9-12 seconds for the above 12 api calls when the sync rate limiter is being created, and after they get created it drops down to ~6 seconds. Whereas with the kindred api, all of those calls get completed in just under a second. No caching is being used, and it does not appear as though there are any calls that are being retried.

Setting Header Limits

Lets say you had 100 calls per 10 seconds, and next call you get it's down to 10 calls per 10 seconds.

Not sure how I'd do this cleanly (though Kindred already is pretty unclean).

Caching interface should be improved

Not everyone wants to type out all the methods they want to cache...

Some people might want to cache the entire static service
Some people might want to cache specific method ttls.

positional rank endpoints

[ 'GET /lol/league/v4/positional-rank-queues',
'GET /lol/league/v4/positions/{positionalQueue}/{tier}/{division}/{position}/{page}' ]

missing these 2 endpoints, firstly going to remove all the v3 stuffs/update types in 1 patch first though

Static Data

GOAL: FINISH BY AUG 26 (1 day before deadline)

Code quality will be sucky, because I started a few days before deadline (aka procrastination). However, I will make sure that it improves over the next couple of weeks after I release it firstly. :)

base endpoints

  • Champion
    • get
    • list
    • listFull
  • Item (list)
  • Language (list)
  • LanguageString (list)
  • Map (list)
  • ProfileIcon (list)
  • Realm
  • ReforgedRunes
  • SummonerSpell (list)
  • Version

not mastery/rune because there's no need? I might add it anyways because it's just a few lines lol

  • debug method for each
    • success
    • error (404 is probably main, but try 503 500? dont know)
  • cache method available for each
  • docs (changelog, pluralsingular, RuneReforged->RunesReforged)
    • Endpoints.md
  • add default locale option

============
out-of-scope for now (aka the deadline release)? comment otherwise if you think it'd be useful

prolly add this next release once I figure it out more thoroughly

  • [] automatic version grabbing for optional version. should respect cache options Not possible

tarball tgz or link (not sure what the use case is yet)
rune (do we need this anymore)
mastery (do we need this anymore)
TS types (will be added in subsequent releases)
images? ddragon provides img urls too (is this necessary)

Experimental API Additions (Summoner)

This is TBD, but I'm thinking of adding additional helpers that may help the library be a lot easier to use.

The base API will remain untouched. I'm simply just adding some helpers (that will mirror kayn's request API) that if useful for my LoL project, would probably be useful for other projects as well. These helpers simply use kayn to reduce the amount of redundant requests and make the code nicer IMO (you don't have to manually call every endpoint), and so performance should be ok if you care about that (one of kindred-api's flaw was that you can make identical requests if you're not careful).

The response objects themselves remain untouched as well! (accountId, name, etc for example)

This idea is kinda based off of Cassiopeia (https://github.com/meraki-analytics/cassiopeia/blob/master/examples/summoner.py) where there are ghost objects that represent what you want, but in this case it'll be asynchronous because of async/await and promises.

I'm going to see if this API works well on one of my other hobby projects before I work on it further though. I'm not going to abstract everything in the API since that's a headache, but I think that having a lot of helpers for specifically the summoner response would be useful since it's used in every other endpoint.

const Summoner = Rhaast(kayn).Summoner;

const main = async () => {
    // A shell that simply has a name property and helper functions.
    const summoner = Summoner({ name: 'Contractz' });

    // summoner.name = 'Contractz';
    // summoner.region = Summoner(args, **region**) or kayn's default region;

    // get() fills out the Summoner properties (name, accountId, etc) while also returning
    // the response.
    const fullSummoner = await summoner.get();
  
    /*
    { name: 'Contractz',
  id: 32932398,
  accountId: 47776491,
  region: 'na',
  profileIconId: 3155,
  revisionDate: 1512621047000,
  summonerLevel: 30 }
     */
    
    // After .get() is called, the **summoner** object in the first line is 'filled out'
    // and can now be accessed just like const fullSummoner.
    // but fullSummoner is simply the raw response data while
    // summoner has tons of helper methods.
     console.log(summoner.id === fullSummoner.id); // true

    // Getting the matchlist with the same summoner helper above. :)
    // Notice how you're able to call this without any mention of the account ID.
    // I guess to refresh the data, users must call get() manually.
    const matchlist = await summoner.matchlist();

   // should be able to pass in config / region too
   // matchlist(args, config, region)
};

main();

ofc one problem is that the data will stay the same unless a new object is constructed (atm), more thinking needs to be done XD

JSON.parse() throws errors when response contains an array

I haven't done much testing but some endpoints seem to respond with arrays which cause JSON.parse() to fail. For example:
ChampionMastery.list returns a stringified array that parses with no issue.
TournamentStub.create returns a normal array that throws errors in JSON.parse().

I'm not sure if it's Riots fault or something in the lib causing it but I'm using this workaround for now.
I haven't tested it with every endpoint but checking for array and calling stringify on it seems to be a working bandaid until a permanent solution is found.

const res = await this.limiter.executing({

let res = await this.limiter.executing({
...
try { 
  if (Array.isArray(res)) {
    res = JSON.stringify(res)
   }
   const blob = JSON.parse(res)
...
}

TypeScript Optional Response Parameters

One huge TypeScript problem (though it's ignorable) is that the response body properties are all optional.

Problem

sw2dts generates optional types by default unless the user sets the required tag in the property of a component.

Point:
  type: object
  properties:
    x:
      type: number
    y:
      type: number
  required: # <-- This
    - x
    - y

=>

export interface Point {
  x: number
  y: number
}

Without the required tags, we end up with

export interface Point {
   x?: number
   y?: number
}

The auto-generated JSON that we read from (http://www.mingweisamuel.com/riotapi-schema/openapi-3.0.0.yml) does not have the required property. However, I do not believe that is the problem of the JSON we read from. required (based on what I know) is based on parameters (path & query) for an endpoint, and not for the response classes themselves.

Manual Workaround

Workaround 1

const name: string = summoner.name // BREAKS. string != string | undefined
const name: string = summoner.name! // probably the best way (non-null assertion operator)
const name = summoner.name as string // force it to work if you know it's not undefined

Workaround 2

Manual file-editing, which is not fun.

If I forget about this and you're having issues due to bad code and run-time errors hit me up

Static Data

Hi, i'm having problem with the static data - getChampions
when i start my node server, and try to call the API, it's return ok

but if I refresh the page, it's return null, and in my debug console, they send me a message of cache.

this problem is with cache ? can you help me ?

MongoDB Caching and Reset Caching

I read in the README.md that if there were people who wanted it, you would work on a mongodb caching system along with a reset function. This is my way of saying I could use these additions. And I assume that the reset function is meant to clear the cache and allow you to query directly from the riot api. Thanks.

Impossible to set Redis config

First of all, I really like your lib. Keep up the good work !

I'm facing issues when trying to set the Redis cache options.
I got this :

this.api = new KindredApi.Kindred({
  key: apiKey,
  defaultRegion: region,
  debug: !prod,
  showKey: true,
  showHeaders: false,
  limits: prod ? KindredApi.LIMITS.PROD : KindredApi.LIMITS.DEV,
  spread: false,
  retryOptions: {
    auto: true,
    numberOfRetriesBeforeBreak: 3,
  },
  timeout: 5000,
  cache: new KindredApi.RedisCache({ // Problem here, I can't set the options
    host: cache.redis.host || 'localhost',
    port: cache.redis.port || 6379,
    keyPrefix: cache.redis.prefix || 'league-tooltips_',
  }),
  cacheTTL: {
    STATIC: cache.TTL,
  },
});

The problem is that it is impossible to set the Redis config I want. They always get overridden by your default config. See here. Your code behaves this way : whatever the user gives, the default options are overriding it.

The way to fix this is to change the line to this :

var options = Object.assign({
  host: '127.0.0.1',
  port: 6379,
  keyPrefix: 'kindredAPI-'
}, opts || {});

The second problem is that the Wiki needs more docs about the RedisCache options. I had to dig in the lib code.

If you don't have the time to fix this, I can send you a PR with the fixed code :)

champListData

I trying to use the static data, get List Champion and get Champion By Id
but if I try to use the champListData: All, doesn't work

i Trying by two ways

 k.Static.champion({
        id: champId, champListData: 'all'
    })

and

const config = {
        options: {
            champData: 'all'
        }
    }
    k.Static.champions({ config }

is something wrong ?
thanks

DDragon endpoint error if version() is not specified

Version:

0.9.1

Steps to reproduce:

The following code, taken from the docs:

kayn.DDragon.Champion.list() // Implicitly targets 8.15.1
    .callback(function(error, champions) {
        console.log(champions)
    })

Expected:

Expected to return an object containing all champions data.

Result:

(node:8844) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'get' of null
    at DDragonRequest.<anonymous> (C:\rito_api\node_modules\kayn\dist\lib\RequestClient\DDragonRequest.js:144:44)
    at Generator.next (<anonymous>)
    at step (C:\rito_api\node_modules\kayn\dist\lib\RequestClient\DDragonRequest.js:18:191)
    at C:\rito_api\node_modules\kayn\dist\lib\RequestClient\DDragonRequest.js:18:437
    at new Promise (<anonymous>)
    at DDragonRequest.<anonymous> (C:\rito_api\node_modules\kayn\dist\lib\RequestClient\DDragonRequest.js:18:99)
    at DDragonRequest.callback (C:\rito_api\node_modules\kayn\dist\lib\RequestClient\DDragonRequest.js:164:21)
    at main (C:\rito_api\index.js:31:6)
    at Object.<anonymous> (C:\rito_api\index.js:62:1)
    at Module._compile (module.js:652:30)
    at Object.Module._extensions..js (module.js:663:10)
    at Module.load (module.js:565:32)
    at tryModuleLoad (module.js:505:12)
    at Function.Module._load (module.js:497:3)
    at Function.Module.runMain (module.js:693:10)
    at startup (bootstrap_node.js:191:16)
    at bootstrap_node.js:612:3
(node:8844) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:8844) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

After adding .version('8.15.1') to the initial code the result is the expected champion data.

How using another Region

Hello, in initialization, this code is :

defaultRegion: REGIONS.NORTH_AMERICA,

but how can i used for EUW or another ?

How to get the rank depending on a Queue and region?

Hi, I really liked your API and decided to use it in a project. But I have one issue - how do I get the current rank of a summoner by passing as an option different queues and regions?

It would be very helpful if you can help!

Handling JSON.Parse exceptions

I sometimes get weird JSON.parse errors, especially when processing requests from all regions at once or parsing large objects.

I'm thinking of adding an option that allows try/catching the JSON.parse calls, and on the catch it could simply just return an empty object or something (lol probably not idk ahh).

v4

https://discussion.developer.riotgames.com/articles/6579/player-universally-unique-identifiers-and-a-new-se.html

Scheduled Deprecation Date: January 14th, 2019

Going to start early and going to separate V4 endpoints from the regular endpoints for now until I think of better ideas.,

Summoner
SummonerV4

I'll release some deprecation warnings later on tomorrow as well. Once the endpoints are actually removed, I can replace the regular endpoints with the V4 ones.

SummonerV4 => Summoner, while SummonerV4 will still work

Checklist:

  • CHAMPION MASTERIES V4
  • LEAGUE V4
  • MATCH V4
  • SPECTATOR V4
  • SUMMONER V4
  • THIRD PARTY CODE V4
  • TOURNAMENT STUB V4
  • TOURNAMENT V4
  • TS Bindings
  • Release
  • Change deprecation date to January 28th (as noted on Riot API docs)

[BREAKING] Improve Error Returned by Request

Currently, users only receive a simple numeric status code when a request error occurs.

Quick example:

kayn.Summoner.by.name('adsfiudsaiufsadofiahfds') => 404

It would be better to be like other libraries and return a more proper error object that contains the status code and URL firstly.

I'll start with that and add more as I study other libraries.

Hanging

setInterval and setTimeouts stall out the process (but are harmless). A developer can use unref to make the process exit if there are no more events besides the interval/timeouts.

Colorfulstan/RiotRateLimiter-node@154eb8a

this works (thanks to https://github.com/MingweiSamuel), and is testable in

https://github.com/cnguy/kayn/tree/with-unref-changes

Will probably be released (using my temporary rate limiter fork) in the next version along with the Tournament stuffs + TS bindings

How to reproduce

Just make a request in some random index.js file, the process will harmlessly hang.

await kayn.SummonerV4.by.name('Contractz')
// process will not exit

Static Data Handling

Tags proposed by user (Djinnes).

This issue will reimplement the old functionality provided by static data (tags) where the feature will do the following:

  • cache individual data by id + params (which can be defined as a URL)
// Flashback to KindredAPI code
const rakanConfig = {
  id: 497,
  options: {
    tags: ['image', 'sanitizedDescription']
  },
  region: REGIONS.BRAZIL
}

k.Static.champion(rakanConfig)
        .then(data => console.log(data))
        .catch(err => console.error(err))

I forgot what the resulting JSON loos like but you get the point

This issue needs rewriting

Before starting on this, it might be a good idea to have some example JSON outputs given some tags.
Note that version and locale used to be tags but is actually already implemented through DDragon urls

summoner/v3 endpoint returning forbidden

Is there a reason this package is still using the v3 summoner endpoint? I am getting a forbidden error every time I make a call to this endpoint using this package.

Integration Tests

Create more integration tests that work off fixtures (values do not have to be exactly equal. for example, if a fixture has a summoner that was level 30, but they are now 31, you can simply assert restApiData.level >= fixture.level).

These tests will not be ran on Travis due to hanging, but this is fine as the tests can just be ran locally for anyone interested.

/typings/index.d.ts(293,1): error TS1046: A 'declare' modifier is required for a top level declaration in a .d.ts file.

Hey,

first: thanks for creating this great API!

I am currently trying to get kayn to work with TypeScript. Unfortunately, it looks like the typings are incorrect for TypeScript v3.2.2 and are missing multiple declare modifiers in front of top level classes/enums.

Repo for reproduction: https://github.com/TiFu/kayn-bug
Output of npm run build:

$ npm run build

> [email protected] build ~\test
> tsc

node_modules/kayn/typings/index.d.ts(293,1): error TS1046: A 'declare' modifier is required for a top level declaration in a .d.ts file.
npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! [email protected] build: `tsc`
npm ERR! Exit status 2
npm ERR!
npm ERR! Failed at the [email protected] build script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     ~\AppData\Roaming\npm-cache\_logs\2018-12-31T02_14_58_111Z-debug.log

A trivial fix would be prepending declare in front of all top level classes/enums in typings/index.d.ts and upgrading the TypeScript version in typings/package.json.

Special characters disallowed

It's possible that it's a mistake on my part, but I've been doing some testing and it appears that special characters that are allowed on League (in my case, the Á in ÁssBlaster) aren't working with the library. Testing the same method with different usernames without them works, which leads me to believe that these characters are screwing it up.

DDragon Helpers + CDragon + blahblahblah

In light of my lack of static compatibility to prevent breaking code, I'll be working also on a few things over the next few days to help out everyone, and be working on things early (pushed ChampionRotation yesterday) and so I have much more time to think about releases. I'll admit that sometimes I don't prioritize certain new things because I don't have to use them (I didn't use static endpoints for example. My app pretty much just uses matchlist+challenger league+master league+literally saved copies of static data). However, I do want kayn to be easy-to-use and have a lot of examples, docs, and copy-pastable code, + good defaults so...

  • My static endpoints are already broken, and I'm sure users either swapped libraries, swapped to DDragon, etcetc. So, they will be removed eventually as it's too late anyways... But:
    • CDragon will be added
    • DDragon helpers will be added
  • Types will be addressed
  • A ton of more ES5 callback examples and ES6+ async/await examples will be added. These examples will be based on real code used by others or myself (#46). I added an example early on how to use the new ChampionRotation endpoints and then convert it into the correct DDragon champion objects, for example.
  • Improve docs so it's easier to refer to examples and endpoint APIs, coverage pic (low priority obviously), tests (these tests are simple to make, low priority)

One example of a DDragon helper is:
For example, DDragon.Champion.get(266) =>

await DDragon.Champion.get(266) // If cache is enabled, DDragon.Champion.list is cached
/*
{
	"version": "6.24.1",
	"id": "Aatrox",
	"key": "266",
	"name": "Aatrox",
	"title": "the Darkin Blade",
	"blurb": "Aatrox is a legendary warrior, one of only five that remain of an ancient race known as the Darkin. He wields his massive blade with grace and poise, slicing through legions in a style that is hypnotic to behold. With each foe felled, Aatrox's ...",
	"info": {
		"attack": 8,
		"defense": 4,
		"magic": 3,
		"difficulty": 4
	},
	"image": {
		"full": "Aatrox.png",
		"sprite": "champion0.png",
		"group": "champion",
		"x": 0,
		"y": 0,
		"w": 48,
		"h": 48
	},
	"tags": [
		"Fighter",
		"Tank"
	],
	"partype": "BloodWell",
	"stats": {
		"hp": 537.8,
		"hpperlevel": 85,
		"mp": 105.6,
		"mpperlevel": 45,
		"movespeed": 345,
		"armor": 24.384,
		"armorperlevel": 3.8,
		"spellblock": 32.1,
		"spellblockperlevel": 1.25,
		"attackrange": 150,
		"hpregen": 6.59,
		"hpregenperlevel": 0.5,
		"mpregen": 0,
		"mpregenperlevel": 0,
		"crit": 0,
		"critperlevel": 0,
		"attackdamage": 60.376,
		"attackdamageperlevel": 3.2,
		"attackspeedoffset": -0.04,
		"attackspeedperlevel": 3
	}
}
*/

v0.9.4 rate limiter may be broken

After integrating this, #59, I did quick testing but didn't re-run all my examples to verify that the library still works properly. The hanging was solved, but batch promises don't seem to resolve via Promise.all.

For now stay on v0.9.3 or upgrade to v0.9.5 (they are the same things). v0.9.4 only introduces this hanging change.

What basically happens is the first promise of Promise.all will resolve, but the rest will not be resolved (in debug, the first request will be a success, while the rest are ignored, and then the process resolves).

For now I'll revert the change for v0.9.5 to prevent new install issues.

TS1005 Error

error in c:....\node_modules\kindred-api\typings\index.d.ts

[tsl] ERROR in c:....\node_modules\kindred-api\typings\index.d.ts(365,27)
TS1005: '(' expected.

I'm running nuxtjs and only accessing the kindred-api library from the backend. I'll fork and raise a PR for the fix

Improve Rate Limiter

At the moment, there are 429 rate limit errors when using the new development key limits (20/1, 100/120) with Kindred. The retry-afters are always 0 which implies that I'm barely missing (this just applies to the keys that work in a really short window like 20/1).

A temporary workaround is to just use the old LIMITS which is LIMITS.OLD_DEV.
Another workaround is to use the modified limits [20, 1.2], [100, 120] just to give some more buffer. Tinker it as necessary.

Improve Error Handling

I'll add the reproducible code later but let's say a user's not running DEBUG=kayn:* for npm/debug, if there is a certain error in the code (I believe it was Rejected Promise), they might get something like this:

rejectedblahblahblah: #Object

which is obviously useless information and should be improved...

if I remember my error is a simple object with no stacktraces too so let's fix that!

If you're interested in helping, leave a note here or on discord (and then I"ll leave a note here for you)

Most important thing is backwards compatibility.

Error: getLeaguePositions request FAILED; required params `id/summonerId/playerId` (int), `accountId/accId` (int), or `name` (string) not passed

function updateLeague(profile) { const { region } = profile; return leagueApi.League.positions({id: profile['summonerId'], region}) .then((league) => { let leagueRanks = []; for (let entry of league) { let { queueType, tier, rank, leaguePoints } = entry; leagueRanks.push({ queueType, tier, division: rank, leaguePoints }); } profile['leagueRanks'] = leagueRanks; return profile; }).catch((err) => { if (err.message == 'Error getting league data: 404 Not Found') { return profile; } else { handleErr(err); } }); }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.