Script Update

This commit is contained in:
daydreamer-json 2024-07-08 03:30:19 +09:00
parent 2e628a36f2
commit acc9a3f492
Signed by: daydreamer-json
GPG key ID: ADC9F0473FC56D8C
5 changed files with 782 additions and 327 deletions

8
.editorconfig Normal file
View file

@ -0,0 +1,8 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true

View file

@ -1,5 +0,0 @@
# mhy-api-backup
certain anime game company API archive
Archived file: [/output](/output)

193
src/generateMarkdown.js Normal file
View file

@ -0,0 +1,193 @@
const fs = require('fs');
const path = require('path');
const { server } = require('typescript');
const rootDirectory = path.resolve(path.join('.', 'output_hoyoplay'));
const namePrettyDefinition = {
'gameName': {
'bh3': 'Honkai Impact 3rd',
'hk4e': 'Genshin Impact',
'hkrpg': 'Honkai: Star Rail',
'nap': 'Zenless Zone Zero'
},
'serverName': {
'cn': 'China',
'global': 'Global',
}
};
async function main () {
const apiMasterData = await apiDataImport();
const generateMdTextOutput = generateMdText(apiMasterData);
await fileUpdater(generateMdTextOutput);
}
function generateMdText (apiMasterData) {
const outputTextArray = new Array();
outputTextArray.push('# miHoYo Game Package API Archive\n');
outputTextArray.push('This repository fetches game package information from the API provided by miHoYo/HoYoverse and archives it in Git. ');
outputTextArray.push('If you wish to refer to previous versions of the game package, please refer to the commit history. ');
outputTextArray.push('If you want to see the raw JSON data returned by the API, see the `output` directory.\n');
outputTextArray.push('The old launcher is no longer supported and the API used until now has stopped being updated. For this reason, information is now fetched from the API of the HoYoPlay launcher.\n');
Object.keys(apiMasterData).forEach(serverName => {
apiMasterData[serverName]['game_packages'].reverse();
apiMasterData[serverName]['game_packages'].forEach(gameObj => {
const gameCodeName = gameObj.game.biz.match(new RegExp('([a-z0-9]*)_([a-z0-9]*)'))[1];
const gameServerName = gameObj.game.biz.match(new RegExp('([a-z0-9]*)_([a-z0-9]*)'))[2];
if (Object.keys(namePrettyDefinition.gameName).includes(gameCodeName) === true && Object.keys(namePrettyDefinition.serverName).includes(gameServerName) === true) {
outputTextArray.push(`## ${namePrettyDefinition.gameName[gameCodeName]} - ${namePrettyDefinition.serverName[gameServerName]}\n`);
outputTextArray.push(`Game version: <span style="font-size: 150%;">${gameObj.main.major.version}</span>\n`);
if (gameObj.main.major['game_pkgs'].length > 0) {
outputTextArray.push(`### Full Package\n`);
outputTextArray.push(`|Link|Size|MD5|`);
outputTextArray.push(`|---|---|---|`);
if (gameObj.main.major['game_pkgs'].length === 1) {
outputTextArray.push(`|[Game](${gameObj.main.major['game_pkgs'][0].url})|${formatFileSize(gameObj.main.major['game_pkgs'][0].size, 2)}|\`${gameObj.main.major['game_pkgs'][0].md5.toLowerCase()}\`|`);
} else {
for (let i = 0; i < gameObj.main.major['game_pkgs'].length; i++) {
outputTextArray.push(`|[Part ${i + 1}](${gameObj.main.major['game_pkgs'][i].url})|${formatFileSize(gameObj.main.major['game_pkgs'][i].size, 2)}|\`${gameObj.main.major['game_pkgs'][i].md5.toLowerCase()}\`|`);
}
}
outputTextArray.push('');
}
if (gameObj.main.major['audio_pkgs'].length > 0) {
outputTextArray.push(`### Audio Package\n`);
outputTextArray.push(`|Link|Size|MD5|`);
outputTextArray.push(`|---|---|---|`);
for (let i = 0; i < gameObj.main.major['audio_pkgs'].length; i++) {
outputTextArray.push(`|[${langDispName(gameObj.main.major['audio_pkgs'][i].language)}](${gameObj.main.major['audio_pkgs'][i].url})|${formatFileSize(gameObj.main.major['audio_pkgs'][i].size, 2)}|\`${gameObj.main.major['audio_pkgs'][i].md5.toLowerCase()}\`|`);
}
outputTextArray.push('');
}
if (gameObj.main.patches.length > 0) {
outputTextArray.push(`### Update Diff Package\n`);
for (let i = 0; i < gameObj.main.patches.length; i++) {
if (gameObj.main.patches[i]['game_pkgs'].length > 0 || gameObj.main.patches[i]['audio_pkgs'].length > 0) {
outputTextArray.push(`|From|Link|Size|MD5|`);
outputTextArray.push(`|---|---|---|---|`);
if (gameObj.main.patches[i]['game_pkgs'].length > 0) {
if (gameObj.main.patches[i]['game_pkgs'].length === 1) {
outputTextArray.push(`|${gameObj.main.patches[i].version}|[Game](${gameObj.main.patches[i]['game_pkgs'][0].url})|${formatFileSize(gameObj.main.patches[i]['game_pkgs'][0].size, 2)}|\`${gameObj.main.patches[i]['game_pkgs'][0].md5.toLowerCase()}\`|`);
} else {
for (let j = 0; j < gameObj.main.patches[i]['game_pkgs'].length; j++) {
outputTextArray.push(`|${gameObj.main.patches[i].version}|[Game - Part ${j + 1}](${gameObj.main.patches[i]['game_pkgs'][j].url})|${formatFileSize(gameObj.main.patches[i]['game_pkgs'][j].size, 2)}|\`${gameObj.main.patches[i]['game_pkgs'][j].md5.toLowerCase()}\`|`);
}
}
}
if (gameObj.main.patches[i]['audio_pkgs'].length > 0) {
for (let j = 0; j < gameObj.main.patches[i]['audio_pkgs'].length; j++) {
outputTextArray.push(`|${gameObj.main.patches[i].version}|[Audio - ${langDispName(gameObj.main.patches[i]['audio_pkgs'][j].language)}](${gameObj.main.patches[i]['audio_pkgs'][j].url})|${formatFileSize(gameObj.main.patches[i]['audio_pkgs'][j].size, 2)}|\`${gameObj.main.patches[i]['audio_pkgs'][j].md5.toLowerCase()}\`|`);
}
}
outputTextArray.push('');
}
}
}
if (gameObj['pre_download'] !== null && gameObj['pre_download'].major !== null) {
outputTextArray.push(`### Pre-download Package\n`);
outputTextArray.push(`Pre-download version: <span style="font-size: 140%;">${gameObj['pre_download'].major.version}</span>\n`);
if (gameObj['pre_download'].major['game_pkgs'].length > 0) {
outputTextArray.push(`#### Full Package\n`);
outputTextArray.push(`|Link|Size|MD5|`);
outputTextArray.push(`|---|---|---|`);
if (gameObj['pre_download'].major['game_pkgs'].length === 1) {
outputTextArray.push(`|[Game](${gameObj['pre_download'].major['game_pkgs'][0].url})|${formatFileSize(gameObj['pre_download'].major['game_pkgs'][0].size, 2)}|\`${gameObj['pre_download'].major['game_pkgs'][0].md5.toLowerCase()}\`|`);
} else {
for (let i = 0; i < gameObj['pre_download'].major['game_pkgs'].length; i++) {
outputTextArray.push(`|[Part ${i + 1}](${gameObj['pre_download'].major['game_pkgs'][i].url})|${formatFileSize(gameObj['pre_download'].major['game_pkgs'][i].size, 2)}|\`${gameObj['pre_download'].major['game_pkgs'][i].md5.toLowerCase()}\`|`);
}
}
outputTextArray.push('');
}
if (gameObj['pre_download'].major['audio_pkgs'].length > 0) {
outputTextArray.push(`#### Audio Package\n`);
outputTextArray.push(`|Link|Size|MD5|`);
outputTextArray.push(`|---|---|---|`);
for (let i = 0; i < gameObj['pre_download'].major['audio_pkgs'].length; i++) {
outputTextArray.push(`|[${langDispName(gameObj['pre_download'].major['audio_pkgs'][i].language)}](${gameObj['pre_download'].major['audio_pkgs'][i].url})|${formatFileSize(gameObj['pre_download'].major['audio_pkgs'][i].size, 2)}|\`${gameObj['pre_download'].major['audio_pkgs'][i].md5.toLowerCase()}\`|`);
}
outputTextArray.push('');
}
if (gameObj['pre_download'].patches.length > 0) {
outputTextArray.push(`#### Update Diff Package\n`);
for (let i = 0; i < gameObj['pre_download'].patches.length; i++) {
if (gameObj['pre_download'].patches[i]['game_pkgs'].length > 0 || gameObj['pre_download'].patches[i]['audio_pkgs'].length > 0) {
outputTextArray.push(`|From|Link|Size|MD5|`);
outputTextArray.push(`|---|---|---|---|`);
if (gameObj['pre_download'].patches[i]['game_pkgs'].length > 0) {
if (gameObj['pre_download'].patches[i]['game_pkgs'].length === 1) {
outputTextArray.push(`|${gameObj['pre_download'].patches[i].version}|[Game](${gameObj['pre_download'].patches[i]['game_pkgs'][0].url})|${formatFileSize(gameObj['pre_download'].patches[i]['game_pkgs'][0].size, 2)}|\`${gameObj['pre_download'].patches[i]['game_pkgs'][0].md5.toLowerCase()}\`|`);
} else {
for (let j = 0; j < gameObj['pre_download'].patches[i]['game_pkgs'].length; j++) {
outputTextArray.push(`|${gameObj['pre_download'].patches[i].version}|[Game - Part ${j + 1}](${gameObj['pre_download'].patches[i]['game_pkgs'][j].url})|${formatFileSize(gameObj['pre_download'].patches[i]['game_pkgs'][j].size, 2)}|\`${gameObj['pre_download'].patches[i]['game_pkgs'][j].md5.toLowerCase()}\`|`);
}
}
}
if (gameObj['pre_download'].patches[i]['audio_pkgs'].length > 0) {
for (let j = 0; j < gameObj['pre_download'].patches[i]['audio_pkgs'].length; j++) {
outputTextArray.push(`|${gameObj['pre_download'].patches[i].version}|[Audio - ${langDispName(gameObj['pre_download'].patches[i]['audio_pkgs'][j].language)}](${gameObj['pre_download'].patches[i]['audio_pkgs'][j].url})|${formatFileSize(gameObj['pre_download'].patches[i]['audio_pkgs'][j].size, 2)}|\`${gameObj['pre_download'].patches[i]['audio_pkgs'][j].md5.toLowerCase()}\`|`);
}
}
outputTextArray.push('');
}
}
}
}
}
})
})
return outputTextArray.join('\n');
}
async function apiDataImport () {
let apiMasterData = null;
if (await checkFileExists(path.join(rootDirectory, 'master.json'))) {
apiMasterData = JSON.parse(await fs.promises.readFile(path.join(rootDirectory, 'master.json'), {encoding: 'utf8'}));
return apiMasterData;
} else {
throw new Error('API master output data not found');
}
}
async function checkFileExists (filePath) {
try {
await fs.promises.access(path.resolve(filePath), fs.constants.F_OK);
return true // exists
} catch (error) {
return false // not exists
}
}
async function fileUpdater (outputText) {
let isNeedWrite = true;
if (await checkFileExists(path.resolve(path.join(rootDirectory, '../', 'README.md'))) === false) {
isNeedWrite = true;
} else {
const loadedFileData = await fs.promises.readFile(path.resolve(path.join(rootDirectory, '../', 'README.md')), {encoding: 'utf8'});
if (loadedFileData === outputText) {
isNeedWrite = false;
} else {
isNeedWrite = true;
}
}
if (isNeedWrite === true) {
await fs.promises.writeFile(path.resolve(path.join(rootDirectory, '../', 'README.md')), outputText, {flag: 'w', encoding: 'utf8'});
}
}
function langDispName (inputText, displayLanguage = 'en-us') {
const nameFunc = new Intl.DisplayNames([displayLanguage], {'type': 'language'});
return nameFunc.of(inputText);
}
function formatFileSize (bytes, decimals = 2) {
if (bytes === 0) return '0 byte';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
main();

View file

@ -1,322 +1,363 @@
const log4js = require('log4js'); const log4js = require('log4js');
const color = require('ansi-colors'); const color = require('ansi-colors');
const clui = require('clui'); const axios = require('axios');
const axios = require('axios'); const fs = require('fs');
const fs = require('fs'); const path = require('path');
const path = require('path'); const termKit = require('terminal-kit').terminal;
const termKit = require('terminal-kit').terminal;
log4js.configure({
log4js.configure({ appenders: {
appenders: { System: {
System: { type: 'stdout'
type: 'stdout' }
} },
}, categories: {
categories: { default: {
default: { appenders: ['System'],
appenders: ['System'], level: 'trace'
level: 'trace' }
} }
} })
}) const logger = log4js.getLogger('System');
const logger = log4js.getLogger('System'); console.log(`Logger started (level: ${logger.level})`);
console.log(`Logger started (level: ${logger.level})`); if (logger.level != 'TRACE') console.log(`Logs with levels less than '${logger.level}' are truncated`);
if (logger.level != 'TRACE') console.log(`Logs with levels less than '${logger.level}' are truncated`); logger.trace('Program has been started');
logger.trace('Program has been started');
const rootDirectory = path.resolve(path.join('.', 'output'));
const spinnerSeq = ["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"]; const apiConnectDelay = 0;
const rootDirectory = path.resolve(path.join('.', 'output')); const apiConnectTimeout = 20000;
const apiConnectDelay = 0; const apiDefinition = {
const apiConnectTimeout = 20000; 'gameList': ['bh3', 'hk4e', 'hkrpg', 'nap'],
const apiDefinition = JSON.parse(atob('eyJnYW1lTGlzdCI6WyJiaDMiLCJoazRlIiwiaGtycGciLCJuYXAiXSwic2VydmVyTGlzdCI6eyJiaDMiOlsiY24iLCJqcCIsInR3Iiwia3IiLCJzZWEiLCJldSJdLCJoazRlIjpbImNuIiwib3MiXSwiaGtycGciOlsiY24iLCJvcyJdLCJuYXAiOlsib3MiXX0sImRlZmluaXRpb24iOnsiYmgzIjp7ImNuIjp7InVybCI6Imh0dHBzOi8vYmgzLWxhdW5jaGVyLXN0YXRpYy5taWhveW8uY29tL2JoM19jbi9tZGsvbGF1bmNoZXIvYXBpL3Jlc291cmNlIiwiaWQiOjQsImtleSI6IlN5dnVQbnFMIiwiZW5hYmxlZCI6dHJ1ZX0sImpwIjp7InVybCI6Imh0dHBzOi8vc2RrLW9zLXN0YXRpYy5ob3lvdmVyc2UuY29tL2JoM19nbG9iYWwvbWRrL2xhdW5jaGVyL2FwaS9yZXNvdXJjZSIsImlkIjoxOSwia2V5Ijoib2pldlowRXlJeVpOQ3k0biIsImVuYWJsZWQiOnRydWV9LCJ0dyI6eyJ1cmwiOiJodHRwczovL3Nkay1vcy1zdGF0aWMuaG95b3ZlcnNlLmNvbS9iaDNfZ2xvYmFsL21kay9sYXVuY2hlci9hcGkvcmVzb3VyY2UiLCJpZCI6OCwia2V5IjoiZGVtaFVUY1ciLCJlbmFibGVkIjp0cnVlfSwia3IiOnsidXJsIjoiaHR0cHM6Ly9zZGstb3Mtc3RhdGljLmhveW92ZXJzZS5jb20vYmgzX2dsb2JhbC9tZGsvbGF1bmNoZXIvYXBpL3Jlc291cmNlIiwiaWQiOjExLCJrZXkiOiJQUmc1NzFYaCIsImVuYWJsZWQiOnRydWV9LCJzZWEiOnsidXJsIjoiaHR0cHM6Ly9zZGstb3Mtc3RhdGljLmhveW92ZXJzZS5jb20vYmgzX2dsb2JhbC9tZGsvbGF1bmNoZXIvYXBpL3Jlc291cmNlIiwiaWQiOjksImtleSI6InRFR050VmhOIiwiZW5hYmxlZCI6dHJ1ZX0sImV1Ijp7InVybCI6Imh0dHBzOi8vc2RrLW9zLXN0YXRpYy5ob3lvdmVyc2UuY29tL2JoM19nbG9iYWwvbWRrL2xhdW5jaGVyL2FwaS9yZXNvdXJjZSIsImlkIjoxMCwia2V5IjoiZHB6NjV4SjMiLCJlbmFibGVkIjp0cnVlfX0sImhrNGUiOnsiY24iOnsidXJsIjoiaHR0cHM6Ly9zZGstc3RhdGljLm1paG95by5jb20vaGs0ZV9jbi9tZGsvbGF1bmNoZXIvYXBpL3Jlc291cmNlIiwiaWQiOjE4LCJrZXkiOiJlWWQ4OUptSiIsImVuYWJsZWQiOnRydWV9LCJvcyI6eyJ1cmwiOiJodHRwczovL3Nkay1vcy1zdGF0aWMuaG95b3ZlcnNlLmNvbS9oazRlX2dsb2JhbC9tZGsvbGF1bmNoZXIvYXBpL3Jlc291cmNlIiwiaWQiOjEwLCJrZXkiOiJnY1N0Z2FyaCIsImVuYWJsZWQiOnRydWV9LCJiZXRhX29zIjp7InVybCI6Imh0dHBzOi8vaGs0ZS1iZXRhLWxhdW5jaGVyLXN0YXRpYy5ob3lvdmVyc2UuY29tL2hrNGVfZ2xvYmFsL21kay9sYXVuY2hlci9hcGkvcmVzb3VyY2UiLCJpZCI6bnVsbCwia2V5IjpudWxsLCJlbmFibGVkIjpmYWxzZX19LCJoa3JwZyI6eyJjbiI6eyJ1cmwiOiJodHRwczovL2FwaS1sYXVuY2hlci5taWhveW8uY29tL2hrcnBnX2NuL21kay9sYXVuY2hlci9hcGkvcmVzb3VyY2UiLCJpZCI6MzMsImtleSI6IjZLY1Z1T2tiY3FqSm9taloiLCJlbmFibGVkIjp0cnVlfSwib3MiOnsidXJsIjoiaHR0cHM6Ly9oa3JwZy1sYXVuY2hlci1zdGF0aWMuaG95b3ZlcnNlLmNvbS9oa3JwZ19nbG9iYWwvbWRrL2xhdW5jaGVyL2FwaS9yZXNvdXJjZSIsImlkIjozNSwia2V5IjoidnBsT1ZYOFZuN2N3Rzh5YiIsImVuYWJsZWQiOnRydWV9fSwibmFwIjp7Im9zIjp7InVybCI6Imh0dHBzOi8vbmFwLWxhdW5jaGVyLXN0YXRpYy5ob3lvdmVyc2UuY29tL25hcF9nbG9iYWwvbWRrL2xhdW5jaGVyL2FwaS9yZXNvdXJjZSIsImlkIjoxMSwia2V5IjoiU3hLT2dsclVzSkZ4cUU0SSIsImVuYWJsZWQiOnRydWV9fX19')); 'serverList': {
const namePrettyDefinition = JSON.parse(atob('eyJnYW1lTmFtZSI6eyJiaDMiOiJIb25rYWkgSW1wYWN0IDNyZCIsImhrNGUiOiJHZW5zaGluIEltcGFjdCIsImhrcnBnIjoiSG9ua2FpOiBTdGFyIFJhaWwiLCJuYXAiOiJaZW5sZXNzIFpvbmUgWmVybyJ9LCJzZXJ2ZXJOYW1lIjp7ImNuIjoiQ04gKENoaW5hKSIsImNuX2JpbGliaWxpIjoiQ04gQmlsaUJpbGkgKENoaW5hKSIsIm9zIjoiR2xvYmFsIChPdmVyc2VhcykiLCJqcCI6IkpQIChKYXBhbikiLCJ0dyI6IlRXL0hLL01PIChUcmFkaXRpb25hbCBDaGluZXNlKSIsImtyIjoiS1IgKEtvcmVhKSIsInNlYSI6IlNFQSAoU291dGhlYXN0IEFzaWEpIiwiZXUiOiJHbG9iYWwgKEV1cm9wZSAvIEFtZXJpY2EpIn19')); 'bh3': ['cn', 'jp', 'tw', 'kr', 'sea', 'eu'],
'hk4e': ['cn', 'os'],
async function main () { 'hkrpg': ['cn', 'os'],
logger.trace(`Running apiConnectRunner ...`); 'nap': ['os']
const apiResponseReturned = await apiConnectRunner(); },
const apiResponseObj = apiResponseReturned[0]; 'definition': {
termKit.table(objArrToArrArr(apiResponseReturned[1]), { 'bh3': {
hasBorder: false, 'cn': {'url': 'https://bh3-launcher-static.mihoyo.com/bh3_cn/mdk/launcher/api/resource', 'id': 4, 'key': 'SyvuPnqL', 'enabled': true},
contentHasMarkup: true, 'jp': {'url': 'https://sdk-os-static.hoyoverse.com/bh3_global/mdk/launcher/api/resource', 'id': 19, 'key': 'ojevZ0EyIyZNCy4n', 'enabled': true},
// borderChars: 'lightRounded', 'tw': {'url': 'https://sdk-os-static.hoyoverse.com/bh3_global/mdk/launcher/api/resource', 'id': 8, 'key': 'demhUTcW', 'enabled': true},
// borderAttr: { color: 'white' }, 'kr': {'url': 'https://sdk-os-static.hoyoverse.com/bh3_global/mdk/launcher/api/resource', 'id': 11, 'key': 'PRg571Xh', 'enabled': true},
textAttr: {bgColor: 'default'}, 'sea': {'url': 'https://sdk-os-static.hoyoverse.com/bh3_global/mdk/launcher/api/resource', 'id': 9, 'key': 'tEGNtVhN', 'enabled': true},
firstRowTextAttr: {bgColor: 'white', color: 'black'}, 'eu': {'url': 'https://sdk-os-static.hoyoverse.com/bh3_global/mdk/launcher/api/resource', 'id': 10, 'key': 'dpz65xJ3', 'enabled': true}
width: 80, },
fit: true 'hk4e': {
}); 'cn': {'url': 'https://sdk-static.mihoyo.com/hk4e_cn/mdk/launcher/api/resource', 'id': 18, 'key': 'eYd89JmJ', 'enabled': true},
logger.trace(`Returned from apiConnectRunner`); 'os': {'url': 'https://sdk-os-static.hoyoverse.com/hk4e_global/mdk/launcher/api/resource', 'id': 10, 'key': 'gcStgarh', 'enabled': true},
logger.info(`All downloads from the API are complete`) 'beta_os': {'url': 'https://hk4e-beta-launcher-static.hoyoverse.com/hk4e_global/mdk/launcher/api/resource', 'id': null, 'key': null, 'enabled': false}
if (apiResponseObj && Object.keys(apiResponseObj).length === apiDefinition.gameList.length) { },
const gameListVerifiedCount = apiDefinition.gameList.reduce((count, gameNameKey) => { 'hkrpg': {
return count + (Object.keys(apiResponseObj[gameNameKey]).length === apiDefinition.serverList[gameNameKey].length ? 1 : 0); 'cn': {'url': 'https://api-launcher.mihoyo.com/hkrpg_cn/mdk/launcher/api/resource', 'id': 33, 'key': '6KcVuOkbcqjJomjZ', 'enabled': true},
}, 0); 'os': {'url': 'https://hkrpg-launcher-static.hoyoverse.com/hkrpg_global/mdk/launcher/api/resource', 'id': 35, 'key': 'vplOVX8Vn7cwG8yb', 'enabled': true}
if (gameListVerifiedCount === apiDefinition.gameList.length) { },
logger.trace(`All API response object key length has been verified`); 'nap': {
logger.debug(`Reading existing local response JSON files ...`); 'os': {'url': 'https://nap-launcher-static.hoyoverse.com/nap_global/mdk/launcher/api/resource', 'id': 11, 'key': 'SxKOglrUsJFxqE4I', 'enabled': true}
logger.trace(`Running fileManageReaderRunner ...`); }
const fileManageReaderOutput = await fileManageReaderRunner(apiResponseObj); }
logger.trace(`Returned from fileManageReaderRunner`); };
logger.debug(`All local response JSON has been loaded`); const namePrettyDefinition = {
logger.debug(`Verifying local and remote files ...`); 'gameName': {
logger.trace(`Running fileManageDiffVerifier ...`); 'bh3': 'Honkai Impact 3rd',
const fileManageDiffVerifyOutput = await fileManageDiffVerifier(apiResponseObj, fileManageReaderOutput.loadedObj, fileManageReaderOutput.notExistsFileArray); 'hk4e': 'Genshin Impact',
logger.trace(`Returned from fileManageDiffVerifier`); 'hkrpg': 'Honkai: Star Rail',
logger.debug(`All local and remote response JSON has been verified`); 'nap': 'Zenless Zone Zero'
// logger.trace(`Verify result:`); },
// if (logger.level == 'TRACE') console.log(fileManageDiffVerifyOutput); 'serverName': {
if (fileManageDiffVerifyOutput.needWriteFlagCount > 0) { 'cn': 'CN (China)',
logger.debug(`Writing response JSON to local file ...`); 'cn_bilibili': 'CN BiliBili (China)',
logger.trace(`Running fileManageWriterRunner ...`); 'os': 'Global (Overseas)',
await fileManageWriterRunner(apiResponseObj, fileManageReaderOutput.loadedObj, fileManageDiffVerifyOutput); 'jp': 'JP (Japan)',
logger.trace(`Returned from fileManageWriterRunner`); 'tw': 'TW/HK/MO (Traditional Chinese)',
logger.debug(`Wrote response JSON to local file`); 'kr': 'KR (Korea)',
} else { 'sea': 'SEA (Southeast Asia)',
logger.info(`Local writes were skipped because there were no remote updates`); 'eu': 'Global (Europe / America)'
} }
logger.info(`All process has been completed (^_^)`); };
await new Promise(resolve => setTimeout(resolve, 1000));
} async function main () {
} logger.trace(`Running apiConnectRunner ...`);
} const apiResponseReturned = await apiConnectRunner();
const apiResponseObj = apiResponseReturned[0];
async function apiConnectRunner () { termKit.table(objArrToArrArr(apiResponseReturned[1]), {
logger.info(`Connecting to API ...`); hasBorder: false,
const definitionList = new Array(); contentHasMarkup: true,
const responseDispList = new Array(); // borderChars: 'lightRounded',
const responseObject = new Object(); // borderAttr: { color: 'white' },
logger.trace(`Extracting independent API definition ...`); textAttr: {bgColor: 'default'},
apiDefinition.gameList.forEach((gameNameKey) => { firstRowTextAttr: {bgColor: 'white', color: 'black'},
responseObject[gameNameKey] = {}; width: 80,
apiDefinition.serverList[gameNameKey].forEach((serverNameKey) => { fit: true
if (apiDefinition.definition[gameNameKey][serverNameKey].enabled === true) { });
definitionList.push({ logger.trace(`Returned from apiConnectRunner`);
'gameName': gameNameKey, logger.info(`All downloads from the API are complete`)
'serverName': serverNameKey, if (apiResponseObj && Object.keys(apiResponseObj).length === apiDefinition.gameList.length) {
'url': apiDefinition.definition[gameNameKey][serverNameKey].url, const gameListVerifiedCount = apiDefinition.gameList.reduce((count, gameNameKey) => {
'id': apiDefinition.definition[gameNameKey][serverNameKey].id, return count + (Object.keys(apiResponseObj[gameNameKey]).length === apiDefinition.serverList[gameNameKey].length ? 1 : 0);
'key': apiDefinition.definition[gameNameKey][serverNameKey].key }, 0);
}); if (gameListVerifiedCount === apiDefinition.gameList.length) {
} logger.trace(`All API response object key length has been verified`);
}) logger.debug(`Reading existing local response JSON files ...`);
}) logger.trace(`Running fileManageReaderRunner ...`);
for (const definition of definitionList) { const fileManageReaderOutput = await fileManageReaderRunner(apiResponseObj);
logger.trace(`Delaying connect ...`) logger.trace(`Returned from fileManageReaderRunner`);
if (apiConnectDelay !== null) await new Promise(resolve => setTimeout(resolve, apiConnectDelay)); logger.debug(`All local response JSON has been loaded`);
logger.trace(`Requesting ${definition.url.replace(/https:\/\/|\/mdk\/launcher\/api\/resource/g, '')} ...`); logger.debug(`Verifying local and remote files ...`);
let spinner = new clui.Spinner (`Requesting ${definition.url.replace(/https:\/\/|\/mdk\/launcher\/api\/resource/g, '')} ...`, spinnerSeq); logger.trace(`Running fileManageDiffVerifier ...`);
spinner.start(); const fileManageDiffVerifyOutput = await fileManageDiffVerifier(apiResponseObj, fileManageReaderOutput.loadedObj, fileManageReaderOutput.notExistsFileArray);
try { logger.trace(`Returned from fileManageDiffVerifier`);
const resData = await apiConnect(`${definition.url}`, definition.id, definition.key, spinner); logger.debug(`All local and remote response JSON has been verified`);
if (resData) { // logger.trace(`Verify result:`);
responseDispList.push({ // if (logger.level == 'TRACE') console.log(fileManageDiffVerifyOutput);
'game': namePrettyDefinition["gameName"][definition.gameName], if (fileManageDiffVerifyOutput.needWriteFlagCount > 0) {
'server': namePrettyDefinition["serverName"][definition.serverName], logger.debug(`Writing response JSON to local file ...`);
'version': resData.data.game.latest.version logger.trace(`Running fileManageWriterRunner ...`);
}); await fileManageWriterRunner(apiResponseObj, fileManageReaderOutput.loadedObj, fileManageDiffVerifyOutput);
responseObject[definition.gameName][definition.serverName] = resData.data; logger.trace(`Returned from fileManageWriterRunner`);
responseObject[definition.gameName][definition.serverName].deprecated_files.sort((a, b) => { logger.debug(`Wrote response JSON to local file`);
const nameA = a.name.toUpperCase(); } else {
const nameB = b.name.toUpperCase(); logger.info(`Local writes were skipped because there were no remote updates`);
if (nameA < nameB) { }
return -1; logger.info(`All process has been completed (^_^)`);
} await new Promise(resolve => setTimeout(resolve, 1000));
if (nameA > nameB) { }
return 1; }
} }
return 0;
}); async function apiConnectRunner () {
logger.debug(`Downloaded ${definition.url.replace(/https:\/\/|\/mdk\/launcher\/api\/resource/g, '')}`); logger.info(`Connecting to API ...`);
} const definitionList = new Array();
} catch (error) { const responseDispList = new Array();
logger.error(`Failed to download data from ${definition.url}`); const responseObject = new Object();
process.exit(1); logger.trace(`Extracting independent API definition ...`);
// console.error(error); apiDefinition.gameList.forEach((gameNameKey) => {
break; responseObject[gameNameKey] = {};
} apiDefinition.serverList[gameNameKey].forEach((serverNameKey) => {
} if (apiDefinition.definition[gameNameKey][serverNameKey].enabled === true) {
if (responseDispList.length === 0) { definitionList.push({
logger.error(`API processing failed`); 'gameName': gameNameKey,
return null; 'serverName': serverNameKey,
} else { 'url': apiDefinition.definition[gameNameKey][serverNameKey].url,
return [ 'id': apiDefinition.definition[gameNameKey][serverNameKey].id,
responseObject, 'key': apiDefinition.definition[gameNameKey][serverNameKey].key
responseDispList });
]; }
} })
} })
for (const definition of definitionList) {
async function apiConnect (url, id, key, spinner) { logger.trace(`Delaying connect ...`)
let connectionTimer = process.hrtime(); if (apiConnectDelay !== null) await new Promise(resolve => setTimeout(resolve, apiConnectDelay));
try { logger.trace(`Requesting ${definition.url.replace(/https:\/\/|\/mdk\/launcher\/api\/resource/g, '')} ...`);
const response = await axios({ try {
'method': 'get', const resData = await apiConnect(`${definition.url}`, definition.id, definition.key);
'url': url, if (resData) {
'params': { responseDispList.push({
'launcher_id': id, 'game': namePrettyDefinition["gameName"][definition.gameName],
'key': key 'server': namePrettyDefinition["serverName"][definition.serverName],
}, 'version': resData.data.game.latest.version
'headers': { });
'User-Agent': 'Mozilla/5.0', responseObject[definition.gameName][definition.serverName] = resData.data;
'Cache-Control': 'no-cache' responseObject[definition.gameName][definition.serverName].deprecated_files.sort((a, b) => {
}, const nameA = a.name.toUpperCase();
'timeout': apiConnectTimeout, const nameB = b.name.toUpperCase();
}); if (nameA < nameB) {
let connectionTimeResult = process.hrtime(connectionTimer); return -1;
spinner.stop(); }
logger.trace(`API connection time: ${(connectionTimeResult[0] * 1e9 + connectionTimeResult[1]) / 1e6} ms`); if (nameA > nameB) {
return response.data; return 1;
} catch (error) { }
let connectionTimeResult = process.hrtime(connectionTimer); return 0;
spinner.stop(); });
logger.trace(`API connection time: ${(connectionTimeResult[0] * 1e9 + connectionTimeResult[1]) / 1e6} ms`); logger.debug(`Downloaded ${definition.url.replace(/https:\/\/|\/mdk\/launcher\/api\/resource/g, '')}`);
logger.error(`API request failed: ${error.code}`); }
// console.error(error); } catch (error) {
throw error; logger.error(`Failed to download data from ${definition.url}`);
return error.response; process.exit(1);
} // console.error(error);
} break;
}
async function fileManageReaderRunner (apiResponseObj) { }
const loadedObj = new Object(); if (responseDispList.length === 0) {
const notExistsFileArray = new Array(); logger.error(`API processing failed`);
const masterFileExists = await checkFileExists(path.join(rootDirectory, 'master.json')); return null;
if (!masterFileExists) { } else {
notExistsFileArray.push(path.join(rootDirectory, 'master.json')); return [
} else { responseObject,
loadedObj.master = JSON.parse(await fs.promises.readFile(path.join(rootDirectory, 'master.json'), {encoding: 'utf8'})); responseDispList
} ];
for (let i = 0; i < apiDefinition.gameList.length; i++) { }
let gameNameKey = apiDefinition.gameList[i]; }
loadedObj[gameNameKey] = {};
for (let j = 0; j < apiDefinition.serverList[gameNameKey].length; j++) { async function apiConnect (url, id, key) {
let serverNameKey = apiDefinition.serverList[gameNameKey][j]; let connectionTimer = process.hrtime();
let fileExists = await checkFileExists(path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`)); try {
if (!fileExists) { const response = await axios({
notExistsFileArray.push(path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`)); 'method': 'get',
} else { 'url': url,
loadedObj[gameNameKey][serverNameKey] = JSON.parse(await fs.promises.readFile(path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`), {encoding: 'utf8'})); 'params': {
} 'launcher_id': id,
} 'key': key
} },
return { 'headers': {
"loadedObj": loadedObj, 'User-Agent': 'Mozilla/5.0',
"notExistsFileArray": notExistsFileArray 'Cache-Control': 'no-cache'
} },
} 'timeout': apiConnectTimeout,
});
async function fileManageWriterRunner (apiResponseObj, loadedObj, needWriteFlagObj) { let connectionTimeResult = process.hrtime(connectionTimer);
logger.trace(`Creating folder structure ...`); logger.trace(`API connection time: ${(connectionTimeResult[0] * 1e9 + connectionTimeResult[1]) / 1e6} ms`);
await createFolder(rootDirectory); return response.data;
await createFolder(path.join(rootDirectory, 'unique')); } catch (error) {
if (needWriteFlagObj.master) { let connectionTimeResult = process.hrtime(connectionTimer);
logger.trace(`Writing to ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'master.json'))} ...`); logger.trace(`API connection time: ${(connectionTimeResult[0] * 1e9 + connectionTimeResult[1]) / 1e6} ms`);
await fs.promises.writeFile(path.join(rootDirectory, 'master.json'), JSON.stringify(apiResponseObj, '', ' '), {flag: 'w', encoding: 'utf8'}); logger.error(`API request failed: ${error.code}`);
logger.debug(`Wrote data to ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'master.json'))}`); // console.error(error);
} throw error;
for (let i = 0; i < apiDefinition.gameList.length; i++) { return error.response;
let gameNameKey = apiDefinition.gameList[i]; }
for (let j = 0; j < apiDefinition.serverList[gameNameKey].length; j++) { }
let serverNameKey = apiDefinition.serverList[gameNameKey][j];
if (needWriteFlagObj[gameNameKey][serverNameKey]) { async function fileManageReaderRunner (apiResponseObj) {
logger.trace(`Writing to ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`))} ...`); const loadedObj = new Object();
await fs.promises.writeFile(path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`), JSON.stringify(apiResponseObj[gameNameKey][serverNameKey], '', ' '), {flag: 'w', encoding: 'utf8'}); const notExistsFileArray = new Array();
logger.debug(`Wrote data to ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`))}`); const masterFileExists = await checkFileExists(path.join(rootDirectory, 'master.json'));
} if (!masterFileExists) {
} notExistsFileArray.push(path.join(rootDirectory, 'master.json'));
} } else {
} loadedObj.master = JSON.parse(await fs.promises.readFile(path.join(rootDirectory, 'master.json'), {encoding: 'utf8'}));
}
async function fileManageDiffVerifier (apiResponseObj, loadedObj, notExistsFileArray) { for (let i = 0; i < apiDefinition.gameList.length; i++) {
let needWriteFlagCount = 0; let gameNameKey = apiDefinition.gameList[i];
const needWriteFlagObj = new Object(); loadedObj[gameNameKey] = {};
if (!notExistsFileArray.includes(path.join(rootDirectory, 'master.json'))) { for (let j = 0; j < apiDefinition.serverList[gameNameKey].length; j++) {
if (JSON.stringify(apiResponseObj) == JSON.stringify(loadedObj.master)) { let serverNameKey = apiDefinition.serverList[gameNameKey][j];
needWriteFlagObj.master = false; let fileExists = await checkFileExists(path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`));
} else { if (!fileExists) {
needWriteFlagObj.master = true; notExistsFileArray.push(path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`));
needWriteFlagCount += 1; } else {
logger.info(`Detected remote update of ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'master.json'))}`); loadedObj[gameNameKey][serverNameKey] = JSON.parse(await fs.promises.readFile(path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`), {encoding: 'utf8'}));
} }
} else { }
needWriteFlagObj.master = true; }
needWriteFlagCount += 1; return {
logger.info(`Detected remote update of ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'master.json'))}`); "loadedObj": loadedObj,
} "notExistsFileArray": notExistsFileArray
apiDefinition.gameList.forEach((gameNameKey) => { }
needWriteFlagObj[gameNameKey] = {}; }
apiDefinition.serverList[gameNameKey].forEach((serverNameKey) => {
if (!notExistsFileArray.includes(path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`))) { async function fileManageWriterRunner (apiResponseObj, loadedObj, needWriteFlagObj) {
if (JSON.stringify(apiResponseObj[gameNameKey][serverNameKey]) == JSON.stringify(loadedObj[gameNameKey][serverNameKey])) { logger.trace(`Creating folder structure ...`);
needWriteFlagObj[gameNameKey][serverNameKey] = false; await createFolder(rootDirectory);
} else { await createFolder(path.join(rootDirectory, 'unique'));
needWriteFlagObj[gameNameKey][serverNameKey] = true; if (needWriteFlagObj.master) {
needWriteFlagCount += 1; logger.trace(`Writing to ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'master.json'))} ...`);
logger.info(`Detected remote update of ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`))}`); await fs.promises.writeFile(path.join(rootDirectory, 'master.json'), JSON.stringify(apiResponseObj, '', ' '), {flag: 'w', encoding: 'utf8'});
} logger.debug(`Wrote data to ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'master.json'))}`);
} else { }
needWriteFlagObj[gameNameKey][serverNameKey] = true; for (let i = 0; i < apiDefinition.gameList.length; i++) {
needWriteFlagCount += 1; let gameNameKey = apiDefinition.gameList[i];
logger.info(`Detected remote update of ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`))}`); for (let j = 0; j < apiDefinition.serverList[gameNameKey].length; j++) {
} let serverNameKey = apiDefinition.serverList[gameNameKey][j];
}) if (needWriteFlagObj[gameNameKey][serverNameKey]) {
}) logger.trace(`Writing to ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`))} ...`);
needWriteFlagObj.needWriteFlagCount = needWriteFlagCount; await fs.promises.writeFile(path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`), JSON.stringify(apiResponseObj[gameNameKey][serverNameKey], '', ' '), {flag: 'w', encoding: 'utf8'});
return needWriteFlagObj; logger.debug(`Wrote data to ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`))}`);
} }
}
async function createFolder (folderPath) { }
try { }
await fs.promises.access(folderPath);
} catch (error) { async function fileManageDiffVerifier (apiResponseObj, loadedObj, notExistsFileArray) {
await fs.promises.mkdir(folderPath, {recursive: true}); let needWriteFlagCount = 0;
} const needWriteFlagObj = new Object();
} if (!notExistsFileArray.includes(path.join(rootDirectory, 'master.json'))) {
if (JSON.stringify(apiResponseObj) == JSON.stringify(loadedObj.master)) {
async function checkFileExists (filePath) { needWriteFlagObj.master = false;
try { } else {
await fs.promises.access(path.resolve(filePath), fs.constants.F_OK) needWriteFlagObj.master = true;
return true // exists needWriteFlagCount += 1;
} catch (error) { logger.info(`Detected remote update of ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'master.json'))}`);
return false // not exists }
} } else {
} needWriteFlagObj.master = true;
needWriteFlagCount += 1;
function formatFileSize (bytes, decimals = 2) { logger.info(`Detected remote update of ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'master.json'))}`);
if (bytes === 0) return '0 byte'; }
const k = 1024; apiDefinition.gameList.forEach((gameNameKey) => {
const dm = decimals < 0 ? 0 : decimals; needWriteFlagObj[gameNameKey] = {};
const sizes = ['bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; apiDefinition.serverList[gameNameKey].forEach((serverNameKey) => {
const i = Math.floor(Math.log(bytes) / Math.log(k)); if (!notExistsFileArray.includes(path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`))) {
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; if (JSON.stringify(apiResponseObj[gameNameKey][serverNameKey]) == JSON.stringify(loadedObj[gameNameKey][serverNameKey])) {
} needWriteFlagObj[gameNameKey][serverNameKey] = false;
} else {
// function truncateObjectByKeyDepth(obj, depth) { needWriteFlagObj[gameNameKey][serverNameKey] = true;
// if (depth === 0) { needWriteFlagCount += 1;
// return "truncated"; logger.info(`Detected remote update of ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`))}`);
// } }
// let newObj = {}; } else {
// for (let key in obj) { needWriteFlagObj[gameNameKey][serverNameKey] = true;
// if (typeof obj[key] === 'object' && obj[key] !== null) { needWriteFlagCount += 1;
// newObj[key] = extractObjectDepth(obj[key], depth - 1); logger.info(`Detected remote update of ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`))}`);
// } else { }
// newObj[key] = obj[key]; })
// } })
// } needWriteFlagObj.needWriteFlagCount = needWriteFlagCount;
// return newObj; return needWriteFlagObj;
// } }
function objArrToArrArr (objectArray) { async function createFolder (folderPath) {
let outputArr = new Array(); try {
outputArr.push(Object.keys(objectArray[0])); await fs.promises.access(folderPath);
for (let i = 0; i < objectArray.length; i++) { } catch (error) {
let pushArr = new Array(); await fs.promises.mkdir(folderPath, {recursive: true});
outputArr[0].forEach((key) => { }
pushArr.push(objectArray[i][key]); }
})
outputArr.push(pushArr); async function checkFileExists (filePath) {
} try {
return outputArr; await fs.promises.access(path.resolve(filePath), fs.constants.F_OK)
} return true // exists
} catch (error) {
main(); return false // not exists
}
}
function formatFileSize (bytes, decimals = 2) {
if (bytes === 0) return '0 byte';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
// function truncateObjectByKeyDepth(obj, depth) {
// if (depth === 0) {
// return "truncated";
// }
// let newObj = {};
// for (let key in obj) {
// if (typeof obj[key] === 'object' && obj[key] !== null) {
// newObj[key] = extractObjectDepth(obj[key], depth - 1);
// } else {
// newObj[key] = obj[key];
// }
// }
// return newObj;
// }
function objArrToArrArr (objectArray) {
let outputArr = new Array();
outputArr.push(Object.keys(objectArray[0]));
for (let i = 0; i < objectArray.length; i++) {
let pushArr = new Array();
outputArr[0].forEach((key) => {
pushArr.push(objectArray[i][key]);
})
outputArr.push(pushArr);
}
return outputArr;
}
main();

218
src/runHYP.js Normal file
View file

@ -0,0 +1,218 @@
const log4js = require('log4js');
const axios = require('axios');
const fs = require('fs');
const path = require('path');
log4js.configure({
appenders: {
System: {
type: 'stdout'
}
},
categories: {
default: {
appenders: ['System'],
level: 'trace'
}
}
})
const logger = log4js.getLogger('System');
console.log(`Logger started (level: ${logger.level})`);
if (logger.level != 'TRACE') console.log(`Logs with levels less than '${logger.level}' are truncated`);
logger.trace('Program has been started');
const rootDirectory = path.resolve(path.join('.', 'output_hoyoplay'));
const apiConnectDelay = null;
const apiConnectTimeout = 30000;
const apiDefinition = {
'serverList': ['global', 'cn'],
'definition': {
'global': {'url': 'https://sg-hyp-api.hoyoverse.com/hyp/hyp-connect/api/getGamePackages', 'id': 'VYTpXlbWo8', 'enabled': true},
'cn': {'url': 'https://hyp-api.mihoyo.com/hyp/hyp-connect/api/getGamePackages', 'id': 'jGHBHlcOq1', 'enabled': true}
}
};
const namePrettyDefinition = {
'gameName': {
'bh3': 'Honkai Impact 3rd',
'hk4e': 'Genshin Impact',
'hkrpg': 'Honkai: Star Rail',
'nap': 'Zenless Zone Zero'
},
'serverName': {
'cn': 'China',
'global': 'Global',
}
};
async function main () {
const apiResponseObj = await apiConnectRunner();
// console.log(apiResponseObj);
const fileManageReaderOutput = await fileManageReader();
const fileManageDiffVerifyOutput = await fileManageDiffVerifier(apiResponseObj, fileManageReaderOutput.loadedObj, fileManageReaderOutput.notExistsFileArray);
if (fileManageDiffVerifyOutput.needWriteFlagCount > 0) {
logger.debug(`Writing response JSON to local file ...`);
await fileManageWriter(apiResponseObj, fileManageDiffVerifyOutput);
logger.debug(`Wrote response JSON to local file`);
} else {
logger.info(`Local writes were skipped because there were no remote updates`);
}
logger.info(`All process has been completed (^_^)`);
await new Promise(resolve => setTimeout(resolve, 1000));
}
async function apiConnectRunner () {
const definitionList = new Array();
const responseObject = new Object();
apiDefinition.serverList.forEach(serverNameKey => {
if (apiDefinition.definition[serverNameKey].enabled === true) {
definitionList.push({'serverName': serverNameKey, ...apiDefinition.definition[serverNameKey]});
}
});
for (const definition of definitionList) {
if (apiConnectDelay !== null) await new Promise(resolve => setTimeout(resolve, apiConnectDelay));
logger.trace(`Requesting ${definition.url.replace('https://', '')} ...`);
try {
const resData = await apiConnect(`${definition.url}`, definition.id);
if (resData) {
responseObject[definition.serverName] = resData.data;
logger.debug(`Downloaded ${definition.url.replace('https://', '')}`);
}
} catch (error) {
logger.error(`Failed to download data from ${definition.url.replace('https://', '')}`);
process.exit(1);
// console.error(error);
break;
}
}
if (!responseObject) {
logger.error(`API processing failed`);
return null;
} else {
return responseObject;
}
}
async function apiConnect (url, id) {
let connectionTimer = process.hrtime();
try {
const response = await axios({
'method': 'get',
'url': url,
'params': {
'launcher_id': id,
},
'headers': {
'User-Agent': 'Mozilla/5.0',
'Cache-Control': 'no-cache'
},
'timeout': apiConnectTimeout,
});
let connectionTimeResult = process.hrtime(connectionTimer);
logger.trace(`API connection time: ${(connectionTimeResult[0] * 1e9 + connectionTimeResult[1]) / 1e6} ms`);
return response.data;
} catch (error) {
let connectionTimeResult = process.hrtime(connectionTimer);
logger.trace(`API connection time: ${(connectionTimeResult[0] * 1e9 + connectionTimeResult[1]) / 1e6} ms`);
logger.error(`API request failed: ${error.code}`);
// console.error(error);
throw error;
}
}
async function fileManageReader () {
const loadedObj = new Object();
const notExistsFileArray = new Array();
const masterFileExists = await checkFileExists(path.join(rootDirectory, 'master.json'));
if (!masterFileExists) {
notExistsFileArray.push(path.join(rootDirectory, 'master.json'));
} else {
loadedObj.master = JSON.parse(await fs.promises.readFile(path.join(rootDirectory, 'master.json'), {encoding: 'utf8'}));
}
for (let i = 0; i < apiDefinition.serverList.length; i++) {
let serverNameKey = apiDefinition.serverList[i];
let fileExists = await checkFileExists(path.join(rootDirectory, 'unique', `${serverNameKey}.json`));
if (!fileExists) {
notExistsFileArray.push(path.join(rootDirectory, 'unique', `${serverNameKey}.json`));
} else {
loadedObj[serverNameKey] = JSON.parse(await fs.promises.readFile(path.join(rootDirectory, 'unique', `${serverNameKey}.json`), {encoding: 'utf8'}));
}
}
return {
"loadedObj": loadedObj,
"notExistsFileArray": notExistsFileArray
}
}
async function fileManageDiffVerifier (apiResponseObj, loadedObj, notExistsFileArray) {
let needWriteFlagCount = 0;
const needWriteFlagObj = new Object();
if (!notExistsFileArray.includes(path.join(rootDirectory, 'master.json'))) {
if (JSON.stringify(apiResponseObj) == JSON.stringify(loadedObj.master)) {
needWriteFlagObj.master = false;
} else {
needWriteFlagObj.master = true;
needWriteFlagCount += 1;
logger.info(`Detected remote update of ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'master.json'))}`);
}
} else {
needWriteFlagObj.master = true;
needWriteFlagCount += 1;
logger.info(`Detected remote update of ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'master.json'))}`);
}
apiDefinition.serverList.forEach((serverNameKey) => {
if (!notExistsFileArray.includes(path.join(rootDirectory, 'unique', `${serverNameKey}.json`))) {
if (JSON.stringify(apiResponseObj[serverNameKey]) == JSON.stringify(loadedObj[serverNameKey])) {
needWriteFlagObj[serverNameKey] = false;
} else {
needWriteFlagObj[serverNameKey] = true;
needWriteFlagCount += 1;
logger.info(`Detected remote update of ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'unique', `${serverNameKey}.json`))}`);
}
} else {
needWriteFlagObj[serverNameKey] = true;
needWriteFlagCount += 1;
logger.info(`Detected remote update of ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'unique', `${serverNameKey}.json`))}`);
}
});
needWriteFlagObj.needWriteFlagCount = needWriteFlagCount;
return needWriteFlagObj;
}
async function fileManageWriter (apiResponseObj, needWriteFlagObj) {
logger.trace(`Creating folder structure ...`);
await createFolder(rootDirectory);
await createFolder(path.join(rootDirectory, 'unique'));
if (needWriteFlagObj.master) {
logger.trace(`Writing to ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'master.json'))} ...`);
await fs.promises.writeFile(path.join(rootDirectory, 'master.json'), JSON.stringify(apiResponseObj, '', ' '), {flag: 'w', encoding: 'utf8'});
logger.debug(`Wrote data to ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'master.json'))}`);
}
for (let i = 0; i < apiDefinition.serverList.length; i++) {
let serverNameKey = apiDefinition.serverList[i];
if (needWriteFlagObj[serverNameKey]) {
logger.trace(`Writing to ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'unique', `${serverNameKey}.json`))} ...`);
await fs.promises.writeFile(path.join(rootDirectory, 'unique', `${serverNameKey}.json`), JSON.stringify(apiResponseObj[serverNameKey], '', ' '), {flag: 'w', encoding: 'utf8'});
logger.debug(`Wrote data to ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'unique', `${serverNameKey}.json`))}`);
}
}
}
async function checkFileExists (filePath) {
try {
await fs.promises.access(path.resolve(filePath), fs.constants.F_OK)
return true // exists
} catch (error) {
return false // not exists
}
}
async function createFolder (folderPath) {
try {
await fs.promises.access(folderPath);
} catch (error) {
await fs.promises.mkdir(folderPath, {recursive: true});
}
}
main();