mirror of
https://github.com/tcgdex/javascript-sdk.git
synced 2025-07-13 18:45:09 +00:00
417 lines
13 KiB
TypeScript
417 lines
13 KiB
TypeScript
import type CacheInterface from '@cachex/core'
|
|
import MemoryCache from '@cachex/memory'
|
|
import LocalStorageCache from '@cachex/web-storage'
|
|
import Query from './Query'
|
|
import Endpoint from './endpoints/Endpoint'
|
|
import SimpleEndpoint from './endpoints/SimpleEndpoint'
|
|
import type {
|
|
Card,
|
|
CardResume,
|
|
Endpoints,
|
|
Serie,
|
|
SerieList,
|
|
SetList,
|
|
StringEndpoint,
|
|
SupportedLanguages,
|
|
Set as TCGdexSet
|
|
} from './interfaces'
|
|
import CardModel from './models/Card'
|
|
import CardResumeModel from './models/CardResume'
|
|
import Model from './models/Model'
|
|
import SerieModel from './models/Serie'
|
|
import SerieResume from './models/SerieResume'
|
|
import SetModel from './models/Set'
|
|
import SetResumeModel from './models/SetResume'
|
|
import StringEndpointModel from './models/StringEndpoint'
|
|
import { ENDPOINTS, detectContext } from './utils'
|
|
import { version } from './version'
|
|
|
|
export default class TCGdex {
|
|
|
|
/**
|
|
* How the remote data is going to be fetched
|
|
*/
|
|
public static fetch: typeof fetch =
|
|
detectContext() === 'browser'
|
|
// fixe: TypeError: 'fetch' called on an object that does not implement interface Window.
|
|
? (...params: Parameters<typeof fetch>) => window.fetch(...params)
|
|
: fetch
|
|
|
|
/**
|
|
* @deprecated to change the lang use {@link TCGdex.getLang} and {@link TCGdex.setLang}
|
|
*/
|
|
public static defaultLang: SupportedLanguages = 'en'
|
|
|
|
/**
|
|
* the previously hidden caching system used by TCGdex to not kill the API
|
|
*/
|
|
public cache: CacheInterface =
|
|
detectContext() === 'browser'
|
|
? new LocalStorageCache('tcgdex-cache')
|
|
: new MemoryCache()
|
|
|
|
/**
|
|
* the default cache TTL, only subsequent requests will have their ttl changed
|
|
*/
|
|
public cacheTTL = 60 * 60
|
|
|
|
// random card/set/serie endpoints
|
|
public readonly random = {
|
|
card: async (): Promise<CardModel> => {
|
|
const res = await this.fetch('random', 'card')
|
|
return Model.build(new CardModel(this), res)
|
|
},
|
|
set: async (): Promise<SetModel> => {
|
|
const res = await this.fetch('random', 'set')
|
|
return Model.build(new SetModel(this), res)
|
|
},
|
|
serie: async (): Promise<SerieModel> => {
|
|
const res = await this.fetch('random', 'serie')
|
|
return Model.build(new SerieModel(this), res)
|
|
}
|
|
}
|
|
|
|
public readonly card = new Endpoint(this, CardModel, CardResumeModel, 'cards')
|
|
public readonly set = new Endpoint(this, SetModel, SetResumeModel, 'sets')
|
|
public readonly serie = new Endpoint(this, SerieModel, SerieResume, 'series')
|
|
|
|
public readonly type = new SimpleEndpoint(this, StringEndpointModel, 'types')
|
|
public readonly retreat = new SimpleEndpoint(this, StringEndpointModel, 'retreats')
|
|
public readonly rarity = new SimpleEndpoint(this, StringEndpointModel, 'rarities')
|
|
public readonly illustrator = new SimpleEndpoint(this, StringEndpointModel, 'illustrators')
|
|
public readonly hp = new SimpleEndpoint(this, StringEndpointModel, 'hp')
|
|
public readonly categorie = new SimpleEndpoint(this, StringEndpointModel, 'categories')
|
|
public readonly dexID = new SimpleEndpoint(this, StringEndpointModel, 'dex-ids')
|
|
public readonly energyType = new SimpleEndpoint(this, StringEndpointModel, 'energy-types')
|
|
public readonly regulationMark = new SimpleEndpoint(this, StringEndpointModel, 'regulation-marks')
|
|
public readonly stage = new SimpleEndpoint(this, StringEndpointModel, 'stages')
|
|
public readonly suffixe = new SimpleEndpoint(this, StringEndpointModel, 'suffixes')
|
|
public readonly trainerType = new SimpleEndpoint(this, StringEndpointModel, 'trainer-types')
|
|
public readonly variant = new SimpleEndpoint(this, StringEndpointModel, 'variants')
|
|
|
|
private lang: SupportedLanguages = 'en'
|
|
private endpointURL = 'https://api.tcgdex.net/v2'
|
|
|
|
public constructor(lang: SupportedLanguages = 'en') {
|
|
this.setLang(lang)
|
|
}
|
|
|
|
/**
|
|
* @deprecated use the constructor parameter or {@link TCGdex.setLang} when in an instance
|
|
*/
|
|
public static setDefaultLang(lang: SupportedLanguages) {
|
|
TCGdex.defaultLang = lang
|
|
}
|
|
|
|
/**
|
|
* @deprecated use {@link TCGdex.setLang} when in an instance
|
|
*/
|
|
public static getDefaultLang(): SupportedLanguages {
|
|
return TCGdex.defaultLang
|
|
}
|
|
|
|
/**
|
|
* the endpoint URL
|
|
* ex: `https://api.tcgdex.net/v2`
|
|
* @param endpoint the url
|
|
*/
|
|
public setEndpoint(endpoint: string) {
|
|
this.endpointURL = endpoint
|
|
}
|
|
public getEndpoint(): string {
|
|
return this.endpointURL
|
|
}
|
|
|
|
/**
|
|
* set the current cache methodology
|
|
* @param cache the cache to use
|
|
*/
|
|
public setCache(cache: CacheInterface) {
|
|
this.cache = cache
|
|
}
|
|
|
|
/**
|
|
* get the current cache methodology
|
|
* @param cache the cache to use
|
|
*/
|
|
public getCache(): CacheInterface {
|
|
return this.cache
|
|
}
|
|
|
|
/**
|
|
* the endpoint URL
|
|
* ex: `https://api.tcgdex.net/v2`
|
|
* @param endpoint the url
|
|
*/
|
|
public setCacheTTL(seconds: number) {
|
|
this.cacheTTL = seconds
|
|
}
|
|
/**
|
|
* get the current useed cache ttl in seconds
|
|
* @returns the cache ttl in seconds
|
|
*/
|
|
public getCacheTTL(): number {
|
|
return this.cacheTTL
|
|
}
|
|
|
|
public getLang(): SupportedLanguages {
|
|
return this.lang ?? TCGdex.defaultLang ?? 'en'
|
|
}
|
|
|
|
public setLang(lang: SupportedLanguages) {
|
|
this.lang = lang
|
|
}
|
|
|
|
/**
|
|
* Shortcut to easily fetch a card using both it's global id and it's local ID
|
|
* @param id the card global/local ID
|
|
* @param set the card set name/ID (optionnal)
|
|
* @returns the card object
|
|
*/
|
|
public async fetchCard(id: string | number, set?: string): Promise<Card | undefined> {
|
|
const path = set ? ['sets', set] : ['cards']
|
|
// @ts-expect-error the base endpoint is 'sets' or 'cards'
|
|
return this.fetch(...path, id)
|
|
}
|
|
|
|
/**
|
|
* Shortcut to easily fetch cards using an optionnal set name/ID
|
|
* @param set the card set name/ID (optionnal)
|
|
* @returns a card list
|
|
*/
|
|
public async fetchCards(set?: string): Promise<Array<CardResume> | undefined> {
|
|
if (set) {
|
|
const fSet = await this.fetch('sets', set)
|
|
return fSet ? fSet.cards : undefined
|
|
}
|
|
return this.fetch('cards')
|
|
}
|
|
|
|
/**
|
|
* @deprecated use `this.fetch('sets', set)`
|
|
*/
|
|
public async fetchSet(set: string): Promise<TCGdexSet | undefined> {
|
|
return this.fetch('sets', set)
|
|
}
|
|
|
|
/**
|
|
* @deprecated use `this.fetch('series', serie)`
|
|
*/
|
|
public async fetchSerie(serie: string): Promise<Serie | undefined> {
|
|
return this.fetch('series', serie)
|
|
}
|
|
|
|
/**
|
|
* @deprecated use `this.fetch('series')`
|
|
*/
|
|
public async fetchSeries(): Promise<SerieList | undefined> {
|
|
return this.fetch('series')
|
|
}
|
|
|
|
/**
|
|
* Shortcut to easily fetch sets using an optionnal serie name/ID
|
|
* @param serie the card set name/ID (optionnal)
|
|
* @returns a card list
|
|
*/
|
|
public async fetchSets(serie?: string): Promise<SetList | undefined> {
|
|
if (serie) {
|
|
const fSerie = await this.fetch('series', serie)
|
|
return fSerie ? fSerie.sets : undefined
|
|
}
|
|
return this.fetch('sets')
|
|
}
|
|
|
|
/**
|
|
* Fetch a card using its global id
|
|
* @param endpoint_0 'cards'
|
|
* @param endpoint_1 {string} the card global ID
|
|
*/
|
|
public async fetch(...type: ['cards', string]): Promise<Card | undefined>
|
|
|
|
/**
|
|
* Fetch every cards in the database
|
|
* @param endpoint_0 'cards'
|
|
*/
|
|
public async fetch(type: 'cards'): Promise<Array<CardResume> | undefined>
|
|
|
|
/**
|
|
* Fetch a card using its local id and its set
|
|
* @param endpoint_0 'sets'
|
|
* @param endpoint_1 {string} the set name or ID
|
|
* @param endpoint_2 {string} the card local ID
|
|
*/
|
|
public async fetch(...endpoint: ['sets', string, string]): Promise<Card | undefined>
|
|
|
|
/**
|
|
* Fetch a set
|
|
* @param endpoint_0 'sets'
|
|
* @param endpoint_1 {string} the set name or ID
|
|
*/
|
|
public async fetch(...endpoint: ['sets', string]): Promise<TCGdexSet | undefined>
|
|
|
|
/**
|
|
* Fetch a random element
|
|
* @param endpoint_0 'random'
|
|
* @param endpoint_1 {'set' | 'card' | 'serie'} the type of random element you want to get
|
|
*/
|
|
public async fetch(...endpoint: ['random', 'set' | 'card' | 'serie']): Promise<Card | TCGdexSet | Serie | undefined>
|
|
|
|
/**
|
|
* Fetch every sets
|
|
* @param endpoint_0 'sets'
|
|
*/
|
|
public async fetch(endpoint: 'sets'): Promise<SetList | undefined>
|
|
|
|
/**
|
|
* Fetch a serie
|
|
* @param endpoint_0 'series'
|
|
* @param endpoint_1 {string} the serie name or ID
|
|
*/
|
|
public async fetch(...endpoint: ['series', string]): Promise<Serie | undefined>
|
|
|
|
/**
|
|
* Fetch every series
|
|
* @param endpoint_0 'series'
|
|
*/
|
|
public async fetch(endpoint: 'series'): Promise<SerieList | undefined>
|
|
|
|
/**
|
|
* Fetch cards depending on a specific filter
|
|
* @param endpoint_0 {'categories' | 'dex-ids' | 'energy-types' | 'hp' | 'illustrators' | 'rarities' | 'regulation-marks' | 'retreats' | 'stages' | 'suffixes' | 'trainer-types' | 'types' | 'variants'}
|
|
* Possible value 'categories' | 'dex-ids' | 'energy-types' | 'hp' | 'illustrators' | 'rarities' | 'regulation-marks' | 'retreats' | 'stages' | 'suffixes' | 'trainer-types' | 'types' | 'variants'
|
|
* @param endpoint_1 {string} the value set while fetching the index
|
|
*/
|
|
public async fetch(...endpoint: ['categories' | 'dex-ids' | 'energy-types' | 'hp' | 'illustrators' | 'rarities' | 'regulation-marks' | 'retreats' | 'stages' | 'suffixes' | 'trainer-types' | 'types' | 'variants', string]): Promise<StringEndpoint | undefined>
|
|
|
|
/**
|
|
* Fetch cards depending on a specific filter
|
|
* @param endpoint_0 {'categories' | 'dex-ids' | 'energy-types' | 'hp' | 'illustrators' | 'rarities' | 'regulation-marks' | 'retreats' | 'stages' | 'suffixes' | 'trainer-types' | 'types' | 'variants'}
|
|
* Possible value 'categories' | 'dex-ids' | 'energy-types' | 'hp' | 'illustrators' | 'rarities' | 'regulation-marks' | 'retreats' | 'stages' | 'suffixes' | 'trainer-types' | 'types' | 'variants'
|
|
* @param endpoint_1 {string} Fetch the possible values to use depending on the endpoint
|
|
*/
|
|
public async fetch(endpoint: 'categories' | 'dex-ids' | 'energy-types' | 'hp' | 'illustrators' | 'rarities' | 'regulation-marks' | 'retreats' | 'stages' | 'suffixes' | 'trainer-types' | 'types' | 'variants'): Promise<Array<string> | undefined>
|
|
|
|
/**
|
|
* Fetch The differents endpoints depending on the first argument
|
|
* @param endpoint_0 {'hp' | 'retreats' | 'categories' | 'illustrators' | 'rarities' | 'types'}
|
|
* Possible value 'cards' | 'categories' | 'hp' | 'illustrators' | 'rarities' | 'retreats' | 'series' | 'sets' | 'types'
|
|
* @param endpoint_1 {string} (Optionnal) some details to go from the index file to the item file (mostly the ID/name)
|
|
* @param endpoint_2 {string} (Optionnal) only for sets the card local ID to fetch the card through the set
|
|
*/
|
|
public async fetch<T = object>(...endpoint: Array<Endpoints | string>): Promise<T | undefined> {
|
|
if (endpoint.length === 0) {
|
|
throw new Error('endpoint to fetch is empty!')
|
|
}
|
|
// @ts-expect-error with the precedent check, we KNOW that type is not empty
|
|
const baseEndpoint = endpoint.shift().toLowerCase() as Endpoint
|
|
if (!ENDPOINTS.includes(baseEndpoint)) {
|
|
throw new Error(`unknown endpoint to fetch! (${baseEndpoint})`)
|
|
}
|
|
return this.actualFetch<T>(this.getFullURL([baseEndpoint, ...endpoint]))
|
|
}
|
|
|
|
/**
|
|
* @param endpoint the endpoint to fetch
|
|
* @param query the query
|
|
*/
|
|
public async fetchWithQuery<T = object>(
|
|
endpoint: [Endpoints, ...Array<string>],
|
|
query?: Array<{ key: string, value: string | number | boolean }>
|
|
): Promise<T | undefined> {
|
|
if (endpoint.length === 0) {
|
|
throw new Error('endpoint to fetch is empty!')
|
|
}
|
|
const baseEndpoint = endpoint[0].toLowerCase() as Endpoints
|
|
if (!ENDPOINTS.includes(baseEndpoint)) {
|
|
throw new Error(`unknown endpoint to fetch! (${baseEndpoint})`)
|
|
}
|
|
return this.actualFetch<T>(this.getFullURL(endpoint, query))
|
|
}
|
|
|
|
/**
|
|
* format the final URL
|
|
*/
|
|
private getFullURL(
|
|
url: Array<string | number>,
|
|
searchParams?: Array<{ key: string, value: string | number | boolean }>
|
|
): string {
|
|
// Normalize path
|
|
let path = url.map(this.encode).join('/')
|
|
|
|
// handle the Search Params
|
|
if (searchParams) {
|
|
path += '?' + searchParams.map((it) => `${this.encode(it.key)}=${this.encode(it.value)}`).join('&')
|
|
}
|
|
|
|
// return with the endpoint and all the shit
|
|
return `${this.getEndpoint()}/${this.getLang()}/${path}`
|
|
}
|
|
|
|
private async actualFetch<T = object>(path: string): Promise<T | undefined> {
|
|
// get and return the cached value if available
|
|
const cached = this.cache.get(path)
|
|
if (cached) {
|
|
return cached as T
|
|
}
|
|
|
|
// the actual Fetch :D
|
|
const resp = await TCGdex.fetch(path, {
|
|
headers: {
|
|
'user-agent': `@tcgdex/javascript-sdk/${version}`
|
|
}
|
|
})
|
|
|
|
// throw if a server-side error is occured
|
|
if (resp.status >= 500) {
|
|
try {
|
|
const json = JSON.stringify(await resp.json())
|
|
throw new Error(json)
|
|
} catch {
|
|
throw new Error('TCGdex Server responded with an invalid error :(')
|
|
}
|
|
}
|
|
|
|
// response is not valid :O
|
|
if (resp.status !== 200) {
|
|
return undefined
|
|
}
|
|
|
|
// parse, put to cache and return
|
|
const json = await resp.json()
|
|
|
|
this.cache.set(path, json, this.cacheTTL)
|
|
return json as T
|
|
}
|
|
|
|
/**
|
|
* encode a string to be used in an url
|
|
* @param str the string to encode to URL
|
|
* @returns the encoded string
|
|
*/
|
|
private encode(str: string | number | boolean): string {
|
|
return encodeURI(
|
|
str
|
|
// Transform numbers to string
|
|
.toString()
|
|
// replace this special character with an escaped one
|
|
.replace('?', '%3F')
|
|
// normalize the string
|
|
.normalize('NFC')
|
|
// remove some special chars
|
|
// eslint-disable-next-line no-misleading-character-class
|
|
.replace(/["'\u0300-\u036f]/gu, '')
|
|
)
|
|
}
|
|
}
|
|
|
|
// export the old interfaces
|
|
export type * from './interfaces.d.ts'
|
|
|
|
// export the new models items and the Query
|
|
export {
|
|
CardModel, CardResumeModel, Endpoint, Model, Query, SerieModel,
|
|
SerieResume as SerieResumeModel,
|
|
SetModel,
|
|
SetResumeModel, SimpleEndpoint
|
|
}
|