import type { RawCard } from "./mapping/mapCard" ; import { mapCard } from "./mapping/mapCard" ; import type { RawCardSet } from "./mapping/mapCardSet" ; import { mapCardSet } from "./mapping/mapCardSet" ; import type { PaginatedResponse } from "./PaginatedResponse" ; import { createEmptyPaginatedResponse } from "./PaginatedResponse" ; import { inject , injectable } from "inversify" ; import type { RawCardValues } from "./mapping/mapCardValues" ; import { mapCardValues } from "./mapping/mapCardValues" ; import type { RawArchetype } from "./mapping/mapArchetype" ; import { mapArchetype } from "./mapping/mapArchetype" ; import type { Card , CardSet , CardValues , Format , UnlinkedCard , } from "@yugioh-deck-tool/core" ; import { EncodingService , Environment , EnvironmentConfig , HttpService , TYPES , } from "@yugioh-deck-tool/core" ; interface CardInfoOptions { readonly includeAliased : boolean ; readonly format ? : Format | null ; readonly passcode ? : string | null ; readonly fuzzyName ? : string | null ; readonly sorting ? : "relevance" | null ; readonly auth ? : Credentials ; } export interface Credentials { readonly username : string ; readonly token : string ; } /**
 * See YGOPRODECK API (https://db.ygoprodeck.com/api-guide/).
 */ @ injectable ( ) export class YgoprodeckApiService { private static readonly CHUNK_SIZE = 2000 ; private static readonly HTTP_STATUS_NO_MATCHES = 400 ; readonly #environmentConfig : EnvironmentConfig ; readonly #httpService : HttpService ; readonly #encodingService : EncodingService ; constructor ( @ inject ( TYPES . HttpService ) httpService , @ inject ( TYPES . EnvironmentConfig ) environmentConfig , @ inject ( TYPES . EncodingService ) encodingService ) { this . #environmentConfig = environmentConfig ; this . #httpService = httpService ; this . #encodingService = encodingService ; } async getSingleCard ( options ) { const response = await this . #httpService . get < { data : RawCard [ ] ; } > ( "cardinfo.php" , { baseUrl : this . #getBaseUrl ( ) , params : this . #createCardInfoParams ( options ) , headers : this . #createAuthHeaders ( options ) , timeout : 5000 , responseType : "json" , validateStatus : this . #createCardInfoStatusValidator ( ) , } ) ; if ( response . status === YgoprodeckApiService . HTTP_STATUS_NO_MATCHES ) { return null ; } const responseData = response . data ; return mapCard ( responseData . data [ 0 ] , this . #environmentConfig . getEnvironment ( ) ) ; } async getCards ( options ) { const params = this . #createCardInfoParams ( options ) ; const authHeaders = this . #createAuthHeaders ( options ) ; const responseData = await this . #loadPaginated < RawCard > ( async ( offset ) => { const response = await this . #httpService . get < PaginatedResponse < RawCard [ ] > > ( "cardinfo.php" , { baseUrl : this . #getBaseUrl ( ) , params : { ... params , num : YgoprodeckApiService . CHUNK_SIZE , offset , } , headers : authHeaders , timeout : 10000 , responseType : "json" , validateStatus : this . #createCardInfoStatusValidator ( ) , } ) ; if ( response . status === YgoprodeckApiService . HTTP_STATUS_NO_MATCHES ) { return createEmptyPaginatedResponse ( [ ] ) ; } return response . data ; } ) ; return responseData . map ( ( rawCard ) => mapCard ( rawCard , this . #environmentConfig . getEnvironment ( ) ) ) ; } #createCardInfoParams ( options ) { const params = { } ; params . misc = "yes" ; if ( options . includeAliased ) { params . includeAliased = "yes" ; } if ( options . format != null ) { params . format = String ( options . format ) . toLowerCase ( ) ; } else { params . format = "all" ; } if ( options . passcode != null ) { params . id = options . passcode ; } if ( options . fuzzyName != null ) { params . fname = options . fuzzyName ; } if ( options . sorting != null ) { params . sort = options . sorting ; } if ( options . auth != null ) { params . cachebust = String ( Date . now ( ) ) ; } return params ; } async getCardSets ( ) { const response = await this . #httpService . get < RawCardSet [ ] > ( "cardsets.php" , { baseUrl : this . #getBaseUrl ( ) , timeout : 10000 , responseType : "json" , } ) ; return response . data . map ( mapCardSet ) ; } async getCardValues ( ) { const response = await this . #httpService . get < RawCardValues > ( "cardvalues.php" , { baseUrl : this . #getBaseUrl ( ) , timeout : 10000 , responseType : "json" , } ) ; return mapCardValues ( response . data ) ; } async getArchetypes ( ) { const response = await this . #httpService . get < RawArchetype [ ] > ( "archetypes.php" , { baseUrl : this . #getBaseUrl ( ) , timeout : 10000 , responseType : "json" , } ) ; return response . data . map ( mapArchetype ) ; } async updateViews ( card ) { await this . #httpService . get < void > ( "updateViews.php" , { baseUrl : this . #getBaseUrl ( ) , timeout : 3000 , responseType : "text" , params : { name : card . name , } , } ) ; } #getBaseUrl ( ) { if ( this . #environmentConfig . getEnvironment ( ) == Environment . YGOPRODECK ) { return "https://db.ygoprodeck.com/api_internal/v7/" ; } return "https://db.ygoprodeck.com/api/v7/" ; } async #loadPaginated < T > ( fetcher ) { const result = [ ] ; let offset = 0 ; let pagesRemaining = null ; while ( pagesRemaining == null || pagesRemaining > 0 ) { const response = await fetcher ( offset ) ; result . push ( ... response . data ) ; pagesRemaining = response . meta . pages_remaining ; if ( response . meta . next_page_offset != null ) { offset = response . meta . next_page_offset ; } } return result ; } #createAuthHeaders ( options ) { if ( options . auth == null ) { return { } ; } const encodedCredentials = this . #encodingService . encodeBase64String ( this . #encodingService . encodeText ( `${ options . auth . username }:${ options . auth . token }` ) , false ) ; return { "X-Authorization" : `Basic ${ encodedCredentials }` , } ; } #createCardInfoStatusValidator ( ) { return ( status ) => ( status >= 200 && status <= 299 ) || status === YgoprodeckApiService . HTTP_STATUS_NO_MATCHES ; } }