igdb-ts
Version:
Unofficial IGDB API TypeScript wrapper.
767 lines (619 loc) • 25.7 kB
text/typescript
import axios from "axios"
import axiosRateLimit, { rateLimitOptions } from "axios-rate-limit"
import { AgeRating, AgeRatingContentDescription, AlternativeName,
Artwork, Character, CharacterMugShot, Collection, Company, CompanyLogo,
CompanyWebsite, Cover, ExternalGame, Franchise, Game, GameEngine,
TwitchAuthResponse, IGDBOptions, Query, ImageOptions, GameEngineLogo,
GameMode, GameVersion, GameVersionFeature, GameVersionFeatureValue, GameVideo, Genre,
InvolvedCompany, Keyword, MultiplayerMode, Platform, PlatformFamily, PlatformLogo,
PlatformVersion, PlatformVersionReleaseDate, PlatformWebsite, PlayerPerspective,
ReleaseDate, Screenshot, Search, Theme, Website, PlatformVersionCompany, UntypedIGDBOptions,
DefaultIGDBOptions, SearchableIGDBOptions, Filter, CombinedFilter, Endpoints,
} from './types'
export class IGDB {
private clientId: string
private clientToken: string
private clientSecret: string
private API_URL = "https://api.igdb.com/v4"
private IMAGE_URL = "https://images.igdb.com/igdb/image/upload"
public _axios = axiosRateLimit(axios.create(), { maxRPS: 4, perMilliseconds: 1000, maxRequests: 4 })
private onAccessTokenRetrieved: (clientToken: string, expiresAt: number) => void
private tokenExpiry: number
/**
* Use function init() before calling endpoints.
*/
constructor(){}
/**
* Initialises wrapper and generates access token (if needed) to call API.
* @param clientId - Twitch Client Id
* @param clientSecret - Twitch Client Secret
* @param clientToken - Twitch App Access Token
* @param rateLimitOptions - Axios Rate Limit Options. Default is 4 requests/s as per IGDB documentation.
* @param onAccessTokenRetrieved - Callback which can be used to save token to storage. Includes timestamp of when the token will expire.
*/
public async init(clientId: string, clientSecret: string, clientToken?: { token: string, tokenExpiry: number }, onAccessTokenRetrieved?: (token: string, tokenExpiry: number) => void, rateLimitOptions?: rateLimitOptions){
this.clientId = clientId
this.clientSecret = clientSecret
this.onAccessTokenRetrieved = onAccessTokenRetrieved
if (!clientToken) {
await this.getToken()
}
else {
this.clientToken = clientToken.token
this.tokenExpiry = clientToken.tokenExpiry
}
if (rateLimitOptions){
this._axios.setRateLimitOptions(rateLimitOptions)
}
}
/**
* Ensures token is valid and has not expired.
*/
private async validateToken(){
let currentTime = new Date().getTime()
if (currentTime >= this.tokenExpiry) {
await this.getToken()
}
}
/**
* Can be used to generate a new access token.
*
* As per documentation, the access token cannot be refreshed. Therefore, when an access token expires you need create a new one.
*
* https://dev.twitch.tv/docs/authentication/refresh-tokens
*/
private async getToken(){
let response = await axios.post<TwitchAuthResponse>(`https://id.twitch.tv/oauth2/token?client_id=${this.clientId}&client_secret=${this.clientSecret}&grant_type=client_credentials`)
.then((response) => {
if (response.data) {
return response.data
}
})
//expires_in is expressed in seconds not milliseconds therefore, *1000
const expiry = new Date().getTime() + (response.expires_in * 1000)
this.clientToken = response.access_token
this.tokenExpiry = expiry
this.onAccessTokenRetrieved(response.access_token, expiry)
}
private buildFilter({ filters, operators }: Filter){
//For every 2, filters there must be 1 operator 8 querys = 4 operators
if (filters.length === 0) {
throw Error("You need to provide at least one filter.")
}
if (filters.length > 1){
if (!operators || (operators.length != filters.length / 2) ) {
throw Error("You must provide 1 operator for every two filters.")
}
}
let _filter = ""
//if 1 then add
for (let i = 0; i < filters.length; i++){
if (i % 2 !== 0) {
_filter += ` ${operators[i - 1]} `
}
let f = filters[i]
_filter += `${f.field} ${f.postfix} ${f.value}`
}
return _filter
}
private buildCombinedFilter({ filters, operators }: CombinedFilter) {
if (filters.length <= 1) {
throw Error("A combined filter required at least two filters.")
}
else {
if (!operators || (operators.length != filters.length / 2) ) {
throw Error("You must provide 1 operator for every two filters.")
}
}
let _combinedFilter = ""
for (let i = 0; i < filters.length; i++){
if (i % 2 !== 0) {
_combinedFilter += ` ${operators[i - 1]} `
}
let f = filters[i]
_combinedFilter += `(${this.buildFilter(f)})`
}
return _combinedFilter
}
private buildOptions<T>(options?: IGDBOptions<T>){
//If no options, get all fields
if (!options) {
return `fields *;`
}
let result = ""
if (options.fields){
result += `fields ${options.fields.join(",")};`
}
else {
result += `fields *;`
}
if (options.exclude){
result += `exclude ${options.exclude.join(",")};`
}
if (options.filter){
let filtersAsString = this.buildFilter(options.filter)
result += `where ${filtersAsString};`
}
if (options.combinedFilter) {
let filtersAsString = this.buildCombinedFilter(options.combinedFilter)
result += `where ${filtersAsString};`
}
if (options.sortBy){
result += `sort ${options.sortBy.field} ${options.sortBy.order};`
}
if (options.search){
result += `search "${options.search}";`
}
if (options.limit){
result += `limit ${options.limit};`
}
if (options.offset){
result += `offset ${options.offset};`
}
return result
}
private buildUntypedOptions(options?: UntypedIGDBOptions){
//If no options, get all fields
if (!options) {
return `fields *;`
}
let result = ""
if (options.fields){
result += `fields ${options.fields.join(",")};`
}
else {
result += `fields *;`
}
if (options.exclude){
result += `exclude ${options.exclude.join(",")};`
}
if (options.filter){
let filtersAsString = this.buildFilter(options.filter)
result += `where ${filtersAsString};`
}
if (options.combinedFilter) {
let filtersAsString = this.buildCombinedFilter(options.combinedFilter)
result += `filters ${filtersAsString};`
}
if (options.sortBy){
result += `sort ${options.sortBy.field} ${options.sortBy.order};`
}
if (options.search){
result += `search "${options.search}";`
}
if (options.limit){
result += `limit ${options.limit};`
}
if (options.offset){
result += `offset ${options.offset};`
}
return result
}
private async request<T>(endpoint: string, options?: IGDBOptions<T>){
if (!this.clientToken) {
throw Error("Client token not found. Make sure to init() before requesting an endpoint.")
}
return this.validateToken().then(() => this._axios.post<T[]>(`${this.API_URL}/${endpoint}`, this.buildOptions(options), {
headers: {
'Client-ID': this.clientId,
'Authorization': `Bearer ${this.clientToken}`,
'Accept': 'application/json',
},
}))
.then((response) => {
return response.data
})
}
private buildMultiQuery(queries: Query[]){
if (queries.length < 2) {
throw Error("You need at least two queries to multiquery.")
}
if (queries.length > 10){
throw Error("You can only run a maxiumum of 10 queries.")
}
let query = ""
queries.forEach((q) => {
query += `query ${q.endpoint} "${q.resultName}" {${q.options ? this.buildUntypedOptions(q.options) : ""}};`
})
return query
}
/**
* Get a multiquery.
*
*
* Maximum of 10 Queries.
*
* {@link https://api-docs.igdb.com/#multi-query}
* @param {Array} queries - an array of [Query]({@link Query})
* @returns any[]
**/
public async multiQuery(queries: Query[]){
if (!this.clientToken) {
throw Error("Client token not found. Make sure to init() before requesting an endpoint.")
}
return this.validateToken().then(() => this._axios.post<any[]>(`${this.API_URL}/multiquery`, this.buildMultiQuery(queries), {
headers: {
'Client-ID': this.clientId,
'Authorization': `Bearer ${this.clientToken}`,
'Accept': 'application/json',
}
})).then((response) => {
return response.data
})
}
/**
* Get an Image URL.
* {@link https://api-docs.igdb.com/#images}
* @param {Object} imageOptions - [Image Options]({@link ImageOptions})
*
**/
public getImageUrl({ imageId, size, retina }: ImageOptions){
return `${this.IMAGE_URL}/t_${size}${retina && "_2x"}/${imageId}.jpg`
}
/**
* Generic Endpoint Call.
*
* Provide your own endpoint and response type.
*
* {@link https://api-docs.igdb.com/#about}
* @param {Object} options - [Untyped Endpoint Options]({@link UntypedIGDBOptions})
*
**/
public async get<T>(endpoint: string, options?: UntypedIGDBOptions){
if (!this.clientToken) {
throw Error("Client token not found. Make sure to init() before requesting an endpoint.")
}
return this.validateToken().then(() => this._axios.post<T>(`${this.API_URL}/${endpoint}`, this.buildUntypedOptions(options), {
headers: {
'Client-ID': this.clientId,
'Authorization': `Bearer ${this.clientToken}`,
'Accept': 'application/json',
},
}))
.then((response) => {
return response.data
})
}
/**
* Get Age Ratings.
* {@link https://api-docs.igdb.com/#age-rating}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getAgeRatings(options?: DefaultIGDBOptions<AgeRating>) {
return this.request<AgeRating>(Endpoints.AGE_RATING, options)
}
/**
* Get Age Rating Content Descriptions.
* {@link https://api-docs.igdb.com/#age-rating-content-description}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getAgeRatingContentDescriptions(options?: DefaultIGDBOptions<AgeRatingContentDescription>){
return this.request<AgeRatingContentDescription>(Endpoints.AGE_RATING_CONTENT_DESCRIPTION, options)
}
/**
* Get Alternative Names.
* {@link https://api-docs.igdb.com/#alternative-name}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getAlternativeNames(options?: DefaultIGDBOptions<AlternativeName>) {
return this.request<AlternativeName>(Endpoints.ALTERNATIVE_NAME, options)
}
/**
* Get Artworks.
* {@link https://api-docs.igdb.com/#artwork}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getArtworks(options?: DefaultIGDBOptions<Artwork>) {
return this.request<Artwork>(Endpoints.ARTWORK, options)
}
/**
* Get Characters.
* {@link https://api-docs.igdb.com/#character}
* @param {Object} options - [Searchable Endpoint Options]({@link SearchableIGDBOptions})
*
**/
public getCharacters(options?: SearchableIGDBOptions<Character>) {
return this.request<Character>(Endpoints.CHARACTER, options)
}
/**
* Get Character Mug Shot.
* {@link https://api-docs.igdb.com/#character-mug-shot}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getCharacterMugShots(options?: DefaultIGDBOptions<CharacterMugShot>) {
return this.request<CharacterMugShot>(Endpoints.CHARACTER_MUG_SHOT, options)
}
/**
* Get Collections.
* {@link https://api-docs.igdb.com/#collection}
* @param {Object} options - [Searchable Endpoint Options]({@link SearchableIGDBOptions})
*
**/
public getCollections(options?: SearchableIGDBOptions<Collection>) {
return this.request<Collection>(Endpoints.COLLECTION, options)
}
/**
* Get Companies.
* {@link https://api-docs.igdb.com/#company}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getCompanies(options?: DefaultIGDBOptions<Company>) {
return this.request<Company>(Endpoints.COMPANY, options)
}
/**
* Get Company Logos.
* {@link https://api-docs.igdb.com/#company-logo}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getCompanyLogos(options?: DefaultIGDBOptions<CompanyLogo>) {
return this.request<CompanyLogo>(Endpoints.COMPANY_LOGO, options)
}
/**
* Get Company Website.
* {@link https://api-docs.igdb.com/#company-website}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getCompanyWebsite(options?: DefaultIGDBOptions<CompanyWebsite>) {
return this.request<CompanyWebsite>(Endpoints.COMPANY_WEBSITE, options)
}
/**
* Get Covers.
* {@link https://api-docs.igdb.com/#cover}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getCovers(options?: DefaultIGDBOptions<Cover>) {
return this.request<Cover>(Endpoints.COVER, options)
}
/**
* Get External Games.
* {@link https://api-docs.igdb.com/#external-game}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getExternalGames(options?: DefaultIGDBOptions<ExternalGame>) {
return this.request<ExternalGame>(Endpoints.EXTERNAL_GAME, options)
}
/**
* Get Franchises.
* {@link https://api-docs.igdb.com/#franchise}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getFranchises(options?: DefaultIGDBOptions<Franchise>) {
return this.request<Franchise>(Endpoints.FRANCHISE, options)
}
/**
* Get Games.
* {@link https://api-docs.igdb.com/#game}
* @param {Object} options - [Searchable Endpoint Options]({@link SearchableIGDBOptions})
*
**/
public getGames(options?: SearchableIGDBOptions<Game>) {
return this.request<Game>(Endpoints.GAME, options)
}
/**
* Get Game Engines.
* {@link https://api-docs.igdb.com/#game-engine}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getGameEngines(options?: DefaultIGDBOptions<GameEngine>) {
return this.request<GameEngine>(Endpoints.GAME_ENGINE, options)
}
/**
* Get Game Engine Logos.
* {@link https://api-docs.igdb.com/#game-engine-logo}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getGameEngineLogos(options?: DefaultIGDBOptions<GameEngineLogo>) {
return this.request<GameEngineLogo>(Endpoints.GAME_ENGINE_LOGO, options)
}
/**
* Get Game Modes.
* {@link https://api-docs.igdb.com/#game-mode}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getGameModes(options?: DefaultIGDBOptions<GameMode>) {
return this.request<GameMode>(Endpoints.GAME_MODE, options)
}
/**
* Get Game Versions.
* {@link https://api-docs.igdb.com/#game-version}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getGameVersions(options?: DefaultIGDBOptions<GameVersion>) {
return this.request<GameVersion>(Endpoints.GAME_VERSION, options)
}
/**
* Get Game Version Features.
* {@link https://api-docs.igdb.com/#game-version-feature}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getGameVersionFeatures(options?: DefaultIGDBOptions<GameVersionFeature>) {
return this.request<GameVersionFeature>(Endpoints.GAME_VERSION_FEATURE, options)
}
/**
* Get Game Version Feature Values.
* {@link https://api-docs.igdb.com/#game-engine-feature-value}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getGameVersionFeatureValues(options?: DefaultIGDBOptions<GameVersionFeatureValue>) {
return this.request<GameVersionFeatureValue>(Endpoints.GAME_VERSION_FEATURE_VALUE, options)
}
/**
* Get Game Videos.
* {@link https://api-docs.igdb.com/#game-video}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getGameVideos(options?: DefaultIGDBOptions<GameVideo>) {
return this.request<GameVideo>(Endpoints.GAME_VIDEO, options)
}
/**
* Get Genres.
* {@link https://api-docs.igdb.com/#genre}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getGenres(options?: DefaultIGDBOptions<Genre>) {
return this.request<Genre>(Endpoints.GENRE, options)
}
/**
* Get Involved Companies.
* {@link https://api-docs.igdb.com/#involved-company}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getInvolvedCompanies(options?: DefaultIGDBOptions<InvolvedCompany>) {
return this.request<InvolvedCompany>(Endpoints.INVOLVED_COMPANY, options)
}
/**
* Get Keywords.
* {@link https://api-docs.igdb.com/#keyword}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getKeywords(options?: DefaultIGDBOptions<Keyword>) {
return this.request<Keyword>(Endpoints.KEYWORD, options)
}
/**
* Get Multiplayer Modes.
* {@link https://api-docs.igdb.com/#multiplayer-mode}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getMultiplayerModes(options?: DefaultIGDBOptions<MultiplayerMode>) {
return this.request<MultiplayerMode>(Endpoints.MULTIPLAYER_MODE, options)
}
/**
* Get Platform.
* {@link https://api-docs.igdb.com/#platform}
* @param {Object} options - [Searchable Endpoint Options]({@link SearchableIGDBOptions})
*
**/
public getPlatforms(options?: SearchableIGDBOptions<Platform>) {
return this.request<Platform>(Endpoints.PLATFORM, options)
}
/**
* Get Platform Families.
* {@link https://api-docs.igdb.com/#platform-family}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getPlatformFamilies(options?: DefaultIGDBOptions<PlatformFamily>) {
return this.request<PlatformFamily>(Endpoints.PLATFORM_FAMILY, options)
}
/**
* Get Platform Logos.
* {@link https://api-docs.igdb.com/#platform-logo}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getPlatformLogos(options?: DefaultIGDBOptions<PlatformLogo>) {
return this.request<PlatformLogo>(Endpoints.PLATFORM_LOGO, options)
}
/**
* Get Platform Versions.
* {@link https://api-docs.igdb.com/#platform-version}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getPlatformVersion(options?: DefaultIGDBOptions<PlatformVersion>) {
return this.request<PlatformVersion>(Endpoints.PLATFORM_VERSION, options)
}
/**
* Get Platform Version Companies.
* {@link https://api-docs.igdb.com/#platform-version-company}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getPlatformVersionCompanies(options?: DefaultIGDBOptions<PlatformVersionCompany>) {
return this.request<PlatformVersionCompany>(Endpoints.PLATFORM_VERSION_COMPANY, options)
}
/**
* Get Platform Version Release Dates.
* {@link https://api-docs.igdb.com/#platform-version-release_date}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getPlatformVersionReleaseDates(options?: DefaultIGDBOptions<PlatformVersionReleaseDate>) {
return this.request<PlatformVersionReleaseDate>(Endpoints.PLATFORM_VERSION_RELEASE_DATE, options)
}
/**
* Get Platform Websites.
* {@link https://api-docs.igdb.com/#platform-website}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getPlatformWebsites(options?: DefaultIGDBOptions<PlatformWebsite>) {
return this.request<PlatformWebsite>(Endpoints.PLATFORM_WEBSITE, options)
}
/**
* Get Player Perspectives.
* {@link https://api-docs.igdb.com/#player-perspective}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getPlayerPerspectives(options?: DefaultIGDBOptions<PlayerPerspective>) {
return this.request<PlayerPerspective>(Endpoints.PLAYER_PERSPECTIVE, options)
}
/**
* Get Release Dates.
* {@link https://api-docs.igdb.com/#release-date}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getReleaseDates(options?: DefaultIGDBOptions<ReleaseDate>) {
return this.request<ReleaseDate>(Endpoints.RELEASE_DATE, options)
}
/**
* Get Screenshots.
* {@link https://api-docs.igdb.com/#screenshot}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getScreenshots(options?: DefaultIGDBOptions<Screenshot>) {
return this.request<Screenshot>(Endpoints.SCREENSHOT, options)
}
/**
* Search IGDB.
* {@link https://api-docs.igdb.com/#search}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public search(options?: DefaultIGDBOptions<Search>) {
return this.request<Search>(Endpoints.SEARCH, options)
}
/**
* Get Themes.
* {@link https://api-docs.igdb.com/#theme}
* @param {Object} options - [Searchable Endpoint Options]({@link SearchableIGDBOptions})
*
**/
public getThemes(options?: SearchableIGDBOptions<Theme>) {
return this.request<Theme>(Endpoints.THEME, options)
}
/**
* Get Websites.
* {@link https://api-docs.igdb.com/#website}
* @param {Object} options - [Default Endpoint Options]({@link DefaultIGDBOptions})
*
**/
public getWebsites(options?: DefaultIGDBOptions<Website>) {
return this.request<Website>(Endpoints.WEBSITE, options)
}
}