import fetch from "node-fetch" import path from "path" import { exists, writeSync, readdirSync, rm, readSync } from "./file.js" // zh_TW uses an older version of charword_table.json const REGIONS = ["zh_CN", "en_US", "ja_JP", "ko_KR", "zh_TW"] const DEFAULT_REGION = REGIONS[0] const NICKNAME = { "zh_CN": "博士", "en_US": "Doctor", "ja_JP": "ドクター", "ko_KR": "박사", "zh_TW": "博士", } export default class CharwordTable { #operatorIDs = Object.values(__config.operators).map(operator => { return this.#getOperatorId(operator) }) #charwordTablePath = path.join(__dirname, __config.folder.operator, __config.folder.share) #charwordTableFile = path.join(this.#charwordTablePath, 'charword_table.json') #charwordTable = JSON.parse(readSync(this.#charwordTableFile)) || { config: { default_region: DEFAULT_REGION, regions: REGIONS, }, operators: {}, } async process() { await Promise.all(REGIONS.map(async (region) => {await this.#load(region)})) writeSync(JSON.stringify(this.#charwordTable), this.#charwordTableFile) } lookup(operatorName) { const operatorBlock = this.#charwordTable.operators[this.#getOperatorId(__config.operators[operatorName])] return { config: this.#charwordTable.config, operator: operatorBlock.voice[DEFAULT_REGION][0].ref ? this.#charwordTable.operators[operatorBlock.alternativeId] : operatorBlock, } } #getOperatorId(operatorConfig) { return operatorConfig.filename.replace(/^(dyn_illust_)(char_[\d]+)(_[\w]+)(|(_.+))$/g, '$2$3$4') } async #load(region) { this.#operatorIDs.forEach(id => { this.#charwordTable.operators[id] = { alternativeId: id.replace(/^(char_)([\d]+)(_[\w]+)(|(_.+))$/g, '$1$2$3'), voice: REGIONS.reduce((acc, cur) => ({ ...acc, [cur]: [] }), {}), // use array to store voice lines info: REGIONS.reduce((acc, cur) => ({ ...acc, [cur]: {} }), {}), // use object to store voice actor info } }) if (region === 'zh_TW') { return await this.#zhTWLoad() } const data = await this.#download(region) // put voice actor info into charword_table for (const [id, element] of Object.entries(this.#charwordTable.operators)) { let operatorId = id if (typeof data.voiceLangDict[operatorId] === 'undefined') { operatorId = element.alternativeId } // not available in other region if (typeof data.voiceLangDict[operatorId] === 'undefined') { console.log(`Voice actor info of ${operatorId} is not available in ${region}.`) continue } Object.values(data.voiceLangDict[operatorId].dict).forEach(item => { if (typeof element.info[region][item.wordkey] === 'undefined') { element.info[region][item.wordkey] = [] } element.info[region][item.wordkey].push({ lang: item.voiceLangType, cvName: item.cvName }) }) } // put voice lines into charword_table Object.values(data.charWords).forEach(item => { const operatorInfo = Object.values(this.#charwordTable.operators).filter(element => element.info[region][item.wordKey]) if (operatorInfo.length > 0) { operatorInfo[0].voice[region].push({ ref: false, id: item.voiceId, title: item.voiceTitle, text: item.voiceText.replace(/{@nickname}/g, NICKNAME[region]), wordKey: item.wordKey }) if (operatorInfo.length > 1) { operatorInfo[1].voice[region].push({ ref: true, id: item.voiceId, wordKey: item.wordKey }) } } }) } async #download(region) { const historyResponse = await fetch(`https://api.github.com/repos/Kengxxiao/ArknightsGameData/commits?path=${region}/gamedata/excel/charword_table.json`) const historyData = await historyResponse.json() const lastCommit = historyData[0] const lastCommitDate = new Date(lastCommit.commit.committer.date) const filepath = path.join(this.#charwordTablePath, `charword_table_${region}_${lastCommitDate.getTime()}.json`) console.log(`Last commit date: ${lastCommitDate.getTime()}`) if (exists(filepath)) { console.log(`charword_table_${region}.json is the latest version.`) return JSON.parse(readSync(filepath)) } const response = await fetch(`https://raw.githubusercontent.com/Kengxxiao/ArknightsGameData/master/${region}/gamedata/excel/charword_table.json`) const data = await response.json() writeSync(JSON.stringify(data), filepath) console.log(`charword_table_${region}.json is updated.`) // remove old file const files = readdirSync(path.join(__dirname, __config.folder.operator, __config.folder.share)) for (const file of files) { if (file.startsWith(`charword_table_${region}`) && file !== path.basename(filepath)) { rm(path.join(__dirname, __config.folder.operator, __config.folder.share, file)) } } return data } async #zhTWLoad() { const region = 'zh_TW' const downloaded = await this.#zhTWDownload() const data = downloaded.data const handbook = downloaded.handbook // put voice actor info into charword_table for (const [id, element] of Object.entries(this.#charwordTable.operators)) { let operatorId = id if (typeof handbook.handbookDict[operatorId] === 'undefined') { operatorId = element.alternativeId } // not available in other region if (typeof handbook.handbookDict[operatorId] === 'undefined') { console.log(`Voice actor info of ${operatorId} is not available in ${region}.`) continue } const charId = handbook.handbookDict[operatorId].charID if (typeof element.info[region][charId] === 'undefined') { element.info[region][charId] = [] } element.info[region][charId].push({ lang: "JP", cvName: handbook.handbookDict[operatorId].infoName }) } // // put voice lines into charword_table Object.values(data).forEach(item => { const operatorInfo = Object.values(this.#charwordTable.operators).filter(element => element.info[region][item.wordKey]) if (operatorInfo.length > 0) { operatorInfo[0].voice[region].push({ ref: false, id: item.voiceId, title: item.voiceTitle, text: item.voiceText.replace(/{@nickname}/g, NICKNAME[region]), wordKey: item.wordKey }) if (operatorInfo.length > 1) { operatorInfo[1].voice[region].push({ ref: true, id: item.voiceId, wordKey: item.wordKey }) } } }) } async #zhTWDownload() { const output = {} const region = 'zh_TW' const historyResponse = await fetch(`https://api.github.com/repos/Kengxxiao/ArknightsGameData/commits?path=${region}/gamedata/excel/charword_table.json`) const handbookHistoryResponse = await fetch(`https://api.github.com/repos/Kengxxiao/ArknightsGameData/commits?path=${region}/gamedata/excel/handbook_info_table.json`) const historyData = await historyResponse.json() const handbookHistoryData = await handbookHistoryResponse.json() const lastCommit = historyData[0] const handboookLastCommit = handbookHistoryData[0] const lastCommitDate = new Date(lastCommit.commit.committer.date) const handbookLastCommitDate = new Date(handboookLastCommit.commit.committer.date) const filepath = path.join(this.#charwordTablePath, `charword_table_${region}_${lastCommitDate.getTime()}.json`) const handbookFilepath = path.join(this.#charwordTablePath, `handbook_info_table_${region}_${handbookLastCommitDate.getTime()}.json`) console.log(`Last commit date: ${lastCommitDate.getTime()}`) console.log(`Handbook last commit date: ${handbookLastCommitDate.getTime()}`) if (exists(filepath)) { console.log(`charword_table_${region}.json is the latest version.`) output.data = JSON.parse(readSync(filepath)) } else { const response = await fetch(`https://raw.githubusercontent.com/Kengxxiao/ArknightsGameData/master/${region}/gamedata/excel/charword_table.json`) const data = await response.json() writeSync(JSON.stringify(data), filepath) console.log(`charword_table_${region}.json is updated.`) // remove old file const files = readdirSync(path.join(__dirname, __config.folder.operator, __config.folder.share)) for (const file of files) { if (file.startsWith(`charword_table_${region}`) && file !== path.basename(filepath)) { rm(path.join(__dirname, __config.folder.operator, __config.folder.share, file)) } } output.data = data } if (exists(handbookFilepath)) { console.log(`handbook_info_table_${region}.json is the latest version.`) output.handbook = JSON.parse(readSync(handbookFilepath)) } else { const response = await fetch(`https://raw.githubusercontent.com/Kengxxiao/ArknightsGameData/master/${region}/gamedata/excel/handbook_info_table.json`) const data = await response.json() writeSync(JSON.stringify(data), handbookFilepath) console.log(`handbook_info_table_${region}.json is updated.`) // remove old file const files = readdirSync(path.join(__dirname, __config.folder.operator, __config.folder.share)) for (const file of files) { if (file.startsWith(`handbook_info_table_${region}`) && file !== path.basename(handbookFilepath)) { rm(path.join(__dirname, __config.folder.operator, __config.folder.share, file)) } } output.handbook = data } return output } }