feat: migrated packages to ts

This commit is contained in:
Haoyu Xu
2025-05-02 02:27:42 +08:00
parent 0af0c785d4
commit 8f6f537c81
111 changed files with 3166 additions and 1155 deletions

View File

@@ -1,3 +1,27 @@
import baseConfig from '@aklive2d/eslint-config'
import { tsConfig } from '@aklive2d/eslint-config'
import tseslint from 'typescript-eslint'
import globals from 'globals'
/** @type {import('eslint').Config} */
export default [...baseConfig]
export default tseslint.config(
...tsConfig,
{
ignores: ['dist', 'data'],
},
{
files: ['**/*.js', '**/*.ts'],
languageOptions: {
ecmaVersion: 2022,
globals: {
...globals.node,
},
},
rules: {
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_' },
],
},
}
)

View File

@@ -7,17 +7,26 @@ import operators, {
getOperatorAlternativeId,
OPERATOR_SOURCE_FOLDER,
} from '@aklive2d/operator'
import type {
Region,
CharwordTableJson,
OperatorCharwordTable,
CharwordTable,
VoiceRegionObject,
InfoRegionObject,
} from './types.ts'
// zh_TW uses an older version of charword_table.json
// zh_TW is removed
const REGIONS = ['zh_CN', 'en_US', 'ja_JP', 'ko_KR']
const REGIONS: Region[] = ['zh_CN', 'en_US', 'ja_JP', 'ko_KR']
const REGION_URLS = {
zh_CN: 'Kengxxiao/ArknightsGameData',
en_US: 'Kengxxiao/ArknightsGameData_YoStar',
ja_JP: 'Kengxxiao/ArknightsGameData_YoStar',
ko_KR: 'Kengxxiao/ArknightsGameData_YoStar',
}
const DEFAULT_REGION = REGIONS[0]
const DEFAULT_REGION: Region = REGIONS[0]
export const defaultRegion = DEFAULT_REGION.replace('_', '-')
const NICKNAME = {
zh_CN: '博士',
@@ -34,22 +43,18 @@ const AUTO_UPDATE_FOLDER = path.resolve(
import.meta.dirname,
config.dir_name.auto_update
)
const CHARWORD_TABLE_FILE = path.resolve(
AUTO_UPDATE_FOLDER,
config.module.charword_table.charword_table_json
)
const CHARWORD_TABLE = JSON.parse(file.readSync(CHARWORD_TABLE_FILE)) || {}
const DIST_DIR = path.resolve(import.meta.dirname, config.dir_name.dist)
export const lookup = (operatorName) => {
const lookup = (operatorName: string, charwordTable: CharwordTable) => {
const operatorId = getOperatorId(operators[operatorName].filename)
const operatorBlock = CHARWORD_TABLE[operatorId]
const operatorBlock = charwordTable[operatorId]
return operatorBlock.ref
? CHARWORD_TABLE[operatorBlock.alternativeId]
? charwordTable[operatorBlock.alternativeId]
: operatorBlock
}
const getDistDir = (name) => {
const getDistDir = (name: string) => {
return path.join(
DIST_DIR,
name,
@@ -57,27 +62,37 @@ const getDistDir = (name) => {
)
}
export const getLangs = (name, voiceJson = null) => {
voiceJson = voiceJson
? voiceJson
: JSON.parse(file.readSync(getDistDir(name)))
const voiceLangs = Object.keys(voiceJson.voiceLangs['zh-CN'])
const subtitleLangs = Object.keys(voiceJson.subtitleLangs)
export const getLangs = (
name: string,
voiceJson: OperatorCharwordTable | null = null
) => {
let data: OperatorCharwordTable
if (!voiceJson) {
const text = file.readSync(getDistDir(name))
if (!text) throw new Error(`File not found: ${getDistDir(name)}`)
data = JSON.parse(text)
} else {
data = voiceJson
}
const voiceLangs = Object.keys(data.voiceLangs['zh-CN'])
const subtitleLangs = Object.keys(data.subtitleLangs)
return { voiceLangs, subtitleLangs }
}
export const build = async (namesToBuild) => {
const err = []
export const build = async (namesToBuild: string[]) => {
const err: string[] = []
const names = !namesToBuild.length ? Object.keys(operators) : namesToBuild
console.log('Generating charword_table for', names.length, 'operators')
await updateFn(true)
const charwordTable = await updateFn(true)
for (const name of names) {
const charwordTableLookup = lookup(name)
const voiceJson = {}
const charwordTableLookup = lookup(name, charwordTable)
const voiceJson = {} as OperatorCharwordTable
voiceJson.voiceLangs = {}
voiceJson.subtitleLangs = {}
const subtitleInfo = Object.keys(charwordTableLookup.info)
let voiceList = {}
const subtitleInfo = Object.keys(charwordTableLookup.info) as Region[]
const voiceList = {} as {
[key: string]: string[]
}
subtitleInfo.forEach((item) => {
if (Object.keys(charwordTableLookup.info[item]).length > 0) {
const key = item.replace('_', '-')
@@ -104,7 +119,7 @@ export const build = async (namesToBuild) => {
)
}
})
let voiceLangs = []
let voiceLangs = [] as string[]
try {
voiceLangs = getLangs(name, voiceJson).voiceLangs
@@ -161,36 +176,57 @@ const updateFn = async (isLocalOnly = false) => {
(acc, cur) => ({ ...acc, [cur]: {} }),
{}
)
const charwordTable = {} as CharwordTable
OPERATOR_IDS.forEach((id) => {
CHARWORD_TABLE[id] = {
charwordTable[id] = {
alternativeId: getOperatorAlternativeId(id),
voice: structuredClone(regionObject),
info: structuredClone(regionObject),
voice: structuredClone(regionObject) as VoiceRegionObject,
info: structuredClone(regionObject) as InfoRegionObject,
infile: false,
ref: false,
}
})
await load(DEFAULT_REGION, isLocalOnly)
await load(DEFAULT_REGION, charwordTable, isLocalOnly)
await Promise.all(
REGIONS.slice(1).map(async (region) => {
await load(region, isLocalOnly)
await load(region, charwordTable, isLocalOnly)
})
)
return charwordTable
}
const load = async (region, isLocalOnly = false) => {
const load = async (
region: Region,
charwordTable: CharwordTable,
isLocalOnly = false
) => {
const basename = `charword_table_${region}`
const filename = file
.readdirSync(AUTO_UPDATE_FOLDER)
.filter((item) => item.startsWith(`charword_table_${region}`))[0]
const localFilePath = path.join(AUTO_UPDATE_FOLDER, filename)
const data = isLocalOnly
? JSON.parse(file.readSync(localFilePath))
: await download(
region,
path.join(path.dirname(localFilePath), `${basename}.json`)
)
let data: CharwordTableJson
const getOnlineData = async () => {
return await download(
region,
path.join(path.dirname(localFilePath), `${basename}.json`)
)
}
if (isLocalOnly) {
const text = file.readSync(localFilePath)
if (!text) {
data = await getOnlineData()
} else {
data = JSON.parse(text)
}
} else {
data = await getOnlineData()
}
// put voice actor info into charword_table
for (const [id, element] of Object.entries(CHARWORD_TABLE)) {
for (const [id, element] of Object.entries(charwordTable)) {
let operatorId = id
let useAlternativeId = false
if (typeof data.voiceLangDict[operatorId] === 'undefined') {
@@ -236,7 +272,7 @@ const load = async (region, isLocalOnly = false) => {
// put voice lines into charword_table
Object.values(data.charWords).forEach((item) => {
const operatorInfo = Object.values(CHARWORD_TABLE).filter(
const operatorInfo = Object.values(charwordTable).filter(
(element) => element.info[region][item.wordKey]
)
if (operatorInfo.length > 0) {
@@ -256,9 +292,14 @@ const load = async (region, isLocalOnly = false) => {
}
}
})
return charwordTable
}
const download = async (region, targetFilePath) => {
const download = async (
region: keyof typeof REGION_URLS,
targetFilePath: string
) => {
return await githubDownload(
`https://api.github.com/repos/${REGION_URLS[region]}/commits?path=${region}/gamedata/excel/charword_table.json`,
`https://raw.githubusercontent.com/${REGION_URLS[region]}/master/${region}/gamedata/excel/charword_table.json`,

View File

@@ -2,7 +2,7 @@
"name": "@aklive2d/charword-table",
"private": true,
"version": "0.0.0",
"main": "index.js",
"main": "index.ts",
"type": "module",
"dependencies": {
"@aklive2d/libs": "workspace:*",
@@ -12,10 +12,15 @@
"@aklive2d/eslint-config": "workspace:*",
"@aklive2d/prettier-config": "workspace:*"
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript-eslint": ">=8.31.1",
"typescript": ">=5.8.2"
},
"scripts": {
"update": "mode=update node runner.js",
"build": "mode=build node runner.js",
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check .",
"update": "mode=update bun runner.ts",
"build": "mode=build bun runner.ts",
"lint": "eslint && prettier --check .",
"build:cleanup": "rm -rf ./dist"
}
}

View File

@@ -1,8 +1,8 @@
import { build, update } from './index.js'
import { build, update } from './index.ts'
import { envParser, error } from '@aklive2d/libs'
async function main() {
let err = []
let err: string[] = []
const { mode, name } = envParser.parse({
mode: {
type: 'string',
@@ -17,7 +17,7 @@ async function main() {
})
switch (mode) {
case 'build':
err = await build(name)
err = await build(name as string[])
break
case 'update':
await update()

View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2024",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2024", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["**/*"],
"exclude": ["dist/**/*", "data/**/*"]
}

View File

@@ -0,0 +1,139 @@
export type Region = 'zh_CN' | 'en_US' | 'ja_JP' | 'ko_KR'
interface CharWordBase {
wordKey: string
charId: string
voiceId: string
voiceText: string
}
interface CharWord extends CharWordBase {
charWordId: string
voiceTitle: string
voiceIndex: number
voiceType: string
unlockType: string
unlockParam: unknown
lockDescription: string
placeType: string
voiceAsset: string
}
type VoiceLangInfo = {
wordkey: string
voiceLangType: string
cvName: string[]
voicePath: string | null
}
type CharVoiceLangDict = {
wordkeys: string[]
charId: string
dict: Record<string, VoiceLangInfo>
}
type VoiceLangType = {
name: string
groupType: string
}
type VoiceLangGroupType = {
name: string
members: string[]
}
type TimestampCharSet = {
timestamp: number
charSet: string[]
}
type TimeData = {
timeType: string
interval: {
startTs: number
endTs: number
}
}
type FesVoiceData = {
showType: string
timeData: TimeData[]
}
type FesVoiceWeight = {
showType: string
weight: number
priority: number
}
type ExtraVoiceConfig = {
voiceId: string
validVoiceLang: string[]
}
export type CharwordTableJson = {
charWords: Record<string, CharWord>
charExtraWords: Record<string, CharWordBase>
voiceLangDict: Record<string, CharVoiceLangDict>
defaultLangType: string
newTagList: string[]
voiceLangTypeDict: Record<string, VoiceLangType>
voiceLangGroupTypeDict: Record<string, VoiceLangGroupType>
charDefaultTypeDict: Record<string, string>
startTimeWithTypeDict: Record<string, TimestampCharSet[]>
displayGroupTypeList: string[]
displayTypeList: string[]
playVoiceRange: string
fesVoiceData: Record<string, FesVoiceData>
fesVoiceWeight: Record<string, FesVoiceWeight>
extraVoiceConfigData: Record<string, ExtraVoiceConfig>
}
export type OperatorCharwordTable = {
voiceLangs: {
[languageCode: string]: {
[voiceLangType: string]: string[]
}
}
subtitleLangs: {
[languageCode: string]: {
[voiceKey: 'default' | string]: {
[voiceId: string]: {
title: string
text: string
}
}
}
}
}
export type VoiceRegionObject = {
// eslint-disable-next-line
[region in Region]: {
[wordkey: string]: {
[voiceId: string]: {
title: string
text: string
}
}
}
}
export type InfoRegionObject = {
// eslint-disable-next-line
[region in Region]: {
[wordkey: string]: {
[voiceLangType: string]: string[]
}
}
}
export type CharwordTable = {
[name: string]: {
alternativeId: string
voice: VoiceRegionObject
info: InfoRegionObject
infile: boolean
ref: boolean
}
}