UNPKG

enkanetwork

Version:

API wrapper for enka.network written on TypeScript which provides localization, caching and convenience

201 lines (200 loc) 9.93 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AssetsUpdater = void 0; const promises_1 = __importDefault(require("node:fs/promises")); const node_path_1 = __importDefault(require("node:path")); // @ts-ignore: JSON IMPORT OUT OF BASE-DIR const config_json_1 = __importDefault(require("../../../assets/config.json")); const errors_1 = require("../../errors"); const constants_1 = require("./constants"); async function request(url) { const res = await fetch(constants_1.BASE_URL + url); if (!res.ok) throw new errors_1.AssetsUpdateError(url, res); return res.json(); } class AssetsUpdater { languages; isFetching; dataManager; localizationManager; constructor({ languages = ["EN"], checkInterval = 30 * 60 * 1000, // 0.5 hour instant = false, }, dataManager, localizationManager) { if (!languages.length) throw new Error("The languages value must contain at least 1 language"); if (languages.filter((language) => !constants_1.localizationLanguages.includes(language)) .length) throw new Error(`Use only supported languages! (${constants_1.localizationLanguages.join(", ")})`); this.dataManager = dataManager; this.localizationManager = localizationManager; // This is done so that the languages are not repeated and are not requested again this.languages = [...new Set(languages)]; this.isFetching = false; if (instant) this.fetchAssets(); if (checkInterval) setInterval(() => this.fetchAssets().catch(() => { }), checkInterval); } // function that checks the relevance of assets data and localization async fetchAssets() { if (this.isFetching) throw new Error("Content is already fetching!"); this.isFetching = true; const res = await fetch(`${constants_1.PROJECT_GITLAB_URL}?since=${config_json_1.default.lastUpdate}`); const data = await res.json(); if (!data.length && !this.languages.filter((language) => !config_json_1.default.languages.includes(language)).length && config_json_1.default.languages.length) { this.isFetching = false; return; } const skillIds = []; const [charactersData, costumesData, constellationsData, skillsetsData, skillsData, weaponsData, namecardsData, reliquaryData, reliquarySetData, reliquaryAffixData,] = await Promise.all([ request(constants_1.CHARACTER_DATA_URL), request(constants_1.COSTUME_DATA_URL), request(constants_1.CONSTELLATION_DATA_URL), request(constants_1.SKILLSET_DATA_URL), request(constants_1.SKILL_DATA_URL), request(constants_1.WEAPON_DATA_URL), request(constants_1.NAMECARD_DATA_URL), request(constants_1.RELIQUARY_DATA_URL), request(constants_1.RELIQUARY_SET_DATA_URL), request(constants_1.RELIQUARY_AFFIX_DATA_URL), ]); const characters = charactersData .filter((character) => !constants_1.excludeCharacters.includes(character.id)) .map((character) => { const skillset = skillsetsData.find((x) => x.id === character.skillDepotId); if (!skillset) return null; const energyBurst = skillsData.find((x) => x.id === skillset?.energySkill); if (!energyBurst) return null; skillIds.push(...skillset.skills.concat(skillset?.energySkill).filter(Boolean)); return { id: character.id, skillDepotId: character.skillDepotId, nameTextMapHash: character.nameTextMapHash, iconName: character.iconName, sideIconName: character.sideIconName, gachaIcon: `UI_Gacha_AvatarImg_${character.iconName .split("_") .at(-1)}`, qualityStars: constants_1.qualityTypesStars[character.qualityType], element: constants_1.elements[energyBurst.costElemType], skills: skillset.skills.concat(skillset?.energySkill).filter(Boolean), talents: skillset.talents.filter(Boolean), }; }) .filter(Boolean); // Aether and Lumine problem for (const character of charactersData) { if (character.id !== 10000005 && character.id !== 10000007) continue; for (const skillDepotId of character.candSkillDepotIds) { const skillset = skillsetsData.find((x) => x.id === skillDepotId); if (!skillset) continue; const energyBurst = skillsData.find((x) => x.id === skillset?.energySkill); if (!energyBurst) continue; skillIds.push(...skillset.skills.filter(Boolean)); characters.push({ id: character.id, skillDepotId, nameTextMapHash: character.nameTextMapHash, iconName: character.iconName, sideIconName: character.sideIconName, gachaIcon: `UI_Gacha_AvatarImg_${character.iconName .split("_") .at(-1)}`, qualityStars: constants_1.qualityTypesStars[character.qualityType], element: constants_1.elements[energyBurst.costElemType], skills: skillset.skills.filter(Boolean), talents: skillset.talents.filter(Boolean), }); } } const skills = skillsData .filter((skill) => skillIds.includes(skill.id)) .map((skill) => ({ id: skill.id, nameTextMapHash: skill.nameTextMapHash, skillIcon: skill.skillIcon, proudSkillGroupId: skill.proudSkillGroupId, })); const constellations = constellationsData.map((constellation) => ({ id: constellation.talentId, nameTextMapHash: constellation.nameTextMapHash, icon: constellation.icon, })); const costumes = costumesData.map((costume) => ({ // id: Object.values(costume).at(0), id: costume.skinId, iconName: costume.sideIconName.replace("_Side", ""), sideIconName: costume.sideIconName, gachaIcon: `UI_Costume_${costume.sideIconName.split("_").at(-1)}`, nameTextMapHash: costume.nameTextMapHash, })); const namecards = namecardsData .filter((namecard) => namecard.materialType === "MATERIAL_NAMECARD") .map((namecard) => ({ id: namecard.id, nameTextMapHash: namecard.nameTextMapHash, icon: namecard.icon, navbar: namecard.picPath.at(0) || null, banner: namecard.picPath.at(1), })); await Promise.all([ this.dataManager.writeCharacters(characters), this.dataManager.writeSkills(skills), this.dataManager.writeConstellations(constellations), this.dataManager.writeCostumes(costumes), this.dataManager.writeNamecards(namecards), ]); const reliquarySets = reliquarySetData .map((reliquarySet) => { const reliquaryAffix = reliquaryAffixData.find((x) => x.id === reliquarySet.equipAffixId && x.level === 1); return reliquaryAffix?.nameTextMapHash; }) // Why doesn't the typescript compiler understand that it won't be undefined anymore ??? // Issue - https://github.com/microsoft/TypeScript/issues/45097 .filter(Boolean); //lang imports const nameTextHashMaps = { characters: characters.map((character) => character?.nameTextMapHash), costumes: costumes.map((costume) => costume.nameTextMapHash), constellations: constellations.map((constellation) => constellation.nameTextMapHash), namecards: namecards.map((namecard) => namecard.nameTextMapHash), skills: skills.map((skill) => skill.nameTextMapHash), weapons: weaponsData.map((weapon) => weapon.nameTextMapHash), reliquary: reliquaryData.map((reliquary) => reliquary.nameTextMapHash), reliquarySets, }; const languagesData = await Promise.all(this.languages.map(async (lang) => { return request(`${constants_1.LOCALIZATION_BASE_URL + lang}.json`); })); for (const dataType in nameTextHashMaps) { const assetType = dataType; const nameTextHashes = nameTextHashMaps[assetType]; const localization = nameTextHashes.reduce((acc, nameTextHash) => { acc[String(nameTextHash)] = {}; this.languages.forEach((lang, index) => { // I refer to the languagesData by index since Promise.all guarantees the preservation of order acc[String(nameTextHash)][lang] = languagesData[index][nameTextHash] ?? "UNKNOWN"; }); return acc; }, {}); await this.localizationManager.write(assetType, localization); } this.isFetching = false; config_json_1.default.lastUpdate = new Date().toISOString(); config_json_1.default.languages = this.languages; await promises_1.default.writeFile(node_path_1.default.resolve(constants_1.ASSETS_PATH, "config.json"), JSON.stringify(config_json_1.default, null, 4)); } } exports.AssetsUpdater = AssetsUpdater;