ygoapi
Version:
TypeScript client for the YGOPRODeck API v7 - Yu-Gi-Oh! card database access
285 lines (281 loc) • 8.95 kB
JavaScript
var import_node_module = require("node:module");
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __moduleCache = /* @__PURE__ */ new WeakMap;
var __toCommonJS = (from) => {
var entry = __moduleCache.get(from), desc;
if (entry)
return entry;
entry = __defProp({}, "__esModule", { value: true });
if (from && typeof from === "object" || typeof from === "function")
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
get: () => from[key],
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
}));
__moduleCache.set(from, entry);
return entry;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, {
get: all[name],
enumerable: true,
configurable: true,
set: (newValue) => all[name] = () => newValue
});
};
// src/index.ts
var exports_src = {};
__export(exports_src, {
isTrapCard: () => isTrapCard,
isSpellCard: () => isSpellCard,
isMonsterCard: () => isMonsterCard,
isExtraDeckMonster: () => isExtraDeckMonster,
getCardImages: () => getCardImages,
buildComparison: () => buildComparison,
YgoApiError: () => YgoApiError,
YgoApi: () => YgoApi
});
module.exports = __toCommonJS(exports_src);
class YgoApiError extends Error {
statusCode;
constructor(statusCode, message) {
super(message);
this.statusCode = statusCode;
this.name = "YgoApiError";
}
}
class YgoApi {
baseURL = "https://db.ygoprodeck.com/api/v7";
headers;
cache;
cacheTtl;
retryConfig;
fallbackConfig;
constructor(options) {
this.headers = {
"Content-Type": "application/json",
...options?.headers
};
this.cache = options?.cache;
this.cacheTtl = options?.cacheTtl ?? 300000;
this.retryConfig = {
maxAttempts: options?.retry?.maxAttempts ?? 3,
baseDelay: options?.retry?.baseDelay ?? 1000,
maxDelay: options?.retry?.maxDelay ?? 1e4,
backoffFactor: options?.retry?.backoffFactor ?? 2
};
this.fallbackConfig = {
urls: options?.fallback?.urls ?? [],
timeout: options?.fallback?.timeout ?? 5000
};
}
buildQueryString(params) {
if (!params)
return "";
const query = new URLSearchParams;
Object.entries(params).forEach(([key, value]) => {
if (value === undefined || value === null)
return;
if (Array.isArray(value)) {
query.append(key, value.join(","));
} else if (typeof value === "boolean") {
query.append(key, value.toString());
} else {
query.append(key, String(value));
}
});
const queryString = query.toString();
return queryString ? `?${queryString}` : "";
}
sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
getCacheKey(endpoint, params) {
const sortedParams = params ? JSON.stringify(params, Object.keys(params).sort()) : "";
return `ygoapi:${endpoint}:${sortedParams}`;
}
async getCached(cacheKey) {
if (!this.cache)
return null;
try {
const cached = await this.cache.get(cacheKey);
return cached ? JSON.parse(cached) : null;
} catch {
return null;
}
}
async setCached(cacheKey, data) {
if (!this.cache)
return;
try {
await this.cache.set(cacheKey, JSON.stringify(data), this.cacheTtl);
} catch {}
}
async request(endpoint, params) {
if (params && (params.offset !== undefined && params.num === undefined || params.num !== undefined && params.offset === undefined)) {
throw new YgoApiError(400, "You cannot use only one of 'offset' or 'num'. You must use both or none.");
}
const cacheKey = this.getCacheKey(endpoint, params);
const cached = await this.getCached(cacheKey);
if (cached)
return cached;
const urls = [this.baseURL, ...this.fallbackConfig.urls];
let lastError;
for (const baseUrl of urls) {
const url = `${baseUrl}${endpoint}${this.buildQueryString(params)}`;
for (let attempt = 1;attempt <= this.retryConfig.maxAttempts; attempt++) {
try {
const controller = new AbortController;
const timeoutId = setTimeout(() => controller.abort(), this.fallbackConfig.timeout);
const response = await fetch(url, {
method: "GET",
headers: this.headers,
signal: controller.signal
});
clearTimeout(timeoutId);
const data = await response.json();
if (!response.ok) {
throw new YgoApiError(response.status, data.error || `API request failed with status ${response.status}`);
}
await this.setCached(cacheKey, data);
return data;
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
if (error instanceof YgoApiError && error.statusCode < 500) {
break;
}
if (attempt < this.retryConfig.maxAttempts) {
const delay = Math.min(this.retryConfig.baseDelay * this.retryConfig.backoffFactor ** (attempt - 1), this.retryConfig.maxDelay);
await this.sleep(delay);
}
}
}
}
if (lastError instanceof YgoApiError) {
throw lastError;
}
throw new YgoApiError(500, `Network error: ${lastError?.message || "Unknown error"}`);
}
async getCardInfo(params) {
return this.request("/cardinfo.php", params);
}
async getCardByName(name) {
try {
const response = await this.getCardInfo({ name });
return response.data[0] || null;
} catch (error) {
if (error instanceof YgoApiError && error.statusCode === 400) {
return null;
}
throw error;
}
}
async getCardById(id) {
try {
const response = await this.getCardInfo({ id });
return response.data[0] || null;
} catch (error) {
if (error instanceof YgoApiError && error.statusCode === 400) {
return null;
}
throw error;
}
}
async searchCards(fname, params) {
return this.getCardInfo({ ...params, fname });
}
async getCardsByArchetype(archetype, params) {
return this.getCardInfo({ ...params, archetype });
}
async getCardsBySet(cardset, params) {
return this.getCardInfo({ ...params, cardset });
}
async getRandomCard() {
const response = await this.request("/randomcard.php");
return response.data[0];
}
async getAllCardSets() {
return this.request("/cardsets.php");
}
async getCardSetInfo(setcode) {
return this.request("/cardsetsinfo.php", { setcode });
}
async getAllArchetypes() {
return this.request("/archetypes.php");
}
async checkDatabaseVersion() {
const response = await this.request("/checkDBVer.php");
return response[0];
}
async getStapleCards(params) {
return this.getCardInfo({ ...params, staple: "yes" });
}
async getCardsByFormat(format, params) {
return this.getCardInfo({ ...params, format });
}
async getCardsByGenesysFormat(params) {
return this.getCardInfo({ ...params, format: "genesys", misc: "yes" });
}
async getBanlistCards(banlist, params) {
return this.getCardInfo({ ...params, banlist });
}
async getCardsWithPagination(num, offset, params) {
return this.getCardInfo({ ...params, num, offset });
}
async getCardsByType(type, params) {
return this.getCardInfo({ ...params, type });
}
async getCardsByAttribute(attribute, params) {
return this.getCardInfo({ ...params, attribute });
}
async getCardsByRace(race, params) {
return this.getCardInfo({ ...params, race });
}
async getCardsByLevel(level, params) {
return this.getCardInfo({ ...params, level });
}
async getCardsByATK(atk, params) {
return this.getCardInfo({ ...params, atk });
}
async getCardsByDEF(def, params) {
return this.getCardInfo({ ...params, def });
}
async getCardsWithMiscInfo(params) {
return this.getCardInfo({ ...params, misc: "yes" });
}
}
function buildComparison(operator, value) {
return `${operator}${value}`;
}
function getCardImages(card) {
const [defaultImage, ...alternates] = card.card_images;
return {
default: defaultImage,
alternates
};
}
function isMonsterCard(card) {
return !card.type.includes("Spell") && !card.type.includes("Trap");
}
function isSpellCard(card) {
return card.type.includes("Spell");
}
function isTrapCard(card) {
return card.type.includes("Trap");
}
function isExtraDeckMonster(card) {
const extraDeckTypes = [
"Fusion Monster",
"Link Monster",
"Pendulum Effect Fusion Monster",
"Synchro Monster",
"Synchro Pendulum Effect Monster",
"Synchro Tuner Monster",
"XYZ Monster",
"XYZ Pendulum Effect Monster"
];
return extraDeckTypes.includes(card.type);
}