Script Update
This commit is contained in:
parent
2e628a36f2
commit
acc9a3f492
5 changed files with 782 additions and 327 deletions
8
.editorconfig
Normal file
8
.editorconfig
Normal file
|
@ -0,0 +1,8 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
|
@ -1,5 +0,0 @@
|
|||
# mhy-api-backup
|
||||
|
||||
certain anime game company API archive
|
||||
|
||||
Archived file: [/output](/output)
|
193
src/generateMarkdown.js
Normal file
193
src/generateMarkdown.js
Normal 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();
|
685
src/run.js
685
src/run.js
|
@ -1,322 +1,363 @@
|
|||
const log4js = require('log4js');
|
||||
const color = require('ansi-colors');
|
||||
const clui = require('clui');
|
||||
const axios = require('axios');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const termKit = require('terminal-kit').terminal;
|
||||
|
||||
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 spinnerSeq = ["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"];
|
||||
const rootDirectory = path.resolve(path.join('.', 'output'));
|
||||
const apiConnectDelay = 0;
|
||||
const apiConnectTimeout = 20000;
|
||||
const apiDefinition = JSON.parse(atob('eyJnYW1lTGlzdCI6WyJiaDMiLCJoazRlIiwiaGtycGciLCJuYXAiXSwic2VydmVyTGlzdCI6eyJiaDMiOlsiY24iLCJqcCIsInR3Iiwia3IiLCJzZWEiLCJldSJdLCJoazRlIjpbImNuIiwib3MiXSwiaGtycGciOlsiY24iLCJvcyJdLCJuYXAiOlsib3MiXX0sImRlZmluaXRpb24iOnsiYmgzIjp7ImNuIjp7InVybCI6Imh0dHBzOi8vYmgzLWxhdW5jaGVyLXN0YXRpYy5taWhveW8uY29tL2JoM19jbi9tZGsvbGF1bmNoZXIvYXBpL3Jlc291cmNlIiwiaWQiOjQsImtleSI6IlN5dnVQbnFMIiwiZW5hYmxlZCI6dHJ1ZX0sImpwIjp7InVybCI6Imh0dHBzOi8vc2RrLW9zLXN0YXRpYy5ob3lvdmVyc2UuY29tL2JoM19nbG9iYWwvbWRrL2xhdW5jaGVyL2FwaS9yZXNvdXJjZSIsImlkIjoxOSwia2V5Ijoib2pldlowRXlJeVpOQ3k0biIsImVuYWJsZWQiOnRydWV9LCJ0dyI6eyJ1cmwiOiJodHRwczovL3Nkay1vcy1zdGF0aWMuaG95b3ZlcnNlLmNvbS9iaDNfZ2xvYmFsL21kay9sYXVuY2hlci9hcGkvcmVzb3VyY2UiLCJpZCI6OCwia2V5IjoiZGVtaFVUY1ciLCJlbmFibGVkIjp0cnVlfSwia3IiOnsidXJsIjoiaHR0cHM6Ly9zZGstb3Mtc3RhdGljLmhveW92ZXJzZS5jb20vYmgzX2dsb2JhbC9tZGsvbGF1bmNoZXIvYXBpL3Jlc291cmNlIiwiaWQiOjExLCJrZXkiOiJQUmc1NzFYaCIsImVuYWJsZWQiOnRydWV9LCJzZWEiOnsidXJsIjoiaHR0cHM6Ly9zZGstb3Mtc3RhdGljLmhveW92ZXJzZS5jb20vYmgzX2dsb2JhbC9tZGsvbGF1bmNoZXIvYXBpL3Jlc291cmNlIiwiaWQiOjksImtleSI6InRFR050VmhOIiwiZW5hYmxlZCI6dHJ1ZX0sImV1Ijp7InVybCI6Imh0dHBzOi8vc2RrLW9zLXN0YXRpYy5ob3lvdmVyc2UuY29tL2JoM19nbG9iYWwvbWRrL2xhdW5jaGVyL2FwaS9yZXNvdXJjZSIsImlkIjoxMCwia2V5IjoiZHB6NjV4SjMiLCJlbmFibGVkIjp0cnVlfX0sImhrNGUiOnsiY24iOnsidXJsIjoiaHR0cHM6Ly9zZGstc3RhdGljLm1paG95by5jb20vaGs0ZV9jbi9tZGsvbGF1bmNoZXIvYXBpL3Jlc291cmNlIiwiaWQiOjE4LCJrZXkiOiJlWWQ4OUptSiIsImVuYWJsZWQiOnRydWV9LCJvcyI6eyJ1cmwiOiJodHRwczovL3Nkay1vcy1zdGF0aWMuaG95b3ZlcnNlLmNvbS9oazRlX2dsb2JhbC9tZGsvbGF1bmNoZXIvYXBpL3Jlc291cmNlIiwiaWQiOjEwLCJrZXkiOiJnY1N0Z2FyaCIsImVuYWJsZWQiOnRydWV9LCJiZXRhX29zIjp7InVybCI6Imh0dHBzOi8vaGs0ZS1iZXRhLWxhdW5jaGVyLXN0YXRpYy5ob3lvdmVyc2UuY29tL2hrNGVfZ2xvYmFsL21kay9sYXVuY2hlci9hcGkvcmVzb3VyY2UiLCJpZCI6bnVsbCwia2V5IjpudWxsLCJlbmFibGVkIjpmYWxzZX19LCJoa3JwZyI6eyJjbiI6eyJ1cmwiOiJodHRwczovL2FwaS1sYXVuY2hlci5taWhveW8uY29tL2hrcnBnX2NuL21kay9sYXVuY2hlci9hcGkvcmVzb3VyY2UiLCJpZCI6MzMsImtleSI6IjZLY1Z1T2tiY3FqSm9taloiLCJlbmFibGVkIjp0cnVlfSwib3MiOnsidXJsIjoiaHR0cHM6Ly9oa3JwZy1sYXVuY2hlci1zdGF0aWMuaG95b3ZlcnNlLmNvbS9oa3JwZ19nbG9iYWwvbWRrL2xhdW5jaGVyL2FwaS9yZXNvdXJjZSIsImlkIjozNSwia2V5IjoidnBsT1ZYOFZuN2N3Rzh5YiIsImVuYWJsZWQiOnRydWV9fSwibmFwIjp7Im9zIjp7InVybCI6Imh0dHBzOi8vbmFwLWxhdW5jaGVyLXN0YXRpYy5ob3lvdmVyc2UuY29tL25hcF9nbG9iYWwvbWRrL2xhdW5jaGVyL2FwaS9yZXNvdXJjZSIsImlkIjoxMSwia2V5IjoiU3hLT2dsclVzSkZ4cUU0SSIsImVuYWJsZWQiOnRydWV9fX19'));
|
||||
const namePrettyDefinition = JSON.parse(atob('eyJnYW1lTmFtZSI6eyJiaDMiOiJIb25rYWkgSW1wYWN0IDNyZCIsImhrNGUiOiJHZW5zaGluIEltcGFjdCIsImhrcnBnIjoiSG9ua2FpOiBTdGFyIFJhaWwiLCJuYXAiOiJaZW5sZXNzIFpvbmUgWmVybyJ9LCJzZXJ2ZXJOYW1lIjp7ImNuIjoiQ04gKENoaW5hKSIsImNuX2JpbGliaWxpIjoiQ04gQmlsaUJpbGkgKENoaW5hKSIsIm9zIjoiR2xvYmFsIChPdmVyc2VhcykiLCJqcCI6IkpQIChKYXBhbikiLCJ0dyI6IlRXL0hLL01PIChUcmFkaXRpb25hbCBDaGluZXNlKSIsImtyIjoiS1IgKEtvcmVhKSIsInNlYSI6IlNFQSAoU291dGhlYXN0IEFzaWEpIiwiZXUiOiJHbG9iYWwgKEV1cm9wZSAvIEFtZXJpY2EpIn19'));
|
||||
|
||||
async function main () {
|
||||
logger.trace(`Running apiConnectRunner ...`);
|
||||
const apiResponseReturned = await apiConnectRunner();
|
||||
const apiResponseObj = apiResponseReturned[0];
|
||||
termKit.table(objArrToArrArr(apiResponseReturned[1]), {
|
||||
hasBorder: false,
|
||||
contentHasMarkup: true,
|
||||
// borderChars: 'lightRounded',
|
||||
// borderAttr: { color: 'white' },
|
||||
textAttr: {bgColor: 'default'},
|
||||
firstRowTextAttr: {bgColor: 'white', color: 'black'},
|
||||
width: 80,
|
||||
fit: true
|
||||
});
|
||||
logger.trace(`Returned from apiConnectRunner`);
|
||||
logger.info(`All downloads from the API are complete`)
|
||||
if (apiResponseObj && Object.keys(apiResponseObj).length === apiDefinition.gameList.length) {
|
||||
const gameListVerifiedCount = apiDefinition.gameList.reduce((count, gameNameKey) => {
|
||||
return count + (Object.keys(apiResponseObj[gameNameKey]).length === apiDefinition.serverList[gameNameKey].length ? 1 : 0);
|
||||
}, 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 ...`);
|
||||
const fileManageReaderOutput = await fileManageReaderRunner(apiResponseObj);
|
||||
logger.trace(`Returned from fileManageReaderRunner`);
|
||||
logger.debug(`All local response JSON has been loaded`);
|
||||
logger.debug(`Verifying local and remote files ...`);
|
||||
logger.trace(`Running fileManageDiffVerifier ...`);
|
||||
const fileManageDiffVerifyOutput = await fileManageDiffVerifier(apiResponseObj, fileManageReaderOutput.loadedObj, fileManageReaderOutput.notExistsFileArray);
|
||||
logger.trace(`Returned from fileManageDiffVerifier`);
|
||||
logger.debug(`All local and remote response JSON has been verified`);
|
||||
// logger.trace(`Verify result:`);
|
||||
// if (logger.level == 'TRACE') console.log(fileManageDiffVerifyOutput);
|
||||
if (fileManageDiffVerifyOutput.needWriteFlagCount > 0) {
|
||||
logger.debug(`Writing response JSON to local file ...`);
|
||||
logger.trace(`Running fileManageWriterRunner ...`);
|
||||
await fileManageWriterRunner(apiResponseObj, fileManageReaderOutput.loadedObj, fileManageDiffVerifyOutput);
|
||||
logger.trace(`Returned from fileManageWriterRunner`);
|
||||
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 () {
|
||||
logger.info(`Connecting to API ...`);
|
||||
const definitionList = new Array();
|
||||
const responseDispList = new Array();
|
||||
const responseObject = new Object();
|
||||
logger.trace(`Extracting independent API definition ...`);
|
||||
apiDefinition.gameList.forEach((gameNameKey) => {
|
||||
responseObject[gameNameKey] = {};
|
||||
apiDefinition.serverList[gameNameKey].forEach((serverNameKey) => {
|
||||
if (apiDefinition.definition[gameNameKey][serverNameKey].enabled === true) {
|
||||
definitionList.push({
|
||||
'gameName': gameNameKey,
|
||||
'serverName': serverNameKey,
|
||||
'url': apiDefinition.definition[gameNameKey][serverNameKey].url,
|
||||
'id': apiDefinition.definition[gameNameKey][serverNameKey].id,
|
||||
'key': apiDefinition.definition[gameNameKey][serverNameKey].key
|
||||
});
|
||||
}
|
||||
})
|
||||
})
|
||||
for (const definition of definitionList) {
|
||||
logger.trace(`Delaying connect ...`)
|
||||
if (apiConnectDelay !== null) await new Promise(resolve => setTimeout(resolve, apiConnectDelay));
|
||||
logger.trace(`Requesting ${definition.url.replace(/https:\/\/|\/mdk\/launcher\/api\/resource/g, '')} ...`);
|
||||
let spinner = new clui.Spinner (`Requesting ${definition.url.replace(/https:\/\/|\/mdk\/launcher\/api\/resource/g, '')} ...`, spinnerSeq);
|
||||
spinner.start();
|
||||
try {
|
||||
const resData = await apiConnect(`${definition.url}`, definition.id, definition.key, spinner);
|
||||
if (resData) {
|
||||
responseDispList.push({
|
||||
'game': namePrettyDefinition["gameName"][definition.gameName],
|
||||
'server': namePrettyDefinition["serverName"][definition.serverName],
|
||||
'version': resData.data.game.latest.version
|
||||
});
|
||||
responseObject[definition.gameName][definition.serverName] = resData.data;
|
||||
responseObject[definition.gameName][definition.serverName].deprecated_files.sort((a, b) => {
|
||||
const nameA = a.name.toUpperCase();
|
||||
const nameB = b.name.toUpperCase();
|
||||
if (nameA < nameB) {
|
||||
return -1;
|
||||
}
|
||||
if (nameA > nameB) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
logger.debug(`Downloaded ${definition.url.replace(/https:\/\/|\/mdk\/launcher\/api\/resource/g, '')}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Failed to download data from ${definition.url}`);
|
||||
process.exit(1);
|
||||
// console.error(error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (responseDispList.length === 0) {
|
||||
logger.error(`API processing failed`);
|
||||
return null;
|
||||
} else {
|
||||
return [
|
||||
responseObject,
|
||||
responseDispList
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
async function apiConnect (url, id, key, spinner) {
|
||||
let connectionTimer = process.hrtime();
|
||||
try {
|
||||
const response = await axios({
|
||||
'method': 'get',
|
||||
'url': url,
|
||||
'params': {
|
||||
'launcher_id': id,
|
||||
'key': key
|
||||
},
|
||||
'headers': {
|
||||
'User-Agent': 'Mozilla/5.0',
|
||||
'Cache-Control': 'no-cache'
|
||||
},
|
||||
'timeout': apiConnectTimeout,
|
||||
});
|
||||
let connectionTimeResult = process.hrtime(connectionTimer);
|
||||
spinner.stop();
|
||||
logger.trace(`API connection time: ${(connectionTimeResult[0] * 1e9 + connectionTimeResult[1]) / 1e6} ms`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
let connectionTimeResult = process.hrtime(connectionTimer);
|
||||
spinner.stop();
|
||||
logger.trace(`API connection time: ${(connectionTimeResult[0] * 1e9 + connectionTimeResult[1]) / 1e6} ms`);
|
||||
logger.error(`API request failed: ${error.code}`);
|
||||
// console.error(error);
|
||||
throw error;
|
||||
return error.response;
|
||||
}
|
||||
}
|
||||
|
||||
async function fileManageReaderRunner (apiResponseObj) {
|
||||
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.gameList.length; i++) {
|
||||
let gameNameKey = apiDefinition.gameList[i];
|
||||
loadedObj[gameNameKey] = {};
|
||||
for (let j = 0; j < apiDefinition.serverList[gameNameKey].length; j++) {
|
||||
let serverNameKey = apiDefinition.serverList[gameNameKey][j];
|
||||
let fileExists = await checkFileExists(path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`));
|
||||
if (!fileExists) {
|
||||
notExistsFileArray.push(path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`));
|
||||
} else {
|
||||
loadedObj[gameNameKey][serverNameKey] = JSON.parse(await fs.promises.readFile(path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`), {encoding: 'utf8'}));
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
"loadedObj": loadedObj,
|
||||
"notExistsFileArray": notExistsFileArray
|
||||
}
|
||||
}
|
||||
|
||||
async function fileManageWriterRunner (apiResponseObj, loadedObj, 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.gameList.length; i++) {
|
||||
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]) {
|
||||
logger.trace(`Writing to ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`))} ...`);
|
||||
await fs.promises.writeFile(path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`), JSON.stringify(apiResponseObj[gameNameKey][serverNameKey], '', ' '), {flag: 'w', encoding: 'utf8'});
|
||||
logger.debug(`Wrote data to ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`))}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.gameList.forEach((gameNameKey) => {
|
||||
needWriteFlagObj[gameNameKey] = {};
|
||||
apiDefinition.serverList[gameNameKey].forEach((serverNameKey) => {
|
||||
if (!notExistsFileArray.includes(path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`))) {
|
||||
if (JSON.stringify(apiResponseObj[gameNameKey][serverNameKey]) == JSON.stringify(loadedObj[gameNameKey][serverNameKey])) {
|
||||
needWriteFlagObj[gameNameKey][serverNameKey] = false;
|
||||
} else {
|
||||
needWriteFlagObj[gameNameKey][serverNameKey] = true;
|
||||
needWriteFlagCount += 1;
|
||||
logger.info(`Detected remote update of ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`))}`);
|
||||
}
|
||||
} else {
|
||||
needWriteFlagObj[gameNameKey][serverNameKey] = true;
|
||||
needWriteFlagCount += 1;
|
||||
logger.info(`Detected remote update of ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`))}`);
|
||||
}
|
||||
})
|
||||
})
|
||||
needWriteFlagObj.needWriteFlagCount = needWriteFlagCount;
|
||||
return needWriteFlagObj;
|
||||
}
|
||||
|
||||
async function createFolder (folderPath) {
|
||||
try {
|
||||
await fs.promises.access(folderPath);
|
||||
} catch (error) {
|
||||
await fs.promises.mkdir(folderPath, {recursive: true});
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
const log4js = require('log4js');
|
||||
const color = require('ansi-colors');
|
||||
const axios = require('axios');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const termKit = require('terminal-kit').terminal;
|
||||
|
||||
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'));
|
||||
const apiConnectDelay = 0;
|
||||
const apiConnectTimeout = 20000;
|
||||
const apiDefinition = {
|
||||
'gameList': ['bh3', 'hk4e', 'hkrpg', 'nap'],
|
||||
'serverList': {
|
||||
'bh3': ['cn', 'jp', 'tw', 'kr', 'sea', 'eu'],
|
||||
'hk4e': ['cn', 'os'],
|
||||
'hkrpg': ['cn', 'os'],
|
||||
'nap': ['os']
|
||||
},
|
||||
'definition': {
|
||||
'bh3': {
|
||||
'cn': {'url': 'https://bh3-launcher-static.mihoyo.com/bh3_cn/mdk/launcher/api/resource', 'id': 4, 'key': 'SyvuPnqL', 'enabled': true},
|
||||
'jp': {'url': 'https://sdk-os-static.hoyoverse.com/bh3_global/mdk/launcher/api/resource', 'id': 19, 'key': 'ojevZ0EyIyZNCy4n', 'enabled': true},
|
||||
'tw': {'url': 'https://sdk-os-static.hoyoverse.com/bh3_global/mdk/launcher/api/resource', 'id': 8, 'key': 'demhUTcW', 'enabled': true},
|
||||
'kr': {'url': 'https://sdk-os-static.hoyoverse.com/bh3_global/mdk/launcher/api/resource', 'id': 11, 'key': 'PRg571Xh', 'enabled': true},
|
||||
'sea': {'url': 'https://sdk-os-static.hoyoverse.com/bh3_global/mdk/launcher/api/resource', 'id': 9, 'key': 'tEGNtVhN', 'enabled': true},
|
||||
'eu': {'url': 'https://sdk-os-static.hoyoverse.com/bh3_global/mdk/launcher/api/resource', 'id': 10, 'key': 'dpz65xJ3', 'enabled': true}
|
||||
},
|
||||
'hk4e': {
|
||||
'cn': {'url': 'https://sdk-static.mihoyo.com/hk4e_cn/mdk/launcher/api/resource', 'id': 18, 'key': 'eYd89JmJ', 'enabled': true},
|
||||
'os': {'url': 'https://sdk-os-static.hoyoverse.com/hk4e_global/mdk/launcher/api/resource', 'id': 10, 'key': 'gcStgarh', 'enabled': true},
|
||||
'beta_os': {'url': 'https://hk4e-beta-launcher-static.hoyoverse.com/hk4e_global/mdk/launcher/api/resource', 'id': null, 'key': null, 'enabled': false}
|
||||
},
|
||||
'hkrpg': {
|
||||
'cn': {'url': 'https://api-launcher.mihoyo.com/hkrpg_cn/mdk/launcher/api/resource', 'id': 33, 'key': '6KcVuOkbcqjJomjZ', 'enabled': true},
|
||||
'os': {'url': 'https://hkrpg-launcher-static.hoyoverse.com/hkrpg_global/mdk/launcher/api/resource', 'id': 35, 'key': 'vplOVX8Vn7cwG8yb', 'enabled': true}
|
||||
},
|
||||
'nap': {
|
||||
'os': {'url': 'https://nap-launcher-static.hoyoverse.com/nap_global/mdk/launcher/api/resource', 'id': 11, 'key': 'SxKOglrUsJFxqE4I', 'enabled': true}
|
||||
}
|
||||
}
|
||||
};
|
||||
const namePrettyDefinition = {
|
||||
'gameName': {
|
||||
'bh3': 'Honkai Impact 3rd',
|
||||
'hk4e': 'Genshin Impact',
|
||||
'hkrpg': 'Honkai: Star Rail',
|
||||
'nap': 'Zenless Zone Zero'
|
||||
},
|
||||
'serverName': {
|
||||
'cn': 'CN (China)',
|
||||
'cn_bilibili': 'CN BiliBili (China)',
|
||||
'os': 'Global (Overseas)',
|
||||
'jp': 'JP (Japan)',
|
||||
'tw': 'TW/HK/MO (Traditional Chinese)',
|
||||
'kr': 'KR (Korea)',
|
||||
'sea': 'SEA (Southeast Asia)',
|
||||
'eu': 'Global (Europe / America)'
|
||||
}
|
||||
};
|
||||
|
||||
async function main () {
|
||||
logger.trace(`Running apiConnectRunner ...`);
|
||||
const apiResponseReturned = await apiConnectRunner();
|
||||
const apiResponseObj = apiResponseReturned[0];
|
||||
termKit.table(objArrToArrArr(apiResponseReturned[1]), {
|
||||
hasBorder: false,
|
||||
contentHasMarkup: true,
|
||||
// borderChars: 'lightRounded',
|
||||
// borderAttr: { color: 'white' },
|
||||
textAttr: {bgColor: 'default'},
|
||||
firstRowTextAttr: {bgColor: 'white', color: 'black'},
|
||||
width: 80,
|
||||
fit: true
|
||||
});
|
||||
logger.trace(`Returned from apiConnectRunner`);
|
||||
logger.info(`All downloads from the API are complete`)
|
||||
if (apiResponseObj && Object.keys(apiResponseObj).length === apiDefinition.gameList.length) {
|
||||
const gameListVerifiedCount = apiDefinition.gameList.reduce((count, gameNameKey) => {
|
||||
return count + (Object.keys(apiResponseObj[gameNameKey]).length === apiDefinition.serverList[gameNameKey].length ? 1 : 0);
|
||||
}, 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 ...`);
|
||||
const fileManageReaderOutput = await fileManageReaderRunner(apiResponseObj);
|
||||
logger.trace(`Returned from fileManageReaderRunner`);
|
||||
logger.debug(`All local response JSON has been loaded`);
|
||||
logger.debug(`Verifying local and remote files ...`);
|
||||
logger.trace(`Running fileManageDiffVerifier ...`);
|
||||
const fileManageDiffVerifyOutput = await fileManageDiffVerifier(apiResponseObj, fileManageReaderOutput.loadedObj, fileManageReaderOutput.notExistsFileArray);
|
||||
logger.trace(`Returned from fileManageDiffVerifier`);
|
||||
logger.debug(`All local and remote response JSON has been verified`);
|
||||
// logger.trace(`Verify result:`);
|
||||
// if (logger.level == 'TRACE') console.log(fileManageDiffVerifyOutput);
|
||||
if (fileManageDiffVerifyOutput.needWriteFlagCount > 0) {
|
||||
logger.debug(`Writing response JSON to local file ...`);
|
||||
logger.trace(`Running fileManageWriterRunner ...`);
|
||||
await fileManageWriterRunner(apiResponseObj, fileManageReaderOutput.loadedObj, fileManageDiffVerifyOutput);
|
||||
logger.trace(`Returned from fileManageWriterRunner`);
|
||||
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 () {
|
||||
logger.info(`Connecting to API ...`);
|
||||
const definitionList = new Array();
|
||||
const responseDispList = new Array();
|
||||
const responseObject = new Object();
|
||||
logger.trace(`Extracting independent API definition ...`);
|
||||
apiDefinition.gameList.forEach((gameNameKey) => {
|
||||
responseObject[gameNameKey] = {};
|
||||
apiDefinition.serverList[gameNameKey].forEach((serverNameKey) => {
|
||||
if (apiDefinition.definition[gameNameKey][serverNameKey].enabled === true) {
|
||||
definitionList.push({
|
||||
'gameName': gameNameKey,
|
||||
'serverName': serverNameKey,
|
||||
'url': apiDefinition.definition[gameNameKey][serverNameKey].url,
|
||||
'id': apiDefinition.definition[gameNameKey][serverNameKey].id,
|
||||
'key': apiDefinition.definition[gameNameKey][serverNameKey].key
|
||||
});
|
||||
}
|
||||
})
|
||||
})
|
||||
for (const definition of definitionList) {
|
||||
logger.trace(`Delaying connect ...`)
|
||||
if (apiConnectDelay !== null) await new Promise(resolve => setTimeout(resolve, apiConnectDelay));
|
||||
logger.trace(`Requesting ${definition.url.replace(/https:\/\/|\/mdk\/launcher\/api\/resource/g, '')} ...`);
|
||||
try {
|
||||
const resData = await apiConnect(`${definition.url}`, definition.id, definition.key);
|
||||
if (resData) {
|
||||
responseDispList.push({
|
||||
'game': namePrettyDefinition["gameName"][definition.gameName],
|
||||
'server': namePrettyDefinition["serverName"][definition.serverName],
|
||||
'version': resData.data.game.latest.version
|
||||
});
|
||||
responseObject[definition.gameName][definition.serverName] = resData.data;
|
||||
responseObject[definition.gameName][definition.serverName].deprecated_files.sort((a, b) => {
|
||||
const nameA = a.name.toUpperCase();
|
||||
const nameB = b.name.toUpperCase();
|
||||
if (nameA < nameB) {
|
||||
return -1;
|
||||
}
|
||||
if (nameA > nameB) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
logger.debug(`Downloaded ${definition.url.replace(/https:\/\/|\/mdk\/launcher\/api\/resource/g, '')}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Failed to download data from ${definition.url}`);
|
||||
process.exit(1);
|
||||
// console.error(error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (responseDispList.length === 0) {
|
||||
logger.error(`API processing failed`);
|
||||
return null;
|
||||
} else {
|
||||
return [
|
||||
responseObject,
|
||||
responseDispList
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
async function apiConnect (url, id, key) {
|
||||
let connectionTimer = process.hrtime();
|
||||
try {
|
||||
const response = await axios({
|
||||
'method': 'get',
|
||||
'url': url,
|
||||
'params': {
|
||||
'launcher_id': id,
|
||||
'key': key
|
||||
},
|
||||
'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;
|
||||
return error.response;
|
||||
}
|
||||
}
|
||||
|
||||
async function fileManageReaderRunner (apiResponseObj) {
|
||||
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.gameList.length; i++) {
|
||||
let gameNameKey = apiDefinition.gameList[i];
|
||||
loadedObj[gameNameKey] = {};
|
||||
for (let j = 0; j < apiDefinition.serverList[gameNameKey].length; j++) {
|
||||
let serverNameKey = apiDefinition.serverList[gameNameKey][j];
|
||||
let fileExists = await checkFileExists(path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`));
|
||||
if (!fileExists) {
|
||||
notExistsFileArray.push(path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`));
|
||||
} else {
|
||||
loadedObj[gameNameKey][serverNameKey] = JSON.parse(await fs.promises.readFile(path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`), {encoding: 'utf8'}));
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
"loadedObj": loadedObj,
|
||||
"notExistsFileArray": notExistsFileArray
|
||||
}
|
||||
}
|
||||
|
||||
async function fileManageWriterRunner (apiResponseObj, loadedObj, 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.gameList.length; i++) {
|
||||
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]) {
|
||||
logger.trace(`Writing to ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`))} ...`);
|
||||
await fs.promises.writeFile(path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`), JSON.stringify(apiResponseObj[gameNameKey][serverNameKey], '', ' '), {flag: 'w', encoding: 'utf8'});
|
||||
logger.debug(`Wrote data to ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`))}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.gameList.forEach((gameNameKey) => {
|
||||
needWriteFlagObj[gameNameKey] = {};
|
||||
apiDefinition.serverList[gameNameKey].forEach((serverNameKey) => {
|
||||
if (!notExistsFileArray.includes(path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`))) {
|
||||
if (JSON.stringify(apiResponseObj[gameNameKey][serverNameKey]) == JSON.stringify(loadedObj[gameNameKey][serverNameKey])) {
|
||||
needWriteFlagObj[gameNameKey][serverNameKey] = false;
|
||||
} else {
|
||||
needWriteFlagObj[gameNameKey][serverNameKey] = true;
|
||||
needWriteFlagCount += 1;
|
||||
logger.info(`Detected remote update of ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`))}`);
|
||||
}
|
||||
} else {
|
||||
needWriteFlagObj[gameNameKey][serverNameKey] = true;
|
||||
needWriteFlagCount += 1;
|
||||
logger.info(`Detected remote update of ${path.relative(path.resolve(process.cwd()), path.join(rootDirectory, 'unique', `${gameNameKey}_${serverNameKey}.json`))}`);
|
||||
}
|
||||
})
|
||||
})
|
||||
needWriteFlagObj.needWriteFlagCount = needWriteFlagCount;
|
||||
return needWriteFlagObj;
|
||||
}
|
||||
|
||||
async function createFolder (folderPath) {
|
||||
try {
|
||||
await fs.promises.access(folderPath);
|
||||
} catch (error) {
|
||||
await fs.promises.mkdir(folderPath, {recursive: true});
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
218
src/runHYP.js
Normal 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();
|
Loading…
Reference in a new issue